diff --git a/.changeset/early-dryers-teach.md b/.changeset/early-dryers-teach.md new file mode 100644 index 0000000000..d747fbc730 --- /dev/null +++ b/.changeset/early-dryers-teach.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-node': minor +--- + +Added `parseEntityYaml` from `@backstage/plugin-catalog-backend`, to make it more easily usable by custom plugins and modules diff --git a/.changeset/sad-showers-begin.md b/.changeset/sad-showers-begin.md index eccbd9981c..6054072620 100644 --- a/.changeset/sad-showers-begin.md +++ b/.changeset/sad-showers-begin.md @@ -33,6 +33,7 @@ The following removed exports are available from `@backstage/plugin-catalog-node - `PlaceholderResolverParams` - `PlaceholderResolverRead` - `PlaceholderResolverResolveUrl` +- `parseEntityYaml` The following removed exports are available from `@backstage/plugin-catalog-common`: diff --git a/plugins/catalog-backend/report.api.md b/plugins/catalog-backend/report.api.md index e92e688aa4..cdabce4732 100644 --- a/plugins/catalog-backend/report.api.md +++ b/plugins/catalog-backend/report.api.md @@ -10,7 +10,6 @@ import { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { CatalogProcessorCache } from '@backstage/plugin-catalog-node'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node'; import { CatalogProcessorParser } from '@backstage/plugin-catalog-node'; -import { CatalogProcessorResult } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { DiscoveryService } from '@backstage/backend-plugin-api'; @@ -215,12 +214,6 @@ export class FileReaderProcessor implements CatalogProcessor { ): Promise; } -// @public (undocumented) -export function parseEntityYaml( - data: Buffer, - location: LocationSpec, -): Iterable; - // @public export class PlaceholderProcessor implements CatalogProcessor { constructor(options: PlaceholderProcessorOptions); diff --git a/plugins/catalog-backend/src/index.ts b/plugins/catalog-backend/src/index.ts index 40235a1724..ea658a9a13 100644 --- a/plugins/catalog-backend/src/index.ts +++ b/plugins/catalog-backend/src/index.ts @@ -25,4 +25,3 @@ export * from './processors'; export * from './processing'; export * from './service'; export * from './constants'; -export * from './util'; diff --git a/plugins/catalog-backend/src/util/index.ts b/plugins/catalog-backend/src/util/index.ts deleted file mode 100644 index b0e291ac60..0000000000 --- a/plugins/catalog-backend/src/util/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { parseEntityYaml } from './parse'; diff --git a/plugins/catalog-backend/src/util/parse.ts b/plugins/catalog-backend/src/util/parse.ts index 778c176323..d87a98ef75 100644 --- a/plugins/catalog-backend/src/util/parse.ts +++ b/plugins/catalog-backend/src/util/parse.ts @@ -14,51 +14,11 @@ * limitations under the License. */ -import { Entity, stringifyLocationRef } from '@backstage/catalog-model'; -import lodash from 'lodash'; -import yaml from 'yaml'; -import { LocationSpec } from '@backstage/plugin-catalog-common'; import { CatalogProcessorParser, - CatalogProcessorResult, - processingResult, + parseEntityYaml, } from '@backstage/plugin-catalog-node'; -/** @public */ -export function* parseEntityYaml( - data: Buffer, - location: LocationSpec, -): Iterable { - let documents: yaml.Document.Parsed[]; - try { - documents = yaml.parseAllDocuments(data.toString('utf8')).filter(d => d); - } catch (e) { - const loc = stringifyLocationRef(location); - const message = `Failed to parse YAML at ${loc}, ${e}`; - yield processingResult.generalError(location, message); - return; - } - - for (const document of documents) { - if (document.errors?.length) { - const loc = stringifyLocationRef(location); - const message = `YAML error at ${loc}, ${document.errors[0]}`; - yield processingResult.generalError(location, message); - } else { - const json = document.toJSON(); - if (lodash.isPlainObject(json)) { - yield processingResult.entity(location, json as Entity); - } else if (json === null) { - // Ignore null values, these happen if there is an empty document in the - // YAML file, for example if --- is added to the end of the file. - } else { - const message = `Expected object at root, got ${typeof json}`; - yield processingResult.generalError(location, message); - } - } - } -} - export const defaultEntityDataParser: CatalogProcessorParser = async function* defaultEntityDataParser({ data, location }) { for (const e of parseEntityYaml(data, location)) { diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 4c12f6b7fb..2aad8b676f 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -67,7 +67,9 @@ "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-node": "workspace:^", - "@backstage/types": "workspace:^" + "@backstage/types": "workspace:^", + "lodash": "^4.17.21", + "yaml": "^2.0.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/catalog-node/report.api.md b/plugins/catalog-node/report.api.md index f2fe86a38c..807bcaa8d5 100644 --- a/plugins/catalog-node/report.api.md +++ b/plugins/catalog-node/report.api.md @@ -297,6 +297,12 @@ export function locationSpecToLocationEntity(opts: { // @public export function locationSpecToMetadataName(location: LocationSpec_2): string; +// @public +export function parseEntityYaml( + data: string | Buffer, + location: LocationSpec_2, +): Iterable; + // @public (undocumented) export type PlaceholderResolver = ( params: PlaceholderResolverParams, diff --git a/plugins/catalog-node/src/processing/index.ts b/plugins/catalog-node/src/processing/index.ts index 4e83e6077d..1610e0cb89 100644 --- a/plugins/catalog-node/src/processing/index.ts +++ b/plugins/catalog-node/src/processing/index.ts @@ -24,3 +24,4 @@ export type { LocationAnalyzer, ScmLocationAnalyzer, } from './types'; +export { parseEntityYaml } from './parse'; diff --git a/plugins/catalog-backend/src/util/parse.test.ts b/plugins/catalog-node/src/processing/parse.test.ts similarity index 100% rename from plugins/catalog-backend/src/util/parse.test.ts rename to plugins/catalog-node/src/processing/parse.test.ts diff --git a/plugins/catalog-node/src/processing/parse.ts b/plugins/catalog-node/src/processing/parse.ts new file mode 100644 index 0000000000..2ef276d06d --- /dev/null +++ b/plugins/catalog-node/src/processing/parse.ts @@ -0,0 +1,76 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity, stringifyLocationRef } from '@backstage/catalog-model'; +import lodash from 'lodash'; +import yaml from 'yaml'; +import { LocationSpec } from '@backstage/plugin-catalog-common'; +import { CatalogProcessorResult } from '../api/processor'; +import { processingResult } from '../api/processingResult'; + +/** + * A helper function that parses a YAML file, properly handling multiple + * documents in a single file. + * + * @public + * @remarks + * + * Each document is expected to be a valid Backstage entity. Each item in the + * iterable is in the form of an entity result if the document seemed like a + * valid object, or in the form of an error result if it seemed incorrect. This + * way, you can choose to retain a partial result if you want. + * + * Note that this function does NOT perform any validation of the entities. No + * assumptions whatsoever can be made on the emnitted entities except that they + * are valid JSON objects. + */ +export function* parseEntityYaml( + data: string | Buffer, + location: LocationSpec, +): Iterable { + let documents: yaml.Document.Parsed[]; + try { + documents = yaml + .parseAllDocuments( + typeof data === 'string' ? data : data.toString('utf8'), + ) + .filter(d => d); + } catch (e) { + const loc = stringifyLocationRef(location); + const message = `Failed to parse YAML at ${loc}, ${e}`; + yield processingResult.generalError(location, message); + return; + } + + for (const document of documents) { + if (document.errors?.length) { + const loc = stringifyLocationRef(location); + const message = `YAML error at ${loc}, ${document.errors[0]}`; + yield processingResult.generalError(location, message); + } else { + const json = document.toJSON(); + if (lodash.isPlainObject(json)) { + yield processingResult.entity(location, json as Entity); + } else if (json === null) { + // Ignore null values, these happen if there is an empty document in the + // YAML file, for example if --- is added to the end of the file. + } else { + const message = `Expected object at root, got ${typeof json}`; + yield processingResult.generalError(location, message); + } + } + } +} diff --git a/yarn.lock b/yarn.lock index 9216e2687f..c7f79ae2f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6187,7 +6187,9 @@ __metadata: "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-permission-node": "workspace:^" "@backstage/types": "workspace:^" + lodash: "npm:^4.17.21" msw: "npm:^1.0.0" + yaml: "npm:^2.0.0" languageName: unknown linkType: soft