diff --git a/.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch b/.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch deleted file mode 100644 index cb24546791..0000000000 --- a/.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch +++ /dev/null @@ -1,280 +0,0 @@ -diff --git a/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js b/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js -index 0b369b27ac244818802de8acd1b2563e7588e647..8f66efaf365dc569335366db566fd40d39d1ec27 100644 ---- a/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js -+++ b/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js -@@ -8,6 +8,8 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau - - var platformPath__default = /*#__PURE__*/_interopDefaultCompat(platformPath); - -+const REDIRECT_STATUS_CODES = [301, 302, 307, 308]; -+const MAX_REDIRECTS = 5; - const isInRange = (num, [start, end]) => { - return num >= start && num <= end; - }; -@@ -36,6 +38,26 @@ const parsePortPredicate = (port) => { - } - return (url) => !url.port; - }; -+function predicateFromConfig(config) { -+ const allow = config.getOptionalConfigArray("backend.reading.allow")?.map((allowConfig) => { -+ const paths = allowConfig.getOptionalStringArray("paths"); -+ const checkPath = paths ? (url) => { -+ const targetPath = platformPath__default.default.posix.normalize(url.pathname); -+ return paths.some( -+ (allowedPath) => targetPath.startsWith(allowedPath) -+ ); -+ } : (_url) => true; -+ const host = allowConfig.getString("host"); -+ const [hostname, port] = host.split(":"); -+ const checkPort = parsePortPredicate(port); -+ if (hostname.startsWith("*.")) { -+ const suffix = hostname.slice(1); -+ return (url) => url.hostname.endsWith(suffix) && checkPath(url) && checkPort(url); -+ } -+ return (url) => url.hostname === hostname && checkPath(url) && checkPort(url); -+ }); -+ return allow?.length ? (url) => allow.some((p) => p(url)) : () => false; -+} - class FetchUrlReader { - /** - * The factory creates a single reader that will be used for reading any URL that's listed -@@ -50,64 +72,72 @@ class FetchUrlReader { - * An optional list of paths which are allowed. If the list is omitted all paths are allowed. - */ - static factory = ({ config }) => { -- const predicates = config.getOptionalConfigArray("backend.reading.allow")?.map((allowConfig) => { -- const paths = allowConfig.getOptionalStringArray("paths"); -- const checkPath = paths ? (url) => { -- const targetPath = platformPath__default.default.posix.normalize(url.pathname); -- return paths.some( -- (allowedPath) => targetPath.startsWith(allowedPath) -- ); -- } : (_url) => true; -- const host = allowConfig.getString("host"); -- const [hostname, port] = host.split(":"); -- const checkPort = parsePortPredicate(port); -- if (hostname.startsWith("*.")) { -- const suffix = hostname.slice(1); -- return (url) => url.hostname.endsWith(suffix) && checkPath(url) && checkPort(url); -- } -- return (url) => url.hostname === hostname && checkPath(url) && checkPort(url); -- }) ?? []; -- const reader = new FetchUrlReader(); -- const predicate = (url) => predicates.some((p) => p(url)); -+ const predicate = predicateFromConfig(config); -+ const reader = new FetchUrlReader({ predicate }); - return [{ reader, predicate }]; - }; -+ static fromConfig(config) { -+ return new FetchUrlReader({ predicate: predicateFromConfig(config) }); -+ } -+ #predicate; -+ constructor(options) { -+ this.#predicate = options.predicate; -+ } - async read(url) { - const response = await this.readUrl(url); - return response.buffer(); - } - async readUrl(url, options) { -- let response; -- try { -- response = await fetch(url, { -- headers: { -- ...options?.etag && { "If-None-Match": options.etag }, -- ...options?.lastModifiedAfter && { -- "If-Modified-Since": options.lastModifiedAfter.toUTCString() -+ let currentUrl = url; -+ for (let redirectCount = 0; redirectCount < MAX_REDIRECTS; redirectCount += 1) { -+ const parsedUrl = new URL(currentUrl); -+ if (!this.#predicate(parsedUrl)) { -+ throw new Error( -+ `URL not allowed by backend.reading.allow configuration: ${currentUrl}` -+ ); -+ } -+ let response; -+ try { -+ response = await fetch(currentUrl, { -+ headers: { -+ ...options?.etag && { "If-None-Match": options.etag }, -+ ...options?.lastModifiedAfter && { -+ "If-Modified-Since": options.lastModifiedAfter.toUTCString() -+ }, -+ ...options?.token && { Authorization: `Bearer ${options.token}` } - }, -- ...options?.token && { Authorization: `Bearer ${options.token}` } -- }, -- // TODO(freben): The signal cast is there because pre-3.x versions of -- // node-fetch have a very slightly deviating AbortSignal type signature. -- // The difference does not affect us in practice however. The cast can -- // be removed after we support ESM for CLI dependencies and migrate to -- // version 3 of node-fetch. -- // https://github.com/backstage/backstage/issues/8242 -- signal: options?.signal -- }); -- } catch (e) { -- throw new Error(`Unable to read ${url}, ${e}`); -- } -- if (response.status === 304) { -- throw new errors.NotModifiedError(); -- } -- if (response.ok) { -- return ReadUrlResponseFactory.ReadUrlResponseFactory.fromResponse(response); -- } -- const message = `could not read ${url}, ${response.status} ${response.statusText}`; -- if (response.status === 404) { -- throw new errors.NotFoundError(message); -+ // Handle redirects manually to validate targets against the allowlist -+ redirect: "manual", -+ // TODO(freben): The signal cast is there because pre-3.x versions of -+ // node-fetch have a very slightly deviating AbortSignal type signature. -+ // The difference does not affect us in practice however. The cast can -+ // be removed after we support ESM for CLI dependencies and migrate to -+ // version 3 of node-fetch. -+ // https://github.com/backstage/backstage/issues/8242 -+ signal: options?.signal -+ }); -+ } catch (e) { -+ throw new Error(`Unable to read ${currentUrl}, ${e}`); -+ } -+ if (response.ok) { -+ return ReadUrlResponseFactory.ReadUrlResponseFactory.fromResponse(response); -+ } -+ if (response.status === 304) { -+ throw new errors.NotModifiedError(); -+ } -+ const location = response.headers.get("location"); -+ if (!REDIRECT_STATUS_CODES.includes(response.status) || !location) { -+ const message = `could not read ${currentUrl}, ${response.status} ${response.statusText}`; -+ if (response.status === 404) { -+ throw new errors.NotFoundError(message); -+ } -+ throw new Error(message); -+ } -+ currentUrl = new URL(location, currentUrl).toString(); - } -- throw new Error(message); -+ throw new Error( -+ `Too many redirects (max ${MAX_REDIRECTS}) when reading ${url}` -+ ); - } - async readTree() { - throw new Error("FetchUrlReader does not implement readTree"); -diff --git a/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map b/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map -index b6ff0e7ab1b362bc33b0decef31bc96836727d90..7a97648444ae7e9716aa38b1fd0f327e22152bfb 100644 ---- a/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map -+++ b/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map -@@ -1 +1 @@ --{"version":3,"file":"FetchUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/FetchUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2020 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 UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport {\n assertError,\n NotFoundError,\n NotModifiedError,\n} from '@backstage/errors';\nimport { ReaderFactory } from './types';\nimport path from 'path';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\n\nconst isInRange = (num: number, [start, end]: [number, number]) => {\n return num >= start && num <= end;\n};\n\nconst parsePortRange = (port: string): [number, number] => {\n const isRange = port.includes('-');\n if (isRange) {\n const range = port\n .split('-')\n .map(v => parseInt(v, 10))\n .filter(Boolean) as [number, number];\n if (range.length !== 2) throw new Error(`Port range is not valid: ${port}`);\n const [start, end] = range;\n if (start <= 0 || end <= 0 || start > end)\n throw new Error(`Port range is not valid: [${start}, ${end}]`);\n return range;\n }\n const parsedPort = parseInt(port, 10);\n return [parsedPort, parsedPort];\n};\n\nconst parsePortPredicate = (port: string | undefined) => {\n if (port) {\n const range = parsePortRange(port);\n return (url: URL) => {\n if (url.port) return isInRange(parseInt(url.port, 10), range);\n\n if (url.protocol === 'http:') return isInRange(80, range);\n if (url.protocol === 'https:') return isInRange(443, range);\n return false;\n };\n }\n return (url: URL) => !url.port;\n};\n\n/**\n * A {@link @backstage/backend-plugin-api#UrlReaderService} that does a plain fetch of the URL.\n *\n * @public\n */\nexport class FetchUrlReader implements UrlReaderService {\n /**\n * The factory creates a single reader that will be used for reading any URL that's listed\n * in configuration at `backend.reading.allow`. The allow list contains a list of objects describing\n * targets to allow, containing the following fields:\n *\n * `host`:\n * Either full hostnames to match, or subdomain wildcard matchers with a leading '*'.\n * For example 'example.com' and '*.example.com' are valid values, 'prod.*.example.com' is not.\n *\n * `paths`:\n * An optional list of paths which are allowed. If the list is omitted all paths are allowed.\n */\n static factory: ReaderFactory = ({ config }) => {\n const predicates =\n config\n .getOptionalConfigArray('backend.reading.allow')\n ?.map(allowConfig => {\n const paths = allowConfig.getOptionalStringArray('paths');\n const checkPath = paths\n ? (url: URL) => {\n const targetPath = path.posix.normalize(url.pathname);\n return paths.some(allowedPath =>\n targetPath.startsWith(allowedPath),\n );\n }\n : (_url: URL) => true;\n const host = allowConfig.getString('host');\n const [hostname, port] = host.split(':');\n\n const checkPort = parsePortPredicate(port);\n\n if (hostname.startsWith('*.')) {\n const suffix = hostname.slice(1);\n return (url: URL) =>\n url.hostname.endsWith(suffix) && checkPath(url) && checkPort(url);\n }\n return (url: URL) =>\n url.hostname === hostname && checkPath(url) && checkPort(url);\n }) ?? [];\n\n const reader = new FetchUrlReader();\n const predicate = (url: URL) => predicates.some(p => p(url));\n return [{ reader, predicate }];\n };\n\n async read(url: string): Promise {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise {\n let response: Response;\n try {\n response = await fetch(url, {\n headers: {\n ...(options?.etag && { 'If-None-Match': options.etag }),\n ...(options?.lastModifiedAfter && {\n 'If-Modified-Since': options.lastModifiedAfter.toUTCString(),\n }),\n ...(options?.token && { Authorization: `Bearer ${options.token}` }),\n },\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can\n // be removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n signal: options?.signal as any,\n });\n } catch (e) {\n throw new Error(`Unable to read ${url}, ${e}`);\n }\n\n if (response.status === 304) {\n throw new NotModifiedError();\n }\n\n if (response.ok) {\n return ReadUrlResponseFactory.fromResponse(response);\n }\n\n const message = `could not read ${url}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n async readTree(): Promise {\n throw new Error('FetchUrlReader does not implement readTree');\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise {\n const { pathname } = new URL(url);\n\n if (pathname.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 (error) {\n assertError(error);\n if (error.name === 'NotFoundError') {\n return {\n files: [],\n etag: '',\n };\n }\n throw error;\n }\n }\n\n toString() {\n return 'fetch{}';\n }\n}\n"],"names":["path","NotModifiedError","ReadUrlResponseFactory","NotFoundError","assertError"],"mappings":"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"} -\ No newline at end of file -+{"version":3,"file":"FetchUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/FetchUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2020 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 UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport {\n assertError,\n NotFoundError,\n NotModifiedError,\n} from '@backstage/errors';\nimport { ReaderFactory } from './types';\nimport path from 'path';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport { Config } from '@backstage/config';\n\nconst REDIRECT_STATUS_CODES = [301, 302, 307, 308];\nconst MAX_REDIRECTS = 5;\n\nconst isInRange = (num: number, [start, end]: [number, number]) => {\n return num >= start && num <= end;\n};\n\nconst parsePortRange = (port: string): [number, number] => {\n const isRange = port.includes('-');\n if (isRange) {\n const range = port\n .split('-')\n .map(v => parseInt(v, 10))\n .filter(Boolean) as [number, number];\n if (range.length !== 2) throw new Error(`Port range is not valid: ${port}`);\n const [start, end] = range;\n if (start <= 0 || end <= 0 || start > end)\n throw new Error(`Port range is not valid: [${start}, ${end}]`);\n return range;\n }\n const parsedPort = parseInt(port, 10);\n return [parsedPort, parsedPort];\n};\n\nconst parsePortPredicate = (port: string | undefined) => {\n if (port) {\n const range = parsePortRange(port);\n return (url: URL) => {\n if (url.port) return isInRange(parseInt(url.port, 10), range);\n\n if (url.protocol === 'http:') return isInRange(80, range);\n if (url.protocol === 'https:') return isInRange(443, range);\n return false;\n };\n }\n return (url: URL) => !url.port;\n};\n\nfunction predicateFromConfig(config: Config): (url: URL) => boolean {\n const allow = config\n .getOptionalConfigArray('backend.reading.allow')\n ?.map(allowConfig => {\n const paths = allowConfig.getOptionalStringArray('paths');\n const checkPath = paths\n ? (url: URL) => {\n const targetPath = path.posix.normalize(url.pathname);\n return paths.some(allowedPath =>\n targetPath.startsWith(allowedPath),\n );\n }\n : (_url: URL) => true;\n const host = allowConfig.getString('host');\n const [hostname, port] = host.split(':');\n\n const checkPort = parsePortPredicate(port);\n\n if (hostname.startsWith('*.')) {\n const suffix = hostname.slice(1);\n return (url: URL) =>\n url.hostname.endsWith(suffix) && checkPath(url) && checkPort(url);\n }\n\n return (url: URL) =>\n url.hostname === hostname && checkPath(url) && checkPort(url);\n });\n\n return allow?.length ? url => allow.some(p => p(url)) : () => false;\n}\n\n/**\n * A {@link @backstage/backend-plugin-api#UrlReaderService} that does a plain fetch of the URL.\n *\n * @public\n */\nexport class FetchUrlReader implements UrlReaderService {\n /**\n * The factory creates a single reader that will be used for reading any URL that's listed\n * in configuration at `backend.reading.allow`. The allow list contains a list of objects describing\n * targets to allow, containing the following fields:\n *\n * `host`:\n * Either full hostnames to match, or subdomain wildcard matchers with a leading '*'.\n * For example 'example.com' and '*.example.com' are valid values, 'prod.*.example.com' is not.\n *\n * `paths`:\n * An optional list of paths which are allowed. If the list is omitted all paths are allowed.\n */\n static factory: ReaderFactory = ({ config }) => {\n const predicate = predicateFromConfig(config);\n const reader = new FetchUrlReader({ predicate });\n return [{ reader, predicate }];\n };\n\n static fromConfig(config: Config): FetchUrlReader {\n return new FetchUrlReader({ predicate: predicateFromConfig(config) });\n }\n\n readonly #predicate: (url: URL) => boolean;\n\n private constructor(options: { predicate: (url: URL) => boolean }) {\n this.#predicate = options.predicate;\n }\n\n async read(url: string): Promise {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise {\n let currentUrl = url;\n\n for (\n let redirectCount = 0;\n redirectCount < MAX_REDIRECTS;\n redirectCount += 1\n ) {\n // Validate URL against predicate if configured\n const parsedUrl = new URL(currentUrl);\n if (!this.#predicate(parsedUrl)) {\n throw new Error(\n `URL not allowed by backend.reading.allow configuration: ${currentUrl}`,\n );\n }\n\n let response: Response;\n try {\n response = await fetch(currentUrl, {\n headers: {\n ...(options?.etag && { 'If-None-Match': options.etag }),\n ...(options?.lastModifiedAfter && {\n 'If-Modified-Since': options.lastModifiedAfter.toUTCString(),\n }),\n ...(options?.token && { Authorization: `Bearer ${options.token}` }),\n },\n // Handle redirects manually to validate targets against the allowlist\n redirect: 'manual',\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can\n // be removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n signal: options?.signal as any,\n });\n } catch (e) {\n throw new Error(`Unable to read ${currentUrl}, ${e}`);\n }\n\n if (response.ok) {\n return ReadUrlResponseFactory.fromResponse(response);\n }\n\n if (response.status === 304) {\n throw new NotModifiedError();\n }\n\n const location = response.headers.get('location');\n if (!REDIRECT_STATUS_CODES.includes(response.status) || !location) {\n const message = `could not read ${currentUrl}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n // Follow the redirect\n currentUrl = new URL(location, currentUrl).toString();\n }\n\n throw new Error(\n `Too many redirects (max ${MAX_REDIRECTS}) when reading ${url}`,\n );\n }\n\n async readTree(): Promise {\n throw new Error('FetchUrlReader does not implement readTree');\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise {\n const { pathname } = new URL(url);\n\n if (pathname.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 (error) {\n assertError(error);\n if (error.name === 'NotFoundError') {\n return {\n files: [],\n etag: '',\n };\n }\n throw error;\n }\n }\n\n toString() {\n return 'fetch{}';\n }\n}\n"],"names":["path","ReadUrlResponseFactory","NotModifiedError","NotFoundError","assertError"],"mappings":"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"} -\ No newline at end of file -diff --git a/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js b/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js -index b2c09c66c7ffd5edae7a43ec93133c09bb65491d..7a93a9b5a792598f62e1ec4a16d6ebbd37cd1f81 100644 ---- a/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js -+++ b/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js -@@ -1,5 +1,6 @@ - 'use strict'; - -+var backendPluginApi = require('@backstage/backend-plugin-api'); - var concatStream = require('concat-stream'); - var platformPath = require('path'); - var getRawBody = require('raw-body'); -@@ -67,7 +68,7 @@ class ReadableArrayResponse { - const dir = options?.targetDir ?? await fs__default.default.mkdtemp(platformPath__default.default.join(this.workDir, "backstage-")); - for (let i = 0; i < this.stream.length; i++) { - if (!this.stream[i].path.endsWith("/")) { -- const filePath = platformPath__default.default.join(dir, this.stream[i].path); -+ const filePath = backendPluginApi.resolveSafeChildPath(dir, this.stream[i].path); - await fs__default.default.mkdir(platformPath.dirname(filePath), { recursive: true }); - await pipeline(this.stream[i].data, fs__default.default.createWriteStream(filePath)); - } -diff --git a/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map b/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map -index 4306ab100f551981ca29b0ce2149140644f92b5e..37e0ef6678b7879c81ed271d46be533eb26e2d3b 100644 ---- a/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map -+++ b/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map -@@ -1 +1 @@ --{"version":3,"file":"ReadableArrayResponse.cjs.js","sources":["../../../../../src/entrypoints/urlReader/lib/tree/ReadableArrayResponse.ts"],"sourcesContent":["/*\n * Copyright 2020 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 UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadTreeResponseDirOptions,\n UrlReaderServiceReadTreeResponseFile,\n} from '@backstage/backend-plugin-api';\nimport concatStream from 'concat-stream';\nimport platformPath, { dirname } from 'path';\nimport getRawBody from 'raw-body';\nimport fs from 'fs-extra';\nimport { promisify } from 'util';\nimport tar from 'tar';\nimport { pipeline as pipelineCb, Readable } from 'stream';\nimport { FromReadableArrayOptions } from '../types';\n\nconst pipeline = promisify(pipelineCb);\n\n/**\n * Wraps a array of Readable objects into a tree response reader.\n */\nexport class ReadableArrayResponse implements UrlReaderServiceReadTreeResponse {\n private read = false;\n private readonly stream: FromReadableArrayOptions;\n private readonly workDir: string;\n public readonly etag: string;\n\n constructor(stream: FromReadableArrayOptions, workDir: string, etag: string) {\n this.stream = stream;\n this.workDir = workDir;\n this.etag = etag;\n }\n\n // Make sure the input stream is only read once\n private onlyOnce() {\n if (this.read) {\n throw new Error('Response has already been read');\n }\n this.read = true;\n }\n\n async files(): Promise {\n this.onlyOnce();\n\n const files = Array();\n\n for (let i = 0; i < this.stream.length; i++) {\n if (!this.stream[i].path.endsWith('/')) {\n files.push({\n path: this.stream[i].path,\n content: () => getRawBody(this.stream[i].data),\n lastModifiedAt: this.stream[i]?.lastModifiedAt,\n });\n }\n }\n\n return files;\n }\n\n async archive(): Promise {\n const tmpDir = await this.dir();\n\n try {\n const data = await new Promise(async resolve => {\n await pipeline(\n tar.create({ cwd: tmpDir }, ['']),\n concatStream(resolve),\n );\n });\n return Readable.from(data);\n } finally {\n await fs.remove(tmpDir);\n }\n }\n\n async dir(\n options?: UrlReaderServiceReadTreeResponseDirOptions,\n ): Promise {\n this.onlyOnce();\n\n const dir =\n options?.targetDir ??\n (await fs.mkdtemp(platformPath.join(this.workDir, 'backstage-')));\n\n for (let i = 0; i < this.stream.length; i++) {\n if (!this.stream[i].path.endsWith('/')) {\n const filePath = platformPath.join(dir, this.stream[i].path);\n await fs.mkdir(dirname(filePath), { recursive: true });\n await pipeline(this.stream[i].data, fs.createWriteStream(filePath));\n }\n }\n\n return dir;\n }\n}\n"],"names":["promisify","pipelineCb","getRawBody","tar","concatStream","Readable","fs","platformPath","dirname"],"mappings":"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"} -\ No newline at end of file -+{"version":3,"file":"ReadableArrayResponse.cjs.js","sources":["../../../../../src/entrypoints/urlReader/lib/tree/ReadableArrayResponse.ts"],"sourcesContent":["/*\n * Copyright 2020 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 resolveSafeChildPath,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadTreeResponseDirOptions,\n UrlReaderServiceReadTreeResponseFile,\n} from '@backstage/backend-plugin-api';\nimport concatStream from 'concat-stream';\nimport platformPath, { dirname } from 'path';\nimport getRawBody from 'raw-body';\nimport fs from 'fs-extra';\nimport { promisify } from 'util';\nimport tar from 'tar';\nimport { pipeline as pipelineCb, Readable } from 'stream';\nimport { FromReadableArrayOptions } from '../types';\n\nconst pipeline = promisify(pipelineCb);\n\n/**\n * Wraps a array of Readable objects into a tree response reader.\n */\nexport class ReadableArrayResponse implements UrlReaderServiceReadTreeResponse {\n private read = false;\n private readonly stream: FromReadableArrayOptions;\n private readonly workDir: string;\n public readonly etag: string;\n\n constructor(stream: FromReadableArrayOptions, workDir: string, etag: string) {\n this.stream = stream;\n this.workDir = workDir;\n this.etag = etag;\n }\n\n // Make sure the input stream is only read once\n private onlyOnce() {\n if (this.read) {\n throw new Error('Response has already been read');\n }\n this.read = true;\n }\n\n async files(): Promise {\n this.onlyOnce();\n\n const files = Array();\n\n for (let i = 0; i < this.stream.length; i++) {\n if (!this.stream[i].path.endsWith('/')) {\n files.push({\n path: this.stream[i].path,\n content: () => getRawBody(this.stream[i].data),\n lastModifiedAt: this.stream[i]?.lastModifiedAt,\n });\n }\n }\n\n return files;\n }\n\n async archive(): Promise {\n const tmpDir = await this.dir();\n\n try {\n const data = await new Promise(async resolve => {\n await pipeline(\n tar.create({ cwd: tmpDir }, ['']),\n concatStream(resolve),\n );\n });\n return Readable.from(data);\n } finally {\n await fs.remove(tmpDir);\n }\n }\n\n async dir(\n options?: UrlReaderServiceReadTreeResponseDirOptions,\n ): Promise {\n this.onlyOnce();\n\n const dir =\n options?.targetDir ??\n (await fs.mkdtemp(platformPath.join(this.workDir, 'backstage-')));\n\n for (let i = 0; i < this.stream.length; i++) {\n if (!this.stream[i].path.endsWith('/')) {\n const filePath = resolveSafeChildPath(dir, this.stream[i].path);\n await fs.mkdir(dirname(filePath), { recursive: true });\n await pipeline(this.stream[i].data, fs.createWriteStream(filePath));\n }\n }\n\n return dir;\n }\n}\n"],"names":["promisify","pipelineCb","getRawBody","tar","concatStream","Readable","fs","platformPath","resolveSafeChildPath","dirname"],"mappings":"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"} -\ No newline at end of file -diff --git a/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js b/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js -index 8beeb818214e021335f54b745989936638bd8365..29c48cb9294d69aa10d006cdaee6271b903fdacb 100644 ---- a/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js -+++ b/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js -@@ -1,5 +1,6 @@ - 'use strict'; - -+var backendPluginApi = require('@backstage/backend-plugin-api'); - var concatStream = require('concat-stream'); - var fs = require('fs-extra'); - var platformPath = require('path'); -@@ -121,6 +122,17 @@ class TarArchiveResponse { - if (filterError) { - return false; - } -+ const entry = stat; -+ if ((entry.type === "SymbolicLink" || entry.type === "Link") && entry.linkpath) { -+ const strippedPath = path.split("/").slice(strip).join("/"); -+ const linkDir = platformPath__default.default.dirname( -+ platformPath__default.default.join(dir, strippedPath) -+ ); -+ const targetPath = platformPath__default.default.resolve(linkDir, entry.linkpath); -+ if (!backendPluginApi.isChildPath(dir, targetPath)) { -+ return false; -+ } -+ } - const relativePath = this.stripFirstDirectory ? util$1.stripFirstDirectoryFromPath(path) : path; - if (this.subPath && !relativePath.startsWith(this.subPath)) { - return false; -diff --git a/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map b/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map -index 2c5b75328153e6a261588390d9af6d05d0c3454e..b1fd43e5b040043be1824f01600d6c131ab88d03 100644 ---- a/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map -+++ b/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map -@@ -1 +1 @@ --{"version":3,"file":"TarArchiveResponse.cjs.js","sources":["../../../../../src/entrypoints/urlReader/lib/tree/TarArchiveResponse.ts"],"sourcesContent":["/*\n * Copyright 2020 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 UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadTreeResponseDirOptions,\n UrlReaderServiceReadTreeResponseFile,\n} from '@backstage/backend-plugin-api';\nimport concatStream from 'concat-stream';\nimport fs from 'fs-extra';\nimport platformPath from 'path';\nimport { pipeline as pipelineCb, Readable } from 'stream';\nimport tar, { Parse, ParseStream, ReadEntry } from 'tar';\nimport { promisify } from 'util';\nimport { stripFirstDirectoryFromPath } from './util';\n\n// Tar types for `Parse` is not a proper constructor, but it should be\nconst TarParseStream = Parse as unknown as { new (): ParseStream };\n\nconst pipeline = promisify(pipelineCb);\n\n/**\n * Wraps a tar archive stream into a tree response reader.\n */\nexport class TarArchiveResponse implements UrlReaderServiceReadTreeResponse {\n private read = false;\n private readonly stream: Readable;\n private readonly subPath: string;\n private readonly workDir: string;\n public readonly etag: string;\n private readonly filter?: (path: string, info: { size: number }) => boolean;\n private readonly stripFirstDirectory: boolean;\n\n constructor(\n stream: Readable,\n subPath: string,\n workDir: string,\n etag: string,\n filter?: (path: string, info: { size: number }) => boolean,\n stripFirstDirectory: boolean = true,\n ) {\n this.stream = stream;\n this.subPath = subPath;\n this.workDir = workDir;\n this.etag = etag;\n this.filter = filter;\n this.stripFirstDirectory = stripFirstDirectory;\n if (subPath) {\n if (!subPath.endsWith('/')) {\n this.subPath += '/';\n }\n if (subPath.startsWith('/')) {\n throw new TypeError(\n `TarArchiveResponse subPath must not start with a /, got '${subPath}'`,\n );\n }\n }\n\n this.etag = etag;\n }\n\n // Make sure the input stream is only read once\n private onlyOnce() {\n if (this.read) {\n throw new Error('Response has already been read');\n }\n this.read = true;\n }\n\n async files(): Promise {\n this.onlyOnce();\n\n const files = Array();\n const parser = new TarParseStream();\n\n parser.on('entry', (entry: ReadEntry & Readable) => {\n if (entry.type === 'Directory') {\n entry.resume();\n return;\n }\n\n // File path relative to the root extracted directory. Will remove the\n // top level dir name from the path since its name is hard to predetermine.\n const relativePath = this.stripFirstDirectory\n ? stripFirstDirectoryFromPath(entry.path)\n : entry.path;\n\n if (this.subPath) {\n if (!relativePath.startsWith(this.subPath)) {\n entry.resume();\n return;\n }\n }\n\n const path = relativePath.slice(this.subPath.length);\n if (this.filter) {\n if (!this.filter(path, { size: entry.remain })) {\n entry.resume();\n return;\n }\n }\n\n const content = new Promise(async resolve => {\n await pipeline(entry, concatStream(resolve));\n });\n\n files.push({\n path,\n content: () => content,\n });\n\n entry.resume();\n });\n\n await pipeline(this.stream, parser);\n\n return files;\n }\n\n async archive(): Promise {\n if (!this.subPath) {\n this.onlyOnce();\n\n return this.stream;\n }\n\n // TODO(Rugvip): method for repacking a tar with a subpath is to simply extract into a\n // tmp dir and recreate the archive. Would be nicer to stream things instead.\n const tmpDir = await this.dir();\n\n try {\n const data = await new Promise(async resolve => {\n await pipeline(\n tar.create({ cwd: tmpDir }, ['']),\n concatStream(resolve),\n );\n });\n return Readable.from(data);\n } finally {\n await fs.remove(tmpDir);\n }\n }\n\n async dir(\n options?: UrlReaderServiceReadTreeResponseDirOptions,\n ): Promise {\n this.onlyOnce();\n\n const dir =\n options?.targetDir ??\n (await fs.mkdtemp(platformPath.join(this.workDir, 'backstage-')));\n\n // Equivalent of tar --strip-components=N\n // When no subPath is given, remove just 1 top level directory\n let strip = this.subPath ? this.subPath.split('/').length : 1;\n if (!this.stripFirstDirectory) {\n strip--;\n }\n\n let filterError: Error | undefined = undefined;\n await pipeline(\n this.stream,\n tar.extract({\n strip,\n cwd: dir,\n filter: (path, stat) => {\n // Filter errors will short-circuit the rest of the filtering and then throw\n if (filterError) {\n return false;\n }\n\n // File path relative to the root extracted directory. Will remove the\n // top level dir name from the path since its name is hard to predetermine.\n const relativePath = this.stripFirstDirectory\n ? stripFirstDirectoryFromPath(path)\n : path;\n if (this.subPath && !relativePath.startsWith(this.subPath)) {\n return false;\n }\n if (this.filter) {\n const innerPath = path.split('/').slice(strip).join('/');\n try {\n return this.filter(innerPath, { size: stat.size });\n } catch (error) {\n filterError = error;\n return false;\n }\n }\n return true;\n },\n }),\n );\n\n if (filterError) {\n // If the dir was provided we don't want to remove it, but if it wasn't it means\n // we created a temporary directory and we should remove it.\n if (!options?.targetDir) {\n await fs.remove(dir).catch(() => {});\n }\n throw filterError;\n }\n\n return dir;\n }\n}\n"],"names":["Parse","promisify","pipelineCb","stripFirstDirectoryFromPath","concatStream","tar","Readable","fs","platformPath"],"mappings":"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"} -\ No newline at end of file -+{"version":3,"file":"TarArchiveResponse.cjs.js","sources":["../../../../../src/entrypoints/urlReader/lib/tree/TarArchiveResponse.ts"],"sourcesContent":["/*\n * Copyright 2020 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 isChildPath,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadTreeResponseDirOptions,\n UrlReaderServiceReadTreeResponseFile,\n} from '@backstage/backend-plugin-api';\nimport concatStream from 'concat-stream';\nimport fs from 'fs-extra';\nimport platformPath from 'path';\nimport { pipeline as pipelineCb, Readable } from 'stream';\nimport tar, { FileStat, Parse, ParseStream, ReadEntry } from 'tar';\nimport { promisify } from 'util';\nimport { stripFirstDirectoryFromPath } from './util';\n\n// Tar types for `Parse` is not a proper constructor, but it should be\nconst TarParseStream = Parse as unknown as { new (): ParseStream };\n\nconst pipeline = promisify(pipelineCb);\n\n/**\n * Wraps a tar archive stream into a tree response reader.\n */\nexport class TarArchiveResponse implements UrlReaderServiceReadTreeResponse {\n private read = false;\n private readonly stream: Readable;\n private readonly subPath: string;\n private readonly workDir: string;\n public readonly etag: string;\n private readonly filter?: (path: string, info: { size: number }) => boolean;\n private readonly stripFirstDirectory: boolean;\n\n constructor(\n stream: Readable,\n subPath: string,\n workDir: string,\n etag: string,\n filter?: (path: string, info: { size: number }) => boolean,\n stripFirstDirectory: boolean = true,\n ) {\n this.stream = stream;\n this.subPath = subPath;\n this.workDir = workDir;\n this.etag = etag;\n this.filter = filter;\n this.stripFirstDirectory = stripFirstDirectory;\n if (subPath) {\n if (!subPath.endsWith('/')) {\n this.subPath += '/';\n }\n if (subPath.startsWith('/')) {\n throw new TypeError(\n `TarArchiveResponse subPath must not start with a /, got '${subPath}'`,\n );\n }\n }\n\n this.etag = etag;\n }\n\n // Make sure the input stream is only read once\n private onlyOnce() {\n if (this.read) {\n throw new Error('Response has already been read');\n }\n this.read = true;\n }\n\n async files(): Promise {\n this.onlyOnce();\n\n const files = Array();\n const parser = new TarParseStream();\n\n parser.on('entry', (entry: ReadEntry & Readable) => {\n if (entry.type === 'Directory') {\n entry.resume();\n return;\n }\n\n // File path relative to the root extracted directory. Will remove the\n // top level dir name from the path since its name is hard to predetermine.\n const relativePath = this.stripFirstDirectory\n ? stripFirstDirectoryFromPath(entry.path)\n : entry.path;\n\n if (this.subPath) {\n if (!relativePath.startsWith(this.subPath)) {\n entry.resume();\n return;\n }\n }\n\n const path = relativePath.slice(this.subPath.length);\n if (this.filter) {\n if (!this.filter(path, { size: entry.remain })) {\n entry.resume();\n return;\n }\n }\n\n const content = new Promise(async resolve => {\n await pipeline(entry, concatStream(resolve));\n });\n\n files.push({\n path,\n content: () => content,\n });\n\n entry.resume();\n });\n\n await pipeline(this.stream, parser);\n\n return files;\n }\n\n async archive(): Promise {\n if (!this.subPath) {\n this.onlyOnce();\n\n return this.stream;\n }\n\n // TODO(Rugvip): method for repacking a tar with a subpath is to simply extract into a\n // tmp dir and recreate the archive. Would be nicer to stream things instead.\n const tmpDir = await this.dir();\n\n try {\n const data = await new Promise(async resolve => {\n await pipeline(\n tar.create({ cwd: tmpDir }, ['']),\n concatStream(resolve),\n );\n });\n return Readable.from(data);\n } finally {\n await fs.remove(tmpDir);\n }\n }\n\n async dir(\n options?: UrlReaderServiceReadTreeResponseDirOptions,\n ): Promise {\n this.onlyOnce();\n\n const dir =\n options?.targetDir ??\n (await fs.mkdtemp(platformPath.join(this.workDir, 'backstage-')));\n\n // Equivalent of tar --strip-components=N\n // When no subPath is given, remove just 1 top level directory\n let strip = this.subPath ? this.subPath.split('/').length : 1;\n if (!this.stripFirstDirectory) {\n strip--;\n }\n\n let filterError: Error | undefined = undefined;\n await pipeline(\n this.stream,\n tar.extract({\n strip,\n cwd: dir,\n filter: (path, stat) => {\n // Filter errors will short-circuit the rest of the filtering and then throw\n if (filterError) {\n return false;\n }\n\n // Block symlinks/hardlinks that escape the extraction directory\n const entry = stat as FileStat & { type?: string; linkpath?: string };\n if (\n (entry.type === 'SymbolicLink' || entry.type === 'Link') &&\n entry.linkpath\n ) {\n const strippedPath = path.split('/').slice(strip).join('/');\n const linkDir = platformPath.dirname(\n platformPath.join(dir, strippedPath),\n );\n const targetPath = platformPath.resolve(linkDir, entry.linkpath);\n if (!isChildPath(dir, targetPath)) {\n return false;\n }\n }\n\n // File path relative to the root extracted directory. Will remove the\n // top level dir name from the path since its name is hard to predetermine.\n const relativePath = this.stripFirstDirectory\n ? stripFirstDirectoryFromPath(path)\n : path;\n if (this.subPath && !relativePath.startsWith(this.subPath)) {\n return false;\n }\n if (this.filter) {\n const innerPath = path.split('/').slice(strip).join('/');\n try {\n return this.filter(innerPath, { size: stat.size });\n } catch (error) {\n filterError = error;\n return false;\n }\n }\n return true;\n },\n }),\n );\n\n if (filterError) {\n // If the dir was provided we don't want to remove it, but if it wasn't it means\n // we created a temporary directory and we should remove it.\n if (!options?.targetDir) {\n await fs.remove(dir).catch(() => {});\n }\n throw filterError;\n }\n\n return dir;\n }\n}\n"],"names":["Parse","promisify","pipelineCb","stripFirstDirectoryFromPath","concatStream","tar","Readable","fs","platformPath","isChildPath"],"mappings":"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"} -\ No newline at end of file -diff --git a/dist/package.json.cjs.js b/dist/package.json.cjs.js -index ac692d26b543f95f44c0e39af7223cc65ce966cd..6b592844cc16594960773ba367e2fa5c975f78a4 100644 ---- a/dist/package.json.cjs.js -+++ b/dist/package.json.cjs.js -@@ -2,7 +2,7 @@ - - Object.defineProperty(exports, '__esModule', { value: true }); - --var version = "0.13.1"; -+var version = "0.13.2"; - var packageinfo = { - version: version}; - -diff --git a/dist/urlReader.d.ts b/dist/urlReader.d.ts -index 4244ea520d4ea50558979e71152e5fd4e7518429..391e52c4ddfa2a9c61efff140b0165096880cd6a 100644 ---- a/dist/urlReader.d.ts -+++ b/dist/urlReader.d.ts -@@ -3,6 +3,7 @@ import { RootConfigService, LoggerService, UrlReaderServiceReadTreeResponse, Url - import { AzureIntegration, AzureDevOpsCredentialsProvider, BitbucketCloudIntegration, BitbucketIntegration, BitbucketServerIntegration, GerritIntegration, GithubIntegration, GithubCredentialsProvider, GitLabIntegration, GiteaIntegration, HarnessIntegration, AwsS3Integration, AzureCredentialsManager, AzureBlobStorageIntergation } from '@backstage/integration'; - import { Readable } from 'stream'; - import { AwsCredentialsManager } from '@backstage/integration-aws-node'; -+import { Config } from '@backstage/config'; - - /** - * A predicate that decides whether a specific {@link @backstage/backend-plugin-api#UrlReaderService} can handle a -@@ -374,6 +375,7 @@ declare class AzureBlobStorageUrlReader implements UrlReaderService { - * @public - */ - declare class FetchUrlReader implements UrlReaderService { -+ #private; - /** - * The factory creates a single reader that will be used for reading any URL that's listed - * in configuration at `backend.reading.allow`. The allow list contains a list of objects describing -@@ -387,6 +389,8 @@ declare class FetchUrlReader implements UrlReaderService { - * An optional list of paths which are allowed. If the list is omitted all paths are allowed. - */ - static factory: ReaderFactory; -+ static fromConfig(config: Config): FetchUrlReader; -+ private constructor(); - read(url: string): Promise; - readUrl(url: string, options?: UrlReaderServiceReadUrlOptions): Promise; - readTree(): Promise; diff --git a/.yarn/patches/@backstage-plugin-scaffolder-backend-npm-3.0.2-01a7364606.patch b/.yarn/patches/@backstage-plugin-scaffolder-backend-npm-3.0.2-01a7364606.patch deleted file mode 100644 index 8b7016a322..0000000000 --- a/.yarn/patches/@backstage-plugin-scaffolder-backend-npm-3.0.2-01a7364606.patch +++ /dev/null @@ -1,9 +0,0 @@ -diff --git a/dist/scaffolder/actions/builtin/filesystem/delete.cjs.js.map b/dist/scaffolder/actions/builtin/filesystem/delete.cjs.js.map -index 060babc8f57cc3c15d7869f78ea4f7b3bb01300a..8b77d360c817656e4f232a815a80b145260d1b90 100644 ---- a/dist/scaffolder/actions/builtin/filesystem/delete.cjs.js.map -+++ b/dist/scaffolder/actions/builtin/filesystem/delete.cjs.js.map -@@ -1 +1 @@ --{"version":3,"file":"delete.cjs.js","sources":["../../../../../src/scaffolder/actions/builtin/filesystem/delete.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { createTemplateAction } from '@backstage/plugin-scaffolder-node';\nimport { InputError } from '@backstage/errors';\nimport { resolveSafeChildPath } from '@backstage/backend-plugin-api';\nimport fs from 'fs-extra';\nimport globby from 'globby';\nimport { examples } from './delete.examples';\n\n/**\n * Creates new action that enables deletion of files and directories in the workspace.\n * @public\n */\nexport const createFilesystemDeleteAction = () => {\n return createTemplateAction({\n id: 'fs:delete',\n description: 'Deletes files and directories from the workspace',\n examples,\n schema: {\n input: {\n files: z =>\n z.array(z.string(), {\n description: 'A list of files and directories that will be deleted',\n }),\n },\n },\n supportsDryRun: true,\n async handler(ctx) {\n if (!Array.isArray(ctx.input?.files)) {\n throw new InputError('files must be an Array');\n }\n\n for (const file of ctx.input.files) {\n // globby cannot handle backslash file separators\n const safeFilepath = resolveSafeChildPath(\n ctx.workspacePath,\n file,\n ).replace(/\\\\/g, '/');\n const resolvedPaths = await globby(safeFilepath, {\n cwd: ctx.workspacePath,\n absolute: true,\n dot: true,\n });\n\n for (const filepath of resolvedPaths) {\n try {\n const safePath = resolveSafeChildPath(ctx.workspacePath, filepath);\n await fs.remove(safePath);\n ctx.logger.info(`File ${safePath} deleted successfully`);\n } catch (err) {\n ctx.logger.error(`Failed to delete file`, err);\n throw err;\n }\n }\n }\n },\n });\n};\n"],"names":["createTemplateAction","examples","InputError","resolveSafeChildPath","globby","fs"],"mappings":";;;;;;;;;;;;;;AA2BO,MAAM,+BAA+B,MAAM;AAChD,EAAA,OAAOA,yCAAA,CAAqB;AAAA,IAC1B,EAAA,EAAI,WAAA;AAAA,IACJ,WAAA,EAAa,kDAAA;AAAA,cACbC,wBAAA;AAAA,IACA,MAAA,EAAQ;AAAA,MACN,KAAA,EAAO;AAAA,QACL,OAAO,CAAA,CAAA,KACL,CAAA,CAAE,KAAA,CAAM,CAAA,CAAE,QAAO,EAAG;AAAA,UAClB,WAAA,EAAa;AAAA,SACd;AAAA;AACL,KACF;AAAA,IACA,cAAA,EAAgB,IAAA;AAAA,IAChB,MAAM,QAAQ,GAAA,EAAK;AACjB,MAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,KAAA,EAAO,KAAK,CAAA,EAAG;AACpC,QAAA,MAAM,IAAIC,kBAAW,wBAAwB,CAAA;AAAA,MAC/C;AAEA,MAAA,KAAA,MAAW,IAAA,IAAQ,GAAA,CAAI,KAAA,CAAM,KAAA,EAAO;AAElC,QAAA,MAAM,YAAA,GAAeC,qCAAA;AAAA,UACnB,GAAA,CAAI,aAAA;AAAA,UACJ;AAAA,SACF,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA;AACpB,QAAA,MAAM,aAAA,GAAgB,MAAMC,uBAAA,CAAO,YAAA,EAAc;AAAA,UAC/C,KAAK,GAAA,CAAI,aAAA;AAAA,UACT,QAAA,EAAU,IAAA;AAAA,UACV,GAAA,EAAK;AAAA,SACN,CAAA;AAED,QAAA,KAAA,MAAW,YAAY,aAAA,EAAe;AACpC,UAAA,IAAI;AACF,YAAA,MAAM,QAAA,GAAWD,qCAAA,CAAqB,GAAA,CAAI,aAAA,EAAe,QAAQ,CAAA;AACjE,YAAA,MAAME,mBAAA,CAAG,OAAO,QAAQ,CAAA;AACxB,YAAA,GAAA,CAAI,MAAA,CAAO,IAAA,CAAK,CAAA,KAAA,EAAQ,QAAQ,CAAA,qBAAA,CAAuB,CAAA;AAAA,UACzD,SAAS,GAAA,EAAK;AACZ,YAAA,GAAA,CAAI,MAAA,CAAO,KAAA,CAAM,CAAA,qBAAA,CAAA,EAAyB,GAAG,CAAA;AAC7C,YAAA,MAAM,GAAA;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,GACD,CAAA;AACH;;;;"} -\ No newline at end of file -+{"version":3,"file":"delete.cjs.js","sources":["../../../../../src/scaffolder/actions/builtin/filesystem/delete.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { createTemplateAction } from '@backstage/plugin-scaffolder-node';\nimport { InputError } from '@backstage/errors';\nimport {\n isChildPath,\n resolveSafeChildPath,\n} from '@backstage/backend-plugin-api';\nimport fs from 'fs-extra';\nimport globby from 'globby';\nimport { examples } from './delete.examples';\n\n/**\n * Creates new action that enables deletion of files and directories in the workspace.\n * @public\n */\nexport const createFilesystemDeleteAction = () => {\n return createTemplateAction({\n id: 'fs:delete',\n description: 'Deletes files and directories from the workspace',\n examples,\n schema: {\n input: {\n files: z =>\n z.array(z.string(), {\n description: 'A list of files and directories that will be deleted',\n }),\n },\n },\n supportsDryRun: true,\n async handler(ctx) {\n if (!Array.isArray(ctx.input?.files)) {\n throw new InputError('files must be an Array');\n }\n\n for (const file of ctx.input.files) {\n // globby cannot handle backslash file separators\n const safeFilepath = resolveSafeChildPath(\n ctx.workspacePath,\n file,\n ).replace(/\\\\/g, '/');\n const resolvedPaths = await globby(safeFilepath, {\n cwd: ctx.workspacePath,\n absolute: true,\n dot: true,\n });\n\n for (const filepath of resolvedPaths) {\n try {\n const safePath = resolveSafeChildPath(ctx.workspacePath, filepath);\n await fs.remove(safePath);\n ctx.logger.info(`File ${safePath} deleted successfully`);\n } catch (err) {\n ctx.logger.error(`Failed to delete file`, err);\n throw err;\n }\n }\n }\n },\n });\n};\n"],"names":["createTemplateAction","examples","InputError","resolveSafeChildPath","globby","fs"],"mappings":";;;;;;;;;;;;;;AA8BO,MAAM,+BAA+B,MAAM;AAChD,EAAA,OAAOA,yCAAA,CAAqB;AAAA,IAC1B,EAAA,EAAI,WAAA;AAAA,IACJ,WAAA,EAAa,kDAAA;AAAA,cACbC,wBAAA;AAAA,IACA,MAAA,EAAQ;AAAA,MACN,KAAA,EAAO;AAAA,QACL,OAAO,CAAA,CAAA,KACL,CAAA,CAAE,KAAA,CAAM,CAAA,CAAE,QAAO,EAAG;AAAA,UAClB,WAAA,EAAa;AAAA,SACd;AAAA;AACL,KACF;AAAA,IACA,cAAA,EAAgB,IAAA;AAAA,IAChB,MAAM,QAAQ,GAAA,EAAK;AACjB,MAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,KAAA,EAAO,KAAK,CAAA,EAAG;AACpC,QAAA,MAAM,IAAIC,kBAAW,wBAAwB,CAAA;AAAA,MAC/C;AAEA,MAAA,KAAA,MAAW,IAAA,IAAQ,GAAA,CAAI,KAAA,CAAM,KAAA,EAAO;AAElC,QAAA,MAAM,YAAA,GAAeC,qCAAA;AAAA,UACnB,GAAA,CAAI,aAAA;AAAA,UACJ;AAAA,SACF,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA;AACpB,QAAA,MAAM,aAAA,GAAgB,MAAMC,uBAAA,CAAO,YAAA,EAAc;AAAA,UAC/C,KAAK,GAAA,CAAI,aAAA;AAAA,UACT,QAAA,EAAU,IAAA;AAAA,UACV,GAAA,EAAK;AAAA,SACN,CAAA;AAED,QAAA,KAAA,MAAW,YAAY,aAAA,EAAe;AACpC,UAAA,IAAI;AACF,YAAA,MAAM,QAAA,GAAWD,qCAAA,CAAqB,GAAA,CAAI,aAAA,EAAe,QAAQ,CAAA;AACjE,YAAA,MAAME,mBAAA,CAAG,OAAO,QAAQ,CAAA;AACxB,YAAA,GAAA,CAAI,MAAA,CAAO,IAAA,CAAK,CAAA,KAAA,EAAQ,QAAQ,CAAA,qBAAA,CAAuB,CAAA;AAAA,UACzD,SAAS,GAAA,EAAK;AACZ,YAAA,GAAA,CAAI,MAAA,CAAO,KAAA,CAAM,CAAA,qBAAA,CAAA,EAAyB,GAAG,CAAA;AAC7C,YAAA,MAAM,GAAA;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,GACD,CAAA;AACH;;;;"} -\ No newline at end of file diff --git a/.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.1-7ce02a35bc.patch b/.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.1-7ce02a35bc.patch deleted file mode 100644 index f3f987786e..0000000000 --- a/.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.1-7ce02a35bc.patch +++ /dev/null @@ -1,22 +0,0 @@ -diff --git a/dist/actions/fetch.cjs.js b/dist/actions/fetch.cjs.js -index 9b4b30cc47e65d3ad71f819adecd6deb52200057..21f534c4a13dfaf084fa21a632a541ace31209dc 100644 ---- a/dist/actions/fetch.cjs.js -+++ b/dist/actions/fetch.cjs.js -@@ -23,7 +23,7 @@ async function fetchContents(options) { - if (!fetchUrlIsAbsolute && baseUrl?.startsWith("file://")) { - const basePath = baseUrl.slice("file://".length); - const srcDir = backendPluginApi.resolveSafeChildPath(path__default.default.dirname(basePath), fetchUrl); -- await fs__default.default.copy(srcDir, outputPath); -+ await fs__default.default.copy(srcDir, outputPath, { filter: (src) => backendPluginApi.isChildPath(srcDir, src) }); - } else { - const readUrl = getReadUrl(fetchUrl, baseUrl, integrations); - const res = await reader.readTree(readUrl, { token }); -diff --git a/dist/actions/fetch.cjs.js.map b/dist/actions/fetch.cjs.js.map -index c7a6ca5ba4adb8eef9b5659541e3c209421f43e2..f9edfd651d826639d0b79919bf72fbed88e873b5 100644 ---- a/dist/actions/fetch.cjs.js.map -+++ b/dist/actions/fetch.cjs.js.map -@@ -1 +1 @@ --{"version":3,"file":"fetch.cjs.js","sources":["../../src/actions/fetch.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { UrlReaderService } from '@backstage/backend-plugin-api';\nimport { resolveSafeChildPath } from '@backstage/backend-plugin-api';\nimport { InputError } from '@backstage/errors';\nimport { ScmIntegrations } from '@backstage/integration';\nimport fs from 'fs-extra';\nimport path from 'path';\n\n/**\n * A helper function that reads the contents of a directory from the given URL.\n * Can be used in your own actions, and also used behind fetch:template and fetch:plain\n *\n * @public\n */\nexport async function fetchContents(options: {\n reader: UrlReaderService;\n integrations: ScmIntegrations;\n baseUrl?: string;\n fetchUrl?: string;\n outputPath: string;\n token?: string;\n}) {\n const {\n reader,\n integrations,\n baseUrl,\n fetchUrl = '.',\n outputPath,\n token,\n } = options;\n\n const fetchUrlIsAbsolute = isFetchUrlAbsolute(fetchUrl);\n\n // We handle both file locations and url ones\n if (!fetchUrlIsAbsolute && baseUrl?.startsWith('file://')) {\n const basePath = baseUrl.slice('file://'.length);\n const srcDir = resolveSafeChildPath(path.dirname(basePath), fetchUrl);\n await fs.copy(srcDir, outputPath);\n } else {\n const readUrl = getReadUrl(fetchUrl, baseUrl, integrations);\n\n const res = await reader.readTree(readUrl, { token });\n await fs.ensureDir(outputPath);\n await res.dir({ targetDir: outputPath });\n }\n}\n\n/**\n * A helper function that reads the content of a single file from the given URL.\n * Can be used in your own actions, and also used behind `fetch:plain:file`\n *\n * @public\n */\nexport async function fetchFile(options: {\n reader: UrlReaderService;\n integrations: ScmIntegrations;\n baseUrl?: string;\n fetchUrl?: string;\n outputPath: string;\n token?: string;\n}) {\n const {\n reader,\n integrations,\n baseUrl,\n fetchUrl = '.',\n outputPath,\n token,\n } = options;\n\n const fetchUrlIsAbsolute = isFetchUrlAbsolute(fetchUrl);\n\n // We handle both file locations and url ones\n if (!fetchUrlIsAbsolute && baseUrl?.startsWith('file://')) {\n const basePath = baseUrl.slice('file://'.length);\n const src = resolveSafeChildPath(path.dirname(basePath), fetchUrl);\n await fs.copyFile(src, outputPath);\n } else {\n const readUrl = getReadUrl(fetchUrl, baseUrl, integrations);\n\n const res = await reader.readUrl(readUrl, { token });\n await fs.ensureDir(path.dirname(outputPath));\n const buffer = await res.buffer();\n await fs.outputFile(outputPath, buffer);\n }\n}\n\nfunction isFetchUrlAbsolute(fetchUrl: string) {\n let fetchUrlIsAbsolute = false;\n try {\n // eslint-disable-next-line no-new\n new URL(fetchUrl);\n fetchUrlIsAbsolute = true;\n } catch {\n /* ignored */\n }\n return fetchUrlIsAbsolute;\n}\n\nfunction getReadUrl(\n fetchUrl: string,\n baseUrl: string | undefined,\n integrations: ScmIntegrations,\n) {\n if (isFetchUrlAbsolute(fetchUrl)) {\n return fetchUrl;\n } else if (baseUrl) {\n const integration = integrations.byUrl(baseUrl);\n if (!integration) {\n throw new InputError(`No integration found for location ${baseUrl}`);\n }\n\n return integration.resolveUrl({\n url: fetchUrl,\n base: baseUrl,\n });\n }\n throw new InputError(\n `Failed to fetch, template location could not be determined and the fetch URL is relative, ${fetchUrl}`,\n );\n}\n"],"names":["resolveSafeChildPath","path","fs","InputError"],"mappings":"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"} -\ No newline at end of file -+{"version":3,"file":"fetch.cjs.js","sources":["../../src/actions/fetch.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { UrlReaderService } from '@backstage/backend-plugin-api';\nimport {\n isChildPath,\n resolveSafeChildPath,\n} from '@backstage/backend-plugin-api';\nimport { InputError } from '@backstage/errors';\nimport { ScmIntegrations } from '@backstage/integration';\nimport fs from 'fs-extra';\nimport path from 'path';\n\n/**\n * A helper function that reads the contents of a directory from the given URL.\n * Can be used in your own actions, and also used behind fetch:template and fetch:plain\n *\n * @public\n */\nexport async function fetchContents(options: {\n reader: UrlReaderService;\n integrations: ScmIntegrations;\n baseUrl?: string;\n fetchUrl?: string;\n outputPath: string;\n token?: string;\n}) {\n const {\n reader,\n integrations,\n baseUrl,\n fetchUrl = '.',\n outputPath,\n token,\n } = options;\n\n const fetchUrlIsAbsolute = isFetchUrlAbsolute(fetchUrl);\n\n // We handle both file locations and url ones\n if (!fetchUrlIsAbsolute && baseUrl?.startsWith('file://')) {\n const basePath = baseUrl.slice('file://'.length);\n const srcDir = resolveSafeChildPath(path.dirname(basePath), fetchUrl);\n await fs.copy(srcDir, outputPath, { filter: src => isChildPath(srcDir, src) });\n } else {\n const readUrl = getReadUrl(fetchUrl, baseUrl, integrations);\n\n const res = await reader.readTree(readUrl, { token });\n await fs.ensureDir(outputPath);\n await res.dir({ targetDir: outputPath });\n }\n}\n\n/**\n * A helper function that reads the content of a single file from the given URL.\n * Can be used in your own actions, and also used behind `fetch:plain:file`\n *\n * @public\n */\nexport async function fetchFile(options: {\n reader: UrlReaderService;\n integrations: ScmIntegrations;\n baseUrl?: string;\n fetchUrl?: string;\n outputPath: string;\n token?: string;\n}) {\n const {\n reader,\n integrations,\n baseUrl,\n fetchUrl = '.',\n outputPath,\n token,\n } = options;\n\n const fetchUrlIsAbsolute = isFetchUrlAbsolute(fetchUrl);\n\n // We handle both file locations and url ones\n if (!fetchUrlIsAbsolute && baseUrl?.startsWith('file://')) {\n const basePath = baseUrl.slice('file://'.length);\n const src = resolveSafeChildPath(path.dirname(basePath), fetchUrl);\n await fs.copyFile(src, outputPath);\n } else {\n const readUrl = getReadUrl(fetchUrl, baseUrl, integrations);\n\n const res = await reader.readUrl(readUrl, { token });\n await fs.ensureDir(path.dirname(outputPath));\n const buffer = await res.buffer();\n await fs.outputFile(outputPath, buffer);\n }\n}\n\nfunction isFetchUrlAbsolute(fetchUrl: string) {\n let fetchUrlIsAbsolute = false;\n try {\n // eslint-disable-next-line no-new\n new URL(fetchUrl);\n fetchUrlIsAbsolute = true;\n } catch {\n /* ignored */\n }\n return fetchUrlIsAbsolute;\n}\n\nfunction getReadUrl(\n fetchUrl: string,\n baseUrl: string | undefined,\n integrations: ScmIntegrations,\n) {\n if (isFetchUrlAbsolute(fetchUrl)) {\n return fetchUrl;\n } else if (baseUrl) {\n const integration = integrations.byUrl(baseUrl);\n if (!integration) {\n throw new InputError(`No integration found for location ${baseUrl}`);\n }\n\n return integration.resolveUrl({\n url: fetchUrl,\n base: baseUrl,\n });\n }\n throw new InputError(\n `Failed to fetch, template location could not be determined and the fetch URL is relative, ${fetchUrl}`,\n );\n}\n"],"names":["resolveSafeChildPath","path","fs","isChildPath","InputError"],"mappings":";;;;;;;;;;;;AAgCA,eAAsB,cAAc,OAAA,EAOjC;AACD,EAAA,MAAM;AAAA,IACJ,MAAA;AAAA,IACA,YAAA;AAAA,IACA,OAAA;AAAA,IACA,QAAA,GAAW,GAAA;AAAA,IACX,UAAA;AAAA,IACA;AAAA,GACF,GAAI,OAAA;AAEJ,EAAA,MAAM,kBAAA,GAAqB,mBAAmB,QAAQ,CAAA;AAGtD,EAAA,IAAI,CAAC,kBAAA,IAAsB,OAAA,EAAS,UAAA,CAAW,SAAS,CAAA,EAAG;AACzD,IAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,KAAA,CAAM,SAAA,CAAU,MAAM,CAAA;AAC/C,IAAA,MAAM,SAASA,qCAAA,CAAqBC,qBAAA,CAAK,OAAA,CAAQ,QAAQ,GAAG,QAAQ,CAAA;AACpE,IAAA,MAAMC,mBAAA,CAAG,IAAA,CAAK,MAAA,EAAQ,UAAA,EAAY,EAAE,MAAA,EAAQ,CAAA,GAAA,KAAOC,4BAAA,CAAY,MAAA,EAAQ,GAAG,CAAA,EAAG,CAAA;AAAA,EAC/E,CAAA,MAAO;AACL,IAAA,MAAM,OAAA,GAAU,UAAA,CAAW,QAAA,EAAU,OAAA,EAAS,YAAY,CAAA;AAE1D,IAAA,MAAM,MAAM,MAAM,MAAA,CAAO,SAAS,OAAA,EAAS,EAAE,OAAO,CAAA;AACpD,IAAA,MAAMD,mBAAA,CAAG,UAAU,UAAU,CAAA;AAC7B,IAAA,MAAM,GAAA,CAAI,GAAA,CAAI,EAAE,SAAA,EAAW,YAAY,CAAA;AAAA,EACzC;AACF;AAQA,eAAsB,UAAU,OAAA,EAO7B;AACD,EAAA,MAAM;AAAA,IACJ,MAAA;AAAA,IACA,YAAA;AAAA,IACA,OAAA;AAAA,IACA,QAAA,GAAW,GAAA;AAAA,IACX,UAAA;AAAA,IACA;AAAA,GACF,GAAI,OAAA;AAEJ,EAAA,MAAM,kBAAA,GAAqB,mBAAmB,QAAQ,CAAA;AAGtD,EAAA,IAAI,CAAC,kBAAA,IAAsB,OAAA,EAAS,UAAA,CAAW,SAAS,CAAA,EAAG;AACzD,IAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,KAAA,CAAM,SAAA,CAAU,MAAM,CAAA;AAC/C,IAAA,MAAM,MAAMF,qCAAA,CAAqBC,qBAAA,CAAK,OAAA,CAAQ,QAAQ,GAAG,QAAQ,CAAA;AACjE,IAAA,MAAMC,mBAAA,CAAG,QAAA,CAAS,GAAA,EAAK,UAAU,CAAA;AAAA,EACnC,CAAA,MAAO;AACL,IAAA,MAAM,OAAA,GAAU,UAAA,CAAW,QAAA,EAAU,OAAA,EAAS,YAAY,CAAA;AAE1D,IAAA,MAAM,MAAM,MAAM,MAAA,CAAO,QAAQ,OAAA,EAAS,EAAE,OAAO,CAAA;AACnD,IAAA,MAAMA,mBAAA,CAAG,SAAA,CAAUD,qBAAA,CAAK,OAAA,CAAQ,UAAU,CAAC,CAAA;AAC3C,IAAA,MAAM,MAAA,GAAS,MAAM,GAAA,CAAI,MAAA,EAAO;AAChC,IAAA,MAAMC,mBAAA,CAAG,UAAA,CAAW,UAAA,EAAY,MAAM,CAAA;AAAA,EACxC;AACF;AAEA,SAAS,mBAAmB,QAAA,EAAkB;AAC5C,EAAA,IAAI,kBAAA,GAAqB,KAAA;AACzB,EAAA,IAAI;AAEF,IAAA,IAAI,IAAI,QAAQ,CAAA;AAChB,IAAA,kBAAA,GAAqB,IAAA;AAAA,EACvB,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,OAAO,kBAAA;AACT;AAEA,SAAS,UAAA,CACP,QAAA,EACA,OAAA,EACA,YAAA,EACA;AACA,EAAA,IAAI,kBAAA,CAAmB,QAAQ,CAAA,EAAG;AAChC,IAAA,OAAO,QAAA;AAAA,EACT,WAAW,OAAA,EAAS;AAClB,IAAA,MAAM,WAAA,GAAc,YAAA,CAAa,KAAA,CAAM,OAAO,CAAA;AAC9C,IAAA,IAAI,CAAC,WAAA,EAAa;AAChB,MAAA,MAAM,IAAIE,iBAAA,CAAW,CAAA,kCAAA,EAAqC,OAAO,CAAA,CAAE,CAAA;AAAA,IACrE;AAEA,IAAA,OAAO,YAAY,UAAA,CAAW;AAAA,MAC5B,GAAA,EAAK,QAAA;AAAA,MACL,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AACA,EAAA,MAAM,IAAIA,iBAAA;AAAA,IACR,6FAA6F,QAAQ,CAAA;AAAA,GACvG;AACF;;;;;"} -\ No newline at end of file diff --git a/dynamic-plugins/.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch b/dynamic-plugins/.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch deleted file mode 100644 index cb24546791..0000000000 --- a/dynamic-plugins/.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch +++ /dev/null @@ -1,280 +0,0 @@ -diff --git a/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js b/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js -index 0b369b27ac244818802de8acd1b2563e7588e647..8f66efaf365dc569335366db566fd40d39d1ec27 100644 ---- a/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js -+++ b/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js -@@ -8,6 +8,8 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau - - var platformPath__default = /*#__PURE__*/_interopDefaultCompat(platformPath); - -+const REDIRECT_STATUS_CODES = [301, 302, 307, 308]; -+const MAX_REDIRECTS = 5; - const isInRange = (num, [start, end]) => { - return num >= start && num <= end; - }; -@@ -36,6 +38,26 @@ const parsePortPredicate = (port) => { - } - return (url) => !url.port; - }; -+function predicateFromConfig(config) { -+ const allow = config.getOptionalConfigArray("backend.reading.allow")?.map((allowConfig) => { -+ const paths = allowConfig.getOptionalStringArray("paths"); -+ const checkPath = paths ? (url) => { -+ const targetPath = platformPath__default.default.posix.normalize(url.pathname); -+ return paths.some( -+ (allowedPath) => targetPath.startsWith(allowedPath) -+ ); -+ } : (_url) => true; -+ const host = allowConfig.getString("host"); -+ const [hostname, port] = host.split(":"); -+ const checkPort = parsePortPredicate(port); -+ if (hostname.startsWith("*.")) { -+ const suffix = hostname.slice(1); -+ return (url) => url.hostname.endsWith(suffix) && checkPath(url) && checkPort(url); -+ } -+ return (url) => url.hostname === hostname && checkPath(url) && checkPort(url); -+ }); -+ return allow?.length ? (url) => allow.some((p) => p(url)) : () => false; -+} - class FetchUrlReader { - /** - * The factory creates a single reader that will be used for reading any URL that's listed -@@ -50,64 +72,72 @@ class FetchUrlReader { - * An optional list of paths which are allowed. If the list is omitted all paths are allowed. - */ - static factory = ({ config }) => { -- const predicates = config.getOptionalConfigArray("backend.reading.allow")?.map((allowConfig) => { -- const paths = allowConfig.getOptionalStringArray("paths"); -- const checkPath = paths ? (url) => { -- const targetPath = platformPath__default.default.posix.normalize(url.pathname); -- return paths.some( -- (allowedPath) => targetPath.startsWith(allowedPath) -- ); -- } : (_url) => true; -- const host = allowConfig.getString("host"); -- const [hostname, port] = host.split(":"); -- const checkPort = parsePortPredicate(port); -- if (hostname.startsWith("*.")) { -- const suffix = hostname.slice(1); -- return (url) => url.hostname.endsWith(suffix) && checkPath(url) && checkPort(url); -- } -- return (url) => url.hostname === hostname && checkPath(url) && checkPort(url); -- }) ?? []; -- const reader = new FetchUrlReader(); -- const predicate = (url) => predicates.some((p) => p(url)); -+ const predicate = predicateFromConfig(config); -+ const reader = new FetchUrlReader({ predicate }); - return [{ reader, predicate }]; - }; -+ static fromConfig(config) { -+ return new FetchUrlReader({ predicate: predicateFromConfig(config) }); -+ } -+ #predicate; -+ constructor(options) { -+ this.#predicate = options.predicate; -+ } - async read(url) { - const response = await this.readUrl(url); - return response.buffer(); - } - async readUrl(url, options) { -- let response; -- try { -- response = await fetch(url, { -- headers: { -- ...options?.etag && { "If-None-Match": options.etag }, -- ...options?.lastModifiedAfter && { -- "If-Modified-Since": options.lastModifiedAfter.toUTCString() -+ let currentUrl = url; -+ for (let redirectCount = 0; redirectCount < MAX_REDIRECTS; redirectCount += 1) { -+ const parsedUrl = new URL(currentUrl); -+ if (!this.#predicate(parsedUrl)) { -+ throw new Error( -+ `URL not allowed by backend.reading.allow configuration: ${currentUrl}` -+ ); -+ } -+ let response; -+ try { -+ response = await fetch(currentUrl, { -+ headers: { -+ ...options?.etag && { "If-None-Match": options.etag }, -+ ...options?.lastModifiedAfter && { -+ "If-Modified-Since": options.lastModifiedAfter.toUTCString() -+ }, -+ ...options?.token && { Authorization: `Bearer ${options.token}` } - }, -- ...options?.token && { Authorization: `Bearer ${options.token}` } -- }, -- // TODO(freben): The signal cast is there because pre-3.x versions of -- // node-fetch have a very slightly deviating AbortSignal type signature. -- // The difference does not affect us in practice however. The cast can -- // be removed after we support ESM for CLI dependencies and migrate to -- // version 3 of node-fetch. -- // https://github.com/backstage/backstage/issues/8242 -- signal: options?.signal -- }); -- } catch (e) { -- throw new Error(`Unable to read ${url}, ${e}`); -- } -- if (response.status === 304) { -- throw new errors.NotModifiedError(); -- } -- if (response.ok) { -- return ReadUrlResponseFactory.ReadUrlResponseFactory.fromResponse(response); -- } -- const message = `could not read ${url}, ${response.status} ${response.statusText}`; -- if (response.status === 404) { -- throw new errors.NotFoundError(message); -+ // Handle redirects manually to validate targets against the allowlist -+ redirect: "manual", -+ // TODO(freben): The signal cast is there because pre-3.x versions of -+ // node-fetch have a very slightly deviating AbortSignal type signature. -+ // The difference does not affect us in practice however. The cast can -+ // be removed after we support ESM for CLI dependencies and migrate to -+ // version 3 of node-fetch. -+ // https://github.com/backstage/backstage/issues/8242 -+ signal: options?.signal -+ }); -+ } catch (e) { -+ throw new Error(`Unable to read ${currentUrl}, ${e}`); -+ } -+ if (response.ok) { -+ return ReadUrlResponseFactory.ReadUrlResponseFactory.fromResponse(response); -+ } -+ if (response.status === 304) { -+ throw new errors.NotModifiedError(); -+ } -+ const location = response.headers.get("location"); -+ if (!REDIRECT_STATUS_CODES.includes(response.status) || !location) { -+ const message = `could not read ${currentUrl}, ${response.status} ${response.statusText}`; -+ if (response.status === 404) { -+ throw new errors.NotFoundError(message); -+ } -+ throw new Error(message); -+ } -+ currentUrl = new URL(location, currentUrl).toString(); - } -- throw new Error(message); -+ throw new Error( -+ `Too many redirects (max ${MAX_REDIRECTS}) when reading ${url}` -+ ); - } - async readTree() { - throw new Error("FetchUrlReader does not implement readTree"); -diff --git a/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map b/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map -index b6ff0e7ab1b362bc33b0decef31bc96836727d90..7a97648444ae7e9716aa38b1fd0f327e22152bfb 100644 ---- a/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map -+++ b/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map -@@ -1 +1 @@ --{"version":3,"file":"FetchUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/FetchUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2020 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 UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport {\n assertError,\n NotFoundError,\n NotModifiedError,\n} from '@backstage/errors';\nimport { ReaderFactory } from './types';\nimport path from 'path';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\n\nconst isInRange = (num: number, [start, end]: [number, number]) => {\n return num >= start && num <= end;\n};\n\nconst parsePortRange = (port: string): [number, number] => {\n const isRange = port.includes('-');\n if (isRange) {\n const range = port\n .split('-')\n .map(v => parseInt(v, 10))\n .filter(Boolean) as [number, number];\n if (range.length !== 2) throw new Error(`Port range is not valid: ${port}`);\n const [start, end] = range;\n if (start <= 0 || end <= 0 || start > end)\n throw new Error(`Port range is not valid: [${start}, ${end}]`);\n return range;\n }\n const parsedPort = parseInt(port, 10);\n return [parsedPort, parsedPort];\n};\n\nconst parsePortPredicate = (port: string | undefined) => {\n if (port) {\n const range = parsePortRange(port);\n return (url: URL) => {\n if (url.port) return isInRange(parseInt(url.port, 10), range);\n\n if (url.protocol === 'http:') return isInRange(80, range);\n if (url.protocol === 'https:') return isInRange(443, range);\n return false;\n };\n }\n return (url: URL) => !url.port;\n};\n\n/**\n * A {@link @backstage/backend-plugin-api#UrlReaderService} that does a plain fetch of the URL.\n *\n * @public\n */\nexport class FetchUrlReader implements UrlReaderService {\n /**\n * The factory creates a single reader that will be used for reading any URL that's listed\n * in configuration at `backend.reading.allow`. The allow list contains a list of objects describing\n * targets to allow, containing the following fields:\n *\n * `host`:\n * Either full hostnames to match, or subdomain wildcard matchers with a leading '*'.\n * For example 'example.com' and '*.example.com' are valid values, 'prod.*.example.com' is not.\n *\n * `paths`:\n * An optional list of paths which are allowed. If the list is omitted all paths are allowed.\n */\n static factory: ReaderFactory = ({ config }) => {\n const predicates =\n config\n .getOptionalConfigArray('backend.reading.allow')\n ?.map(allowConfig => {\n const paths = allowConfig.getOptionalStringArray('paths');\n const checkPath = paths\n ? (url: URL) => {\n const targetPath = path.posix.normalize(url.pathname);\n return paths.some(allowedPath =>\n targetPath.startsWith(allowedPath),\n );\n }\n : (_url: URL) => true;\n const host = allowConfig.getString('host');\n const [hostname, port] = host.split(':');\n\n const checkPort = parsePortPredicate(port);\n\n if (hostname.startsWith('*.')) {\n const suffix = hostname.slice(1);\n return (url: URL) =>\n url.hostname.endsWith(suffix) && checkPath(url) && checkPort(url);\n }\n return (url: URL) =>\n url.hostname === hostname && checkPath(url) && checkPort(url);\n }) ?? [];\n\n const reader = new FetchUrlReader();\n const predicate = (url: URL) => predicates.some(p => p(url));\n return [{ reader, predicate }];\n };\n\n async read(url: string): Promise {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise {\n let response: Response;\n try {\n response = await fetch(url, {\n headers: {\n ...(options?.etag && { 'If-None-Match': options.etag }),\n ...(options?.lastModifiedAfter && {\n 'If-Modified-Since': options.lastModifiedAfter.toUTCString(),\n }),\n ...(options?.token && { Authorization: `Bearer ${options.token}` }),\n },\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can\n // be removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n signal: options?.signal as any,\n });\n } catch (e) {\n throw new Error(`Unable to read ${url}, ${e}`);\n }\n\n if (response.status === 304) {\n throw new NotModifiedError();\n }\n\n if (response.ok) {\n return ReadUrlResponseFactory.fromResponse(response);\n }\n\n const message = `could not read ${url}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n async readTree(): Promise {\n throw new Error('FetchUrlReader does not implement readTree');\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise {\n const { pathname } = new URL(url);\n\n if (pathname.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 (error) {\n assertError(error);\n if (error.name === 'NotFoundError') {\n return {\n files: [],\n etag: '',\n };\n }\n throw error;\n }\n }\n\n toString() {\n return 'fetch{}';\n }\n}\n"],"names":["path","NotModifiedError","ReadUrlResponseFactory","NotFoundError","assertError"],"mappings":"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"} -\ No newline at end of file -+{"version":3,"file":"FetchUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/FetchUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2020 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 UrlReaderServiceSearchOptions,\n UrlReaderServiceSearchResponse,\n} from '@backstage/backend-plugin-api';\nimport {\n assertError,\n NotFoundError,\n NotModifiedError,\n} from '@backstage/errors';\nimport { ReaderFactory } from './types';\nimport path from 'path';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport { Config } from '@backstage/config';\n\nconst REDIRECT_STATUS_CODES = [301, 302, 307, 308];\nconst MAX_REDIRECTS = 5;\n\nconst isInRange = (num: number, [start, end]: [number, number]) => {\n return num >= start && num <= end;\n};\n\nconst parsePortRange = (port: string): [number, number] => {\n const isRange = port.includes('-');\n if (isRange) {\n const range = port\n .split('-')\n .map(v => parseInt(v, 10))\n .filter(Boolean) as [number, number];\n if (range.length !== 2) throw new Error(`Port range is not valid: ${port}`);\n const [start, end] = range;\n if (start <= 0 || end <= 0 || start > end)\n throw new Error(`Port range is not valid: [${start}, ${end}]`);\n return range;\n }\n const parsedPort = parseInt(port, 10);\n return [parsedPort, parsedPort];\n};\n\nconst parsePortPredicate = (port: string | undefined) => {\n if (port) {\n const range = parsePortRange(port);\n return (url: URL) => {\n if (url.port) return isInRange(parseInt(url.port, 10), range);\n\n if (url.protocol === 'http:') return isInRange(80, range);\n if (url.protocol === 'https:') return isInRange(443, range);\n return false;\n };\n }\n return (url: URL) => !url.port;\n};\n\nfunction predicateFromConfig(config: Config): (url: URL) => boolean {\n const allow = config\n .getOptionalConfigArray('backend.reading.allow')\n ?.map(allowConfig => {\n const paths = allowConfig.getOptionalStringArray('paths');\n const checkPath = paths\n ? (url: URL) => {\n const targetPath = path.posix.normalize(url.pathname);\n return paths.some(allowedPath =>\n targetPath.startsWith(allowedPath),\n );\n }\n : (_url: URL) => true;\n const host = allowConfig.getString('host');\n const [hostname, port] = host.split(':');\n\n const checkPort = parsePortPredicate(port);\n\n if (hostname.startsWith('*.')) {\n const suffix = hostname.slice(1);\n return (url: URL) =>\n url.hostname.endsWith(suffix) && checkPath(url) && checkPort(url);\n }\n\n return (url: URL) =>\n url.hostname === hostname && checkPath(url) && checkPort(url);\n });\n\n return allow?.length ? url => allow.some(p => p(url)) : () => false;\n}\n\n/**\n * A {@link @backstage/backend-plugin-api#UrlReaderService} that does a plain fetch of the URL.\n *\n * @public\n */\nexport class FetchUrlReader implements UrlReaderService {\n /**\n * The factory creates a single reader that will be used for reading any URL that's listed\n * in configuration at `backend.reading.allow`. The allow list contains a list of objects describing\n * targets to allow, containing the following fields:\n *\n * `host`:\n * Either full hostnames to match, or subdomain wildcard matchers with a leading '*'.\n * For example 'example.com' and '*.example.com' are valid values, 'prod.*.example.com' is not.\n *\n * `paths`:\n * An optional list of paths which are allowed. If the list is omitted all paths are allowed.\n */\n static factory: ReaderFactory = ({ config }) => {\n const predicate = predicateFromConfig(config);\n const reader = new FetchUrlReader({ predicate });\n return [{ reader, predicate }];\n };\n\n static fromConfig(config: Config): FetchUrlReader {\n return new FetchUrlReader({ predicate: predicateFromConfig(config) });\n }\n\n readonly #predicate: (url: URL) => boolean;\n\n private constructor(options: { predicate: (url: URL) => boolean }) {\n this.#predicate = options.predicate;\n }\n\n async read(url: string): Promise {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise {\n let currentUrl = url;\n\n for (\n let redirectCount = 0;\n redirectCount < MAX_REDIRECTS;\n redirectCount += 1\n ) {\n // Validate URL against predicate if configured\n const parsedUrl = new URL(currentUrl);\n if (!this.#predicate(parsedUrl)) {\n throw new Error(\n `URL not allowed by backend.reading.allow configuration: ${currentUrl}`,\n );\n }\n\n let response: Response;\n try {\n response = await fetch(currentUrl, {\n headers: {\n ...(options?.etag && { 'If-None-Match': options.etag }),\n ...(options?.lastModifiedAfter && {\n 'If-Modified-Since': options.lastModifiedAfter.toUTCString(),\n }),\n ...(options?.token && { Authorization: `Bearer ${options.token}` }),\n },\n // Handle redirects manually to validate targets against the allowlist\n redirect: 'manual',\n // TODO(freben): The signal cast is there because pre-3.x versions of\n // node-fetch have a very slightly deviating AbortSignal type signature.\n // The difference does not affect us in practice however. The cast can\n // be removed after we support ESM for CLI dependencies and migrate to\n // version 3 of node-fetch.\n // https://github.com/backstage/backstage/issues/8242\n signal: options?.signal as any,\n });\n } catch (e) {\n throw new Error(`Unable to read ${currentUrl}, ${e}`);\n }\n\n if (response.ok) {\n return ReadUrlResponseFactory.fromResponse(response);\n }\n\n if (response.status === 304) {\n throw new NotModifiedError();\n }\n\n const location = response.headers.get('location');\n if (!REDIRECT_STATUS_CODES.includes(response.status) || !location) {\n const message = `could not read ${currentUrl}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n // Follow the redirect\n currentUrl = new URL(location, currentUrl).toString();\n }\n\n throw new Error(\n `Too many redirects (max ${MAX_REDIRECTS}) when reading ${url}`,\n );\n }\n\n async readTree(): Promise {\n throw new Error('FetchUrlReader does not implement readTree');\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise {\n const { pathname } = new URL(url);\n\n if (pathname.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 (error) {\n assertError(error);\n if (error.name === 'NotFoundError') {\n return {\n files: [],\n etag: '',\n };\n }\n throw error;\n }\n }\n\n toString() {\n return 'fetch{}';\n }\n}\n"],"names":["path","ReadUrlResponseFactory","NotModifiedError","NotFoundError","assertError"],"mappings":"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"} -\ No newline at end of file -diff --git a/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js b/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js -index b2c09c66c7ffd5edae7a43ec93133c09bb65491d..7a93a9b5a792598f62e1ec4a16d6ebbd37cd1f81 100644 ---- a/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js -+++ b/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js -@@ -1,5 +1,6 @@ - 'use strict'; - -+var backendPluginApi = require('@backstage/backend-plugin-api'); - var concatStream = require('concat-stream'); - var platformPath = require('path'); - var getRawBody = require('raw-body'); -@@ -67,7 +68,7 @@ class ReadableArrayResponse { - const dir = options?.targetDir ?? await fs__default.default.mkdtemp(platformPath__default.default.join(this.workDir, "backstage-")); - for (let i = 0; i < this.stream.length; i++) { - if (!this.stream[i].path.endsWith("/")) { -- const filePath = platformPath__default.default.join(dir, this.stream[i].path); -+ const filePath = backendPluginApi.resolveSafeChildPath(dir, this.stream[i].path); - await fs__default.default.mkdir(platformPath.dirname(filePath), { recursive: true }); - await pipeline(this.stream[i].data, fs__default.default.createWriteStream(filePath)); - } -diff --git a/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map b/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map -index 4306ab100f551981ca29b0ce2149140644f92b5e..37e0ef6678b7879c81ed271d46be533eb26e2d3b 100644 ---- a/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map -+++ b/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map -@@ -1 +1 @@ --{"version":3,"file":"ReadableArrayResponse.cjs.js","sources":["../../../../../src/entrypoints/urlReader/lib/tree/ReadableArrayResponse.ts"],"sourcesContent":["/*\n * Copyright 2020 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 UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadTreeResponseDirOptions,\n UrlReaderServiceReadTreeResponseFile,\n} from '@backstage/backend-plugin-api';\nimport concatStream from 'concat-stream';\nimport platformPath, { dirname } from 'path';\nimport getRawBody from 'raw-body';\nimport fs from 'fs-extra';\nimport { promisify } from 'util';\nimport tar from 'tar';\nimport { pipeline as pipelineCb, Readable } from 'stream';\nimport { FromReadableArrayOptions } from '../types';\n\nconst pipeline = promisify(pipelineCb);\n\n/**\n * Wraps a array of Readable objects into a tree response reader.\n */\nexport class ReadableArrayResponse implements UrlReaderServiceReadTreeResponse {\n private read = false;\n private readonly stream: FromReadableArrayOptions;\n private readonly workDir: string;\n public readonly etag: string;\n\n constructor(stream: FromReadableArrayOptions, workDir: string, etag: string) {\n this.stream = stream;\n this.workDir = workDir;\n this.etag = etag;\n }\n\n // Make sure the input stream is only read once\n private onlyOnce() {\n if (this.read) {\n throw new Error('Response has already been read');\n }\n this.read = true;\n }\n\n async files(): Promise {\n this.onlyOnce();\n\n const files = Array();\n\n for (let i = 0; i < this.stream.length; i++) {\n if (!this.stream[i].path.endsWith('/')) {\n files.push({\n path: this.stream[i].path,\n content: () => getRawBody(this.stream[i].data),\n lastModifiedAt: this.stream[i]?.lastModifiedAt,\n });\n }\n }\n\n return files;\n }\n\n async archive(): Promise {\n const tmpDir = await this.dir();\n\n try {\n const data = await new Promise(async resolve => {\n await pipeline(\n tar.create({ cwd: tmpDir }, ['']),\n concatStream(resolve),\n );\n });\n return Readable.from(data);\n } finally {\n await fs.remove(tmpDir);\n }\n }\n\n async dir(\n options?: UrlReaderServiceReadTreeResponseDirOptions,\n ): Promise {\n this.onlyOnce();\n\n const dir =\n options?.targetDir ??\n (await fs.mkdtemp(platformPath.join(this.workDir, 'backstage-')));\n\n for (let i = 0; i < this.stream.length; i++) {\n if (!this.stream[i].path.endsWith('/')) {\n const filePath = platformPath.join(dir, this.stream[i].path);\n await fs.mkdir(dirname(filePath), { recursive: true });\n await pipeline(this.stream[i].data, fs.createWriteStream(filePath));\n }\n }\n\n return dir;\n }\n}\n"],"names":["promisify","pipelineCb","getRawBody","tar","concatStream","Readable","fs","platformPath","dirname"],"mappings":"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"} -\ No newline at end of file -+{"version":3,"file":"ReadableArrayResponse.cjs.js","sources":["../../../../../src/entrypoints/urlReader/lib/tree/ReadableArrayResponse.ts"],"sourcesContent":["/*\n * Copyright 2020 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 resolveSafeChildPath,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadTreeResponseDirOptions,\n UrlReaderServiceReadTreeResponseFile,\n} from '@backstage/backend-plugin-api';\nimport concatStream from 'concat-stream';\nimport platformPath, { dirname } from 'path';\nimport getRawBody from 'raw-body';\nimport fs from 'fs-extra';\nimport { promisify } from 'util';\nimport tar from 'tar';\nimport { pipeline as pipelineCb, Readable } from 'stream';\nimport { FromReadableArrayOptions } from '../types';\n\nconst pipeline = promisify(pipelineCb);\n\n/**\n * Wraps a array of Readable objects into a tree response reader.\n */\nexport class ReadableArrayResponse implements UrlReaderServiceReadTreeResponse {\n private read = false;\n private readonly stream: FromReadableArrayOptions;\n private readonly workDir: string;\n public readonly etag: string;\n\n constructor(stream: FromReadableArrayOptions, workDir: string, etag: string) {\n this.stream = stream;\n this.workDir = workDir;\n this.etag = etag;\n }\n\n // Make sure the input stream is only read once\n private onlyOnce() {\n if (this.read) {\n throw new Error('Response has already been read');\n }\n this.read = true;\n }\n\n async files(): Promise {\n this.onlyOnce();\n\n const files = Array();\n\n for (let i = 0; i < this.stream.length; i++) {\n if (!this.stream[i].path.endsWith('/')) {\n files.push({\n path: this.stream[i].path,\n content: () => getRawBody(this.stream[i].data),\n lastModifiedAt: this.stream[i]?.lastModifiedAt,\n });\n }\n }\n\n return files;\n }\n\n async archive(): Promise {\n const tmpDir = await this.dir();\n\n try {\n const data = await new Promise(async resolve => {\n await pipeline(\n tar.create({ cwd: tmpDir }, ['']),\n concatStream(resolve),\n );\n });\n return Readable.from(data);\n } finally {\n await fs.remove(tmpDir);\n }\n }\n\n async dir(\n options?: UrlReaderServiceReadTreeResponseDirOptions,\n ): Promise {\n this.onlyOnce();\n\n const dir =\n options?.targetDir ??\n (await fs.mkdtemp(platformPath.join(this.workDir, 'backstage-')));\n\n for (let i = 0; i < this.stream.length; i++) {\n if (!this.stream[i].path.endsWith('/')) {\n const filePath = resolveSafeChildPath(dir, this.stream[i].path);\n await fs.mkdir(dirname(filePath), { recursive: true });\n await pipeline(this.stream[i].data, fs.createWriteStream(filePath));\n }\n }\n\n return dir;\n }\n}\n"],"names":["promisify","pipelineCb","getRawBody","tar","concatStream","Readable","fs","platformPath","resolveSafeChildPath","dirname"],"mappings":"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"} -\ No newline at end of file -diff --git a/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js b/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js -index 8beeb818214e021335f54b745989936638bd8365..29c48cb9294d69aa10d006cdaee6271b903fdacb 100644 ---- a/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js -+++ b/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js -@@ -1,5 +1,6 @@ - 'use strict'; - -+var backendPluginApi = require('@backstage/backend-plugin-api'); - var concatStream = require('concat-stream'); - var fs = require('fs-extra'); - var platformPath = require('path'); -@@ -121,6 +122,17 @@ class TarArchiveResponse { - if (filterError) { - return false; - } -+ const entry = stat; -+ if ((entry.type === "SymbolicLink" || entry.type === "Link") && entry.linkpath) { -+ const strippedPath = path.split("/").slice(strip).join("/"); -+ const linkDir = platformPath__default.default.dirname( -+ platformPath__default.default.join(dir, strippedPath) -+ ); -+ const targetPath = platformPath__default.default.resolve(linkDir, entry.linkpath); -+ if (!backendPluginApi.isChildPath(dir, targetPath)) { -+ return false; -+ } -+ } - const relativePath = this.stripFirstDirectory ? util$1.stripFirstDirectoryFromPath(path) : path; - if (this.subPath && !relativePath.startsWith(this.subPath)) { - return false; -diff --git a/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map b/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map -index 2c5b75328153e6a261588390d9af6d05d0c3454e..b1fd43e5b040043be1824f01600d6c131ab88d03 100644 ---- a/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map -+++ b/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map -@@ -1 +1 @@ --{"version":3,"file":"TarArchiveResponse.cjs.js","sources":["../../../../../src/entrypoints/urlReader/lib/tree/TarArchiveResponse.ts"],"sourcesContent":["/*\n * Copyright 2020 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 UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadTreeResponseDirOptions,\n UrlReaderServiceReadTreeResponseFile,\n} from '@backstage/backend-plugin-api';\nimport concatStream from 'concat-stream';\nimport fs from 'fs-extra';\nimport platformPath from 'path';\nimport { pipeline as pipelineCb, Readable } from 'stream';\nimport tar, { Parse, ParseStream, ReadEntry } from 'tar';\nimport { promisify } from 'util';\nimport { stripFirstDirectoryFromPath } from './util';\n\n// Tar types for `Parse` is not a proper constructor, but it should be\nconst TarParseStream = Parse as unknown as { new (): ParseStream };\n\nconst pipeline = promisify(pipelineCb);\n\n/**\n * Wraps a tar archive stream into a tree response reader.\n */\nexport class TarArchiveResponse implements UrlReaderServiceReadTreeResponse {\n private read = false;\n private readonly stream: Readable;\n private readonly subPath: string;\n private readonly workDir: string;\n public readonly etag: string;\n private readonly filter?: (path: string, info: { size: number }) => boolean;\n private readonly stripFirstDirectory: boolean;\n\n constructor(\n stream: Readable,\n subPath: string,\n workDir: string,\n etag: string,\n filter?: (path: string, info: { size: number }) => boolean,\n stripFirstDirectory: boolean = true,\n ) {\n this.stream = stream;\n this.subPath = subPath;\n this.workDir = workDir;\n this.etag = etag;\n this.filter = filter;\n this.stripFirstDirectory = stripFirstDirectory;\n if (subPath) {\n if (!subPath.endsWith('/')) {\n this.subPath += '/';\n }\n if (subPath.startsWith('/')) {\n throw new TypeError(\n `TarArchiveResponse subPath must not start with a /, got '${subPath}'`,\n );\n }\n }\n\n this.etag = etag;\n }\n\n // Make sure the input stream is only read once\n private onlyOnce() {\n if (this.read) {\n throw new Error('Response has already been read');\n }\n this.read = true;\n }\n\n async files(): Promise {\n this.onlyOnce();\n\n const files = Array();\n const parser = new TarParseStream();\n\n parser.on('entry', (entry: ReadEntry & Readable) => {\n if (entry.type === 'Directory') {\n entry.resume();\n return;\n }\n\n // File path relative to the root extracted directory. Will remove the\n // top level dir name from the path since its name is hard to predetermine.\n const relativePath = this.stripFirstDirectory\n ? stripFirstDirectoryFromPath(entry.path)\n : entry.path;\n\n if (this.subPath) {\n if (!relativePath.startsWith(this.subPath)) {\n entry.resume();\n return;\n }\n }\n\n const path = relativePath.slice(this.subPath.length);\n if (this.filter) {\n if (!this.filter(path, { size: entry.remain })) {\n entry.resume();\n return;\n }\n }\n\n const content = new Promise(async resolve => {\n await pipeline(entry, concatStream(resolve));\n });\n\n files.push({\n path,\n content: () => content,\n });\n\n entry.resume();\n });\n\n await pipeline(this.stream, parser);\n\n return files;\n }\n\n async archive(): Promise {\n if (!this.subPath) {\n this.onlyOnce();\n\n return this.stream;\n }\n\n // TODO(Rugvip): method for repacking a tar with a subpath is to simply extract into a\n // tmp dir and recreate the archive. Would be nicer to stream things instead.\n const tmpDir = await this.dir();\n\n try {\n const data = await new Promise(async resolve => {\n await pipeline(\n tar.create({ cwd: tmpDir }, ['']),\n concatStream(resolve),\n );\n });\n return Readable.from(data);\n } finally {\n await fs.remove(tmpDir);\n }\n }\n\n async dir(\n options?: UrlReaderServiceReadTreeResponseDirOptions,\n ): Promise {\n this.onlyOnce();\n\n const dir =\n options?.targetDir ??\n (await fs.mkdtemp(platformPath.join(this.workDir, 'backstage-')));\n\n // Equivalent of tar --strip-components=N\n // When no subPath is given, remove just 1 top level directory\n let strip = this.subPath ? this.subPath.split('/').length : 1;\n if (!this.stripFirstDirectory) {\n strip--;\n }\n\n let filterError: Error | undefined = undefined;\n await pipeline(\n this.stream,\n tar.extract({\n strip,\n cwd: dir,\n filter: (path, stat) => {\n // Filter errors will short-circuit the rest of the filtering and then throw\n if (filterError) {\n return false;\n }\n\n // File path relative to the root extracted directory. Will remove the\n // top level dir name from the path since its name is hard to predetermine.\n const relativePath = this.stripFirstDirectory\n ? stripFirstDirectoryFromPath(path)\n : path;\n if (this.subPath && !relativePath.startsWith(this.subPath)) {\n return false;\n }\n if (this.filter) {\n const innerPath = path.split('/').slice(strip).join('/');\n try {\n return this.filter(innerPath, { size: stat.size });\n } catch (error) {\n filterError = error;\n return false;\n }\n }\n return true;\n },\n }),\n );\n\n if (filterError) {\n // If the dir was provided we don't want to remove it, but if it wasn't it means\n // we created a temporary directory and we should remove it.\n if (!options?.targetDir) {\n await fs.remove(dir).catch(() => {});\n }\n throw filterError;\n }\n\n return dir;\n }\n}\n"],"names":["Parse","promisify","pipelineCb","stripFirstDirectoryFromPath","concatStream","tar","Readable","fs","platformPath"],"mappings":";;;;;;;;;;;;;;;;;AA8BA,MAAM,cAAA,GAAiBA,SAAA;AAEvB,MAAM,QAAA,GAAWC,eAAUC,eAAU,CAAA;AAK9B,MAAM,kBAAA,CAA+D;AAAA,EAClE,IAAA,GAAO,KAAA;AAAA,EACE,MAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACD,IAAA;AAAA,EACC,MAAA;AAAA,EACA,mBAAA;AAAA,EAEjB,YACE,MAAA,EACA,OAAA,EACA,SACA,IAAA,EACA,MAAA,EACA,sBAA+B,IAAA,EAC/B;AACA,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AACf,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,mBAAA,GAAsB,mBAAA;AAC3B,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,IAAI,CAAC,OAAA,CAAQ,QAAA,CAAS,GAAG,CAAA,EAAG;AAC1B,QAAA,IAAA,CAAK,OAAA,IAAW,GAAA;AAAA,MAClB;AACA,MAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAA,EAAG;AAC3B,QAAA,MAAM,IAAI,SAAA;AAAA,UACR,4DAA4D,OAAO,CAAA,CAAA;AAAA,SACrE;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AAAA;AAAA,EAGQ,QAAA,GAAW;AACjB,IAAA,IAAI,KAAK,IAAA,EAAM;AACb,MAAA,MAAM,IAAI,MAAM,gCAAgC,CAAA;AAAA,IAClD;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AAAA,EAEA,MAAM,KAAA,GAAyD;AAC7D,IAAA,IAAA,CAAK,QAAA,EAAS;AAEd,IAAA,MAAM,QAAQ,KAAA,EAA4C;AAC1D,IAAA,MAAM,MAAA,GAAS,IAAI,cAAA,EAAe;AAElC,IAAA,MAAA,CAAO,EAAA,CAAG,OAAA,EAAS,CAAC,KAAA,KAAgC;AAClD,MAAA,IAAI,KAAA,CAAM,SAAS,WAAA,EAAa;AAC9B,QAAA,KAAA,CAAM,MAAA,EAAO;AACb,QAAA;AAAA,MACF;AAIA,MAAA,MAAM,eAAe,IAAA,CAAK,mBAAA,GACtBC,mCAA4B,KAAA,CAAM,IAAI,IACtC,KAAA,CAAM,IAAA;AAEV,MAAA,IAAI,KAAK,OAAA,EAAS;AAChB,QAAA,IAAI,CAAC,YAAA,CAAa,UAAA,CAAW,IAAA,CAAK,OAAO,CAAA,EAAG;AAC1C,UAAA,KAAA,CAAM,MAAA,EAAO;AACb,UAAA;AAAA,QACF;AAAA,MACF;AAEA,MAAA,MAAM,IAAA,GAAO,YAAA,CAAa,KAAA,CAAM,IAAA,CAAK,QAAQ,MAAM,CAAA;AACnD,MAAA,IAAI,KAAK,MAAA,EAAQ;AACf,QAAA,IAAI,CAAC,KAAK,MAAA,CAAO,IAAA,EAAM,EAAE,IAAA,EAAM,KAAA,CAAM,MAAA,EAAQ,CAAA,EAAG;AAC9C,UAAA,KAAA,CAAM,MAAA,EAAO;AACb,UAAA;AAAA,QACF;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAgB,OAAM,OAAA,KAAW;AACnD,QAAA,MAAM,QAAA,CAAS,KAAA,EAAOC,6BAAA,CAAa,OAAO,CAAC,CAAA;AAAA,MAC7C,CAAC,CAAA;AAED,MAAA,KAAA,CAAM,IAAA,CAAK;AAAA,QACT,IAAA;AAAA,QACA,SAAS,MAAM;AAAA,OAChB,CAAA;AAED,MAAA,KAAA,CAAM,MAAA,EAAO;AAAA,IACf,CAAC,CAAA;AAED,IAAA,MAAM,QAAA,CAAS,IAAA,CAAK,MAAA,EAAQ,MAAM,CAAA;AAElC,IAAA,OAAO,KAAA;AAAA,EACT;AAAA,EAEA,MAAM,OAAA,GAA6B;AACjC,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AACjB,MAAA,IAAA,CAAK,QAAA,EAAS;AAEd,MAAA,OAAO,IAAA,CAAK,MAAA;AAAA,IACd;AAIA,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,GAAA,EAAI;AAE9B,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAM,IAAI,OAAA,CAAgB,OAAM,OAAA,KAAW;AACtD,QAAA,MAAM,QAAA;AAAA,UACJC,oBAAA,CAAI,OAAO,EAAE,GAAA,EAAK,QAAO,EAAG,CAAC,EAAE,CAAC,CAAA;AAAA,UAChCD,8BAAa,OAAO;AAAA,SACtB;AAAA,MACF,CAAC,CAAA;AACD,MAAA,OAAOE,eAAA,CAAS,KAAK,IAAI,CAAA;AAAA,IAC3B,CAAA,SAAE;AACA,MAAA,MAAMC,mBAAA,CAAG,OAAO,MAAM,CAAA;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,MAAM,IACJ,OAAA,EACiB;AACjB,IAAA,IAAA,CAAK,QAAA,EAAS;AAEd,IAAA,MAAM,GAAA,GACJ,OAAA,EAAS,SAAA,IACR,MAAMA,mBAAA,CAAG,OAAA,CAAQC,6BAAA,CAAa,IAAA,CAAK,IAAA,CAAK,OAAA,EAAS,YAAY,CAAC,CAAA;AAIjE,IAAA,IAAI,KAAA,GAAQ,KAAK,OAAA,GAAU,IAAA,CAAK,QAAQ,KAAA,CAAM,GAAG,EAAE,MAAA,GAAS,CAAA;AAC5D,IAAA,IAAI,CAAC,KAAK,mBAAA,EAAqB;AAC7B,MAAA,KAAA,EAAA;AAAA,IACF;AAEA,IAAA,IAAI,WAAA,GAAiC,MAAA;AACrC,IAAA,MAAM,QAAA;AAAA,MACJ,IAAA,CAAK,MAAA;AAAA,MACLH,qBAAI,OAAA,CAAQ;AAAA,QACV,KAAA;AAAA,QACA,GAAA,EAAK,GAAA;AAAA,QACL,MAAA,EAAQ,CAAC,IAAA,EAAM,IAAA,KAAS;AAEtB,UAAA,IAAI,WAAA,EAAa;AACf,YAAA,OAAO,KAAA;AAAA,UACT;AAIA,UAAA,MAAM,YAAA,GAAe,IAAA,CAAK,mBAAA,GACtBF,kCAAA,CAA4B,IAAI,CAAA,GAChC,IAAA;AACJ,UAAA,IAAI,KAAK,OAAA,IAAW,CAAC,aAAa,UAAA,CAAW,IAAA,CAAK,OAAO,CAAA,EAAG;AAC1D,YAAA,OAAO,KAAA;AAAA,UACT;AACA,UAAA,IAAI,KAAK,MAAA,EAAQ;AACf,YAAA,MAAM,SAAA,GAAY,KAAK,KAAA,CAAM,GAAG,EAAE,KAAA,CAAM,KAAK,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AACvD,YAAA,IAAI;AACF,cAAA,OAAO,KAAK,MAAA,CAAO,SAAA,EAAW,EAAE,IAAA,EAAM,IAAA,CAAK,MAAM,CAAA;AAAA,YACnD,SAAS,KAAA,EAAO;AACd,cAAA,WAAA,GAAc,KAAA;AACd,cAAA,OAAO,KAAA;AAAA,YACT;AAAA,UACF;AACA,UAAA,OAAO,IAAA;AAAA,QACT;AAAA,OACD;AAAA,KACH;AAEA,IAAA,IAAI,WAAA,EAAa;AAGf,MAAA,IAAI,CAAC,SAAS,SAAA,EAAW;AACvB,QAAA,MAAMI,mBAAA,CAAG,MAAA,CAAO,GAAG,CAAA,CAAE,MAAM,MAAM;AAAA,QAAC,CAAC,CAAA;AAAA,MACrC;AACA,MAAA,MAAM,WAAA;AAAA,IACR;AAEA,IAAA,OAAO,GAAA;AAAA,EACT;AACF;;;;"} -\ No newline at end of file -+{"version":3,"file":"TarArchiveResponse.cjs.js","sources":["../../../../../src/entrypoints/urlReader/lib/tree/TarArchiveResponse.ts"],"sourcesContent":["/*\n * Copyright 2020 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 isChildPath,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadTreeResponseDirOptions,\n UrlReaderServiceReadTreeResponseFile,\n} from '@backstage/backend-plugin-api';\nimport concatStream from 'concat-stream';\nimport fs from 'fs-extra';\nimport platformPath from 'path';\nimport { pipeline as pipelineCb, Readable } from 'stream';\nimport tar, { FileStat, Parse, ParseStream, ReadEntry } from 'tar';\nimport { promisify } from 'util';\nimport { stripFirstDirectoryFromPath } from './util';\n\n// Tar types for `Parse` is not a proper constructor, but it should be\nconst TarParseStream = Parse as unknown as { new (): ParseStream };\n\nconst pipeline = promisify(pipelineCb);\n\n/**\n * Wraps a tar archive stream into a tree response reader.\n */\nexport class TarArchiveResponse implements UrlReaderServiceReadTreeResponse {\n private read = false;\n private readonly stream: Readable;\n private readonly subPath: string;\n private readonly workDir: string;\n public readonly etag: string;\n private readonly filter?: (path: string, info: { size: number }) => boolean;\n private readonly stripFirstDirectory: boolean;\n\n constructor(\n stream: Readable,\n subPath: string,\n workDir: string,\n etag: string,\n filter?: (path: string, info: { size: number }) => boolean,\n stripFirstDirectory: boolean = true,\n ) {\n this.stream = stream;\n this.subPath = subPath;\n this.workDir = workDir;\n this.etag = etag;\n this.filter = filter;\n this.stripFirstDirectory = stripFirstDirectory;\n if (subPath) {\n if (!subPath.endsWith('/')) {\n this.subPath += '/';\n }\n if (subPath.startsWith('/')) {\n throw new TypeError(\n `TarArchiveResponse subPath must not start with a /, got '${subPath}'`,\n );\n }\n }\n\n this.etag = etag;\n }\n\n // Make sure the input stream is only read once\n private onlyOnce() {\n if (this.read) {\n throw new Error('Response has already been read');\n }\n this.read = true;\n }\n\n async files(): Promise {\n this.onlyOnce();\n\n const files = Array();\n const parser = new TarParseStream();\n\n parser.on('entry', (entry: ReadEntry & Readable) => {\n if (entry.type === 'Directory') {\n entry.resume();\n return;\n }\n\n // File path relative to the root extracted directory. Will remove the\n // top level dir name from the path since its name is hard to predetermine.\n const relativePath = this.stripFirstDirectory\n ? stripFirstDirectoryFromPath(entry.path)\n : entry.path;\n\n if (this.subPath) {\n if (!relativePath.startsWith(this.subPath)) {\n entry.resume();\n return;\n }\n }\n\n const path = relativePath.slice(this.subPath.length);\n if (this.filter) {\n if (!this.filter(path, { size: entry.remain })) {\n entry.resume();\n return;\n }\n }\n\n const content = new Promise(async resolve => {\n await pipeline(entry, concatStream(resolve));\n });\n\n files.push({\n path,\n content: () => content,\n });\n\n entry.resume();\n });\n\n await pipeline(this.stream, parser);\n\n return files;\n }\n\n async archive(): Promise {\n if (!this.subPath) {\n this.onlyOnce();\n\n return this.stream;\n }\n\n // TODO(Rugvip): method for repacking a tar with a subpath is to simply extract into a\n // tmp dir and recreate the archive. Would be nicer to stream things instead.\n const tmpDir = await this.dir();\n\n try {\n const data = await new Promise(async resolve => {\n await pipeline(\n tar.create({ cwd: tmpDir }, ['']),\n concatStream(resolve),\n );\n });\n return Readable.from(data);\n } finally {\n await fs.remove(tmpDir);\n }\n }\n\n async dir(\n options?: UrlReaderServiceReadTreeResponseDirOptions,\n ): Promise {\n this.onlyOnce();\n\n const dir =\n options?.targetDir ??\n (await fs.mkdtemp(platformPath.join(this.workDir, 'backstage-')));\n\n // Equivalent of tar --strip-components=N\n // When no subPath is given, remove just 1 top level directory\n let strip = this.subPath ? this.subPath.split('/').length : 1;\n if (!this.stripFirstDirectory) {\n strip--;\n }\n\n let filterError: Error | undefined = undefined;\n await pipeline(\n this.stream,\n tar.extract({\n strip,\n cwd: dir,\n filter: (path, stat) => {\n // Filter errors will short-circuit the rest of the filtering and then throw\n if (filterError) {\n return false;\n }\n\n // Block symlinks/hardlinks that escape the extraction directory\n const entry = stat as FileStat & { type?: string; linkpath?: string };\n if (\n (entry.type === 'SymbolicLink' || entry.type === 'Link') &&\n entry.linkpath\n ) {\n const strippedPath = path.split('/').slice(strip).join('/');\n const linkDir = platformPath.dirname(\n platformPath.join(dir, strippedPath),\n );\n const targetPath = platformPath.resolve(linkDir, entry.linkpath);\n if (!isChildPath(dir, targetPath)) {\n return false;\n }\n }\n\n // File path relative to the root extracted directory. Will remove the\n // top level dir name from the path since its name is hard to predetermine.\n const relativePath = this.stripFirstDirectory\n ? stripFirstDirectoryFromPath(path)\n : path;\n if (this.subPath && !relativePath.startsWith(this.subPath)) {\n return false;\n }\n if (this.filter) {\n const innerPath = path.split('/').slice(strip).join('/');\n try {\n return this.filter(innerPath, { size: stat.size });\n } catch (error) {\n filterError = error;\n return false;\n }\n }\n return true;\n },\n }),\n );\n\n if (filterError) {\n // If the dir was provided we don't want to remove it, but if it wasn't it means\n // we created a temporary directory and we should remove it.\n if (!options?.targetDir) {\n await fs.remove(dir).catch(() => {});\n }\n throw filterError;\n }\n\n return dir;\n }\n}\n"],"names":["Parse","promisify","pipelineCb","stripFirstDirectoryFromPath","concatStream","tar","Readable","fs","platformPath","isChildPath"],"mappings":"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"} -\ No newline at end of file -diff --git a/dist/package.json.cjs.js b/dist/package.json.cjs.js -index ac692d26b543f95f44c0e39af7223cc65ce966cd..6b592844cc16594960773ba367e2fa5c975f78a4 100644 ---- a/dist/package.json.cjs.js -+++ b/dist/package.json.cjs.js -@@ -2,7 +2,7 @@ - - Object.defineProperty(exports, '__esModule', { value: true }); - --var version = "0.13.1"; -+var version = "0.13.2"; - var packageinfo = { - version: version}; - -diff --git a/dist/urlReader.d.ts b/dist/urlReader.d.ts -index 4244ea520d4ea50558979e71152e5fd4e7518429..391e52c4ddfa2a9c61efff140b0165096880cd6a 100644 ---- a/dist/urlReader.d.ts -+++ b/dist/urlReader.d.ts -@@ -3,6 +3,7 @@ import { RootConfigService, LoggerService, UrlReaderServiceReadTreeResponse, Url - import { AzureIntegration, AzureDevOpsCredentialsProvider, BitbucketCloudIntegration, BitbucketIntegration, BitbucketServerIntegration, GerritIntegration, GithubIntegration, GithubCredentialsProvider, GitLabIntegration, GiteaIntegration, HarnessIntegration, AwsS3Integration, AzureCredentialsManager, AzureBlobStorageIntergation } from '@backstage/integration'; - import { Readable } from 'stream'; - import { AwsCredentialsManager } from '@backstage/integration-aws-node'; -+import { Config } from '@backstage/config'; - - /** - * A predicate that decides whether a specific {@link @backstage/backend-plugin-api#UrlReaderService} can handle a -@@ -374,6 +375,7 @@ declare class AzureBlobStorageUrlReader implements UrlReaderService { - * @public - */ - declare class FetchUrlReader implements UrlReaderService { -+ #private; - /** - * The factory creates a single reader that will be used for reading any URL that's listed - * in configuration at `backend.reading.allow`. The allow list contains a list of objects describing -@@ -387,6 +389,8 @@ declare class FetchUrlReader implements UrlReaderService { - * An optional list of paths which are allowed. If the list is omitted all paths are allowed. - */ - static factory: ReaderFactory; -+ static fromConfig(config: Config): FetchUrlReader; -+ private constructor(); - read(url: string): Promise; - readUrl(url: string, options?: UrlReaderServiceReadUrlOptions): Promise; - readTree(): Promise; diff --git a/dynamic-plugins/.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.2-ce51e5eff8.patch b/dynamic-plugins/.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.2-ce51e5eff8.patch deleted file mode 100644 index fac9df0dcd..0000000000 --- a/dynamic-plugins/.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.2-ce51e5eff8.patch +++ /dev/null @@ -1,44 +0,0 @@ -diff --git a/dist/actions/fetch.cjs.js b/dist/actions/fetch.cjs.js -index 9b4b30cc47e65d3ad71f819adecd6deb52200057..21f534c4a13dfaf084fa21a632a541ace31209dc 100644 ---- a/dist/actions/fetch.cjs.js -+++ b/dist/actions/fetch.cjs.js -@@ -23,7 +23,7 @@ async function fetchContents(options) { - if (!fetchUrlIsAbsolute && baseUrl?.startsWith("file://")) { - const basePath = baseUrl.slice("file://".length); - const srcDir = backendPluginApi.resolveSafeChildPath(path__default.default.dirname(basePath), fetchUrl); -- await fs__default.default.copy(srcDir, outputPath); -+ await fs__default.default.copy(srcDir, outputPath, { filter: (src) => backendPluginApi.isChildPath(srcDir, src) }); - } else { - const readUrl = getReadUrl(fetchUrl, baseUrl, integrations); - const res = await reader.readTree(readUrl, { token }); -diff --git a/dist/actions/fetch.cjs.js.map b/dist/actions/fetch.cjs.js.map -index c7a6ca5ba4adb8eef9b5659541e3c209421f43e2..f9edfd651d826639d0b79919bf72fbed88e873b5 100644 ---- a/dist/actions/fetch.cjs.js.map -+++ b/dist/actions/fetch.cjs.js.map -@@ -1 +1 @@ --{"version":3,"file":"fetch.cjs.js","sources":["../../src/actions/fetch.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { UrlReaderService } from '@backstage/backend-plugin-api';\nimport { resolveSafeChildPath } from '@backstage/backend-plugin-api';\nimport { InputError } from '@backstage/errors';\nimport { ScmIntegrations } from '@backstage/integration';\nimport fs from 'fs-extra';\nimport path from 'path';\n\n/**\n * A helper function that reads the contents of a directory from the given URL.\n * Can be used in your own actions, and also used behind fetch:template and fetch:plain\n *\n * @public\n */\nexport async function fetchContents(options: {\n reader: UrlReaderService;\n integrations: ScmIntegrations;\n baseUrl?: string;\n fetchUrl?: string;\n outputPath: string;\n token?: string;\n}) {\n const {\n reader,\n integrations,\n baseUrl,\n fetchUrl = '.',\n outputPath,\n token,\n } = options;\n\n const fetchUrlIsAbsolute = isFetchUrlAbsolute(fetchUrl);\n\n // We handle both file locations and url ones\n if (!fetchUrlIsAbsolute && baseUrl?.startsWith('file://')) {\n const basePath = baseUrl.slice('file://'.length);\n const srcDir = resolveSafeChildPath(path.dirname(basePath), fetchUrl);\n await fs.copy(srcDir, outputPath);\n } else {\n const readUrl = getReadUrl(fetchUrl, baseUrl, integrations);\n\n const res = await reader.readTree(readUrl, { token });\n await fs.ensureDir(outputPath);\n await res.dir({ targetDir: outputPath });\n }\n}\n\n/**\n * A helper function that reads the content of a single file from the given URL.\n * Can be used in your own actions, and also used behind `fetch:plain:file`\n *\n * @public\n */\nexport async function fetchFile(options: {\n reader: UrlReaderService;\n integrations: ScmIntegrations;\n baseUrl?: string;\n fetchUrl?: string;\n outputPath: string;\n token?: string;\n}) {\n const {\n reader,\n integrations,\n baseUrl,\n fetchUrl = '.',\n outputPath,\n token,\n } = options;\n\n const fetchUrlIsAbsolute = isFetchUrlAbsolute(fetchUrl);\n\n // We handle both file locations and url ones\n if (!fetchUrlIsAbsolute && baseUrl?.startsWith('file://')) {\n const basePath = baseUrl.slice('file://'.length);\n const src = resolveSafeChildPath(path.dirname(basePath), fetchUrl);\n await fs.copyFile(src, outputPath);\n } else {\n const readUrl = getReadUrl(fetchUrl, baseUrl, integrations);\n\n const res = await reader.readUrl(readUrl, { token });\n await fs.ensureDir(path.dirname(outputPath));\n const buffer = await res.buffer();\n await fs.outputFile(outputPath, buffer);\n }\n}\n\nfunction isFetchUrlAbsolute(fetchUrl: string) {\n let fetchUrlIsAbsolute = false;\n try {\n // eslint-disable-next-line no-new\n new URL(fetchUrl);\n fetchUrlIsAbsolute = true;\n } catch {\n /* ignored */\n }\n return fetchUrlIsAbsolute;\n}\n\nfunction getReadUrl(\n fetchUrl: string,\n baseUrl: string | undefined,\n integrations: ScmIntegrations,\n) {\n if (isFetchUrlAbsolute(fetchUrl)) {\n return fetchUrl;\n } else if (baseUrl) {\n const integration = integrations.byUrl(baseUrl);\n if (!integration) {\n throw new InputError(`No integration found for location ${baseUrl}`);\n }\n\n return integration.resolveUrl({\n url: fetchUrl,\n base: baseUrl,\n });\n }\n throw new InputError(\n `Failed to fetch, template location could not be determined and the fetch URL is relative, ${fetchUrl}`,\n );\n}\n"],"names":["resolveSafeChildPath","path","fs","InputError"],"mappings":"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"} -\ No newline at end of file -+{"version":3,"file":"fetch.cjs.js","sources":["../../src/actions/fetch.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { UrlReaderService } from '@backstage/backend-plugin-api';\nimport {\n isChildPath,\n resolveSafeChildPath,\n} from '@backstage/backend-plugin-api';\nimport { InputError } from '@backstage/errors';\nimport { ScmIntegrations } from '@backstage/integration';\nimport fs from 'fs-extra';\nimport path from 'path';\n\n/**\n * A helper function that reads the contents of a directory from the given URL.\n * Can be used in your own actions, and also used behind fetch:template and fetch:plain\n *\n * @public\n */\nexport async function fetchContents(options: {\n reader: UrlReaderService;\n integrations: ScmIntegrations;\n baseUrl?: string;\n fetchUrl?: string;\n outputPath: string;\n token?: string;\n}) {\n const {\n reader,\n integrations,\n baseUrl,\n fetchUrl = '.',\n outputPath,\n token,\n } = options;\n\n const fetchUrlIsAbsolute = isFetchUrlAbsolute(fetchUrl);\n\n // We handle both file locations and url ones\n if (!fetchUrlIsAbsolute && baseUrl?.startsWith('file://')) {\n const basePath = baseUrl.slice('file://'.length);\n const srcDir = resolveSafeChildPath(path.dirname(basePath), fetchUrl);\n await fs.copy(srcDir, outputPath, { filter: src => isChildPath(srcDir, src) });\n } else {\n const readUrl = getReadUrl(fetchUrl, baseUrl, integrations);\n\n const res = await reader.readTree(readUrl, { token });\n await fs.ensureDir(outputPath);\n await res.dir({ targetDir: outputPath });\n }\n}\n\n/**\n * A helper function that reads the content of a single file from the given URL.\n * Can be used in your own actions, and also used behind `fetch:plain:file`\n *\n * @public\n */\nexport async function fetchFile(options: {\n reader: UrlReaderService;\n integrations: ScmIntegrations;\n baseUrl?: string;\n fetchUrl?: string;\n outputPath: string;\n token?: string;\n}) {\n const {\n reader,\n integrations,\n baseUrl,\n fetchUrl = '.',\n outputPath,\n token,\n } = options;\n\n const fetchUrlIsAbsolute = isFetchUrlAbsolute(fetchUrl);\n\n // We handle both file locations and url ones\n if (!fetchUrlIsAbsolute && baseUrl?.startsWith('file://')) {\n const basePath = baseUrl.slice('file://'.length);\n const src = resolveSafeChildPath(path.dirname(basePath), fetchUrl);\n await fs.copyFile(src, outputPath);\n } else {\n const readUrl = getReadUrl(fetchUrl, baseUrl, integrations);\n\n const res = await reader.readUrl(readUrl, { token });\n await fs.ensureDir(path.dirname(outputPath));\n const buffer = await res.buffer();\n await fs.outputFile(outputPath, buffer);\n }\n}\n\nfunction isFetchUrlAbsolute(fetchUrl: string) {\n let fetchUrlIsAbsolute = false;\n try {\n // eslint-disable-next-line no-new\n new URL(fetchUrl);\n fetchUrlIsAbsolute = true;\n } catch {\n /* ignored */\n }\n return fetchUrlIsAbsolute;\n}\n\nfunction getReadUrl(\n fetchUrl: string,\n baseUrl: string | undefined,\n integrations: ScmIntegrations,\n) {\n if (isFetchUrlAbsolute(fetchUrl)) {\n return fetchUrl;\n } else if (baseUrl) {\n const integration = integrations.byUrl(baseUrl);\n if (!integration) {\n throw new InputError(`No integration found for location ${baseUrl}`);\n }\n\n return integration.resolveUrl({\n url: fetchUrl,\n base: baseUrl,\n });\n }\n throw new InputError(\n `Failed to fetch, template location could not be determined and the fetch URL is relative, ${fetchUrl}`,\n );\n}\n"],"names":["resolveSafeChildPath","path","fs","isChildPath","InputError"],"mappings":"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"} -\ No newline at end of file -diff --git a/dist/alpha.d.ts b/dist/alpha.d.ts -index acaf636674351ee84fe74b5c68ee200183a80dfd..9d69e3074d7dac700c67422663dc2965768ce91e 100644 ---- a/dist/alpha.d.ts -+++ b/dist/alpha.d.ts -@@ -245,5 +245,4 @@ interface ScaffolderWorkspaceProviderExtensionPoint { - */ - declare const scaffolderWorkspaceProviderExtensionPoint: _backstage_backend_plugin_api.ExtensionPoint; - --export { createTemplateFilter, createTemplateGlobalFunction, createTemplateGlobalValue, restoreWorkspace, scaffolderAutocompleteExtensionPoint, scaffolderTaskBrokerExtensionPoint, scaffolderTemplatingExtensionPoint, scaffolderWorkspaceProviderExtensionPoint, serializeWorkspace }; --export type { AutocompleteHandler, CheckpointContext, CheckpointState, CheckpointStateValue, CheckpointStatus, CreatedTemplateFilter, CreatedTemplateGlobal, CreatedTemplateGlobalFunction, CreatedTemplateGlobalValue, ScaffolderAutocompleteExtensionPoint, ScaffolderTaskBrokerExtensionPoint, ScaffolderTemplatingExtensionPoint, ScaffolderWorkspaceProviderExtensionPoint, TemplateFilterExample, TemplateGlobalFunctionExample, UpdateTaskCheckpointOptions, WorkspaceProvider, ZodFunctionSchema }; -+export { type AutocompleteHandler, type CheckpointContext, type CheckpointState, type CheckpointStateValue, type CheckpointStatus, type CreatedTemplateFilter, type CreatedTemplateGlobal, type CreatedTemplateGlobalFunction, type CreatedTemplateGlobalValue, type ScaffolderAutocompleteExtensionPoint, type ScaffolderTaskBrokerExtensionPoint, type ScaffolderTemplatingExtensionPoint, type ScaffolderWorkspaceProviderExtensionPoint, type TemplateFilterExample, type TemplateGlobalFunctionExample, type UpdateTaskCheckpointOptions, type WorkspaceProvider, type ZodFunctionSchema, createTemplateFilter, createTemplateGlobalFunction, createTemplateGlobalValue, restoreWorkspace, scaffolderAutocompleteExtensionPoint, scaffolderTaskBrokerExtensionPoint, scaffolderTemplatingExtensionPoint, scaffolderWorkspaceProviderExtensionPoint, serializeWorkspace }; -diff --git a/dist/index.d.ts b/dist/index.d.ts -index 1e1dfb9977f0c9dc0f673aad328dbeca1b845f1f..9a1a17fbfa2736021ef37b31a4f55e06788098a2 100644 ---- a/dist/index.d.ts -+++ b/dist/index.d.ts -@@ -586,5 +586,4 @@ interface ScaffolderActionsExtensionPoint { - */ - declare const scaffolderActionsExtensionPoint: _backstage_backend_plugin_api.ExtensionPoint; - --export { addFiles, cloneRepo, commitAndPushBranch, commitAndPushRepo, createBranch, createTemplateAction, deserializeDirectoryContents, executeShellCommand, fetchContents, fetchFile, getRepoSourceDirectory, initRepoAndPush, isNotGitDirectoryOrContents, parseRepoUrl, scaffolderActionsExtensionPoint, serializeDirectoryContents }; --export type { ActionContext, ExecuteShellCommandOptions, ScaffolderActionsExtensionPoint, SerializedFile, SerializedTask, SerializedTaskEvent, TaskBroker, TaskBrokerDispatchOptions, TaskBrokerDispatchResult, TaskCompletionState, TaskContext, TaskEventType, TaskFilter, TaskFilters, TaskSecrets, TaskStatus, TemplateAction, TemplateActionOptions, TemplateExample }; -+export { type ActionContext, type ExecuteShellCommandOptions, type ScaffolderActionsExtensionPoint, type SerializedFile, type SerializedTask, type SerializedTaskEvent, type TaskBroker, type TaskBrokerDispatchOptions, type TaskBrokerDispatchResult, type TaskCompletionState, type TaskContext, type TaskEventType, type TaskFilter, type TaskFilters, type TaskSecrets, type TaskStatus, type TemplateAction, type TemplateActionOptions, type TemplateExample, addFiles, cloneRepo, commitAndPushBranch, commitAndPushRepo, createBranch, createTemplateAction, deserializeDirectoryContents, executeShellCommand, fetchContents, fetchFile, getRepoSourceDirectory, initRepoAndPush, isNotGitDirectoryOrContents, parseRepoUrl, scaffolderActionsExtensionPoint, serializeDirectoryContents }; diff --git a/dynamic-plugins/package.json b/dynamic-plugins/package.json index 19a1007ad1..d71625d817 100644 --- a/dynamic-plugins/package.json +++ b/dynamic-plugins/package.json @@ -44,11 +44,6 @@ "@backstage/backend-plugin-api@^1.6.0": "patch:@backstage/backend-plugin-api@npm%3A1.6.0#./.yarn/patches/@backstage-backend-plugin-api-npm-1.6.0-eaa7987a8e.patch", "@backstage/cli-common@^0.1.15": "patch:@backstage/cli-common@npm%3A0.1.16#./.yarn/patches/@backstage-cli-common-npm-0.1.16-576dbc8aa9.patch", "@backstage/cli-common@^0.1.16": "patch:@backstage/cli-common@npm%3A0.1.16#./.yarn/patches/@backstage-cli-common-npm-0.1.16-576dbc8aa9.patch", - "@backstage/plugin-scaffolder-node@^0.12.1": "patch:@backstage/plugin-scaffolder-node@npm%3A0.12.2#./.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.2-ce51e5eff8.patch", - "@backstage/plugin-scaffolder-node@^0.12.0": "patch:@backstage/plugin-scaffolder-node@npm%3A0.12.2#./.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.2-ce51e5eff8.patch", - "@backstage/plugin-scaffolder-node@^0.12.2": "patch:@backstage/plugin-scaffolder-node@npm%3A0.12.2#./.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.2-ce51e5eff8.patch", - "@backstage/backend-defaults@^0.13.1": "patch:@backstage/backend-defaults@npm%3A0.13.1#./.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch", - "@backstage/backend-defaults@^0.14.0": "patch:@backstage/backend-defaults@npm%3A0.13.1#./.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch", "infinispan": "0.13.0" }, "packageManager": "yarn@3.8.7" diff --git a/dynamic-plugins/yarn.lock b/dynamic-plugins/yarn.lock index 4e38e940d0..d415448557 100644 --- a/dynamic-plugins/yarn.lock +++ b/dynamic-plugins/yarn.lock @@ -3242,9 +3242,20 @@ __metadata: languageName: node linkType: hard -"@backstage/backend-defaults@npm:0.13.1": - version: 0.13.1 - resolution: "@backstage/backend-defaults@npm:0.13.1" +"@backstage/backend-app-api@npm:^1.4.0": + version: 1.6.1 + resolution: "@backstage/backend-app-api@npm:1.6.1" + dependencies: + "@backstage/backend-plugin-api": ^1.9.0 + "@backstage/config": ^1.3.7 + "@backstage/errors": ^1.3.0 + checksum: 3400130446093e2649fdf6d908b8a3ccefdfa67b8a856df79c8258cdd729eae34444685a110879ab4003d3d66904e914b8410594a222324746e857dc26d0ef82 + languageName: node + linkType: hard + +"@backstage/backend-defaults@npm:^0.13.1": + version: 0.13.2 + resolution: "@backstage/backend-defaults@npm:0.13.2" dependencies: "@aws-sdk/abort-controller": ^3.347.0 "@aws-sdk/client-codecommit": ^3.350.0 @@ -3322,13 +3333,13 @@ __metadata: peerDependenciesMeta: "@google-cloud/cloud-sql-connector": optional: true - checksum: 8a9a1feca3a1a7dcca8c7cd05e651168b7a2ed485ddc2043b671827f7465fa609ad29416dfdc3b03dfb5ce97494b9d6f945ce921a2a46efbdbd0ce470e1688d0 + checksum: 8e929bd03316dcfa59cf10689de4b185efa1290d9f00591a6adf4def14591b6f2dce9e7b19e75a4f06b0f9a2af085024bdb7390c797fd7b3eda0929c86b6dea5 languageName: node linkType: hard -"@backstage/backend-defaults@patch:@backstage/backend-defaults@npm%3A0.13.1#./.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch::locator=dynamic-plugins-root%40workspace%3A.": - version: 0.13.1 - resolution: "@backstage/backend-defaults@patch:@backstage/backend-defaults@npm%3A0.13.1#./.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch::version=0.13.1&hash=c0d965&locator=dynamic-plugins-root%40workspace%3A." +"@backstage/backend-defaults@npm:^0.14.0": + version: 0.14.2 + resolution: "@backstage/backend-defaults@npm:0.14.2" dependencies: "@aws-sdk/abort-controller": ^3.347.0 "@aws-sdk/client-codecommit": ^3.350.0 @@ -3336,18 +3347,18 @@ __metadata: "@aws-sdk/credential-providers": ^3.350.0 "@aws-sdk/types": ^3.347.0 "@azure/storage-blob": ^12.5.0 - "@backstage/backend-app-api": ^1.3.0 - "@backstage/backend-dev-utils": ^0.1.5 - "@backstage/backend-plugin-api": ^1.5.0 - "@backstage/cli-node": ^0.2.15 + "@backstage/backend-app-api": ^1.4.0 + "@backstage/backend-dev-utils": ^0.1.6 + "@backstage/backend-plugin-api": ^1.6.0 + "@backstage/cli-node": ^0.2.16 "@backstage/config": ^1.3.6 - "@backstage/config-loader": ^1.10.6 + "@backstage/config-loader": ^1.10.7 "@backstage/errors": ^1.2.7 - "@backstage/integration": ^1.18.2 + "@backstage/integration": ^1.19.1 "@backstage/integration-aws-node": ^0.1.19 - "@backstage/plugin-auth-node": ^0.6.9 - "@backstage/plugin-events-node": ^0.4.17 - "@backstage/plugin-permission-node": ^0.10.6 + "@backstage/plugin-auth-node": ^0.6.10 + "@backstage/plugin-events-node": ^0.4.18 + "@backstage/plugin-permission-node": ^0.10.7 "@backstage/types": ^1.2.2 "@google-cloud/storage": ^7.0.0 "@keyv/memcache": ^2.0.1 @@ -3360,13 +3371,12 @@ __metadata: "@types/express": ^4.17.6 archiver: ^7.0.0 base64-stream: ^1.0.0 - better-sqlite3: ^12.0.0 compression: ^1.7.4 concat-stream: ^2.0.0 cookie: ^0.7.0 cors: ^2.8.5 cron: ^3.0.0 - express: ^4.17.1 + express: ^4.22.0 express-promise-router: ^4.1.0 express-rate-limit: ^7.5.0 fs-extra: ^11.2.0 @@ -3383,7 +3393,7 @@ __metadata: minimatch: ^9.0.0 mysql2: ^3.0.0 node-fetch: ^2.7.0 - node-forge: ^1.3.1 + node-forge: ^1.3.2 p-limit: ^3.1.0 path-to-regexp: ^8.0.0 pg: ^8.11.3 @@ -3392,7 +3402,7 @@ __metadata: rate-limit-redis: ^4.2.0 raw-body: ^2.4.1 selfsigned: ^2.0.0 - tar: ^6.1.12 + tar: ^7.4.3 triple-beam: ^1.4.1 uuid: ^11.0.0 winston: ^3.2.1 @@ -3403,10 +3413,13 @@ __metadata: zod-to-json-schema: ^3.20.4 peerDependencies: "@google-cloud/cloud-sql-connector": ^1.4.0 + better-sqlite3: ^12.0.0 peerDependenciesMeta: "@google-cloud/cloud-sql-connector": optional: true - checksum: b1b78a2dcf6d661c8a21ab51d5419ea4db1e49eea443b09fedb0551658213e1b1646e640b6b456df0de1567bbb3d70b3fbeca0ebcc3f1576c69c49c702755a7f + better-sqlite3: + optional: true + checksum: d3f5b41efb89801473950231b45c0e157c43bd8041bcefcb6d212b0696f7443dc0840f18bee036f919d9d92c0e4209c951bf279a3e642cbb2c0e28700e682187 languageName: node linkType: hard @@ -3417,6 +3430,13 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-dev-utils@npm:^0.1.6": + version: 0.1.7 + resolution: "@backstage/backend-dev-utils@npm:0.1.7" + checksum: c10ca3636d48dc5963f269f3b5c504bb48956ca5d90b54895b4e5c76e05c5813f1f498e58f0020f99d073abedc2b31c76083e720e01252a1726940249663d09b + languageName: node + linkType: hard + "@backstage/backend-dynamic-feature-service@npm:^0.7.6": version: 0.7.7 resolution: "@backstage/backend-dynamic-feature-service@npm:0.7.7" @@ -3498,6 +3518,28 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-plugin-api@npm:^1.6.1, @backstage/backend-plugin-api@npm:^1.9.0": + version: 1.9.0 + resolution: "@backstage/backend-plugin-api@npm:1.9.0" + dependencies: + "@backstage/cli-common": ^0.2.1 + "@backstage/config": ^1.3.7 + "@backstage/errors": ^1.3.0 + "@backstage/plugin-auth-node": ^0.7.0 + "@backstage/plugin-permission-common": ^0.9.8 + "@backstage/plugin-permission-node": ^0.10.12 + "@backstage/types": ^1.2.2 + "@types/express": ^4.17.6 + "@types/json-schema": ^7.0.6 + "@types/luxon": ^3.0.0 + json-schema: ^0.4.0 + knex: ^3.0.0 + luxon: ^3.0.0 + zod: ^3.25.76 || ^4.0.0 + checksum: acdfde9429ed3b7c54eee1fbd38df5aa1b8a45344a0047d24bd666ec6a946dbea99d1c4eab0b57dd2551dd1b28af4aadd04eff213d76e6b8a0ba1567e97543c3 + languageName: node + linkType: hard + "@backstage/backend-plugin-api@patch:@backstage/backend-plugin-api@npm%3A1.6.0#./.yarn/patches/@backstage-backend-plugin-api-npm-1.6.0-eaa7987a8e.patch::locator=dynamic-plugins-root%40workspace%3A.": version: 1.6.0 resolution: "@backstage/backend-plugin-api@patch:@backstage/backend-plugin-api@npm%3A1.6.0#./.yarn/patches/@backstage-backend-plugin-api-npm-1.6.0-eaa7987a8e.patch::version=1.6.0&hash=e8ef03&locator=dynamic-plugins-root%40workspace%3A." @@ -3556,6 +3598,18 @@ __metadata: languageName: node linkType: hard +"@backstage/cli-common@npm:^0.2.1": + version: 0.2.1 + resolution: "@backstage/cli-common@npm:0.2.1" + dependencies: + "@backstage/errors": ^1.3.0 + cross-spawn: ^7.0.3 + global-agent: ^3.0.0 + undici: ^7.24.5 + checksum: f460083f5276caac3b04bbc86552d12047af438a08209dc8774bbecfa4c97d95ced188611f17e8f6c862b28af9137a0c7072e28ca9b0e97f8352e39035dc53d8 + languageName: node + linkType: hard + "@backstage/cli-common@patch:@backstage/cli-common@npm%3A0.1.16#./.yarn/patches/@backstage-cli-common-npm-0.1.16-576dbc8aa9.patch::locator=dynamic-plugins-root%40workspace%3A.": version: 0.1.16 resolution: "@backstage/cli-common@patch:@backstage/cli-common@npm%3A0.1.16#./.yarn/patches/@backstage-cli-common-npm-0.1.16-576dbc8aa9.patch::version=0.1.16&hash=1f1779&locator=dynamic-plugins-root%40workspace%3A." @@ -3759,6 +3813,17 @@ __metadata: languageName: node linkType: hard +"@backstage/config@npm:^1.3.7": + version: 1.3.7 + resolution: "@backstage/config@npm:1.3.7" + dependencies: + "@backstage/errors": ^1.3.0 + "@backstage/types": ^1.2.2 + ms: ^2.1.3 + checksum: 9fe63f73a4348f8f85f79f90bdf1624ee8ab8f37367fe5d71157e8407a56302104cab81951a2e6374a73f2e9f4e6655812147f24928406f9ecc2d78884d3eb9e + languageName: node + linkType: hard + "@backstage/core-app-api@npm:^1.19.3": version: 1.19.3 resolution: "@backstage/core-app-api@npm:1.19.3" @@ -3920,6 +3985,16 @@ __metadata: languageName: node linkType: hard +"@backstage/errors@npm:^1.3.0": + version: 1.3.0 + resolution: "@backstage/errors@npm:1.3.0" + dependencies: + "@backstage/types": ^1.2.2 + serialize-error: ^8.0.1 + checksum: 3ab1ec4e39646b01d8720508cb0551ec60ee632f1a62e75b1be5c0363bc6336f82b64300ea1facac10222ddb40d58e4b24413be834012d3f880dc3425177b9b6 + languageName: node + linkType: hard + "@backstage/eslint-plugin@npm:^0.2.0": version: 0.2.0 resolution: "@backstage/eslint-plugin@npm:0.2.0" @@ -4061,9 +4136,9 @@ __metadata: languageName: node linkType: hard -"@backstage/integration@npm:^1.18.1, @backstage/integration@npm:^1.18.2, @backstage/integration@npm:^1.19.0": - version: 1.19.1 - resolution: "@backstage/integration@npm:1.19.1" +"@backstage/integration@npm:^1.18.1, @backstage/integration@npm:^1.18.2, @backstage/integration@npm:^1.19.0, @backstage/integration@npm:^1.19.2": + version: 1.19.2 + resolution: "@backstage/integration@npm:1.19.2" dependencies: "@azure/identity": ^4.0.0 "@azure/storage-blob": ^12.5.0 @@ -4075,7 +4150,25 @@ __metadata: git-url-parse: ^15.0.0 lodash: ^4.17.21 luxon: ^3.0.0 - checksum: c670fe93630bb016b453f5c0601fdf6730633b92aac6de9e7df67093c98d89df6352f2fae13b5c07226f98736eb6af237434d67233107b768557e3bf3c7ea110 + checksum: 0bc4d3454da9059c72ca65298f904de79f5d2d456e2af4cfaee48256ceb1aaf65417470b0edc0e0856b8127d702e33a6f32778052d9c3920286642b7822ed7ed + languageName: node + linkType: hard + +"@backstage/integration@npm:^1.19.1": + version: 1.20.1 + resolution: "@backstage/integration@npm:1.20.1" + dependencies: + "@azure/identity": ^4.0.0 + "@azure/storage-blob": ^12.5.0 + "@backstage/config": ^1.3.6 + "@backstage/errors": ^1.2.7 + "@octokit/auth-app": ^4.0.0 + "@octokit/rest": ^19.0.3 + cross-fetch: ^4.0.0 + git-url-parse: ^15.0.0 + lodash: ^4.17.21 + luxon: ^3.0.0 + checksum: 3aeee8d2603d1f27dc4e1ec5715d269bf0deb9f7f901647122559fbab25557cd9e87bd5eac14d744e6c80f05b42660ee6899d62d3682b77480a631758dc6c75d languageName: node linkType: hard @@ -4893,9 +4986,9 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-permission-common@npm:^0.9.3": - version: 0.9.3 - resolution: "@backstage/plugin-permission-common@npm:0.9.3" +"@backstage/plugin-permission-common@npm:^0.9.3, @backstage/plugin-permission-common@npm:^0.9.4": + version: 0.9.4 + resolution: "@backstage/plugin-permission-common@npm:0.9.4" dependencies: "@backstage/config": ^1.3.6 "@backstage/errors": ^1.2.7 @@ -4903,26 +4996,59 @@ __metadata: cross-fetch: ^4.0.0 uuid: ^11.0.0 zod: ^3.22.4 - zod-to-json-schema: ^3.20.4 - checksum: 5967b54b3b7e0761790bf7eac37be9c7b1ff92f07616dc13a5696b8c44eb01f99f166e749b34bb757ef28ceda2ab14984bc1b78f9ce658c31132d5adb5c5a474 + zod-to-json-schema: ^3.25.1 + checksum: 2a168230d8c97d2056275eaa4f167995f5847fd98341dea3116e07f3b5f6dc384017a7ad9940be2f95505c986815d5230dc3e843ed1798782240f4ae63d07733 + languageName: node + linkType: hard + +"@backstage/plugin-permission-common@npm:^0.9.8": + version: 0.9.8 + resolution: "@backstage/plugin-permission-common@npm:0.9.8" + dependencies: + "@backstage/config": ^1.3.7 + "@backstage/errors": ^1.3.0 + "@backstage/types": ^1.2.2 + cross-fetch: ^4.0.0 + uuid: ^11.0.0 + zod: ^3.25.76 || ^4.0.0 + zod-to-json-schema: ^3.25.1 + checksum: 065a1b21e7175e7e29963b841cbd282adc775604ab59892629c1b2d2703387abb747314b85af234768fdccf922e132ae679045359fe0badf5cee5ef583b23333 + languageName: node + linkType: hard + +"@backstage/plugin-permission-node@npm:^0.10.12": + version: 0.10.12 + resolution: "@backstage/plugin-permission-node@npm:0.10.12" + dependencies: + "@backstage/backend-plugin-api": ^1.9.0 + "@backstage/config": ^1.3.7 + "@backstage/errors": ^1.3.0 + "@backstage/plugin-auth-node": ^0.7.0 + "@backstage/plugin-permission-common": ^0.9.8 + "@types/express": ^4.17.6 + express: ^4.22.0 + express-promise-router: ^4.1.0 + zod: ^3.25.76 || ^4.0.0 + zod-to-json-schema: ^3.25.1 + checksum: 08f996db284ba4fee5ec48ee9a4422a5e60e17e1f863bec7688879b7ad772ed04f6338aed0609cecee9833fc995860792e3d318848dec8efa1142b35ae2dbc5d languageName: node linkType: hard "@backstage/plugin-permission-node@npm:^0.10.6, @backstage/plugin-permission-node@npm:^0.10.7": - version: 0.10.7 - resolution: "@backstage/plugin-permission-node@npm:0.10.7" + version: 0.10.8 + resolution: "@backstage/plugin-permission-node@npm:0.10.8" dependencies: - "@backstage/backend-plugin-api": ^1.6.0 + "@backstage/backend-plugin-api": ^1.6.1 "@backstage/config": ^1.3.6 "@backstage/errors": ^1.2.7 - "@backstage/plugin-auth-node": ^0.6.10 - "@backstage/plugin-permission-common": ^0.9.3 + "@backstage/plugin-auth-node": ^0.6.11 + "@backstage/plugin-permission-common": ^0.9.4 "@types/express": ^4.17.6 express: ^4.22.0 express-promise-router: ^4.1.0 zod: ^3.22.4 - zod-to-json-schema: ^3.20.4 - checksum: 645e3a2278885dab0f642ce48139cdf20df45b195e604905092e158e42827b0517d69d7f0f98a9cfbd738d7482727cc0b1bc34a79e86f95ff60f85b0d008f4f3 + zod-to-json-schema: ^3.25.1 + checksum: 2245d6eeb0908f9a8de7e0cc6a02d5cd1edb84f840b7cd8e3dae3f4d56293f8651f5903fd6d754f7a7041a54c7f11dcc4f47a6f2629039ca49a8ea69c4e60a9d languageName: node linkType: hard @@ -4986,14 +5112,14 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-scaffolder-common@npm:^1.7.4": - version: 1.7.4 - resolution: "@backstage/plugin-scaffolder-common@npm:1.7.4" +"@backstage/plugin-scaffolder-common@npm:^1.7.4, @backstage/plugin-scaffolder-common@npm:^1.7.5": + version: 1.7.5 + resolution: "@backstage/plugin-scaffolder-common@npm:1.7.5" dependencies: "@backstage/catalog-model": ^1.7.6 "@backstage/errors": ^1.2.7 - "@backstage/integration": ^1.19.0 - "@backstage/plugin-permission-common": ^0.9.3 + "@backstage/integration": ^1.19.2 + "@backstage/plugin-permission-common": ^0.9.4 "@backstage/types": ^1.2.2 "@microsoft/fetch-event-source": ^2.0.1 "@types/json-schema": ^7.0.9 @@ -5001,20 +5127,20 @@ __metadata: json-schema: ^0.4.0 uri-template: ^2.0.0 zen-observable: ^0.10.0 - checksum: 5c2cb70237a38974a286566ca0de6072d2f934e0246c5e8d16f90757299a87ae8efc8ddc47b31fc6f6d9db6da70615ae58bc241c293fc7dac28f035bd70990d3 + checksum: a951de5218047a79bcdddfd9e568498f129bb7de8dd6177cd8ef1859a66f1889b0016e601ce3b44701da129e285b760c4783b757ad446b4a9a861e58d61d162a languageName: node linkType: hard -"@backstage/plugin-scaffolder-node@npm:0.12.2": - version: 0.12.2 - resolution: "@backstage/plugin-scaffolder-node@npm:0.12.2" +"@backstage/plugin-scaffolder-node@npm:^0.12.0, @backstage/plugin-scaffolder-node@npm:^0.12.1, @backstage/plugin-scaffolder-node@npm:^0.12.2": + version: 0.12.3 + resolution: "@backstage/plugin-scaffolder-node@npm:0.12.3" dependencies: - "@backstage/backend-plugin-api": ^1.6.0 + "@backstage/backend-plugin-api": ^1.6.1 "@backstage/catalog-model": ^1.7.6 "@backstage/errors": ^1.2.7 - "@backstage/integration": ^1.19.0 - "@backstage/plugin-permission-common": ^0.9.3 - "@backstage/plugin-scaffolder-common": ^1.7.4 + "@backstage/integration": ^1.19.2 + "@backstage/plugin-permission-common": ^0.9.4 + "@backstage/plugin-scaffolder-common": ^1.7.5 "@backstage/types": ^1.2.2 "@isomorphic-git/pgp-plugin": ^0.0.7 concat-stream: ^2.0.0 @@ -5028,36 +5154,8 @@ __metadata: winston: ^3.2.1 winston-transport: ^4.7.0 zod: ^3.22.4 - zod-to-json-schema: ^3.20.4 - checksum: fd02fd59bfb2cad11163a3617de7a6b4671f50a8bae72eef8723c6bcc44ca2bb53e5a2c7fdedbe9d63cacfd6fcb99f21faa657546979d378847853030b9a0e62 - languageName: node - linkType: hard - -"@backstage/plugin-scaffolder-node@patch:@backstage/plugin-scaffolder-node@npm%3A0.12.2#./.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.2-ce51e5eff8.patch::locator=dynamic-plugins-root%40workspace%3A.": - version: 0.12.2 - resolution: "@backstage/plugin-scaffolder-node@patch:@backstage/plugin-scaffolder-node@npm%3A0.12.2#./.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.2-ce51e5eff8.patch::version=0.12.2&hash=ca41c7&locator=dynamic-plugins-root%40workspace%3A." - dependencies: - "@backstage/backend-plugin-api": ^1.6.0 - "@backstage/catalog-model": ^1.7.6 - "@backstage/errors": ^1.2.7 - "@backstage/integration": ^1.19.0 - "@backstage/plugin-permission-common": ^0.9.3 - "@backstage/plugin-scaffolder-common": ^1.7.4 - "@backstage/types": ^1.2.2 - "@isomorphic-git/pgp-plugin": ^0.0.7 - concat-stream: ^2.0.0 - fs-extra: ^11.2.0 - globby: ^11.0.0 - isomorphic-git: ^1.23.0 - jsonschema: ^1.5.0 - lodash: ^4.17.21 - p-limit: ^3.1.0 - tar: ^6.1.12 - winston: ^3.2.1 - winston-transport: ^4.7.0 - zod: ^3.22.4 - zod-to-json-schema: ^3.20.4 - checksum: 2d0996f9994dccb4da73726a9a4fb212aff53e5b71491e91ce6bdfd12c7a0ee1b3c61745b9c2c4fb2d63437ed77c420a382a1801300b012063b1bbee7ceb895c + zod-to-json-schema: ^3.25.1 + checksum: 0466d81fc71f275c2f9fda0fce985378a1b2cfeb66afd4ebe166c5a0a886b9bd6554b95633d9339ebea0b11a3df063816913ebd4e68fba26e2f130c6c8fcd1e1 languageName: node linkType: hard @@ -26345,14 +26443,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.16.4, lodash@npm:^4.17.0, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4": - version: 4.18.1 - resolution: "lodash@npm:4.18.1" - checksum: bb5f5b49aad29614e709af02b64c56b0f8b78c6a81434a3c1ae527d2f0f78ca08f9d9fb22aa825a053876c9d2166e9c01f31c356014b5e2bdc0556c057433102 - languageName: node - linkType: hard - -"lodash@npm:^4.18.1": +"lodash@npm:^4.16.4, lodash@npm:^4.17.0, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:^4.18.1": version: 4.18.1 resolution: "lodash@npm:4.18.1" checksum: bb5f5b49aad29614e709af02b64c56b0f8b78c6a81434a3c1ae527d2f0f78ca08f9d9fb22aa825a053876c9d2166e9c01f31c356014b5e2bdc0556c057433102 @@ -27985,6 +28076,13 @@ __metadata: languageName: node linkType: hard +"node-forge@npm:^1.3.2": + version: 1.4.0 + resolution: "node-forge@npm:1.4.0" + checksum: c97c634d4d483aae815677db5b1bd14bfea4d873ab48817e020610a2b4d8bc6b3e77994860189b44151ff8e0842c0c4ba6faa80b9a6e6fbd6989865e8eb80b96 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 12.1.0 resolution: "node-gyp@npm:12.1.0" @@ -33316,6 +33414,19 @@ __metadata: languageName: node linkType: hard +"tar@npm:^7.4.3": + version: 7.5.13 + resolution: "tar@npm:7.5.13" + dependencies: + "@isaacs/fs-minipass": ^4.0.0 + chownr: ^3.0.0 + minipass: ^7.1.2 + minizlib: ^3.1.0 + yallist: ^5.0.0 + checksum: adcc2a9179dab1b36ecb26575e698d2df8491a1df2cc83e2a0fdd8eaefd076da60dd2e20383a37760b5790bee34e9291aa2b2a9b3deef37ff03c1046219e5df7 + languageName: node + linkType: hard + "tarn@npm:^3.0.2": version: 3.0.2 resolution: "tarn@npm:3.0.2" @@ -34277,6 +34388,13 @@ __metadata: languageName: node linkType: hard +"undici@npm:^7.24.5": + version: 7.25.0 + resolution: "undici@npm:7.25.0" + checksum: 502f855d69dd0f343a4b4999b995b99eab8764639983776bb5afd09b50671863244defe093e7fb97df767c948d6548c0c3d97f8dbb35fec16c1c8c1c15843f17 + languageName: node + linkType: hard + "unfetch@npm:^3.1.1": version: 3.1.2 resolution: "unfetch@npm:3.1.2" @@ -36049,6 +36167,15 @@ __metadata: languageName: node linkType: hard +"zod-to-json-schema@npm:^3.25.1": + version: 3.25.2 + resolution: "zod-to-json-schema@npm:3.25.2" + peerDependencies: + zod: ^3.25.28 || ^4 + checksum: 8b728476374a38711e0ca5c066be16ed8facf5a5eb5a76f371b0062e5b6b11f906f446c616d6879b9a4d373738c74cd26e4dfe1d460e7fb9a155a3fe9d04b690 + languageName: node + linkType: hard + "zod-validation-error@npm:^3.4.0": version: 3.5.4 resolution: "zod-validation-error@npm:3.5.4" @@ -36065,6 +36192,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^3.25.76 || ^4.0.0": + version: 4.3.6 + resolution: "zod@npm:4.3.6" + checksum: 19cec761b46bae4b6e7e861ea740f3f248e50a6671825afc8a5758e27b35d6f20ccde9942422fd5cf6f8b697f18bd05ef8bb33f5f2db112ab25cc628de2fae47 + languageName: node + linkType: hard + "zstd-codec@npm:^0.1.5": version: 0.1.5 resolution: "zstd-codec@npm:0.1.5" diff --git a/package.json b/package.json index f3d971d485..7fc3fc747d 100644 --- a/package.json +++ b/package.json @@ -69,11 +69,6 @@ "@backstage/backend-plugin-api@^1.6.0": "patch:@backstage/backend-plugin-api@npm%3A1.5.0#./.yarn/patches/@backstage-backend-plugin-api-npm-1.5.0-7a004cae77.patch", "@backstage/backend-plugin-api@^1.4.2": "patch:@backstage/backend-plugin-api@npm%3A1.5.0#./.yarn/patches/@backstage-backend-plugin-api-npm-1.5.0-7a004cae77.patch", "@backstage/cli-common@^0.1.15": "patch:@backstage/cli-common@npm%3A0.1.16#./.yarn/patches/@backstage-cli-common-npm-0.1.16-576dbc8aa9.patch", - "@backstage/plugin-scaffolder-backend@3.0.2": "patch:@backstage/plugin-scaffolder-backend@npm%3A3.0.2#./.yarn/patches/@backstage-plugin-scaffolder-backend-npm-3.0.2-01a7364606.patch", - "@backstage/backend-defaults@0.13.1": "patch:@backstage/backend-defaults@npm%3A0.13.1#./.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch", - "@backstage/backend-defaults@^0.13.1": "patch:@backstage/backend-defaults@npm%3A0.13.1#./.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch", - "@backstage/backend-defaults@^0.13.2": "patch:@backstage/backend-defaults@npm%3A0.13.1#./.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch", - "@backstage/plugin-scaffolder-node@^0.12.1": "patch:@backstage/plugin-scaffolder-node@npm%3A0.12.1#./.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.1-7ce02a35bc.patch", "infinispan": "0.13.0", "@backstage/plugin-scaffolder-react@npm:1.19.3/flatted": "^3.4.2" }, diff --git a/packages/backend/package.json b/packages/backend/package.json index f97d9c6817..5abb832044 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -25,7 +25,7 @@ "@backstage-community/plugin-rbac-node": "1.16.0", "@backstage-community/plugin-scaffolder-backend-module-annotator": "2.12.0", "@backstage/backend-app-api": "1.3.0", - "@backstage/backend-defaults": "0.13.1", + "@backstage/backend-defaults": "0.13.3", "@backstage/backend-dynamic-feature-service": "0.7.6", "@backstage/backend-plugin-api": "1.5.0", "@backstage/catalog-client": "1.12.1", @@ -60,7 +60,7 @@ "@backstage/plugin-events-node": "0.4.17", "@backstage/plugin-permission-backend": "0.7.6", "@backstage/plugin-proxy-backend": "0.6.8", - "@backstage/plugin-scaffolder-backend": "3.0.2", + "@backstage/plugin-scaffolder-backend": "3.0.3", "@backstage/plugin-search-backend": "2.0.8", "@backstage/plugin-search-backend-module-catalog": "0.3.10", "@backstage/plugin-search-backend-module-pg": "0.5.50", diff --git a/plugins/dynamic-plugins-info-backend/package.json b/plugins/dynamic-plugins-info-backend/package.json index 7a4fe77ce6..2d538acbf8 100644 --- a/plugins/dynamic-plugins-info-backend/package.json +++ b/plugins/dynamic-plugins-info-backend/package.json @@ -32,7 +32,7 @@ "prettier:fix": "prettier --ignore-unknown --write ." }, "dependencies": { - "@backstage/backend-defaults": "0.13.1", + "@backstage/backend-defaults": "0.13.3", "@backstage/backend-dynamic-feature-service": "0.7.6", "@backstage/backend-plugin-api": "1.5.0", "@backstage/config": "1.3.6", diff --git a/plugins/licensed-users-info-backend/package.json b/plugins/licensed-users-info-backend/package.json index 4c3d655b40..aae4597481 100644 --- a/plugins/licensed-users-info-backend/package.json +++ b/plugins/licensed-users-info-backend/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage-community/plugin-rbac-common": "1.22.0", - "@backstage/backend-defaults": "0.13.1", + "@backstage/backend-defaults": "0.13.3", "@backstage/backend-plugin-api": "1.5.0", "@backstage/catalog-client": "1.12.1", "@backstage/catalog-model": "1.7.6", diff --git a/plugins/scalprum-backend/package.json b/plugins/scalprum-backend/package.json index b95ca612f6..6779e07572 100644 --- a/plugins/scalprum-backend/package.json +++ b/plugins/scalprum-backend/package.json @@ -31,7 +31,7 @@ "prettier:fix": "prettier --ignore-unknown --write ." }, "dependencies": { - "@backstage/backend-defaults": "0.13.1", + "@backstage/backend-defaults": "0.13.3", "@backstage/backend-dynamic-feature-service": "0.7.6", "@backstage/backend-plugin-api": "1.5.0", "@backstage/config": "1.3.6", diff --git a/scripts/yarn-lockfile-surgeon/.gitignore b/scripts/yarn-lockfile-surgeon/.gitignore new file mode 100644 index 0000000000..c2658d7d1b --- /dev/null +++ b/scripts/yarn-lockfile-surgeon/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/scripts/yarn-lockfile-surgeon/README.md b/scripts/yarn-lockfile-surgeon/README.md new file mode 100644 index 0000000000..6d3b4740a6 --- /dev/null +++ b/scripts/yarn-lockfile-surgeon/README.md @@ -0,0 +1,76 @@ +# yarn-lockfile-surgeon + +Surgically bump packages in a Yarn Berry (v3+) lockfile to their **minimum +satisfying versions**, without re-resolving unrelated transitive dependencies. + +## Why + +Yarn's built-in `yarn up` and `yarn install` always resolve new dependency +ranges to the **latest** matching version. On an LTS branch where lockfile +stability matters (e.g. security patches), this pulls in far more changes than +necessary. + +This tool resolves to the **lowest** version that satisfies each range, +keeping the lockfile diff as small as possible. + +## How it works + +1. Removes old lockfile entries for the target packages (including any `patch:` entries) +2. Fetches the new version's metadata from the npm registry +3. For each new dependency range not satisfied by an existing lockfile entry, + resolves to the **minimum satisfying version** +4. Walks transitive dependencies to catch cascading range bumps +5. Writes the updated lockfile with empty checksums — + `yarn install --mode=update-lockfile` fills these in + +## Usage + +The tool only modifies `yarn.lock`. Before running it, you need to: + +1. Update direct dependency versions in your `package.json` files +2. Remove any `patch:` resolutions from `package.json` for the packages being upgraded +3. Delete the corresponding `.yarn/patches/` files + +Then run: + +```bash +cd scripts/yarn-lockfile-surgeon +npm install # first time only + +yarn-lockfile-surgeon yarn.lock \ + @scope/package-a@1.2.3 \ + @scope/package-b@4.5.6 + +# Then, from the directory containing yarn.lock +yarn install --mode=update-lockfile +``` + +## Comparison with `yarn up` + +Given `@backstage/backend-defaults@0.12.2` which declares +`@backstage/config: ^1.3.4` (current lockfile has 1.3.3): + +| Tool | Resolves `^1.3.4` to | Result | +| ---------------------- | -------------------- | ------------------------------- | +| `yarn up` | 1.3.7 (latest) | Cascading transitive upgrades | +| `yarn-lockfile-surgeon` | 1.3.4 (minimum) | Only the required patch version | + +## Known limitations + +**Extra entries from `yarn install`**: When `yarn install --mode=update-lockfile` +fills in checksums, it may also add new lockfile entries for dependency ranges +introduced by second-order transitive dependencies. These resolve to the +**latest** version since they go through Yarn's standard resolver. The extra +entries are additive and harmless, but make the diff slightly larger than the +theoretical minimum. + +**Unresolvable ranges**: If no published version satisfies a transitive +dependency range, the tool logs a warning and skips it. The range will be left +for `yarn install` to resolve using its default (latest) strategy. + +## Prior art + +pnpm has a built-in [`resolution-mode: lowest-direct`](https://pnpm.io/settings#resolution-mode) +setting that resolves direct dependencies to their lowest matching version. +Yarn Berry has no equivalent — this tool fills that gap for lockfile-level +bumps. diff --git a/scripts/yarn-lockfile-surgeon/index.ts b/scripts/yarn-lockfile-surgeon/index.ts new file mode 100755 index 0000000000..6d17663d05 --- /dev/null +++ b/scripts/yarn-lockfile-surgeon/index.ts @@ -0,0 +1,58 @@ +#!/usr/bin/env node +/** + * yarn-lockfile-surgeon + * + * Surgically bumps packages in a Yarn Berry (v3+) lockfile to their minimum + * satisfying versions, without re-resolving unrelated transitive dependencies. + * + * Unlike `yarn up` or `yarn install`, this tool resolves new ranges to the + * LOWEST version that satisfies them — not the latest — keeping the lockfile + * as close to the original as possible. + * + * Usage: + * yarn dlx yarn-lockfile-surgeon [ ...] + * + * Example: + * yarn dlx yarn-lockfile-surgeon yarn.lock \ + * @scope/package-a@1.2.3 \ + * @scope/package-b@4.5.6 + * + * After running, execute `yarn install --mode=update-lockfile` to fill in + * checksums without re-resolving dependencies. + */ + +import { resolve } from "node:path"; +import { parseArgs } from "node:util"; +import { structUtils } from "@yarnpkg/core"; +import { bumpLockfile } from "./lib.ts"; +import { createNpmFetcher } from "./registry.ts"; + +const USAGE = `Usage: yarn-lockfile-surgeon [--help] [ ...] + +Example: + yarn-lockfile-surgeon yarn.lock @scope/package@1.2.3`; + +const { values, positionals } = parseArgs({ + options: { + help: { type: "boolean", short: "h", default: false }, + }, + allowPositionals: true, +}); + +const [lockfileArg, ...targetArgs] = positionals; + +if (values.help || !lockfileArg || targetArgs.length === 0) { + console.error(USAGE); + process.exit(values.help ? 0 : 1); +} + +const lockfilePath = resolve(lockfileArg); +const targets = targetArgs.map((arg) => structUtils.parseDescriptor(arg)); + +console.log("🔪 Yarn Lockfile Surgeon — minimum-version strategy\n"); +console.log(`Lockfile: ${lockfilePath}`); +console.log( + `Targets: ${targets.map((t) => structUtils.stringifyDescriptor(t)).join(", ")}`, +); + +await bumpLockfile(lockfilePath, targets, createNpmFetcher()); diff --git a/scripts/yarn-lockfile-surgeon/lib.test.ts b/scripts/yarn-lockfile-surgeon/lib.test.ts new file mode 100644 index 0000000000..6f96f67b94 --- /dev/null +++ b/scripts/yarn-lockfile-surgeon/lib.test.ts @@ -0,0 +1,557 @@ +import { describe, it, beforeEach, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, readFileSync, writeFileSync, rmSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { stringifySyml, parseSyml } from "@yarnpkg/parsers"; +import { structUtils } from "@yarnpkg/core"; +import type { AbbreviatedVersion } from "package-json"; +import { + splitDescriptorKey, + descriptorPackageName, + extractSpecs, + buildDescriptorKey, + buildLockEntry, + bumpLockfile, + type RegistryFetcher, +} from "./lib.ts"; + +function fakeVersion(overrides: Pick & Partial): AbbreviatedVersion { + return { + name: overrides.name ?? "test", + dist: { tarball: "", shasum: "", ...(overrides.dist ?? {}) }, + ...overrides, + }; +} + +describe("splitDescriptorKey", () => { + it("splits a compound key on commas", () => { + assert.deepEqual( + splitDescriptorKey("@foo/bar@npm:^1.0.0, @foo/bar@npm:1.2.3"), + ["@foo/bar@npm:^1.0.0", "@foo/bar@npm:1.2.3"], + ); + }); + + it("handles no spaces around commas", () => { + assert.deepEqual( + splitDescriptorKey("@foo/bar@npm:^1.0.0,@foo/bar@npm:1.2.3"), + ["@foo/bar@npm:^1.0.0", "@foo/bar@npm:1.2.3"], + ); + }); + + it("handles extra spaces around commas", () => { + assert.deepEqual( + splitDescriptorKey("@foo/bar@npm:^1.0.0 , @foo/bar@npm:1.2.3"), + ["@foo/bar@npm:^1.0.0", "@foo/bar@npm:1.2.3"], + ); + }); +}); + +describe("descriptorPackageName", () => { + it("extracts name from first descriptor in a compound key", () => { + assert.equal( + descriptorPackageName("@foo/bar@npm:^1.0.0, @foo/bar@npm:1.2.3"), + "@foo/bar", + ); + }); + + it("returns null for empty string", () => { + assert.equal(descriptorPackageName(""), null); + }); +}); + +describe("extractSpecs", () => { + it("extracts specs from a compound key", () => { + assert.deepEqual( + extractSpecs("@foo/bar@npm:^1.0.0, @foo/bar@npm:1.2.3"), + ["^1.0.0", "1.2.3"], + ); + }); + + it("returns only npm specs when mixed with patch descriptors", () => { + const key = + "@foo/bar@npm:^1.0.0, @foo/bar@patch:@foo/bar@npm%3A1.0.0#./patches/foo.patch"; + assert.deepEqual(extractSpecs(key), ["^1.0.0"]); + }); +}); + +describe("buildDescriptorKey", () => { + it("builds a key for a single spec", () => { + assert.equal( + buildDescriptorKey("@foo/bar", ["1.0.0"]), + "@foo/bar@npm:1.0.0", + ); + }); + + it("builds a sorted compound key for multiple specs", () => { + const key = buildDescriptorKey("@foo/bar", ["^1.0.0", "1.2.3"]); + assert.equal(key, "@foo/bar@npm:1.2.3, @foo/bar@npm:^1.0.0"); + }); + + it("round-trips through extractSpecs", () => { + const specs = ["^1.0.0", "1.2.3"]; + const key = buildDescriptorKey("@foo/bar", specs); + const extracted = extractSpecs(key); + assert.deepEqual(extracted.sort(), specs.sort()); + }); +}); + +describe("buildLockEntry", () => { + it("produces a complete lockfile entry", () => { + const deps = { baz: "^2.0.0" }; + const peerDeps = { react: "^18.0.0" }; + const peerDepsMeta = { react: { optional: true as const } }; + const meta = fakeVersion({ + version: "1.2.3", + dependencies: deps, + peerDependencies: peerDeps, + peerDependenciesMeta: peerDepsMeta, + }); + assert.deepEqual(buildLockEntry("@foo/bar", "1.2.3", meta), { + version: "1.2.3", + resolution: "@foo/bar@npm:1.2.3", + languageName: "node", + linkType: "hard", + checksum: undefined, + dependencies: deps, + peerDependencies: peerDeps, + peerDependenciesMeta: peerDepsMeta, + dist: meta.dist, + }); + }); +}); + +describe("bumpLockfile", () => { + let tmpDir: string; + + beforeEach(() => { + tmpDir = mkdtempSync(join(tmpdir(), "lockfile-bump-test-")); + }); + + afterEach(() => { + rmSync(tmpDir, { recursive: true, force: true }); + }); + + function makeFetcher( + versionData: Record, + allVersionsData: Record>, + ): RegistryFetcher { + return { + async fetchVersion(name, version) { + const key = `${name}@${version}`; + if (key in versionData) return versionData[key]; + throw new Error(`Unexpected fetchVersion: ${key}`); + }, + async fetchAllVersions(name) { + if (name in allVersionsData) return allVersionsData[name]; + throw new Error(`Unexpected fetchAllVersions: ${name}`); + }, + }; + } + + it("replaces a target package and resolves transitive deps to minimum versions", async () => { + const fetcher = makeFetcher( + { + "@scope/target@1.1.0": fakeVersion({ + name: "@scope/target", + version: "1.1.0", + dependencies: { "@scope/transitive": "^2.1.0" }, + }), + }, + { + "@scope/transitive": { + "2.0.0": fakeVersion({ name: "@scope/transitive", version: "2.0.0" }), + "2.1.0": fakeVersion({ name: "@scope/transitive", version: "2.1.0" }), + "2.2.0": fakeVersion({ name: "@scope/transitive", version: "2.2.0" }), + "2.3.0": fakeVersion({ name: "@scope/transitive", version: "2.3.0" }), + }, + }, + ); + + const lockfileContent = stringifySyml({ + __metadata: { version: 6, cacheKey: "8" }, + "@scope/target@npm:^1.0.0, @scope/target@npm:1.0.0": { + version: "1.0.0", + resolution: "@scope/target@npm:1.0.0", + dependencies: { "@scope/transitive": "^2.0.0" }, + checksum: "abc123", + languageName: "node", + linkType: "hard", + }, + "@scope/transitive@npm:^2.0.0": { + version: "2.0.0", + resolution: "@scope/transitive@npm:2.0.0", + checksum: "def456", + languageName: "node", + linkType: "hard", + }, + "@scope/unrelated@npm:^3.0.0": { + version: "3.0.0", + resolution: "@scope/unrelated@npm:3.0.0", + checksum: "ghi789", + languageName: "node", + linkType: "hard", + }, + }); + + const lockfilePath = join(tmpDir, "yarn.lock"); + writeFileSync(lockfilePath, lockfileContent); + + await bumpLockfile( + lockfilePath, + [structUtils.parseDescriptor("@scope/target@1.1.0")], + fetcher, + ); + + const result = parseSyml(readFileSync(lockfilePath, "utf-8")); + + // Old target entry removed, new one present + assert.equal( + "@scope/target@npm:^1.0.0, @scope/target@npm:1.0.0" in result, + false, + "old target key should be removed", + ); + const newTargetKey = Object.keys(result).find( + (k) => k.includes("@scope/target@npm:1.1.0"), + ); + assert.ok(newTargetKey, "new target entry should exist"); + assert.equal(result[newTargetKey!].version, "1.1.0"); + + // ^1.0.0 should be carried over (1.1.0 satisfies it) + assert.ok( + newTargetKey!.includes("@scope/target@npm:^1.0.0"), + "^1.0.0 range alias should be preserved", + ); + + // Transitive: ^2.1.0 should resolve to 2.1.0 (minimum), not 2.3.0 + const transitiveKey = Object.keys(result).find( + (k) => k.includes("@scope/transitive@npm:^2.1.0"), + ); + assert.ok(transitiveKey, "new transitive entry should exist for ^2.1.0"); + assert.equal( + result[transitiveKey!].version, + "2.1.0", + "transitive should resolve to minimum satisfying version", + ); + + // Old transitive entry for ^2.0.0 -> 2.0.0 should still be there + assert.ok( + "@scope/transitive@npm:^2.0.0" in result, + "existing transitive entry should be preserved", + ); + assert.equal(result["@scope/transitive@npm:^2.0.0"].version, "2.0.0"); + + // __metadata preserved + assert.ok("__metadata" in result, "__metadata should be preserved"); + assert.equal(result["__metadata"].version, "6"); + + // Unrelated package untouched + assert.ok( + "@scope/unrelated@npm:^3.0.0" in result, + "unrelated package should be untouched", + ); + assert.equal(result["@scope/unrelated@npm:^3.0.0"].checksum, "ghi789"); + }); + + it("skips transitive deps already satisfied by existing lockfile entries", async () => { + const fetcher = makeFetcher( + { + "@scope/pkg@2.0.0": fakeVersion({ + name: "@scope/pkg", + version: "2.0.0", + dependencies: { "@scope/dep": "^1.0.0" }, + }), + }, + {}, + ); + + const lockfileContent = stringifySyml({ + __metadata: { version: 6, cacheKey: "8" }, + "@scope/pkg@npm:1.0.0": { + version: "1.0.0", + resolution: "@scope/pkg@npm:1.0.0", + checksum: "old", + languageName: "node", + linkType: "hard", + }, + "@scope/dep@npm:^1.0.0": { + version: "1.5.0", + resolution: "@scope/dep@npm:1.5.0", + checksum: "existing", + languageName: "node", + linkType: "hard", + }, + }); + + const lockfilePath = join(tmpDir, "yarn.lock"); + writeFileSync(lockfilePath, lockfileContent); + + await bumpLockfile( + lockfilePath, + [structUtils.parseDescriptor("@scope/pkg@2.0.0")], + fetcher, + ); + + const result = parseSyml(readFileSync(lockfilePath, "utf-8")); + + // fetchAllVersions should NOT have been called (dep already satisfied) + // and the existing dep entry should be untouched + assert.equal(result["@scope/dep@npm:^1.0.0"].version, "1.5.0"); + assert.equal(result["@scope/dep@npm:^1.0.0"].checksum, "existing"); + }); + + it("removes patch entries alongside npm entries", async () => { + const fetcher = makeFetcher( + { + "@scope/patched@1.1.0": fakeVersion({ + name: "@scope/patched", + version: "1.1.0", + }), + }, + {}, + ); + + const lockfileContent = stringifySyml({ + __metadata: { version: 6, cacheKey: "8" }, + "@scope/patched@npm:1.0.0": { + version: "1.0.0", + resolution: "@scope/patched@npm:1.0.0", + checksum: "npm-checksum", + languageName: "node", + linkType: "hard", + }, + "@scope/patched@patch:@scope/patched@npm%3A1.0.0#./patches/fix.patch": { + version: "1.0.0", + resolution: "@scope/patched@patch:@scope/patched@npm%3A1.0.0#./patches/fix.patch::version=1.0.0&hash=abc", + checksum: "patch-checksum", + languageName: "node", + linkType: "hard", + }, + }); + + const lockfilePath = join(tmpDir, "yarn.lock"); + writeFileSync(lockfilePath, lockfileContent); + + await bumpLockfile( + lockfilePath, + [structUtils.parseDescriptor("@scope/patched@1.1.0")], + fetcher, + ); + + const result = parseSyml(readFileSync(lockfilePath, "utf-8")); + + // Both old entries should be gone + const keys = Object.keys(result).filter((k) => k.includes("@scope/patched")); + assert.equal(keys.length, 1, "should have exactly one entry for the package"); + assert.ok(keys[0].includes("1.1.0"), "entry should be for 1.1.0"); + + // No patch entries remain + assert.ok( + !keys.some((k) => k.includes("patch:")), + "no patch entries should remain", + ); + }); + + it("includes range aliases from all new entries that a transitive satisfies", async () => { + // Two targets both depend on the same transitive with different ranges. + // The new transitive entry should alias both ranges in its descriptor key. + const fetcher = makeFetcher( + { + "@scope/a@2.0.0": fakeVersion({ + name: "@scope/a", + version: "2.0.0", + dependencies: { "@scope/shared": "^1.2.0" }, + }), + "@scope/b@3.0.0": fakeVersion({ + name: "@scope/b", + version: "3.0.0", + dependencies: { "@scope/shared": "^1.3.0" }, + }), + }, + { + "@scope/shared": { + "1.2.0": fakeVersion({ name: "@scope/shared", version: "1.2.0" }), + "1.3.0": fakeVersion({ name: "@scope/shared", version: "1.3.0" }), + "1.4.0": fakeVersion({ name: "@scope/shared", version: "1.4.0" }), + }, + }, + ); + + const lockfileContent = stringifySyml({ + __metadata: { version: 6, cacheKey: "8" }, + "@scope/a@npm:1.0.0": { + version: "1.0.0", + resolution: "@scope/a@npm:1.0.0", + checksum: "a-old", + languageName: "node", + linkType: "hard", + }, + "@scope/b@npm:2.0.0": { + version: "2.0.0", + resolution: "@scope/b@npm:2.0.0", + checksum: "b-old", + languageName: "node", + linkType: "hard", + }, + "@scope/shared@npm:^1.0.0": { + version: "1.1.0", + resolution: "@scope/shared@npm:1.1.0", + checksum: "shared-old", + languageName: "node", + linkType: "hard", + }, + }); + + const lockfilePath = join(tmpDir, "yarn.lock"); + writeFileSync(lockfilePath, lockfileContent); + + await bumpLockfile( + lockfilePath, + [ + structUtils.parseDescriptor("@scope/a@2.0.0"), + structUtils.parseDescriptor("@scope/b@3.0.0"), + ], + fetcher, + ); + + const result = parseSyml(readFileSync(lockfilePath, "utf-8")); + + // The shared dep should resolve to 1.3.0 (minimum satisfying ^1.3.0) + const sharedKey = Object.keys(result).find( + (k) => k.includes("@scope/shared") && k.includes("1.3.0"), + ); + assert.ok(sharedKey, "new shared entry should exist"); + assert.equal(result[sharedKey!].version, "1.3.0"); + + // The key should alias BOTH ^1.2.0 and ^1.3.0 (since 1.3.0 satisfies both) + assert.ok( + sharedKey!.includes("@scope/shared@npm:^1.2.0"), + "should alias ^1.2.0", + ); + assert.ok( + sharedKey!.includes("@scope/shared@npm:^1.3.0"), + "should alias ^1.3.0", + ); + + // Old ^1.0.0 entry should still exist (1.1.0 satisfies it, untouched) + assert.ok( + "@scope/shared@npm:^1.0.0" in result, + "existing shared entry should be preserved", + ); + }); + + it("aliases ranges from existing lockfile entries' dependency lists", async () => { + const fetcher = makeFetcher( + { + "@scope/pkg@1.2.0": fakeVersion({ + name: "@scope/pkg", + version: "1.2.0", + }), + }, + {}, + ); + + const lockfileContent = stringifySyml({ + __metadata: { version: 6, cacheKey: "8" }, + "@scope/pkg@npm:1.0.0": { + version: "1.0.0", + resolution: "@scope/pkg@npm:1.0.0", + checksum: "old", + languageName: "node", + linkType: "hard", + }, + "@scope/other@npm:^1.0.0": { + version: "1.0.0", + resolution: "@scope/other@npm:1.0.0", + dependencies: { "@scope/pkg": "^1.0.0" }, + checksum: "other", + languageName: "node", + linkType: "hard", + }, + }); + + const lockfilePath = join(tmpDir, "yarn.lock"); + writeFileSync(lockfilePath, lockfileContent); + + await bumpLockfile( + lockfilePath, + [structUtils.parseDescriptor("@scope/pkg@1.2.0")], + fetcher, + ); + + const result = parseSyml(readFileSync(lockfilePath, "utf-8")); + + // The new entry should alias ^1.0.0 from @scope/other's dependency list + const pkgKey = Object.keys(result).find( + (k) => k.includes("@scope/pkg") && k.includes("1.2.0"), + ); + assert.ok(pkgKey, "new pkg entry should exist"); + assert.ok( + pkgKey!.includes("@scope/pkg@npm:^1.0.0"), + "should alias ^1.0.0 from existing entry's dependencies", + ); + }); + + it("resolves multi-level transitive dependencies", async () => { + const fetcher = makeFetcher( + { + "@scope/root@2.0.0": fakeVersion({ + name: "@scope/root", + version: "2.0.0", + dependencies: { "@scope/mid": "^1.1.0" }, + }), + }, + { + "@scope/mid": { + "1.0.0": fakeVersion({ name: "@scope/mid", version: "1.0.0" }), + "1.1.0": fakeVersion({ + name: "@scope/mid", + version: "1.1.0", + dependencies: { "@scope/leaf": "^3.2.0" }, + }), + }, + "@scope/leaf": { + "3.1.0": fakeVersion({ name: "@scope/leaf", version: "3.1.0" }), + "3.2.0": fakeVersion({ name: "@scope/leaf", version: "3.2.0" }), + "3.3.0": fakeVersion({ name: "@scope/leaf", version: "3.3.0" }), + }, + }, + ); + + const lockfileContent = stringifySyml({ + __metadata: { version: 6, cacheKey: "8" }, + "@scope/root@npm:1.0.0": { + version: "1.0.0", + resolution: "@scope/root@npm:1.0.0", + checksum: "old", + languageName: "node", + linkType: "hard", + }, + }); + + const lockfilePath = join(tmpDir, "yarn.lock"); + writeFileSync(lockfilePath, lockfileContent); + + await bumpLockfile( + lockfilePath, + [structUtils.parseDescriptor("@scope/root@2.0.0")], + fetcher, + ); + + const result = parseSyml(readFileSync(lockfilePath, "utf-8")); + + // Mid-level: ^1.1.0 -> 1.1.0 (minimum) + const midKey = Object.keys(result).find( + (k) => k.includes("@scope/mid"), + ); + assert.ok(midKey, "mid-level transitive should be added"); + assert.equal(result[midKey!].version, "1.1.0"); + + // Leaf-level: ^3.2.0 -> 3.2.0 (minimum, not 3.3.0) + const leafKey = Object.keys(result).find( + (k) => k.includes("@scope/leaf"), + ); + assert.ok(leafKey, "leaf-level transitive should be added"); + assert.equal(result[leafKey!].version, "3.2.0"); + }); +}); diff --git a/scripts/yarn-lockfile-surgeon/lib.ts b/scripts/yarn-lockfile-surgeon/lib.ts new file mode 100644 index 0000000000..589ead4cf2 --- /dev/null +++ b/scripts/yarn-lockfile-surgeon/lib.ts @@ -0,0 +1,278 @@ +import { readFileSync, writeFileSync } from "node:fs"; +import { minSatisfying, satisfies } from "semver"; +import { parseSyml, stringifySyml } from "@yarnpkg/parsers"; +import { structUtils, Manifest } from "@yarnpkg/core"; +import type { Descriptor } from "@yarnpkg/core"; +import type { AbbreviatedVersion } from "package-json"; + +// --------------------------------------------------------------------------- +// Lockfile descriptor helpers +// --------------------------------------------------------------------------- + +/** + * Splits a compound lockfile descriptor key into individual descriptor strings. + * Uses the same regex as Yarn's `Project.ts`. + */ +export function splitDescriptorKey(key: string): string[] { + return key.split(/ *, */); +} + +/** + * Extracts the full package name (e.g. `@backstage/backend-defaults`) from a + * lockfile descriptor key. Handles `npm:`, `patch:`, and compound keys. + */ +export function descriptorPackageName(key: string): string | null { + const first = splitDescriptorKey(key)[0]; + const descriptor = structUtils.tryParseDescriptor(first); + if (!descriptor) return null; + return structUtils.stringifyIdent(descriptor); +} + +/** + * Extracts all semver selectors from the `npm:` descriptors in a compound + * descriptor key. Non-npm descriptors (e.g. `patch:`) are skipped. + * + * e.g. `"@foo/bar@npm:^1.0.0, @foo/bar@npm:1.2.3"` -> `["^1.0.0", "1.2.3"]` + */ +export function extractSpecs(key: string): string[] { + return splitDescriptorKey(key) + .map((part) => { + const descriptor = structUtils.tryParseDescriptor(part); + if (!descriptor) return null; + const range = structUtils.parseRange(descriptor.range); + return range.protocol === "npm:" ? range.selector : null; + }) + .filter((s) => s !== null); +} + +/** + * Builds a compound lockfile descriptor key from a package name and a list of + * semver specs. Mirrors the serialization logic in Yarn's `Project.ts`. + */ +export function buildDescriptorKey(name: string, specs: string[]): string { + const ident = structUtils.parseIdent(name); + return specs + .map((s) => structUtils.stringifyDescriptor(structUtils.makeDescriptor(ident, `npm:${s}`))) + .sort() + .join(`, `); +} + +/** + * Builds a lockfile entry object for a given package version, mirroring + * the serialization in Yarn's `Project.generateLockfile()`. The checksum + * is left undefined — `yarn install` will fetch the tarball and fill it in. + */ +export function buildLockEntry( + name: string, + version: string, + meta: AbbreviatedVersion, +): Record { + const ident = structUtils.parseIdent(name); + const locator = structUtils.makeLocator(ident, `npm:${version}`); + + const manifest = new Manifest(); + manifest.load(meta as any); + manifest.name = null; + manifest.languageName = "node"; + + return { + ...manifest.exportTo({}, { compatibilityMode: false }), + linkType: "hard", + resolution: structUtils.stringifyLocator(locator), + checksum: undefined, + }; +} + +// --------------------------------------------------------------------------- +// Core +// --------------------------------------------------------------------------- + +/** Ponyfill for `Map.prototype.getOrInsertComputed` (ES2027). */ +function getOrInsertComputed(map: Map, key: K, compute: (key: K) => V): V { + if (map.has(key)) return map.get(key)!; + const value = compute(key); + map.set(key, value); + return value; +} + +interface LockfileEntry { + name: string; + version: string; + meta: AbbreviatedVersion; + specs: string[]; +} + +export interface RegistryFetcher { + fetchVersion: (name: string, version: string) => Promise; + fetchAllVersions: (name: string) => Promise>; +} + +/** + * Core entry point. Parses the lockfile, replaces target package entries with + * new versions, resolves unsatisfied transitive dependency ranges to their + * minimum satisfying versions, and writes the updated lockfile. + */ +export async function bumpLockfile( + lockfilePath: string, + targets: Descriptor[], + registry: RegistryFetcher, +): Promise { + const lock = parseSyml( + readFileSync(lockfilePath, "utf-8"), + ); + + const keysByPkg = new Map(); + const resolvedVersions = new Map>(); + + for (const [key, entry] of Object.entries(lock)) { + if (key === "__metadata") continue; + const name = descriptorPackageName(key); + if (!name) continue; + + getOrInsertComputed(keysByPkg, name, () => []).push(key); + + if (entry?.version) { + getOrInsertComputed(resolvedVersions, name, () => new Set()).add(entry.version); + } + } + + // Phase 1: Replace target packages + const newEntries: LockfileEntry[] = []; + + for (const target of targets) { + const name = structUtils.stringifyIdent(target); + + console.log(`\n📦 ${name}@${target.range}`); + const meta = await registry.fetchVersion(name, target.range); + const existingKeys = keysByPkg.get(name) ?? []; + + const specs = new Set([target.range]); + for (const key of existingKeys) { + for (const spec of extractSpecs(key)) { + if (satisfies(target.range, spec) || spec === target.range) + specs.add(spec); + } + } + + for (const key of existingKeys) { + console.log( + ` Remove: ${key.slice(0, 80)}${key.length > 80 ? "..." : ""}`, + ); + delete lock[key]; + } + + newEntries.push({ + name, + version: target.range, + meta, + specs: [...specs].sort(), + }); + console.log(` Specs: ${[...specs].join(", ")}`); + resolvedVersions.set(name, new Set([target.range])); + } + + // Phase 2: Walk transitive deps, adding minimum-version entries as needed + console.log("\n🔍 Analyzing transitive dependencies..."); + const queue = [...newEntries]; + const visited = new Set(); + + while (queue.length > 0) { + const item = queue.shift()!; + const key = `${item.name}@${item.version}`; + // Avoid processing the same package@version twice + if (visited.has(key)) continue; + visited.add(key); + + // Nothing to resolve if this entry has no dependencies + if (!item.meta.dependencies) continue; + + for (const [depName, depRange] of Object.entries(item.meta.dependencies)) { + const existing = resolvedVersions.get(depName) ?? new Set(); + // Skip if any version already in the lockfile satisfies this range + if ([...existing].some((v) => satisfies(v, depRange))) continue; + // Skip if a version we're already adding satisfies this range + if (newEntries.some((e) => e.name === depName && satisfies(e.version, depRange))) + continue; + + const allVersions = await registry.fetchAllVersions(depName); + const minVer = minSatisfying(Object.keys(allVersions), depRange); + if (!minVer) { + console.warn(` ⚠️ No version of ${depName} satisfies ${depRange}`); + continue; + } + + console.log(` ${depName}: ${depRange} → ${minVer}`); + const entry = { + name: depName, + version: minVer, + meta: allVersions[minVer], + specs: [depRange], + }; + newEntries.push(entry); + queue.push(entry); + + getOrInsertComputed(resolvedVersions, depName, () => new Set()).add(minVer); + } + } + + // Phase 3: Collect all range aliases that each new entry should satisfy. + // This ensures Yarn won't re-resolve ranges that our entries already cover. + + // Build a set of all dependency ranges declared across the lockfile and + // new entries, so we can alias ranges that our new versions satisfy. + const allDeclaredRanges = new Map>(); + for (const [key, lockEntry] of Object.entries(lock)) { + if (key === "__metadata") continue; + const deps = lockEntry?.dependencies; + if (typeof deps !== "object" || deps === null) continue; + for (const [depName, depRange] of Object.entries(deps)) { + if (typeof depRange === "string") { + getOrInsertComputed(allDeclaredRanges, depName, () => new Set()).add(depRange); + } + } + } + for (const entry of newEntries) { + if (!entry.meta.dependencies) continue; + for (const [depName, depRange] of Object.entries(entry.meta.dependencies)) { + getOrInsertComputed(allDeclaredRanges, depName, () => new Set()).add(depRange); + } + } + + for (const entry of newEntries) { + const allSpecs = new Set(entry.specs); + + // Check all ranges declared anywhere in the lockfile for this package + for (const range of allDeclaredRanges.get(entry.name) ?? []) { + if (satisfies(entry.version, range)) + allSpecs.add(range); + } + + // Also check ranges from existing descriptor keys + for (const existingKey of keysByPkg.get(entry.name) ?? []) { + for (const spec of extractSpecs(existingKey)) { + if (satisfies(entry.version, spec)) + allSpecs.add(spec); + } + } + + entry.specs = [...allSpecs].sort(); + } + + // Phase 4: Write new entries into the lockfile object + console.log("\n✏️ Writing entries..."); + for (const entry of newEntries) { + const key = buildDescriptorKey(entry.name, entry.specs); + lock[key] = buildLockEntry(entry.name, entry.version, entry.meta); + } + + // Phase 4: Serialize using Yarn's own formatter + writeFileSync(lockfilePath, stringifySyml(lock)); + + console.log("\n--- Summary ---"); + console.log(`Lockfile: ${lockfilePath}`); + console.log(`Packages bumped: ${targets.length}`); + console.log(`New transitive entries: ${newEntries.length - targets.length}`); + console.log( + "\nRun `yarn install --mode=update-lockfile` to fill in checksums.", + ); +} diff --git a/scripts/yarn-lockfile-surgeon/package-lock.json b/scripts/yarn-lockfile-surgeon/package-lock.json new file mode 100644 index 0000000000..6ab06343f8 --- /dev/null +++ b/scripts/yarn-lockfile-surgeon/package-lock.json @@ -0,0 +1,1281 @@ +{ + "name": "yarn-lockfile-surgeon", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "yarn-lockfile-surgeon", + "version": "0.1.0", + "dependencies": { + "@yarnpkg/core": "^4.7.0", + "@yarnpkg/parsers": "^3.0.3", + "package-json": "^10.0.1", + "semver": "^7.7.4" + }, + "bin": { + "yarn-lockfile-surgeon": "index.ts" + }, + "devDependencies": { + "@types/semver": "^7.7.1" + } + }, + "node_modules/@arcanis/slice-ansi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@arcanis/slice-ansi/-/slice-ansi-1.1.1.tgz", + "integrity": "sha512-xguP2WR2Dv0gQ7Ykbdb7BNCnPnIPB94uTi0Z2NvkRBEnhbwjOQ7QyQKJXrVQg4qDpiD9hA5l5cCwy/z2OXgc3w==", + "license": "MIT", + "dependencies": { + "grapheme-splitter": "^1.0.4" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/npm-conf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", + "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", + "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/emscripten": { + "version": "1.41.5", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz", + "integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==", + "license": "MIT" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "license": "MIT" + }, + "node_modules/@types/treeify": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/treeify/-/treeify-1.0.3.tgz", + "integrity": "sha512-hx0o7zWEUU4R2Amn+pjCBQQt23Khy/Dk56gQU5xi5jtPL1h83ACJCeFaB2M/+WO1AntvWrSoVnnCAfI1AQH4Cg==", + "license": "MIT" + }, + "node_modules/@yarnpkg/core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/core/-/core-4.7.0.tgz", + "integrity": "sha512-/zOgPAcDvwx8NDzLidDFaZDg+3Jgv824C4fudqZFlUuWv/BzOEtjfRTAyi+O0h15FyT7YvlsMnQpmnIQB+LgKw==", + "license": "BSD-2-Clause", + "dependencies": { + "@arcanis/slice-ansi": "^1.1.1", + "@types/semver": "^7.1.0", + "@types/treeify": "^1.0.0", + "@yarnpkg/fslib": "^3.1.5", + "@yarnpkg/libzip": "^3.2.2", + "@yarnpkg/parsers": "^3.0.3", + "@yarnpkg/shell": "^4.1.3", + "camelcase": "^5.3.1", + "chalk": "^4.1.2", + "ci-info": "^4.0.0", + "clipanion": "^4.0.0-rc.2", + "cross-spawn": "^7.0.3", + "diff": "^5.1.0", + "dotenv": "^16.3.1", + "es-toolkit": "^1.39.7", + "fast-glob": "^3.2.2", + "got": "^11.7.0", + "hpagent": "^1.2.0", + "micromatch": "^4.0.2", + "p-limit": "^2.2.0", + "semver": "^7.1.2", + "strip-ansi": "^6.0.0", + "tar": "^7.5.3", + "tinylogic": "^2.0.0", + "treeify": "^1.1.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@yarnpkg/fslib": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@yarnpkg/fslib/-/fslib-3.1.5.tgz", + "integrity": "sha512-hXaPIWl5GZA+rXcx+yaKWUuePJruZuD+3A5A2X6paEBfFsyCD7oEp88lSMj1ym1ehBWUmhNH/YGOp+SrbmSBPg==", + "license": "BSD-2-Clause", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@yarnpkg/libzip": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@yarnpkg/libzip/-/libzip-3.2.2.tgz", + "integrity": "sha512-Kqxgjfy6SwwC4tTGQYToIWtUhIORTpkowqgd9kkMiBixor0eourHZZAggt/7N4WQKt9iCyPSkO3Xvr44vXUBAw==", + "license": "BSD-2-Clause", + "dependencies": { + "@types/emscripten": "^1.39.6", + "@yarnpkg/fslib": "^3.1.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "@yarnpkg/fslib": "^3.1.3" + } + }, + "node_modules/@yarnpkg/parsers": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.3.tgz", + "integrity": "sha512-mQZgUSgFurUtA07ceMjxrWkYz8QtDuYkvPlu0ZqncgjopQ0t6CNEo/OSealkmnagSUx8ZD5ewvezUwUuMqutQg==", + "license": "BSD-2-Clause", + "dependencies": { + "js-yaml": "^3.10.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@yarnpkg/shell": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@yarnpkg/shell/-/shell-4.1.3.tgz", + "integrity": "sha512-5igwsHbPtSAlLdmMdKqU3atXjwhtLFQXsYAG0sn1XcPb3yF8WxxtWxN6fycBoUvFyIHFz1G0KeRefnAy8n6gdw==", + "license": "BSD-2-Clause", + "dependencies": { + "@yarnpkg/fslib": "^3.1.2", + "@yarnpkg/parsers": "^3.0.3", + "chalk": "^4.1.2", + "clipanion": "^4.0.0-rc.2", + "cross-spawn": "^7.0.3", + "fast-glob": "^3.2.2", + "micromatch": "^4.0.2", + "tslib": "^2.4.0" + }, + "bin": { + "shell": "lib/cli.js" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clipanion": { + "version": "4.0.0-rc.4", + "resolved": "https://registry.npmjs.org/clipanion/-/clipanion-4.0.0-rc.4.tgz", + "integrity": "sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q==", + "license": "MIT", + "workspaces": [ + "website" + ], + "dependencies": { + "typanion": "^3.8.0" + }, + "peerDependencies": { + "typanion": "*" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/diff": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-toolkit": { + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz", + "integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "license": "ISC" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/ky": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.3.tgz", + "integrity": "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/ky?sponsor=1" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-10.0.1.tgz", + "integrity": "sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==", + "license": "MIT", + "dependencies": { + "ky": "^1.2.0", + "registry-auth-token": "^5.0.2", + "registry-url": "^6.0.1", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "license": "ISC" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/registry-auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", + "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", + "license": "MIT", + "dependencies": { + "@pnpm/npm-conf": "^3.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "license": "MIT", + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinylogic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinylogic/-/tinylogic-2.0.0.tgz", + "integrity": "sha512-dljTkiLLITtsjqBvTA1MRZQK/sGP4kI3UJKc3yA9fMzYbMF2RhcN04SeROVqJBIYYOoJMM8u0WDnhFwMSFQotw==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typanion": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/typanion/-/typanion-3.14.0.tgz", + "integrity": "sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==", + "license": "MIT", + "workspaces": [ + "website" + ] + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + } + } +} diff --git a/scripts/yarn-lockfile-surgeon/package.json b/scripts/yarn-lockfile-surgeon/package.json new file mode 100644 index 0000000000..8cb254aac1 --- /dev/null +++ b/scripts/yarn-lockfile-surgeon/package.json @@ -0,0 +1,22 @@ +{ + "name": "yarn-lockfile-surgeon", + "version": "0.1.0", + "private": true, + "description": "Surgically bump packages in a Yarn Berry lockfile using minimum-version resolution", + "type": "module", + "bin": { + "yarn-lockfile-surgeon": "index.ts" + }, + "scripts": { + "test": "node --test" + }, + "dependencies": { + "@yarnpkg/core": "^4.7.0", + "@yarnpkg/parsers": "^3.0.3", + "package-json": "^10.0.1", + "semver": "^7.7.4" + }, + "devDependencies": { + "@types/semver": "^7.7.1" + } +} diff --git a/scripts/yarn-lockfile-surgeon/registry.test.ts b/scripts/yarn-lockfile-surgeon/registry.test.ts new file mode 100644 index 0000000000..cd93c0a92d --- /dev/null +++ b/scripts/yarn-lockfile-surgeon/registry.test.ts @@ -0,0 +1,48 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; + +import { createNpmFetcher } from "./registry.ts"; + +describe("createNpmFetcher", () => { + describe("fetchVersion", () => { + it("fetches metadata for a specific version", async () => { + const fetcher = createNpmFetcher(); + const meta = await fetcher.fetchVersion("is-odd", "1.0.0"); + assert.equal(meta.version, "1.0.0"); + assert.ok(meta.dependencies, "should have dependencies"); + assert.ok("is-number" in meta.dependencies!, "should depend on is-number"); + }); + + it("throws for a non-existent version", async () => { + const fetcher = createNpmFetcher(); + await assert.rejects( + () => fetcher.fetchVersion("is-odd", "999.999.999"), + ); + }); + }); + + describe("fetchAllVersions", () => { + it("fetches all versions of a package", async () => { + const fetcher = createNpmFetcher(); + const versions = await fetcher.fetchAllVersions("is-odd"); + assert.ok("1.0.0" in versions, "should include 1.0.0"); + assert.ok("3.0.1" in versions, "should include 3.0.1"); + assert.equal(versions["1.0.0"].version, "1.0.0"); + }); + + it("caches results across calls", async () => { + const fetcher = createNpmFetcher(); + const first = await fetcher.fetchAllVersions("is-odd"); + const second = await fetcher.fetchAllVersions("is-odd"); + assert.equal(first, second, "should return the same object reference"); + }); + + it("does not share cache between fetcher instances", async () => { + const a = createNpmFetcher(); + const b = createNpmFetcher(); + const fromA = await a.fetchAllVersions("is-odd"); + const fromB = await b.fetchAllVersions("is-odd"); + assert.notEqual(fromA, fromB, "different instances should have independent caches"); + }); + }); +}); diff --git a/scripts/yarn-lockfile-surgeon/registry.ts b/scripts/yarn-lockfile-surgeon/registry.ts new file mode 100644 index 0000000000..66580ed7a1 --- /dev/null +++ b/scripts/yarn-lockfile-surgeon/registry.ts @@ -0,0 +1,36 @@ +import packageJson from "package-json"; +import type { AbbreviatedVersion, AbbreviatedMetadata } from "package-json"; +import type { RegistryFetcher } from "./lib.ts"; + +/** Creates a new registry fetcher with its own cache. */ +export function createNpmFetcher(): RegistryFetcher { + const cache = new Map(); + + return { + /** + * Fetches all published versions of a package from the npm registry. + * Results are cached per package name to avoid redundant requests when the + * same package appears as a transitive dependency of multiple targets. + */ + async fetchAllVersions( + name: string, + ): Promise> { + if (cache.has(name)) return cache.get(name)!.versions; + + const data = await packageJson(name, { + allVersions: true, + omitDeprecated: false, + }); + cache.set(name, data); + return data.versions; + }, + + /** Fetches abbreviated metadata for a single package version. */ + async fetchVersion( + name: string, + version: string, + ): Promise { + return packageJson(name, { version }); + }, + }; +} diff --git a/yarn.lock b/yarn.lock index 32ee0c3a84..45fb001aac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2831,93 +2831,9 @@ __metadata: languageName: node linkType: hard -"@backstage/backend-defaults@npm:0.13.1": - version: 0.13.1 - resolution: "@backstage/backend-defaults@npm:0.13.1" - dependencies: - "@aws-sdk/abort-controller": ^3.347.0 - "@aws-sdk/client-codecommit": ^3.350.0 - "@aws-sdk/client-s3": ^3.350.0 - "@aws-sdk/credential-providers": ^3.350.0 - "@aws-sdk/types": ^3.347.0 - "@azure/storage-blob": ^12.5.0 - "@backstage/backend-app-api": ^1.3.0 - "@backstage/backend-dev-utils": ^0.1.5 - "@backstage/backend-plugin-api": ^1.5.0 - "@backstage/cli-node": ^0.2.15 - "@backstage/config": ^1.3.6 - "@backstage/config-loader": ^1.10.6 - "@backstage/errors": ^1.2.7 - "@backstage/integration": ^1.18.2 - "@backstage/integration-aws-node": ^0.1.19 - "@backstage/plugin-auth-node": ^0.6.9 - "@backstage/plugin-events-node": ^0.4.17 - "@backstage/plugin-permission-node": ^0.10.6 - "@backstage/types": ^1.2.2 - "@google-cloud/storage": ^7.0.0 - "@keyv/memcache": ^2.0.1 - "@keyv/redis": ^4.0.1 - "@keyv/valkey": ^1.0.1 - "@manypkg/get-packages": ^1.1.3 - "@octokit/rest": ^19.0.3 - "@opentelemetry/api": ^1.9.0 - "@types/cors": ^2.8.6 - "@types/express": ^4.17.6 - archiver: ^7.0.0 - base64-stream: ^1.0.0 - better-sqlite3: ^12.0.0 - compression: ^1.7.4 - concat-stream: ^2.0.0 - cookie: ^0.7.0 - cors: ^2.8.5 - cron: ^3.0.0 - express: ^4.17.1 - express-promise-router: ^4.1.0 - express-rate-limit: ^7.5.0 - fs-extra: ^11.2.0 - git-url-parse: ^15.0.0 - helmet: ^6.0.0 - infinispan: ^0.12.0 - is-glob: ^4.0.3 - jose: ^5.0.0 - keyv: ^5.2.1 - knex: ^3.0.0 - lodash: ^4.17.21 - logform: ^2.3.2 - luxon: ^3.0.0 - minimatch: ^9.0.0 - mysql2: ^3.0.0 - node-fetch: ^2.7.0 - node-forge: ^1.3.1 - p-limit: ^3.1.0 - path-to-regexp: ^8.0.0 - pg: ^8.11.3 - pg-connection-string: ^2.3.0 - pg-format: ^1.0.4 - rate-limit-redis: ^4.2.0 - raw-body: ^2.4.1 - selfsigned: ^2.0.0 - tar: ^6.1.12 - triple-beam: ^1.4.1 - uuid: ^11.0.0 - winston: ^3.2.1 - winston-transport: ^4.5.0 - yauzl: ^3.0.0 - yn: ^4.0.0 - zod: ^3.22.4 - zod-to-json-schema: ^3.20.4 - peerDependencies: - "@google-cloud/cloud-sql-connector": ^1.4.0 - peerDependenciesMeta: - "@google-cloud/cloud-sql-connector": - optional: true - checksum: 8a9a1feca3a1a7dcca8c7cd05e651168b7a2ed485ddc2043b671827f7465fa609ad29416dfdc3b03dfb5ce97494b9d6f945ce921a2a46efbdbd0ce470e1688d0 - languageName: node - linkType: hard - -"@backstage/backend-defaults@patch:@backstage/backend-defaults@npm%3A0.13.1#./.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch::locator=root%40workspace%3A.": - version: 0.13.1 - resolution: "@backstage/backend-defaults@patch:@backstage/backend-defaults@npm%3A0.13.1#./.yarn/patches/@backstage-backend-defaults-npm-0.13.1-51efe19efd.patch::version=0.13.1&hash=c0d965&locator=root%40workspace%3A." +"@backstage/backend-defaults@npm:0.13.3, @backstage/backend-defaults@npm:^0.13.1, @backstage/backend-defaults@npm:^0.13.3": + version: 0.13.3 + resolution: "@backstage/backend-defaults@npm:0.13.3" dependencies: "@aws-sdk/abort-controller": ^3.347.0 "@aws-sdk/client-codecommit": ^3.350.0 @@ -2981,7 +2897,7 @@ __metadata: rate-limit-redis: ^4.2.0 raw-body: ^2.4.1 selfsigned: ^2.0.0 - tar: ^6.1.12 + tar: ^7.4.3 triple-beam: ^1.4.1 uuid: ^11.0.0 winston: ^3.2.1 @@ -2995,7 +2911,7 @@ __metadata: peerDependenciesMeta: "@google-cloud/cloud-sql-connector": optional: true - checksum: b1b78a2dcf6d661c8a21ab51d5419ea4db1e49eea443b09fedb0551658213e1b1646e640b6b456df0de1567bbb3d70b3fbeca0ebcc3f1576c69c49c702755a7f + checksum: 917256ea3cbf0a11542afb156c56eb178f7269f0b08fadb10793a9cbdb4e0f3980f779557e23b327e2edb12c66d09b093bd593993a16f56eb64e531b00fe4517 languageName: node linkType: hard @@ -3087,6 +3003,28 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-plugin-api@npm:^1.6.1, @backstage/backend-plugin-api@npm:^1.9.0": + version: 1.9.0 + resolution: "@backstage/backend-plugin-api@npm:1.9.0" + dependencies: + "@backstage/cli-common": ^0.2.1 + "@backstage/config": ^1.3.7 + "@backstage/errors": ^1.3.0 + "@backstage/plugin-auth-node": ^0.7.0 + "@backstage/plugin-permission-common": ^0.9.8 + "@backstage/plugin-permission-node": ^0.10.12 + "@backstage/types": ^1.2.2 + "@types/express": ^4.17.6 + "@types/json-schema": ^7.0.6 + "@types/luxon": ^3.0.0 + json-schema: ^0.4.0 + knex: ^3.0.0 + luxon: ^3.0.0 + zod: ^3.25.76 || ^4.0.0 + checksum: acdfde9429ed3b7c54eee1fbd38df5aa1b8a45344a0047d24bd666ec6a946dbea99d1c4eab0b57dd2551dd1b28af4aadd04eff213d76e6b8a0ba1567e97543c3 + languageName: node + linkType: hard + "@backstage/backend-plugin-api@patch:@backstage/backend-plugin-api@npm%3A1.5.0#./.yarn/patches/@backstage-backend-plugin-api-npm-1.5.0-7a004cae77.patch::locator=root%40workspace%3A.": version: 1.5.0 resolution: "@backstage/backend-plugin-api@patch:@backstage/backend-plugin-api@npm%3A1.5.0#./.yarn/patches/@backstage-backend-plugin-api-npm-1.5.0-7a004cae77.patch::version=1.5.0&hash=8d8f56&locator=root%40workspace%3A." @@ -3185,6 +3123,18 @@ __metadata: languageName: node linkType: hard +"@backstage/cli-common@npm:^0.2.1": + version: 0.2.1 + resolution: "@backstage/cli-common@npm:0.2.1" + dependencies: + "@backstage/errors": ^1.3.0 + cross-spawn: ^7.0.3 + global-agent: ^3.0.0 + undici: ^7.24.5 + checksum: f460083f5276caac3b04bbc86552d12047af438a08209dc8774bbecfa4c97d95ced188611f17e8f6c862b28af9137a0c7072e28ca9b0e97f8352e39035dc53d8 + languageName: node + linkType: hard + "@backstage/cli-common@patch:@backstage/cli-common@npm%3A0.1.16#./.yarn/patches/@backstage-cli-common-npm-0.1.16-576dbc8aa9.patch::locator=root%40workspace%3A.": version: 0.1.16 resolution: "@backstage/cli-common@patch:@backstage/cli-common@npm%3A0.1.16#./.yarn/patches/@backstage-cli-common-npm-0.1.16-576dbc8aa9.patch::version=0.1.16&hash=1f1779&locator=root%40workspace%3A." @@ -3388,6 +3338,17 @@ __metadata: languageName: node linkType: hard +"@backstage/config@npm:^1.3.7": + version: 1.3.7 + resolution: "@backstage/config@npm:1.3.7" + dependencies: + "@backstage/errors": ^1.3.0 + "@backstage/types": ^1.2.2 + ms: ^2.1.3 + checksum: 9fe63f73a4348f8f85f79f90bdf1624ee8ab8f37367fe5d71157e8407a56302104cab81951a2e6374a73f2e9f4e6655812147f24928406f9ecc2d78884d3eb9e + languageName: node + linkType: hard + "@backstage/core-app-api@npm:1.19.2": version: 1.19.2 resolution: "@backstage/core-app-api@npm:1.19.2" @@ -3632,6 +3593,16 @@ __metadata: languageName: node linkType: hard +"@backstage/errors@npm:^1.3.0": + version: 1.3.0 + resolution: "@backstage/errors@npm:1.3.0" + dependencies: + "@backstage/types": ^1.2.2 + serialize-error: ^8.0.1 + checksum: 3ab1ec4e39646b01d8720508cb0551ec60ee632f1a62e75b1be5c0363bc6336f82b64300ea1facac10222ddb40d58e4b24413be834012d3f880dc3425177b9b6 + languageName: node + linkType: hard + "@backstage/eslint-plugin@npm:^0.2.0": version: 0.2.0 resolution: "@backstage/eslint-plugin@npm:0.2.0" @@ -3820,9 +3791,9 @@ __metadata: languageName: node linkType: hard -"@backstage/integration@npm:^1.18.2": - version: 1.18.2 - resolution: "@backstage/integration@npm:1.18.2" +"@backstage/integration@npm:^1.18.2, @backstage/integration@npm:^1.19.2": + version: 1.19.2 + resolution: "@backstage/integration@npm:1.19.2" dependencies: "@azure/identity": ^4.0.0 "@azure/storage-blob": ^12.5.0 @@ -3834,7 +3805,7 @@ __metadata: git-url-parse: ^15.0.0 lodash: ^4.17.21 luxon: ^3.0.0 - checksum: 5d037450c4673a0eada8df7f000d4294b970b67c7937fa875165e7bac96a04943f1e8438fe2de06bd6ddfd39777e56c553a6b0cbe30cf1442ed49ae5399f3d67 + checksum: 0bc4d3454da9059c72ca65298f904de79f5d2d456e2af4cfaee48256ceb1aaf65417470b0edc0e0856b8127d702e33a6f32778052d9c3920286642b7822ed7ed languageName: node linkType: hard @@ -4743,7 +4714,7 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-permission-common@npm:0.9.3, @backstage/plugin-permission-common@npm:^0.9.1, @backstage/plugin-permission-common@npm:^0.9.3": +"@backstage/plugin-permission-common@npm:0.9.3": version: 0.9.3 resolution: "@backstage/plugin-permission-common@npm:0.9.3" dependencies: @@ -4758,21 +4729,69 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-permission-common@npm:^0.9.1, @backstage/plugin-permission-common@npm:^0.9.3, @backstage/plugin-permission-common@npm:^0.9.4": + version: 0.9.4 + resolution: "@backstage/plugin-permission-common@npm:0.9.4" + dependencies: + "@backstage/config": ^1.3.6 + "@backstage/errors": ^1.2.7 + "@backstage/types": ^1.2.2 + cross-fetch: ^4.0.0 + uuid: ^11.0.0 + zod: ^3.22.4 + zod-to-json-schema: ^3.25.1 + checksum: 2a168230d8c97d2056275eaa4f167995f5847fd98341dea3116e07f3b5f6dc384017a7ad9940be2f95505c986815d5230dc3e843ed1798782240f4ae63d07733 + languageName: node + linkType: hard + +"@backstage/plugin-permission-common@npm:^0.9.8": + version: 0.9.8 + resolution: "@backstage/plugin-permission-common@npm:0.9.8" + dependencies: + "@backstage/config": ^1.3.7 + "@backstage/errors": ^1.3.0 + "@backstage/types": ^1.2.2 + cross-fetch: ^4.0.0 + uuid: ^11.0.0 + zod: ^3.25.76 || ^4.0.0 + zod-to-json-schema: ^3.25.1 + checksum: 065a1b21e7175e7e29963b841cbd282adc775604ab59892629c1b2d2703387abb747314b85af234768fdccf922e132ae679045359fe0badf5cee5ef583b23333 + languageName: node + linkType: hard + +"@backstage/plugin-permission-node@npm:^0.10.12": + version: 0.10.12 + resolution: "@backstage/plugin-permission-node@npm:0.10.12" + dependencies: + "@backstage/backend-plugin-api": ^1.9.0 + "@backstage/config": ^1.3.7 + "@backstage/errors": ^1.3.0 + "@backstage/plugin-auth-node": ^0.7.0 + "@backstage/plugin-permission-common": ^0.9.8 + "@types/express": ^4.17.6 + express: ^4.22.0 + express-promise-router: ^4.1.0 + zod: ^3.25.76 || ^4.0.0 + zod-to-json-schema: ^3.25.1 + checksum: 08f996db284ba4fee5ec48ee9a4422a5e60e17e1f863bec7688879b7ad772ed04f6338aed0609cecee9833fc995860792e3d318848dec8efa1142b35ae2dbc5d + languageName: node + linkType: hard + "@backstage/plugin-permission-node@npm:^0.10.3, @backstage/plugin-permission-node@npm:^0.10.6": - version: 0.10.7 - resolution: "@backstage/plugin-permission-node@npm:0.10.7" + version: 0.10.8 + resolution: "@backstage/plugin-permission-node@npm:0.10.8" dependencies: - "@backstage/backend-plugin-api": ^1.6.0 + "@backstage/backend-plugin-api": ^1.6.1 "@backstage/config": ^1.3.6 "@backstage/errors": ^1.2.7 - "@backstage/plugin-auth-node": ^0.6.10 - "@backstage/plugin-permission-common": ^0.9.3 + "@backstage/plugin-auth-node": ^0.6.11 + "@backstage/plugin-permission-common": ^0.9.4 "@types/express": ^4.17.6 express: ^4.22.0 express-promise-router: ^4.1.0 zod: ^3.22.4 - zod-to-json-schema: ^3.20.4 - checksum: 645e3a2278885dab0f642ce48139cdf20df45b195e604905092e158e42827b0517d69d7f0f98a9cfbd738d7482727cc0b1bc34a79e86f95ff60f85b0d008f4f3 + zod-to-json-schema: ^3.25.1 + checksum: 2245d6eeb0908f9a8de7e0cc6a02d5cd1edb84f840b7cd8e3dae3f4d56293f8651f5903fd6d754f7a7041a54c7f11dcc4f47a6f2629039ca49a8ea69c4e60a9d languageName: node linkType: hard @@ -4952,70 +4971,11 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-scaffolder-backend@npm:3.0.2": - version: 3.0.2 - resolution: "@backstage/plugin-scaffolder-backend@npm:3.0.2" - dependencies: - "@backstage/backend-defaults": ^0.13.2 - "@backstage/backend-openapi-utils": ^0.6.3 - "@backstage/backend-plugin-api": ^1.5.0 - "@backstage/catalog-model": ^1.7.6 - "@backstage/config": ^1.3.6 - "@backstage/errors": ^1.2.7 - "@backstage/integration": ^1.18.2 - "@backstage/plugin-auth-node": ^0.6.9 - "@backstage/plugin-bitbucket-cloud-common": ^0.3.4 - "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": ^0.2.14 - "@backstage/plugin-catalog-node": ^1.20.0 - "@backstage/plugin-events-node": ^0.4.17 - "@backstage/plugin-permission-common": ^0.9.3 - "@backstage/plugin-permission-node": ^0.10.6 - "@backstage/plugin-scaffolder-backend-module-azure": ^0.2.15 - "@backstage/plugin-scaffolder-backend-module-bitbucket": ^0.3.16 - "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": ^0.2.15 - "@backstage/plugin-scaffolder-backend-module-bitbucket-server": ^0.2.15 - "@backstage/plugin-scaffolder-backend-module-gerrit": ^0.2.15 - "@backstage/plugin-scaffolder-backend-module-gitea": ^0.2.15 - "@backstage/plugin-scaffolder-backend-module-github": ^0.9.2 - "@backstage/plugin-scaffolder-backend-module-gitlab": ^0.10.0 - "@backstage/plugin-scaffolder-common": ^1.7.3 - "@backstage/plugin-scaffolder-node": ^0.12.1 - "@backstage/types": ^1.2.2 - "@opentelemetry/api": ^1.9.0 - "@types/luxon": ^3.0.0 - concat-stream: ^2.0.0 - express: ^4.17.1 - fs-extra: ^11.2.0 - globby: ^11.0.0 - isbinaryfile: ^5.0.0 - isolated-vm: ^5.0.1 - jsonschema: ^1.5.0 - knex: ^3.0.0 - lodash: ^4.17.21 - logform: ^2.3.2 - luxon: ^3.0.0 - nunjucks: ^3.2.3 - p-limit: ^3.1.0 - p-queue: ^6.6.2 - prom-client: ^15.0.0 - tar: ^6.1.12 - triple-beam: ^1.4.1 - uuid: ^11.0.0 - winston: ^3.2.1 - winston-transport: ^4.7.0 - yaml: ^2.0.0 - zen-observable: ^0.10.0 - zod: ^3.22.4 - zod-to-json-schema: ^3.20.4 - checksum: 2b91f486df229ce4bc60da4172c589fa2ecaa1b4564ea5e9e6b379cdffc8149aaf8f0aba40bb99856fd83757b0cf59e1b4d3ea88a7c395f4298c0b60988563d4 - languageName: node - linkType: hard - -"@backstage/plugin-scaffolder-backend@patch:@backstage/plugin-scaffolder-backend@npm%3A3.0.2#./.yarn/patches/@backstage-plugin-scaffolder-backend-npm-3.0.2-01a7364606.patch::locator=root%40workspace%3A.": - version: 3.0.2 - resolution: "@backstage/plugin-scaffolder-backend@patch:@backstage/plugin-scaffolder-backend@npm%3A3.0.2#./.yarn/patches/@backstage-plugin-scaffolder-backend-npm-3.0.2-01a7364606.patch::version=3.0.2&hash=8fb51b&locator=root%40workspace%3A." +"@backstage/plugin-scaffolder-backend@npm:3.0.3": + version: 3.0.3 + resolution: "@backstage/plugin-scaffolder-backend@npm:3.0.3" dependencies: - "@backstage/backend-defaults": ^0.13.2 + "@backstage/backend-defaults": ^0.13.3 "@backstage/backend-openapi-utils": ^0.6.3 "@backstage/backend-plugin-api": ^1.5.0 "@backstage/catalog-model": ^1.7.6 @@ -5057,7 +5017,6 @@ __metadata: p-limit: ^3.1.0 p-queue: ^6.6.2 prom-client: ^15.0.0 - tar: ^6.1.12 triple-beam: ^1.4.1 uuid: ^11.0.0 winston: ^3.2.1 @@ -5066,18 +5025,18 @@ __metadata: zen-observable: ^0.10.0 zod: ^3.22.4 zod-to-json-schema: ^3.20.4 - checksum: 002e20c206eec88cf452a8b5bce33bb63f3c81eab594f15c859cdbabfe46cf432edcae4f6a8181113a4201301d7d57aa7a3eb91fe9af58948b58a8094a5df7b9 + checksum: 055f2cf30d0b245ce3449c6651bd1b972fdfe946ba04519491fdc93f74b95015e306033a3a089cb18975fdf8fa8de1df75001322f57a3387f22742506c76ff74 languageName: node linkType: hard -"@backstage/plugin-scaffolder-common@npm:^1.7.3": - version: 1.7.3 - resolution: "@backstage/plugin-scaffolder-common@npm:1.7.3" +"@backstage/plugin-scaffolder-common@npm:^1.7.3, @backstage/plugin-scaffolder-common@npm:^1.7.5": + version: 1.7.5 + resolution: "@backstage/plugin-scaffolder-common@npm:1.7.5" dependencies: "@backstage/catalog-model": ^1.7.6 "@backstage/errors": ^1.2.7 - "@backstage/integration": ^1.18.2 - "@backstage/plugin-permission-common": ^0.9.3 + "@backstage/integration": ^1.19.2 + "@backstage/plugin-permission-common": ^0.9.4 "@backstage/types": ^1.2.2 "@microsoft/fetch-event-source": ^2.0.1 "@types/json-schema": ^7.0.9 @@ -5085,20 +5044,20 @@ __metadata: json-schema: ^0.4.0 uri-template: ^2.0.0 zen-observable: ^0.10.0 - checksum: 25fc884596dd1e88b1006d72fc1c60ef808ad4d8e8a7d70338d8b3a2bfdae70878f7333c935055486342aeacea42875fe3ddfac7e319ed16dfbec285417d2909 + checksum: a951de5218047a79bcdddfd9e568498f129bb7de8dd6177cd8ef1859a66f1889b0016e601ce3b44701da129e285b760c4783b757ad446b4a9a861e58d61d162a languageName: node linkType: hard -"@backstage/plugin-scaffolder-node@npm:0.12.1": - version: 0.12.1 - resolution: "@backstage/plugin-scaffolder-node@npm:0.12.1" +"@backstage/plugin-scaffolder-node@npm:^0.12.1": + version: 0.12.3 + resolution: "@backstage/plugin-scaffolder-node@npm:0.12.3" dependencies: - "@backstage/backend-plugin-api": ^1.5.0 + "@backstage/backend-plugin-api": ^1.6.1 "@backstage/catalog-model": ^1.7.6 "@backstage/errors": ^1.2.7 - "@backstage/integration": ^1.18.2 - "@backstage/plugin-permission-common": ^0.9.3 - "@backstage/plugin-scaffolder-common": ^1.7.3 + "@backstage/integration": ^1.19.2 + "@backstage/plugin-permission-common": ^0.9.4 + "@backstage/plugin-scaffolder-common": ^1.7.5 "@backstage/types": ^1.2.2 "@isomorphic-git/pgp-plugin": ^0.0.7 concat-stream: ^2.0.0 @@ -5112,36 +5071,8 @@ __metadata: winston: ^3.2.1 winston-transport: ^4.7.0 zod: ^3.22.4 - zod-to-json-schema: ^3.20.4 - checksum: 8c075497314b0f55a16b7d595a6815ce67eb07b891881151634c114801277b19ed15ccea0cb3ece170cf4d27663d63d122ce25cf68d43c9038badaf43f89cd79 - languageName: node - linkType: hard - -"@backstage/plugin-scaffolder-node@patch:@backstage/plugin-scaffolder-node@npm%3A0.12.1#./.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.1-7ce02a35bc.patch::locator=root%40workspace%3A.": - version: 0.12.1 - resolution: "@backstage/plugin-scaffolder-node@patch:@backstage/plugin-scaffolder-node@npm%3A0.12.1#./.yarn/patches/@backstage-plugin-scaffolder-node-npm-0.12.1-7ce02a35bc.patch::version=0.12.1&hash=7aa336&locator=root%40workspace%3A." - dependencies: - "@backstage/backend-plugin-api": ^1.5.0 - "@backstage/catalog-model": ^1.7.6 - "@backstage/errors": ^1.2.7 - "@backstage/integration": ^1.18.2 - "@backstage/plugin-permission-common": ^0.9.3 - "@backstage/plugin-scaffolder-common": ^1.7.3 - "@backstage/types": ^1.2.2 - "@isomorphic-git/pgp-plugin": ^0.0.7 - concat-stream: ^2.0.0 - fs-extra: ^11.2.0 - globby: ^11.0.0 - isomorphic-git: ^1.23.0 - jsonschema: ^1.5.0 - lodash: ^4.17.21 - p-limit: ^3.1.0 - tar: ^6.1.12 - winston: ^3.2.1 - winston-transport: ^4.7.0 - zod: ^3.22.4 - zod-to-json-schema: ^3.20.4 - checksum: 69d675b300d40a9883c4bc88e8f94092d7ef848680801bb3139fa975f64a5ebc2b387563f4425dd47bf5bbbf7bb06c2602cda264893774014f8af286af53d3ab + zod-to-json-schema: ^3.25.1 + checksum: 0466d81fc71f275c2f9fda0fce985378a1b2cfeb66afd4ebe166c5a0a886b9bd6554b95633d9339ebea0b11a3df063816913ebd4e68fba26e2f130c6c8fcd1e1 languageName: node linkType: hard @@ -7449,7 +7380,7 @@ __metadata: version: 0.0.0-use.local resolution: "@internal/plugin-dynamic-plugins-info-backend@workspace:plugins/dynamic-plugins-info-backend" dependencies: - "@backstage/backend-defaults": 0.13.1 + "@backstage/backend-defaults": 0.13.3 "@backstage/backend-dynamic-feature-service": 0.7.6 "@backstage/backend-plugin-api": 1.5.0 "@backstage/backend-test-utils": 1.10.1 @@ -7472,7 +7403,7 @@ __metadata: resolution: "@internal/plugin-licensed-users-info-backend@workspace:plugins/licensed-users-info-backend" dependencies: "@backstage-community/plugin-rbac-common": 1.22.0 - "@backstage/backend-defaults": 0.13.1 + "@backstage/backend-defaults": 0.13.3 "@backstage/backend-plugin-api": 1.5.0 "@backstage/backend-test-utils": 1.10.1 "@backstage/catalog-client": 1.12.1 @@ -7503,7 +7434,7 @@ __metadata: version: 0.0.0-use.local resolution: "@internal/plugin-scalprum-backend@workspace:plugins/scalprum-backend" dependencies: - "@backstage/backend-defaults": 0.13.1 + "@backstage/backend-defaults": 0.13.3 "@backstage/backend-dynamic-feature-service": 0.7.6 "@backstage/backend-plugin-api": 1.5.0 "@backstage/backend-test-utils": 1.10.1 @@ -7597,6 +7528,15 @@ __metadata: languageName: node linkType: hard +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.0 + resolution: "@isaacs/fs-minipass@npm:4.0.0" + dependencies: + minipass: ^7.0.4 + checksum: a8f46881bcffe48d8b467173fa709ef656e732d28fb4acdef2e14ba9de2ecc97527cc921f52081fff327737880af6f0b5201414c27451b56bb3b47d903f54a97 + languageName: node + linkType: hard + "@isomorphic-git/pgp-plugin@npm:^0.0.7": version: 0.0.7 resolution: "@isomorphic-git/pgp-plugin@npm:0.0.7" @@ -18966,7 +18906,7 @@ __metadata: "@backstage-community/plugin-rbac-node": 1.16.0 "@backstage-community/plugin-scaffolder-backend-module-annotator": 2.12.0 "@backstage/backend-app-api": 1.3.0 - "@backstage/backend-defaults": 0.13.1 + "@backstage/backend-defaults": 0.13.3 "@backstage/backend-dynamic-feature-service": 0.7.6 "@backstage/backend-plugin-api": 1.5.0 "@backstage/backend-test-utils": 1.10.1 @@ -19004,7 +18944,7 @@ __metadata: "@backstage/plugin-events-node": 0.4.17 "@backstage/plugin-permission-backend": 0.7.6 "@backstage/plugin-proxy-backend": 0.6.8 - "@backstage/plugin-scaffolder-backend": 3.0.2 + "@backstage/plugin-scaffolder-backend": 3.0.3 "@backstage/plugin-search-backend": 2.0.8 "@backstage/plugin-search-backend-module-catalog": 0.3.10 "@backstage/plugin-search-backend-module-pg": 0.5.50 @@ -19945,6 +19885,13 @@ __metadata: languageName: node linkType: hard +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: fd73a4bab48b79e66903fe1cafbdc208956f41ea4f856df883d0c7277b7ab29fd33ee65f93b2ec9192fc0169238f2f8307b7735d27c155821d886b84aa97aa8d + languageName: node + linkType: hard + "chrome-trace-event@npm:^1.0.2": version: 1.0.3 resolution: "chrome-trace-event@npm:1.0.3" @@ -24655,7 +24602,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.1, glob@npm:^10.4.5": +"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.1, glob@npm:^10.4.5": version: 10.5.0 resolution: "glob@npm:10.5.0" dependencies: @@ -28543,13 +28490,6 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.18.1": - version: 4.18.1 - resolution: "lodash@npm:4.18.1" - checksum: bb5f5b49aad29614e709af02b64c56b0f8b78c6a81434a3c1ae527d2f0f78ca08f9d9fb22aa825a053876c9d2166e9c01f31c356014b5e2bdc0556c057433102 - languageName: node - linkType: hard - "log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" @@ -29824,6 +29764,13 @@ __metadata: languageName: node linkType: hard +"minipass@npm:^7.0.4": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 2ede17c0bf8fec499be3360fd07f0ec7666189e3907320a9b653f1530cf84af98928c5b12d80bfb75f321833bf2e97785b940540213ebdafe97a5f10327e664d + languageName: node + linkType: hard + "minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": version: 2.1.2 resolution: "minizlib@npm:2.1.2" @@ -29834,6 +29781,16 @@ __metadata: languageName: node linkType: hard +"minizlib@npm:^3.0.1": + version: 3.0.1 + resolution: "minizlib@npm:3.0.1" + dependencies: + minipass: ^7.0.4 + rimraf: ^5.0.5 + checksum: da0a53899252380475240c587e52c824f8998d9720982ba5c4693c68e89230718884a209858c156c6e08d51aad35700a3589987e540593c36f6713fe30cd7338 + languageName: node + linkType: hard + "mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": version: 0.5.3 resolution: "mkdirp-classic@npm:0.5.3" @@ -29861,6 +29818,15 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 972deb188e8fb55547f1e58d66bd6b4a3623bf0c7137802582602d73e6480c1c2268dcbafbfb1be466e00cc7e56ac514d7fd9334b7cf33e3e2ab547c16f83a8d + languageName: node + linkType: hard + "mock-fs@npm:5.5.0": version: 5.5.0 resolution: "mock-fs@npm:5.5.0" @@ -34347,6 +34313,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:^5.0.5": + version: 5.0.5 + resolution: "rimraf@npm:5.0.5" + dependencies: + glob: ^10.3.7 + bin: + rimraf: dist/esm/bin.mjs + checksum: d66eef829b2e23b16445f34e73d75c7b7cf4cbc8834b04720def1c8f298eb0753c3d76df77325fad79d0a2c60470525d95f89c2475283ad985fd7441c32732d1 + languageName: node + linkType: hard + "ripemd160@npm:=2.0.1": version: 2.0.1 resolution: "ripemd160@npm:2.0.1" @@ -36503,6 +36480,20 @@ __metadata: languageName: node linkType: hard +"tar@npm:^7.4.3": + version: 7.4.3 + resolution: "tar@npm:7.4.3" + dependencies: + "@isaacs/fs-minipass": ^4.0.0 + chownr: ^3.0.0 + minipass: ^7.1.2 + minizlib: ^3.0.1 + mkdirp: ^3.0.1 + yallist: ^5.0.0 + checksum: 8485350c0688331c94493031f417df069b778aadb25598abdad51862e007c39d1dd5310702c7be4a6784731a174799d8885d2fde0484269aea205b724d7b2ffa + languageName: node + linkType: hard + "tarn@npm:^3.0.2": version: 3.0.2 resolution: "tarn@npm:3.0.2" @@ -37744,6 +37735,13 @@ __metadata: languageName: node linkType: hard +"undici@npm:^7.24.5": + version: 7.25.0 + resolution: "undici@npm:7.25.0" + checksum: 502f855d69dd0f343a4b4999b995b99eab8764639983776bb5afd09b50671863244defe093e7fb97df767c948d6548c0c3d97f8dbb35fec16c1c8c1c15843f17 + languageName: node + linkType: hard + "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0" @@ -39076,6 +39074,13 @@ __metadata: languageName: node linkType: hard +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: eba51182400b9f35b017daa7f419f434424410691bbc5de4f4240cc830fdef906b504424992700dc047f16b4d99100a6f8b8b11175c193f38008e9c96322b6a5 + languageName: node + linkType: hard + "yaml@npm:^1.10.0, yaml@npm:^1.10.2, yaml@npm:^1.7.2": version: 1.10.2 resolution: "yaml@npm:1.10.2" @@ -39277,12 +39282,12 @@ __metadata: languageName: node linkType: hard -"zod-to-json-schema@npm:^3.20.4, zod-to-json-schema@npm:^3.21.4, zod-to-json-schema@npm:^3.24.1, zod-to-json-schema@npm:^3.24.5, zod-to-json-schema@npm:^3.24.6": - version: 3.24.6 - resolution: "zod-to-json-schema@npm:3.24.6" +"zod-to-json-schema@npm:^3.20.4, zod-to-json-schema@npm:^3.21.4, zod-to-json-schema@npm:^3.24.1, zod-to-json-schema@npm:^3.24.5, zod-to-json-schema@npm:^3.24.6, zod-to-json-schema@npm:^3.25.1": + version: 3.25.1 + resolution: "zod-to-json-schema@npm:3.25.1" peerDependencies: - zod: ^3.24.1 - checksum: 5f4d29597cfd88d8fb8a539f0169affb8705d67ee9cbe478aa01bb1d2554e0540ca713fa4ddeb2fd834e87e7cdff61fa396f6d1925a9006de70afe6cd68bf7d2 + zod: ^3.25 || ^4 + checksum: 2033915aed81729544398a0000a63fb474972a654df712610343b8143c254d26f5d76cbee02b135648f299a0dc71be79a724d25a71a755085b438c7bfac6b9c8 languageName: node linkType: hard @@ -39309,6 +39314,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^3.25.76 || ^4.0.0": + version: 4.3.6 + resolution: "zod@npm:4.3.6" + checksum: 19cec761b46bae4b6e7e861ea740f3f248e50a6671825afc8a5758e27b35d6f20ccde9942422fd5cf6f8b697f18bd05ef8bb33f5f2db112ab25cc628de2fae47 + languageName: node + linkType: hard + "zstd-codec@npm:^0.1.5": version: 0.1.5 resolution: "zstd-codec@npm:0.1.5"