@backstage/plugin-techdocs-node 1.16.0-next.1 → 2.0.1-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"googleStorage.cjs.js","sources":["../../../src/stages/publish/googleStorage.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 */\nimport { Entity, CompoundEntityRef } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { toError } from '@backstage/errors';\nimport {\n File,\n FileExistsResponse,\n Storage,\n StorageOptions,\n} from '@google-cloud/storage';\nimport express from 'express';\nimport JSON5 from 'json5';\nimport path from 'node:path';\nimport { Readable } from 'node:stream';\nimport {\n getFileTreeRecursively,\n getHeadersForFileExtension,\n isValidContentPath,\n lowerCaseEntityTriplet,\n lowerCaseEntityTripletInStoragePath,\n bulkStorageOperation,\n getCloudPathForLocalPath,\n getStaleFiles,\n normalizeExternalStorageRootPath,\n} from './helpers';\nimport { MigrateWriteStream } from './migrations';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nexport class GoogleGCSPublish implements PublisherBase {\n private readonly storageClient: Storage;\n private readonly bucketName: string;\n private readonly legacyPathCasing: boolean;\n private readonly logger: LoggerService;\n private readonly bucketRootPath: string;\n\n constructor(options: {\n storageClient: Storage;\n bucketName: string;\n legacyPathCasing: boolean;\n logger: LoggerService;\n bucketRootPath: string;\n }) {\n this.storageClient = options.storageClient;\n this.bucketName = options.bucketName;\n this.legacyPathCasing = options.legacyPathCasing;\n this.logger = options.logger;\n this.bucketRootPath = options.bucketRootPath;\n }\n\n static fromConfig(\n config: Config,\n logger: LoggerService,\n options?: StorageOptions,\n ): PublisherBase {\n let bucketName = '';\n try {\n bucketName = config.getString('techdocs.publisher.googleGcs.bucketName');\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'googleGcs' in your app config, \" +\n 'techdocs.publisher.googleGcs.bucketName is required.',\n );\n }\n\n const bucketRootPath = normalizeExternalStorageRootPath(\n config.getOptionalString('techdocs.publisher.googleGcs.bucketRootPath') ||\n '',\n );\n\n // Credentials is an optional config. If missing, default GCS environment variables will be used.\n // Read more here https://cloud.google.com/docs/authentication/production\n const credentials = config.getOptionalString(\n 'techdocs.publisher.googleGcs.credentials',\n );\n const projectId = config.getOptionalString(\n 'techdocs.publisher.googleGcs.projectId',\n );\n let credentialsJson: any = {};\n if (credentials) {\n try {\n credentialsJson = JSON.parse(credentials);\n } catch (err) {\n throw new Error(\n 'Error in parsing techdocs.publisher.googleGcs.credentials config to JSON.',\n );\n }\n }\n\n const clientOpts: StorageOptions = options ?? {};\n if (projectId) {\n clientOpts.projectId = projectId;\n }\n\n const storageClient = new Storage({\n ...(credentials && {\n projectId: credentialsJson.project_id,\n credentials: credentialsJson,\n }),\n ...clientOpts,\n });\n\n const legacyPathCasing =\n config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n ) || false;\n\n return new GoogleGCSPublish({\n storageClient,\n bucketName,\n legacyPathCasing,\n logger,\n bucketRootPath,\n });\n }\n\n /**\n * Check if the defined bucket exists. Being able to connect means the configuration is good\n * and the storage client will work.\n */\n async getReadiness(): Promise<ReadinessResponse> {\n try {\n await this.storageClient.bucket(this.bucketName).getMetadata();\n this.logger.info(\n `Successfully connected to the GCS bucket ${this.bucketName}.`,\n );\n\n return {\n isAvailable: true,\n };\n } catch (err) {\n this.logger.error(\n `Could not retrieve metadata about the GCS bucket ${this.bucketName}. ` +\n 'Make sure the bucket exists. Also make sure that authentication is setup either by explicitly defining ' +\n 'techdocs.publisher.googleGcs.credentials in app config or by using environment variables. ' +\n 'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',\n );\n this.logger.error(`from GCS client library: ${toError(err).message}`);\n\n return { isAvailable: false };\n }\n }\n\n /**\n * Upload all the files from the generated `directory` to the GCS bucket.\n * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html\n */\n async publish({\n entity,\n directory,\n }: PublishRequest): Promise<PublishResponse> {\n const objects: string[] = [];\n const useLegacyPathCasing = this.legacyPathCasing;\n const bucket = this.storageClient.bucket(this.bucketName);\n const bucketRootPath = this.bucketRootPath;\n\n // First, try to retrieve a list of all individual files currently existing\n let existingFiles: string[] = [];\n try {\n const remoteFolder = getCloudPathForLocalPath(\n entity,\n undefined,\n useLegacyPathCasing,\n bucketRootPath,\n );\n existingFiles = await this.getFilesForFolder(remoteFolder);\n } catch (e) {\n this.logger.error(\n `Unable to list files for Entity ${entity.metadata.name}: ${\n toError(e).message\n }`,\n );\n }\n\n // Then, merge new files into the same folder\n let absoluteFilesToUpload;\n try {\n // Remove the absolute path prefix of the source directory\n // Path of all files to upload, relative to the root of the source directory\n // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png']\n absoluteFilesToUpload = await getFileTreeRecursively(directory);\n\n await bulkStorageOperation(\n async absoluteFilePath => {\n const relativeFilePath = path.relative(directory, absoluteFilePath);\n const destination = getCloudPathForLocalPath(\n entity,\n relativeFilePath,\n useLegacyPathCasing,\n bucketRootPath,\n );\n objects.push(destination);\n return await bucket.upload(absoluteFilePath, { destination });\n },\n absoluteFilesToUpload,\n { concurrencyLimit: 10 },\n );\n\n this.logger.info(\n `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${absoluteFilesToUpload.length}`,\n );\n } catch (e) {\n const errorMessage = `Unable to upload file(s) to Google Cloud Storage. ${e}`;\n this.logger.error(errorMessage);\n throw new Error(errorMessage);\n }\n\n // Last, try to remove the files that were *only* present previously\n try {\n const relativeFilesToUpload = absoluteFilesToUpload.map(\n absoluteFilePath =>\n getCloudPathForLocalPath(\n entity,\n path.relative(directory, absoluteFilePath),\n useLegacyPathCasing,\n bucketRootPath,\n ),\n );\n const staleFiles = getStaleFiles(relativeFilesToUpload, existingFiles);\n\n await bulkStorageOperation(\n async relativeFilePath => {\n return await bucket.file(relativeFilePath).delete();\n },\n staleFiles,\n { concurrencyLimit: 10 },\n );\n\n this.logger.info(\n `Successfully deleted stale files for Entity ${entity.metadata.name}. Total number of files: ${staleFiles.length}`,\n );\n } catch (error) {\n const errorMessage = `Unable to delete file(s) from Google Cloud Storage. ${error}`;\n this.logger.error(errorMessage);\n }\n\n return { objects };\n }\n\n fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n return new Promise((resolve, reject) => {\n const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;\n const entityDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n const entityRootDir = path.posix.join(this.bucketRootPath, entityDir);\n if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {\n this.logger.error(\n `Invalid content path found while fetching TechDocs metadata: ${entityRootDir}`,\n );\n reject(new Error(`Metadata Not Found`));\n }\n\n const fileStreamChunks: Array<any> = [];\n this.storageClient\n .bucket(this.bucketName)\n .file(`${entityRootDir}/techdocs_metadata.json`)\n .createReadStream()\n .on('error', err => {\n this.logger.error(err.message);\n reject(err);\n })\n .on('data', chunk => {\n fileStreamChunks.push(chunk);\n })\n .on('end', () => {\n const techdocsMetadataJson =\n Buffer.concat(fileStreamChunks).toString('utf-8');\n resolve(JSON5.parse(techdocsMetadataJson));\n });\n });\n }\n\n /**\n * Express route middleware to serve static files on a route in techdocs-backend.\n */\n docsRouter(): express.Handler {\n return (req, res) => {\n const decodedUri = decodeURI(req.path.replace(/^\\//, ''));\n\n // filePath example - /default/component/documented-component/index.html\n const filePathNoRoot = this.legacyPathCasing\n ? decodedUri\n : lowerCaseEntityTripletInStoragePath(decodedUri);\n\n // Prepend the root path to the relative file path\n const filePath = path.posix.join(this.bucketRootPath, filePathNoRoot);\n if (!isValidContentPath(this.bucketRootPath, filePath)) {\n this.logger.error(\n `Attempted to fetch TechDocs content for a file outside of the bucket root: ${filePathNoRoot}`,\n );\n res.status(404).send('File Not Found');\n return;\n }\n\n // Files with different extensions (CSS, HTML) need to be served with different headers\n const fileExtension = path.extname(filePath);\n const responseHeaders = getHeadersForFileExtension(fileExtension);\n\n // Pipe file chunks directly from storage to client.\n this.storageClient\n .bucket(this.bucketName)\n .file(filePath)\n .createReadStream()\n .on('pipe', () => {\n res.writeHead(200, responseHeaders);\n })\n .on('error', err => {\n this.logger.warn(\n `TechDocs Google GCS router failed to serve content from bucket ${this.bucketName} at path ${filePath}: ${err.message}`,\n );\n // Send a 404 with a meaningful message if possible.\n if (!res.headersSent) {\n res.status(404).send('File Not Found');\n } else {\n res.destroy();\n }\n })\n .pipe(res);\n };\n }\n\n /**\n * A helper function which checks if index.html of an Entity's docs site is available. This\n * can be used to verify if there are any pre-generated docs available to serve.\n */\n async hasDocsBeenGenerated(entity: Entity): Promise<boolean> {\n return new Promise(resolve => {\n const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;\n const entityDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n const entityRootDir = path.posix.join(this.bucketRootPath, entityDir);\n if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {\n this.logger.error(\n `Invalid content path found while checking if docs have been generated: ${entityRootDir}`,\n );\n resolve(false);\n }\n\n this.storageClient\n .bucket(this.bucketName)\n .file(`${entityRootDir}/index.html`)\n .exists()\n .then((response: FileExistsResponse) => {\n resolve(response[0]);\n })\n .catch(() => {\n resolve(false);\n });\n });\n }\n\n migrateDocsCase({ removeOriginal = false, concurrency = 25 }): Promise<void> {\n return new Promise((resolve, reject) => {\n // Iterate through every file in the root of the publisher.\n const allFileMetadata: Readable = this.storageClient\n .bucket(this.bucketName)\n .getFilesStream();\n const migrateFiles = new MigrateWriteStream(\n this.logger,\n removeOriginal,\n concurrency,\n );\n migrateFiles.on('finish', resolve).on('error', reject);\n allFileMetadata.pipe(migrateFiles).on('error', error => {\n migrateFiles.destroy();\n reject(error);\n });\n });\n }\n\n private getFilesForFolder(folder: string): Promise<string[]> {\n const fileMetadataStream: Readable = this.storageClient\n .bucket(this.bucketName)\n .getFilesStream({ prefix: folder });\n\n return new Promise((resolve, reject) => {\n const files: string[] = [];\n\n fileMetadataStream.on('error', error => {\n // push file to file array\n reject(error);\n });\n\n fileMetadataStream.on('data', (file: File) => {\n // push file to file array\n files.push(file.name);\n });\n\n fileMetadataStream.on('end', () => {\n // resolve promise\n resolve(files);\n });\n });\n }\n}\n"],"names":["normalizeExternalStorageRootPath","Storage","toError","getCloudPathForLocalPath","getFileTreeRecursively","bulkStorageOperation","path","getStaleFiles","lowerCaseEntityTriplet","isValidContentPath","JSON5","lowerCaseEntityTripletInStoragePath","getHeadersForFileExtension","MigrateWriteStream"],"mappings":"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"}
1
+ {"version":3,"file":"googleStorage.cjs.js","sources":["../../../src/stages/publish/googleStorage.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 */\nimport { Entity, CompoundEntityRef } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { toError } from '@backstage/errors';\nimport {\n File,\n FileExistsResponse,\n Storage,\n StorageOptions,\n} from '@google-cloud/storage';\nimport express from 'express';\nimport JSON5 from 'json5';\nimport path from 'node:path';\nimport { Readable } from 'node:stream';\nimport {\n getFileTreeRecursively,\n getHeadersForFileExtension,\n isValidContentPath,\n lowerCaseEntityTriplet,\n lowerCaseEntityTripletInStoragePath,\n bulkStorageOperation,\n getCloudPathForLocalPath,\n getStaleFiles,\n normalizeExternalStorageRootPath,\n} from './helpers';\nimport { MigrateWriteStream } from './migrations';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nexport class GoogleGCSPublish implements PublisherBase {\n private readonly storageClient: Storage;\n private readonly bucketName: string;\n private readonly legacyPathCasing: boolean;\n private readonly logger: LoggerService;\n private readonly bucketRootPath: string;\n\n constructor(options: {\n storageClient: Storage;\n bucketName: string;\n legacyPathCasing: boolean;\n logger: LoggerService;\n bucketRootPath: string;\n }) {\n this.storageClient = options.storageClient;\n this.bucketName = options.bucketName;\n this.legacyPathCasing = options.legacyPathCasing;\n this.logger = options.logger;\n this.bucketRootPath = options.bucketRootPath;\n }\n\n static fromConfig(\n config: Config,\n logger: LoggerService,\n options?: StorageOptions,\n ): PublisherBase {\n let bucketName = '';\n try {\n bucketName = config.getString('techdocs.publisher.googleGcs.bucketName');\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'googleGcs' in your app config, \" +\n 'techdocs.publisher.googleGcs.bucketName is required.',\n );\n }\n\n const bucketRootPath = normalizeExternalStorageRootPath(\n config.getOptionalString('techdocs.publisher.googleGcs.bucketRootPath') ||\n '',\n );\n\n // Credentials is an optional config. If missing, default GCS environment variables will be used.\n // Read more here https://cloud.google.com/docs/authentication/production\n const credentials = config.getOptionalString(\n 'techdocs.publisher.googleGcs.credentials',\n );\n const projectId = config.getOptionalString(\n 'techdocs.publisher.googleGcs.projectId',\n );\n let credentialsJson: any = {};\n if (credentials) {\n try {\n credentialsJson = JSON.parse(credentials);\n } catch (err) {\n throw new Error(\n 'Error in parsing techdocs.publisher.googleGcs.credentials config to JSON.',\n );\n }\n }\n\n const clientOpts: StorageOptions = options ?? {};\n if (projectId) {\n clientOpts.projectId = projectId;\n }\n\n const storageClient = new Storage({\n ...(credentials && {\n projectId: credentialsJson.project_id,\n credentials: credentialsJson,\n }),\n ...clientOpts,\n });\n\n const legacyPathCasing =\n config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n ) || false;\n\n return new GoogleGCSPublish({\n storageClient,\n bucketName,\n legacyPathCasing,\n logger,\n bucketRootPath,\n });\n }\n\n /**\n * Check if the defined bucket exists. Being able to connect means the configuration is good\n * and the storage client will work.\n */\n async getReadiness(): Promise<ReadinessResponse> {\n try {\n await this.storageClient.bucket(this.bucketName).getMetadata();\n this.logger.info(\n `Successfully connected to the GCS bucket ${this.bucketName}.`,\n );\n\n return {\n isAvailable: true,\n };\n } catch (err) {\n this.logger.error(\n `Could not retrieve metadata about the GCS bucket ${this.bucketName}. ` +\n 'Make sure the bucket exists. Also make sure that authentication is setup either by explicitly defining ' +\n 'techdocs.publisher.googleGcs.credentials in app config or by using environment variables. ' +\n 'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',\n );\n this.logger.error(`from GCS client library: ${toError(err).message}`);\n\n return { isAvailable: false };\n }\n }\n\n /**\n * Upload all the files from the generated `directory` to the GCS bucket.\n * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html\n */\n async publish({\n entity,\n directory,\n }: PublishRequest): Promise<PublishResponse> {\n const objects: string[] = [];\n const useLegacyPathCasing = this.legacyPathCasing;\n const bucket = this.storageClient.bucket(this.bucketName);\n const bucketRootPath = this.bucketRootPath;\n\n // First, try to retrieve a list of all individual files currently existing\n let existingFiles: string[] = [];\n try {\n const remoteFolder = getCloudPathForLocalPath(\n entity,\n undefined,\n useLegacyPathCasing,\n bucketRootPath,\n );\n existingFiles = await this.getFilesForFolder(remoteFolder);\n } catch (e) {\n this.logger.error(\n `Unable to list files for Entity ${entity.metadata.name}: ${\n toError(e).message\n }`,\n );\n }\n\n // Then, merge new files into the same folder\n let absoluteFilesToUpload;\n try {\n // Remove the absolute path prefix of the source directory\n // Path of all files to upload, relative to the root of the source directory\n // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png']\n absoluteFilesToUpload = await getFileTreeRecursively(directory);\n\n await bulkStorageOperation(\n async absoluteFilePath => {\n const relativeFilePath = path.relative(directory, absoluteFilePath);\n const destination = getCloudPathForLocalPath(\n entity,\n relativeFilePath,\n useLegacyPathCasing,\n bucketRootPath,\n );\n objects.push(destination);\n return await bucket.upload(absoluteFilePath, { destination });\n },\n absoluteFilesToUpload,\n { concurrencyLimit: 10 },\n );\n\n this.logger.info(\n `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${absoluteFilesToUpload.length}`,\n );\n } catch (e) {\n const errorMessage = `Unable to upload file(s) to Google Cloud Storage. ${e}`;\n this.logger.error(errorMessage);\n throw new Error(errorMessage);\n }\n\n // Last, try to remove the files that were *only* present previously\n try {\n const relativeFilesToUpload = absoluteFilesToUpload.map(\n absoluteFilePath =>\n getCloudPathForLocalPath(\n entity,\n path.relative(directory, absoluteFilePath),\n useLegacyPathCasing,\n bucketRootPath,\n ),\n );\n const staleFiles = getStaleFiles(relativeFilesToUpload, existingFiles);\n\n await bulkStorageOperation(\n async relativeFilePath => {\n return await bucket.file(relativeFilePath).delete();\n },\n staleFiles,\n { concurrencyLimit: 10 },\n );\n\n this.logger.info(\n `Successfully deleted stale files for Entity ${entity.metadata.name}. Total number of files: ${staleFiles.length}`,\n );\n } catch (error) {\n const errorMessage = `Unable to delete file(s) from Google Cloud Storage. ${error}`;\n this.logger.error(errorMessage);\n }\n\n return { objects };\n }\n\n fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n return new Promise((resolve, reject) => {\n const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;\n const entityDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n const entityRootDir = path.posix.join(this.bucketRootPath, entityDir);\n if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {\n this.logger.error(\n `Invalid content path found while fetching TechDocs metadata: ${entityRootDir}`,\n );\n reject(new Error(`Metadata Not Found`));\n }\n\n const fileStreamChunks: Array<any> = [];\n this.storageClient\n .bucket(this.bucketName)\n .file(`${entityRootDir}/techdocs_metadata.json`)\n .createReadStream()\n .on('error', err => {\n this.logger.error(err.message);\n reject(err);\n })\n .on('data', chunk => {\n fileStreamChunks.push(chunk);\n })\n .on('end', () => {\n const techdocsMetadataJson =\n Buffer.concat(fileStreamChunks).toString('utf-8');\n resolve(JSON5.parse(techdocsMetadataJson));\n });\n });\n }\n\n /**\n * Express route middleware to serve static files on a route in techdocs-backend.\n */\n docsRouter(): express.Handler {\n return (req, res) => {\n const decodedUri = decodeURI(req.path.replace(/^\\//, ''));\n\n // filePath example - /default/component/documented-component/index.html\n const filePathNoRoot = this.legacyPathCasing\n ? decodedUri\n : lowerCaseEntityTripletInStoragePath(decodedUri);\n\n // Prepend the root path to the relative file path\n const filePath = path.posix.join(this.bucketRootPath, filePathNoRoot);\n if (!isValidContentPath(this.bucketRootPath, filePath)) {\n this.logger.error(\n `Attempted to fetch TechDocs content for a file outside of the bucket root: ${filePathNoRoot}`,\n );\n res.status(404).send('File Not Found');\n return;\n }\n\n // Files with different extensions (CSS, HTML) need to be served with different headers\n const fileExtension = path.extname(filePath);\n const responseHeaders = getHeadersForFileExtension(fileExtension);\n\n // Pipe file chunks directly from storage to client.\n this.storageClient\n .bucket(this.bucketName)\n .file(filePath)\n .createReadStream()\n .on('pipe', () => {\n res.writeHead(200, responseHeaders);\n })\n .on('error', err => {\n this.logger.warn(\n `TechDocs Google GCS router failed to serve content from bucket ${this.bucketName} at path ${filePath}: ${err.message}`,\n );\n // Send a 404 with a meaningful message if possible.\n if (!res.headersSent) {\n res.status(404).send('File Not Found');\n } else {\n res.destroy();\n }\n })\n .pipe(res);\n };\n }\n\n /**\n * A helper function which checks if index.html of an Entity's docs site is available. This\n * can be used to verify if there are any pre-generated docs available to serve.\n */\n async hasDocsBeenGenerated(entity: Entity): Promise<boolean> {\n return new Promise(resolve => {\n const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;\n const entityDir = this.legacyPathCasing\n ? entityTriplet\n : lowerCaseEntityTriplet(entityTriplet);\n\n const entityRootDir = path.posix.join(this.bucketRootPath, entityDir);\n if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {\n this.logger.error(\n `Invalid content path found while checking if docs have been generated: ${entityRootDir}`,\n );\n resolve(false);\n }\n\n this.storageClient\n .bucket(this.bucketName)\n .file(`${entityRootDir}/index.html`)\n .exists()\n .then((response: FileExistsResponse) => {\n resolve(response[0]);\n })\n .catch(() => {\n resolve(false);\n });\n });\n }\n\n migrateDocsCase({ removeOriginal = false, concurrency = 25 }): Promise<void> {\n return new Promise((resolve, reject) => {\n // Iterate through every file in the root of the publisher.\n const allFileMetadata: Readable = this.storageClient\n .bucket(this.bucketName)\n .getFilesStream();\n const migrateFiles = new MigrateWriteStream(\n this.logger,\n removeOriginal,\n concurrency,\n );\n migrateFiles.on('finish', resolve).on('error', reject);\n allFileMetadata.pipe(migrateFiles).on('error', error => {\n migrateFiles.destroy();\n reject(error);\n });\n });\n }\n\n private getFilesForFolder(folder: string): Promise<string[]> {\n const fileMetadataStream: Readable = this.storageClient\n .bucket(this.bucketName)\n .getFilesStream({ prefix: folder });\n\n return new Promise((resolve, reject) => {\n const files: string[] = [];\n\n fileMetadataStream.on('error', error => {\n // push file to file array\n reject(error);\n });\n\n fileMetadataStream.on('data', (file: File) => {\n // push file to file array\n files.push(file.name);\n });\n\n fileMetadataStream.on('end', () => {\n // resolve promise\n resolve(files);\n });\n });\n }\n}\n"],"names":["normalizeExternalStorageRootPath","Storage","toError","getCloudPathForLocalPath","getFileTreeRecursively","bulkStorageOperation","path","getStaleFiles","lowerCaseEntityTriplet","isValidContentPath","JSON5","lowerCaseEntityTripletInStoragePath","getHeadersForFileExtension","MigrateWriteStream"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.cjs.js","sources":["../../../src/stages/publish/helpers.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 */\nimport { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model';\nimport mime from 'mime-types';\nimport path from 'node:path';\nimport createLimiter from 'p-limit';\nimport recursiveReadDir from 'recursive-readdir';\n\n/**\n * Helper to get the expected content-type for a given file extension. Also\n * takes XSS mitigation into account.\n */\nconst getContentTypeForExtension = (ext: string): string => {\n const defaultContentType = 'text/plain; charset=utf-8';\n const excludedTypes = [\n 'text/html',\n 'text/xml',\n 'image/svg+xml',\n 'text/xsl',\n 'application/vnd.wap.xhtml+xml',\n 'multipart/x-mixed-replace',\n 'text/rdf',\n 'application/mathml+xml',\n 'application/octet-stream',\n 'application/rdf+xml',\n 'application/xhtml+xml',\n 'application/xml',\n 'text/cache-manifest',\n 'text/vtt',\n ];\n\n // Prevent sanitization bypass by preventing browsers from directly rendering\n // the contents of untrusted files.\n if (\n ext.match(\n /htm|xml|svg|appcache|manifest|mathml|owl|rdf|rng|vtt|xht|xsd|xsl/i,\n )\n ) {\n return defaultContentType;\n }\n\n // Check again to make sure that the content type is not in the excluded mime-type list\n // We use .lookup here to avoid the \"; charset=...\" addition\n const contentType = mime.lookup(ext);\n if (contentType && excludedTypes.includes(contentType)) {\n return defaultContentType;\n }\n\n return mime.contentType(ext) || defaultContentType;\n};\n\nexport type responseHeadersType = {\n 'Content-Type': string;\n};\n\n/**\n * Some files need special headers to be used correctly by the frontend. This function\n * generates headers in the response to those file requests.\n * @param fileExtension - .html, .css, .js, .png etc.\n */\nexport const getHeadersForFileExtension = (\n fileExtension: string,\n): responseHeadersType => {\n return {\n 'Content-Type': getContentTypeForExtension(fileExtension),\n };\n};\n\n/**\n * Recursively traverse all the sub-directories of a path and return\n * a list of absolute paths of all the files. e.g. tree command in Unix\n *\n * @example\n *\n * /User/username/my_dir\n * dirA\n * | subDirA\n * | | file1\n * EmptyDir\n * dirB\n * | file2\n * file3\n *\n * getFileListRecursively('/Users/username/myDir')\n * // returns\n * [\n * '/User/username/my_dir/dirA/subDirA/file1',\n * '/User/username/my_dir/dirB/file2',\n * '/User/username/my_dir/file3'\n * ]\n * @param rootDirPath - Absolute path to the root directory.\n */\nexport const getFileTreeRecursively = async (\n rootDirPath: string,\n): Promise<string[]> => {\n // Iterate on all the files in the directory and its sub-directories\n const fileList = await recursiveReadDir(rootDirPath).catch(error => {\n throw new Error(`Failed to read template directory: ${error.message}`);\n });\n return fileList;\n};\n\n/**\n * Takes a posix path and returns a lower-cased version of entity's triplet\n * with the remaining path in posix.\n *\n * Path must not include a starting slash.\n *\n * @example\n * lowerCaseEntityTriplet('default/Component/backstage')\n * // return default/component/backstage\n */\nexport const lowerCaseEntityTriplet = (posixPath: string): string => {\n const [namespace, kind, name, ...rest] = posixPath.split(path.posix.sep);\n const lowerNamespace = namespace.toLowerCase();\n const lowerKind = kind.toLowerCase();\n const lowerName = name.toLowerCase();\n return [lowerNamespace, lowerKind, lowerName, ...rest].join(path.posix.sep);\n};\n\n/**\n * Takes either a win32 or posix path and returns a lower-cased version of entity's triplet\n * with the remaining path in posix.\n *\n * Starting slashes will be trimmed.\n *\n * Throws an error if the path does not appear to be an entity triplet.\n *\n * @example\n * lowerCaseEntityTripletInStoragePath('/default/Component/backstage/file.txt')\n * // return default/component/backstage/file.txt\n */\nexport const lowerCaseEntityTripletInStoragePath = (\n originalPath: string,\n): string => {\n let posixPath = originalPath;\n if (originalPath.includes(path.win32.sep)) {\n posixPath = originalPath.split(path.win32.sep).join(path.posix.sep);\n }\n\n // remove leading slash\n const parts = posixPath.split(path.posix.sep);\n if (parts[0] === '') {\n parts.shift();\n }\n\n // check if all parts of the entity exist (name, namespace, kind) plus filename\n if (parts.length <= 3) {\n throw new Error(\n `Encountered file unmanaged by TechDocs ${originalPath}. Skipping.`,\n );\n }\n\n return lowerCaseEntityTriplet(parts.join(path.posix.sep));\n};\n\n/**\n * Take a posix path and return a path without leading and trailing\n * separators\n *\n * @example\n * normalizeExternalStorageRootPath('/backstage-data/techdocs/')\n * // return backstage-data/techdocs\n */\nexport const normalizeExternalStorageRootPath = (posixPath: string): string => {\n // remove leading slash\n let normalizedPath = posixPath;\n if (posixPath.startsWith(path.posix.sep)) {\n normalizedPath = posixPath.slice(1);\n }\n\n // remove trailing slash\n if (normalizedPath.endsWith(path.posix.sep)) {\n normalizedPath = normalizedPath.slice(0, normalizedPath.length - 1);\n }\n\n return normalizedPath;\n};\n\n// Only returns the files that existed previously and are not present anymore.\nexport const getStaleFiles = (\n newFiles: string[],\n oldFiles: string[],\n): string[] => {\n const staleFiles = new Set(oldFiles);\n const removedParentDirs = new Set();\n newFiles.forEach(newFile => {\n staleFiles.delete(newFile);\n\n // We have to traverse through the directory hierarchy of a new file and\n // ensure that we won't try to delete one of the parent directories.\n let parentDir = newFile.substring(0, newFile.lastIndexOf('/'));\n while (\n !removedParentDirs.has(parentDir) &&\n parentDir.length >= newFile.indexOf('/')\n ) {\n staleFiles.delete(parentDir);\n removedParentDirs.add(parentDir);\n parentDir = parentDir.substring(0, parentDir.lastIndexOf('/'));\n }\n });\n return Array.from(staleFiles);\n};\n\n// Compose actual filename on remote bucket including entity information\nexport const getCloudPathForLocalPath = (\n entity: Entity,\n localPath = '',\n useLegacyPathCasing = false,\n externalStorageRootPath = '',\n): string => {\n // Convert destination file path to a POSIX path for uploading.\n // GCS expects / as path separator and relativeFilePath will contain \\\\ on Windows.\n // https://cloud.google.com/storage/docs/gsutil/addlhelp/HowSubdirectoriesWork\n const relativeFilePathPosix = localPath.split(path.sep).join(path.posix.sep);\n\n // The / delimiter is intentional since it represents the cloud storage and not the local file system.\n const entityRootDir = `${entity.metadata?.namespace ?? DEFAULT_NAMESPACE}/${\n entity.kind\n }/${entity.metadata.name}`;\n\n const relativeFilePathTriplet = `${entityRootDir}/${relativeFilePathPosix}`;\n\n const destination = useLegacyPathCasing\n ? relativeFilePathTriplet\n : lowerCaseEntityTriplet(relativeFilePathTriplet);\n\n // Again, the / delimiter is intentional, as it represents remote storage.\n const destinationWithRoot = [\n // The extra filter prevents unintended double slashes and prefixes.\n ...externalStorageRootPath.split(path.posix.sep).filter(s => s !== ''),\n destination,\n ].join('/');\n\n return destinationWithRoot; // Remote storage file relative path\n};\n\n// Perform rate limited generic operations by passing a function and a list of arguments\nexport const bulkStorageOperation = async <T>(\n operation: (arg: T) => Promise<unknown>,\n args: T[],\n { concurrencyLimit } = { concurrencyLimit: 25 },\n) => {\n const limiter = createLimiter(concurrencyLimit);\n await Promise.all(args.map(arg => limiter(operation, arg)));\n};\n\n// Checks content path is the same as or a child path of bucketRoot, specifically for posix paths.\nexport const isValidContentPath = (\n bucketRoot: string,\n contentPath: string,\n): boolean => {\n const relativePath = path.posix.relative(bucketRoot, contentPath);\n if (relativePath === '') {\n // The same directory\n return true;\n }\n\n const outsideBase = relativePath.startsWith('..'); // not outside base\n const differentDrive = path.posix.isAbsolute(relativePath); // on Windows, this means dir is on a different drive from base.\n\n return !outsideBase && !differentDrive;\n};\n"],"names":["mime","recursiveReadDir","path","DEFAULT_NAMESPACE","createLimiter"],"mappings":"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"}
1
+ {"version":3,"file":"helpers.cjs.js","sources":["../../../src/stages/publish/helpers.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 */\nimport { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model';\nimport mime from 'mime-types';\nimport path from 'node:path';\nimport createLimiter from 'p-limit';\nimport recursiveReadDir from 'recursive-readdir';\n\n/**\n * Helper to get the expected content-type for a given file extension. Also\n * takes XSS mitigation into account.\n */\nconst getContentTypeForExtension = (ext: string): string => {\n const defaultContentType = 'text/plain; charset=utf-8';\n const excludedTypes = [\n 'text/html',\n 'text/xml',\n 'image/svg+xml',\n 'text/xsl',\n 'application/vnd.wap.xhtml+xml',\n 'multipart/x-mixed-replace',\n 'text/rdf',\n 'application/mathml+xml',\n 'application/octet-stream',\n 'application/rdf+xml',\n 'application/xhtml+xml',\n 'application/xml',\n 'text/cache-manifest',\n 'text/vtt',\n ];\n\n // Prevent sanitization bypass by preventing browsers from directly rendering\n // the contents of untrusted files.\n if (\n ext.match(\n /htm|xml|svg|appcache|manifest|mathml|owl|rdf|rng|vtt|xht|xsd|xsl/i,\n )\n ) {\n return defaultContentType;\n }\n\n // Check again to make sure that the content type is not in the excluded mime-type list\n // We use .lookup here to avoid the \"; charset=...\" addition\n const contentType = mime.lookup(ext);\n if (contentType && excludedTypes.includes(contentType)) {\n return defaultContentType;\n }\n\n return mime.contentType(ext) || defaultContentType;\n};\n\nexport type responseHeadersType = {\n 'Content-Type': string;\n};\n\n/**\n * Some files need special headers to be used correctly by the frontend. This function\n * generates headers in the response to those file requests.\n * @param fileExtension - .html, .css, .js, .png etc.\n */\nexport const getHeadersForFileExtension = (\n fileExtension: string,\n): responseHeadersType => {\n return {\n 'Content-Type': getContentTypeForExtension(fileExtension),\n };\n};\n\n/**\n * Recursively traverse all the sub-directories of a path and return\n * a list of absolute paths of all the files. e.g. tree command in Unix\n *\n * @example\n *\n * /User/username/my_dir\n * dirA\n * | subDirA\n * | | file1\n * EmptyDir\n * dirB\n * | file2\n * file3\n *\n * getFileListRecursively('/Users/username/myDir')\n * // returns\n * [\n * '/User/username/my_dir/dirA/subDirA/file1',\n * '/User/username/my_dir/dirB/file2',\n * '/User/username/my_dir/file3'\n * ]\n * @param rootDirPath - Absolute path to the root directory.\n */\nexport const getFileTreeRecursively = async (\n rootDirPath: string,\n): Promise<string[]> => {\n // Iterate on all the files in the directory and its sub-directories\n const fileList = await recursiveReadDir(rootDirPath).catch(error => {\n throw new Error(`Failed to read template directory: ${error.message}`);\n });\n return fileList;\n};\n\n/**\n * Takes a posix path and returns a lower-cased version of entity's triplet\n * with the remaining path in posix.\n *\n * Path must not include a starting slash.\n *\n * @example\n * lowerCaseEntityTriplet('default/Component/backstage')\n * // return default/component/backstage\n */\nexport const lowerCaseEntityTriplet = (posixPath: string): string => {\n const [namespace, kind, name, ...rest] = posixPath.split(path.posix.sep);\n const lowerNamespace = namespace.toLowerCase();\n const lowerKind = kind.toLowerCase();\n const lowerName = name.toLowerCase();\n return [lowerNamespace, lowerKind, lowerName, ...rest].join(path.posix.sep);\n};\n\n/**\n * Takes either a win32 or posix path and returns a lower-cased version of entity's triplet\n * with the remaining path in posix.\n *\n * Starting slashes will be trimmed.\n *\n * Throws an error if the path does not appear to be an entity triplet.\n *\n * @example\n * lowerCaseEntityTripletInStoragePath('/default/Component/backstage/file.txt')\n * // return default/component/backstage/file.txt\n */\nexport const lowerCaseEntityTripletInStoragePath = (\n originalPath: string,\n): string => {\n let posixPath = originalPath;\n if (originalPath.includes(path.win32.sep)) {\n posixPath = originalPath.split(path.win32.sep).join(path.posix.sep);\n }\n\n // remove leading slash\n const parts = posixPath.split(path.posix.sep);\n if (parts[0] === '') {\n parts.shift();\n }\n\n // check if all parts of the entity exist (name, namespace, kind) plus filename\n if (parts.length <= 3) {\n throw new Error(\n `Encountered file unmanaged by TechDocs ${originalPath}. Skipping.`,\n );\n }\n\n return lowerCaseEntityTriplet(parts.join(path.posix.sep));\n};\n\n/**\n * Take a posix path and return a path without leading and trailing\n * separators\n *\n * @example\n * normalizeExternalStorageRootPath('/backstage-data/techdocs/')\n * // return backstage-data/techdocs\n */\nexport const normalizeExternalStorageRootPath = (posixPath: string): string => {\n // remove leading slash\n let normalizedPath = posixPath;\n if (posixPath.startsWith(path.posix.sep)) {\n normalizedPath = posixPath.slice(1);\n }\n\n // remove trailing slash\n if (normalizedPath.endsWith(path.posix.sep)) {\n normalizedPath = normalizedPath.slice(0, normalizedPath.length - 1);\n }\n\n return normalizedPath;\n};\n\n// Only returns the files that existed previously and are not present anymore.\nexport const getStaleFiles = (\n newFiles: string[],\n oldFiles: string[],\n): string[] => {\n const staleFiles = new Set(oldFiles);\n const removedParentDirs = new Set();\n newFiles.forEach(newFile => {\n staleFiles.delete(newFile);\n\n // We have to traverse through the directory hierarchy of a new file and\n // ensure that we won't try to delete one of the parent directories.\n let parentDir = newFile.substring(0, newFile.lastIndexOf('/'));\n while (\n !removedParentDirs.has(parentDir) &&\n parentDir.length >= newFile.indexOf('/')\n ) {\n staleFiles.delete(parentDir);\n removedParentDirs.add(parentDir);\n parentDir = parentDir.substring(0, parentDir.lastIndexOf('/'));\n }\n });\n return Array.from(staleFiles);\n};\n\n// Compose actual filename on remote bucket including entity information\nexport const getCloudPathForLocalPath = (\n entity: Entity,\n localPath = '',\n useLegacyPathCasing = false,\n externalStorageRootPath = '',\n): string => {\n // Convert destination file path to a POSIX path for uploading.\n // GCS expects / as path separator and relativeFilePath will contain \\\\ on Windows.\n // https://cloud.google.com/storage/docs/gsutil/addlhelp/HowSubdirectoriesWork\n const relativeFilePathPosix = localPath.split(path.sep).join(path.posix.sep);\n\n // The / delimiter is intentional since it represents the cloud storage and not the local file system.\n const entityRootDir = `${entity.metadata?.namespace ?? DEFAULT_NAMESPACE}/${\n entity.kind\n }/${entity.metadata.name}`;\n\n const relativeFilePathTriplet = `${entityRootDir}/${relativeFilePathPosix}`;\n\n const destination = useLegacyPathCasing\n ? relativeFilePathTriplet\n : lowerCaseEntityTriplet(relativeFilePathTriplet);\n\n // Again, the / delimiter is intentional, as it represents remote storage.\n const destinationWithRoot = [\n // The extra filter prevents unintended double slashes and prefixes.\n ...externalStorageRootPath.split(path.posix.sep).filter(s => s !== ''),\n destination,\n ].join('/');\n\n return destinationWithRoot; // Remote storage file relative path\n};\n\n// Perform rate limited generic operations by passing a function and a list of arguments\nexport const bulkStorageOperation = async <T>(\n operation: (arg: T) => Promise<unknown>,\n args: T[],\n { concurrencyLimit } = { concurrencyLimit: 25 },\n) => {\n const limiter = createLimiter(concurrencyLimit);\n await Promise.all(args.map(arg => limiter(operation, arg)));\n};\n\n// Checks content path is the same as or a child path of bucketRoot, specifically for posix paths.\nexport const isValidContentPath = (\n bucketRoot: string,\n contentPath: string,\n): boolean => {\n const relativePath = path.posix.relative(bucketRoot, contentPath);\n if (relativePath === '') {\n // The same directory\n return true;\n }\n\n const outsideBase = relativePath.startsWith('..'); // not outside base\n const differentDrive = path.posix.isAbsolute(relativePath); // on Windows, this means dir is on a different drive from base.\n\n return !outsideBase && !differentDrive;\n};\n"],"names":["mime","recursiveReadDir","path","DEFAULT_NAMESPACE","createLimiter"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"local.cjs.js","sources":["../../../src/stages/publish/local.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 DiscoveryService,\n isChildPath,\n LoggerService,\n resolvePackagePath,\n resolveSafeChildPath,\n} from '@backstage/backend-plugin-api';\nimport {\n Entity,\n CompoundEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport express from 'express';\nimport fs from 'fs-extra';\nimport os from 'node:os';\nimport createLimiter from 'p-limit';\nimport path from 'node:path';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport {\n getFileTreeRecursively,\n getHeadersForFileExtension,\n lowerCaseEntityTripletInStoragePath,\n} from './helpers';\nimport { ForwardedError, NotAllowedError } from '@backstage/errors';\n\nasync function validateNoExternalSymlinks(\n dir: string,\n root: string,\n): Promise<void> {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isSymbolicLink()) {\n if (!isChildPath(root, fullPath)) {\n throw new NotAllowedError(\n `Symlink at ${fullPath} points outside the output directory`,\n );\n }\n }\n if (entry.isDirectory()) {\n await validateNoExternalSymlinks(fullPath, root);\n }\n }\n}\n\n/**\n * Local publisher which uses the local filesystem to store the generated static files. It uses by default a\n * directory called \"static\" at the root of techdocs-backend plugin unless a directory has been configured by\n * \"techdocs.publisher.local.publishDirectory\".\n */\nexport class LocalPublish implements PublisherBase {\n private readonly legacyPathCasing: boolean;\n private readonly logger: LoggerService;\n private readonly discovery: DiscoveryService;\n private readonly staticDocsDir: string;\n\n constructor(options: {\n logger: LoggerService;\n discovery: DiscoveryService;\n legacyPathCasing: boolean;\n staticDocsDir: string;\n }) {\n this.logger = options.logger;\n this.discovery = options.discovery;\n this.legacyPathCasing = options.legacyPathCasing;\n this.staticDocsDir = options.staticDocsDir;\n }\n\n static fromConfig(\n config: Config,\n logger: LoggerService,\n discovery: DiscoveryService,\n ): PublisherBase {\n const legacyPathCasing =\n config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n ) || false;\n\n let staticDocsDir = config.getOptionalString(\n 'techdocs.publisher.local.publishDirectory',\n );\n if (!staticDocsDir) {\n try {\n staticDocsDir = resolvePackagePath(\n '@backstage/plugin-techdocs-backend',\n 'static/docs',\n );\n } catch (err) {\n // This will most probably never be used.\n // The try/catch is introduced so that techdocs-cli can import @backstage/plugin-techdocs-node\n // on CI/CD without installing techdocs backend plugin.\n staticDocsDir = os.tmpdir();\n }\n }\n\n return new LocalPublish({\n logger,\n discovery,\n legacyPathCasing,\n staticDocsDir,\n });\n }\n\n async getReadiness(): Promise<ReadinessResponse> {\n return {\n isAvailable: true,\n };\n }\n\n async publish({\n entity,\n directory,\n }: PublishRequest): Promise<PublishResponse> {\n const entityNamespace = entity.metadata.namespace ?? 'default';\n let publishDir: string;\n\n try {\n publishDir = this.staticEntityPathJoin(\n entityNamespace,\n entity.kind,\n entity.metadata.name,\n );\n } catch (error) {\n throw new ForwardedError(\n `Unable to publish TechDocs site for entity: ${stringifyEntityRef(\n entity,\n )}`,\n error,\n );\n }\n\n if (!fs.existsSync(publishDir)) {\n this.logger.info(`Could not find ${publishDir}, creating the directory.`);\n fs.mkdirSync(publishDir, { recursive: true });\n }\n\n try {\n await validateNoExternalSymlinks(directory, directory);\n await fs.copy(directory, publishDir);\n this.logger.info(`Published site stored at ${publishDir}`);\n } catch (error) {\n this.logger.debug(\n `Failed to copy docs from ${directory} to ${publishDir}`,\n );\n throw error;\n }\n\n // Generate publish response.\n const techdocsApiUrl = await this.discovery.getBaseUrl('techdocs');\n const publishedFilePaths = (await getFileTreeRecursively(publishDir)).map(\n abs => {\n return abs.split(`${this.staticDocsDir}/`)[1];\n },\n );\n\n return {\n remoteUrl: `${techdocsApiUrl}/static/docs/${encodeURIComponent(\n entity.metadata.name,\n )}`,\n objects: publishedFilePaths,\n };\n }\n\n async fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n let metadataPath: string;\n\n try {\n metadataPath = this.staticEntityPathJoin(\n entityName.namespace,\n entityName.kind,\n entityName.name,\n 'techdocs_metadata.json',\n );\n } catch (err) {\n throw new ForwardedError(\n `Unexpected entity when fetching metadata: ${stringifyEntityRef(\n entityName,\n )}`,\n err,\n );\n }\n\n try {\n return await fs.readJson(metadataPath);\n } catch (err) {\n throw new ForwardedError(\n `Unable to read techdocs_metadata.json at ${metadataPath}. Error: ${err}`,\n err,\n );\n }\n }\n\n docsRouter(): express.Handler {\n const router = express.Router();\n\n // Redirect middleware ensuring that requests to case-sensitive entity\n // triplet paths are always sent to lower-case versions.\n router.use((req, res, next) => {\n // If legacy path casing is on, let the request immediately continue.\n if (this.legacyPathCasing) {\n return next();\n }\n\n // Generate a lower-case entity triplet path.\n const [_, namespace, kind, name, ...rest] = req.path.split('/');\n\n // Ignore non-triplet objects.\n if (!namespace || !kind || !name) {\n return next();\n }\n\n const newPath = [\n _,\n namespace.toLowerCase(),\n kind.toLowerCase(),\n name.toLowerCase(),\n ...rest,\n ].join('/');\n\n // If there was no change, then let express.static() handle the request.\n if (newPath === req.path) {\n return next();\n }\n\n // Otherwise, redirect to the new path.\n return res.redirect(301, req.baseUrl + newPath);\n });\n router.use(\n express.static(this.staticDocsDir, {\n // Handle content-type header the same as all other publishers.\n setHeaders: (res, filePath) => {\n const fileExtension = path.extname(filePath);\n const headers = getHeadersForFileExtension(fileExtension);\n for (const [header, value] of Object.entries(headers)) {\n res.setHeader(header, value);\n }\n },\n }),\n );\n\n return router;\n }\n\n async hasDocsBeenGenerated(entity: Entity): Promise<boolean> {\n const namespace = entity.metadata.namespace ?? 'default';\n\n // Check if the file exists\n try {\n const indexHtmlPath = this.staticEntityPathJoin(\n namespace,\n entity.kind,\n entity.metadata.name,\n 'index.html',\n );\n\n await fs.access(indexHtmlPath, fs.constants.F_OK);\n\n return true;\n } catch (err) {\n if (err.name === 'NotAllowedError') {\n this.logger.error(\n `Unexpected entity when checking if generated: ${stringifyEntityRef(\n entity,\n )}`,\n );\n }\n return false;\n }\n }\n\n /**\n * This code will never run in practice. It is merely here to illustrate how\n * to implement this method for other storage providers.\n */\n async migrateDocsCase({\n removeOriginal = false,\n concurrency = 25,\n }): Promise<void> {\n // Iterate through every file in the root of the publisher.\n const files = await getFileTreeRecursively(this.staticDocsDir);\n const limit = createLimiter(concurrency);\n\n await Promise.all(\n files.map(f =>\n limit(async file => {\n const relativeFile = file.replace(\n `${this.staticDocsDir}${path.sep}`,\n '',\n );\n const newFile = lowerCaseEntityTripletInStoragePath(relativeFile);\n\n // If all parts are already lowercase, ignore.\n if (relativeFile === newFile) {\n return;\n }\n\n // Otherwise, copy or move the file.\n await new Promise<void>(resolve => {\n const migrate = removeOriginal ? fs.move : fs.copyFile;\n this.logger.debug(`Migrating ${relativeFile}`);\n migrate(file, newFile, err => {\n if (err) {\n this.logger.warn(\n `Unable to migrate ${relativeFile}: ${err.message}`,\n );\n }\n resolve();\n });\n });\n }, f),\n ),\n );\n }\n\n /**\n * Utility wrapper around path.join(), used to control legacy case logic.\n */\n protected staticEntityPathJoin(...allParts: string[]): string {\n let staticEntityPath = this.staticDocsDir;\n\n allParts\n .map(part => part.split(path.sep))\n .flat()\n .forEach((part, index) => {\n // Respect legacy path casing when operating on namespace, kind, or name.\n if (index < 3) {\n staticEntityPath = resolveSafeChildPath(\n staticEntityPath,\n this.legacyPathCasing ? part : part.toLowerCase(),\n );\n return;\n }\n\n // Otherwise, respect the provided case.\n staticEntityPath = resolveSafeChildPath(staticEntityPath, part);\n });\n\n return staticEntityPath;\n }\n}\n"],"names":["fs","path","isChildPath","NotAllowedError","resolvePackagePath","os","ForwardedError","stringifyEntityRef","getFileTreeRecursively","express","getHeadersForFileExtension","createLimiter","lowerCaseEntityTripletInStoragePath","resolveSafeChildPath"],"mappings":"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"}
1
+ {"version":3,"file":"local.cjs.js","sources":["../../../src/stages/publish/local.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 DiscoveryService,\n isChildPath,\n LoggerService,\n resolvePackagePath,\n resolveSafeChildPath,\n} from '@backstage/backend-plugin-api';\nimport {\n Entity,\n CompoundEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport express from 'express';\nimport fs from 'fs-extra';\nimport os from 'node:os';\nimport createLimiter from 'p-limit';\nimport path from 'node:path';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport {\n getFileTreeRecursively,\n getHeadersForFileExtension,\n lowerCaseEntityTripletInStoragePath,\n} from './helpers';\nimport { ForwardedError, NotAllowedError } from '@backstage/errors';\n\nasync function validateNoExternalSymlinks(\n dir: string,\n root: string,\n): Promise<void> {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isSymbolicLink()) {\n if (!isChildPath(root, fullPath)) {\n throw new NotAllowedError(\n `Symlink at ${fullPath} points outside the output directory`,\n );\n }\n }\n if (entry.isDirectory()) {\n await validateNoExternalSymlinks(fullPath, root);\n }\n }\n}\n\n/**\n * Local publisher which uses the local filesystem to store the generated static files. It uses by default a\n * directory called \"static\" at the root of techdocs-backend plugin unless a directory has been configured by\n * \"techdocs.publisher.local.publishDirectory\".\n */\nexport class LocalPublish implements PublisherBase {\n private readonly legacyPathCasing: boolean;\n private readonly logger: LoggerService;\n private readonly discovery: DiscoveryService;\n private readonly staticDocsDir: string;\n\n constructor(options: {\n logger: LoggerService;\n discovery: DiscoveryService;\n legacyPathCasing: boolean;\n staticDocsDir: string;\n }) {\n this.logger = options.logger;\n this.discovery = options.discovery;\n this.legacyPathCasing = options.legacyPathCasing;\n this.staticDocsDir = options.staticDocsDir;\n }\n\n static fromConfig(\n config: Config,\n logger: LoggerService,\n discovery: DiscoveryService,\n ): PublisherBase {\n const legacyPathCasing =\n config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n ) || false;\n\n let staticDocsDir = config.getOptionalString(\n 'techdocs.publisher.local.publishDirectory',\n );\n if (!staticDocsDir) {\n try {\n staticDocsDir = resolvePackagePath(\n '@backstage/plugin-techdocs-backend',\n 'static/docs',\n );\n } catch (err) {\n // This will most probably never be used.\n // The try/catch is introduced so that techdocs-cli can import @backstage/plugin-techdocs-node\n // on CI/CD without installing techdocs backend plugin.\n staticDocsDir = os.tmpdir();\n }\n }\n\n return new LocalPublish({\n logger,\n discovery,\n legacyPathCasing,\n staticDocsDir,\n });\n }\n\n async getReadiness(): Promise<ReadinessResponse> {\n return {\n isAvailable: true,\n };\n }\n\n async publish({\n entity,\n directory,\n }: PublishRequest): Promise<PublishResponse> {\n const entityNamespace = entity.metadata.namespace ?? 'default';\n let publishDir: string;\n\n try {\n publishDir = this.staticEntityPathJoin(\n entityNamespace,\n entity.kind,\n entity.metadata.name,\n );\n } catch (error) {\n throw new ForwardedError(\n `Unable to publish TechDocs site for entity: ${stringifyEntityRef(\n entity,\n )}`,\n error,\n );\n }\n\n if (!fs.existsSync(publishDir)) {\n this.logger.info(`Could not find ${publishDir}, creating the directory.`);\n fs.mkdirSync(publishDir, { recursive: true });\n }\n\n try {\n await validateNoExternalSymlinks(directory, directory);\n await fs.copy(directory, publishDir);\n this.logger.info(`Published site stored at ${publishDir}`);\n } catch (error) {\n this.logger.debug(\n `Failed to copy docs from ${directory} to ${publishDir}`,\n );\n throw error;\n }\n\n // Generate publish response.\n const techdocsApiUrl = await this.discovery.getBaseUrl('techdocs');\n const publishedFilePaths = (await getFileTreeRecursively(publishDir)).map(\n abs => {\n return abs.split(`${this.staticDocsDir}/`)[1];\n },\n );\n\n return {\n remoteUrl: `${techdocsApiUrl}/static/docs/${encodeURIComponent(\n entity.metadata.name,\n )}`,\n objects: publishedFilePaths,\n };\n }\n\n async fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n let metadataPath: string;\n\n try {\n metadataPath = this.staticEntityPathJoin(\n entityName.namespace,\n entityName.kind,\n entityName.name,\n 'techdocs_metadata.json',\n );\n } catch (err) {\n throw new ForwardedError(\n `Unexpected entity when fetching metadata: ${stringifyEntityRef(\n entityName,\n )}`,\n err,\n );\n }\n\n try {\n return await fs.readJson(metadataPath);\n } catch (err) {\n throw new ForwardedError(\n `Unable to read techdocs_metadata.json at ${metadataPath}. Error: ${err}`,\n err,\n );\n }\n }\n\n docsRouter(): express.Handler {\n const router = express.Router();\n\n // Redirect middleware ensuring that requests to case-sensitive entity\n // triplet paths are always sent to lower-case versions.\n router.use((req, res, next) => {\n // If legacy path casing is on, let the request immediately continue.\n if (this.legacyPathCasing) {\n return next();\n }\n\n // Generate a lower-case entity triplet path.\n const [_, namespace, kind, name, ...rest] = req.path.split('/');\n\n // Ignore non-triplet objects.\n if (!namespace || !kind || !name) {\n return next();\n }\n\n const newPath = [\n _,\n namespace.toLowerCase(),\n kind.toLowerCase(),\n name.toLowerCase(),\n ...rest,\n ].join('/');\n\n // If there was no change, then let express.static() handle the request.\n if (newPath === req.path) {\n return next();\n }\n\n // Otherwise, redirect to the new path.\n return res.redirect(301, req.baseUrl + newPath);\n });\n router.use(\n express.static(this.staticDocsDir, {\n // Handle content-type header the same as all other publishers.\n setHeaders: (res, filePath) => {\n const fileExtension = path.extname(filePath);\n const headers = getHeadersForFileExtension(fileExtension);\n for (const [header, value] of Object.entries(headers)) {\n res.setHeader(header, value);\n }\n },\n }),\n );\n\n return router;\n }\n\n async hasDocsBeenGenerated(entity: Entity): Promise<boolean> {\n const namespace = entity.metadata.namespace ?? 'default';\n\n // Check if the file exists\n try {\n const indexHtmlPath = this.staticEntityPathJoin(\n namespace,\n entity.kind,\n entity.metadata.name,\n 'index.html',\n );\n\n await fs.access(indexHtmlPath, fs.constants.F_OK);\n\n return true;\n } catch (err) {\n if (err.name === 'NotAllowedError') {\n this.logger.error(\n `Unexpected entity when checking if generated: ${stringifyEntityRef(\n entity,\n )}`,\n );\n }\n return false;\n }\n }\n\n /**\n * This code will never run in practice. It is merely here to illustrate how\n * to implement this method for other storage providers.\n */\n async migrateDocsCase({\n removeOriginal = false,\n concurrency = 25,\n }): Promise<void> {\n // Iterate through every file in the root of the publisher.\n const files = await getFileTreeRecursively(this.staticDocsDir);\n const limit = createLimiter(concurrency);\n\n await Promise.all(\n files.map(f =>\n limit(async file => {\n const relativeFile = file.replace(\n `${this.staticDocsDir}${path.sep}`,\n '',\n );\n const newFile = lowerCaseEntityTripletInStoragePath(relativeFile);\n\n // If all parts are already lowercase, ignore.\n if (relativeFile === newFile) {\n return;\n }\n\n // Otherwise, copy or move the file.\n await new Promise<void>(resolve => {\n const migrate = removeOriginal ? fs.move : fs.copyFile;\n this.logger.debug(`Migrating ${relativeFile}`);\n migrate(file, newFile, err => {\n if (err) {\n this.logger.warn(\n `Unable to migrate ${relativeFile}: ${err.message}`,\n );\n }\n resolve();\n });\n });\n }, f),\n ),\n );\n }\n\n /**\n * Utility wrapper around path.join(), used to control legacy case logic.\n */\n protected staticEntityPathJoin(...allParts: string[]): string {\n let staticEntityPath = this.staticDocsDir;\n\n allParts\n .map(part => part.split(path.sep))\n .flat()\n .forEach((part, index) => {\n // Respect legacy path casing when operating on namespace, kind, or name.\n if (index < 3) {\n staticEntityPath = resolveSafeChildPath(\n staticEntityPath,\n this.legacyPathCasing ? part : part.toLowerCase(),\n );\n return;\n }\n\n // Otherwise, respect the provided case.\n staticEntityPath = resolveSafeChildPath(staticEntityPath, part);\n });\n\n return staticEntityPath;\n }\n}\n"],"names":["fs","path","isChildPath","NotAllowedError","resolvePackagePath","os","ForwardedError","stringifyEntityRef","getFileTreeRecursively","express","getHeadersForFileExtension","createLimiter","lowerCaseEntityTripletInStoragePath","resolveSafeChildPath"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"GoogleMigration.cjs.js","sources":["../../../../src/stages/publish/migrations/GoogleMigration.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 { toError } from '@backstage/errors';\nimport { File } from '@google-cloud/storage';\nimport { Writable } from 'node:stream';\nimport { lowerCaseEntityTripletInStoragePath } from '../helpers';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\n/**\n * Writable stream to handle object copy/move operations. This implementation\n * ensures we don't read in files from GCS faster than GCS can copy/move them.\n */\nexport class MigrateWriteStream extends Writable {\n protected logger: LoggerService;\n protected removeOriginal: boolean;\n protected maxConcurrency: number;\n protected inFlight = 0;\n\n constructor(\n logger: LoggerService,\n removeOriginal: boolean,\n concurrency: number,\n ) {\n super({ objectMode: true });\n this.logger = logger;\n this.removeOriginal = removeOriginal;\n this.maxConcurrency = concurrency;\n }\n\n _write(file: File, _encoding: BufferEncoding, next: Function) {\n let shouldCallNext = true;\n let newFile;\n try {\n newFile = lowerCaseEntityTripletInStoragePath(file.name);\n } catch (e) {\n this.logger.warn(toError(e).message);\n next();\n return;\n }\n\n // If all parts are already lowercase, ignore.\n if (newFile === file.name) {\n next();\n return;\n }\n\n // Allow up to n-many files to be migrated at a time.\n this.inFlight++;\n if (this.inFlight < this.maxConcurrency) {\n next();\n shouldCallNext = false;\n }\n\n // Otherwise, copy or move the file.\n const migrate = this.removeOriginal\n ? file.move.bind(file)\n : file.copy.bind(file);\n this.logger.debug(`Migrating ${file.name}`);\n migrate(newFile)\n .catch(e =>\n this.logger.warn(`Unable to migrate ${file.name}: ${e.message}`),\n )\n .finally(() => {\n this.inFlight--;\n if (shouldCallNext) {\n next();\n }\n });\n }\n}\n"],"names":["Writable","lowerCaseEntityTripletInStoragePath","toError"],"mappings":";;;;;;AA0BO,MAAM,2BAA2BA,oBAAA,CAAS;AAAA,EACrC,MAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,QAAA,GAAW,CAAA;AAAA,EAErB,WAAA,CACE,MAAA,EACA,cAAA,EACA,WAAA,EACA;AACA,IAAA,KAAA,CAAM,EAAE,UAAA,EAAY,IAAA,EAAM,CAAA;AAC1B,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,cAAA,GAAiB,cAAA;AACtB,IAAA,IAAA,CAAK,cAAA,GAAiB,WAAA;AAAA,EACxB;AAAA,EAEA,MAAA,CAAO,IAAA,EAAY,SAAA,EAA2B,IAAA,EAAgB;AAC5D,IAAA,IAAI,cAAA,GAAiB,IAAA;AACrB,IAAA,IAAI,OAAA;AACJ,IAAA,IAAI;AACF,MAAA,OAAA,GAAUC,2CAAA,CAAoC,KAAK,IAAI,CAAA;AAAA,IACzD,SAAS,CAAA,EAAG;AACV,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA,CAAKC,cAAA,CAAQ,CAAC,EAAE,OAAO,CAAA;AACnC,MAAA,IAAA,EAAK;AACL,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,OAAA,KAAY,KAAK,IAAA,EAAM;AACzB,MAAA,IAAA,EAAK;AACL,MAAA;AAAA,IACF;AAGA,IAAA,IAAA,CAAK,QAAA,EAAA;AACL,IAAA,IAAI,IAAA,CAAK,QAAA,GAAW,IAAA,CAAK,cAAA,EAAgB;AACvC,MAAA,IAAA,EAAK;AACL,MAAA,cAAA,GAAiB,KAAA;AAAA,IACnB;AAGA,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,cAAA,GACjB,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,GACnB,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA;AACvB,IAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAA,UAAA,EAAa,IAAA,CAAK,IAAI,CAAA,CAAE,CAAA;AAC1C,IAAA,OAAA,CAAQ,OAAO,CAAA,CACZ,KAAA;AAAA,MAAM,CAAA,CAAA,KACL,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,CAAA,kBAAA,EAAqB,KAAK,IAAI,CAAA,EAAA,EAAK,CAAA,CAAE,OAAO,CAAA,CAAE;AAAA,KACjE,CACC,QAAQ,MAAM;AACb,MAAA,IAAA,CAAK,QAAA,EAAA;AACL,MAAA,IAAI,cAAA,EAAgB;AAClB,QAAA,IAAA,EAAK;AAAA,MACP;AAAA,IACF,CAAC,CAAA;AAAA,EACL;AACF;;;;"}
1
+ {"version":3,"file":"GoogleMigration.cjs.js","sources":["../../../../src/stages/publish/migrations/GoogleMigration.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 { toError } from '@backstage/errors';\nimport { File } from '@google-cloud/storage';\nimport { Writable } from 'node:stream';\nimport { lowerCaseEntityTripletInStoragePath } from '../helpers';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\n/**\n * Writable stream to handle object copy/move operations. This implementation\n * ensures we don't read in files from GCS faster than GCS can copy/move them.\n */\nexport class MigrateWriteStream extends Writable {\n protected logger: LoggerService;\n protected removeOriginal: boolean;\n protected maxConcurrency: number;\n protected inFlight = 0;\n\n constructor(\n logger: LoggerService,\n removeOriginal: boolean,\n concurrency: number,\n ) {\n super({ objectMode: true });\n this.logger = logger;\n this.removeOriginal = removeOriginal;\n this.maxConcurrency = concurrency;\n }\n\n _write(file: File, _encoding: BufferEncoding, next: Function) {\n let shouldCallNext = true;\n let newFile;\n try {\n newFile = lowerCaseEntityTripletInStoragePath(file.name);\n } catch (e) {\n this.logger.warn(toError(e).message);\n next();\n return;\n }\n\n // If all parts are already lowercase, ignore.\n if (newFile === file.name) {\n next();\n return;\n }\n\n // Allow up to n-many files to be migrated at a time.\n this.inFlight++;\n if (this.inFlight < this.maxConcurrency) {\n next();\n shouldCallNext = false;\n }\n\n // Otherwise, copy or move the file.\n const migrate = this.removeOriginal\n ? file.move.bind(file)\n : file.copy.bind(file);\n this.logger.debug(`Migrating ${file.name}`);\n migrate(newFile)\n .catch(e =>\n this.logger.warn(`Unable to migrate ${file.name}: ${e.message}`),\n )\n .finally(() => {\n this.inFlight--;\n if (shouldCallNext) {\n next();\n }\n });\n }\n}\n"],"names":["Writable","lowerCaseEntityTripletInStoragePath","toError"],"mappings":";;;;;;AA0BO,MAAM,2BAA2BA,oBAAA,CAAS;AAAA,EACrC,MAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,QAAA,GAAW,CAAA;AAAA,EAErB,WAAA,CACE,MAAA,EACA,cAAA,EACA,WAAA,EACA;AACA,IAAA,KAAA,CAAM,EAAE,UAAA,EAAY,IAAA,EAAM,CAAA;AAC1B,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,cAAA,GAAiB,cAAA;AACtB,IAAA,IAAA,CAAK,cAAA,GAAiB,WAAA;AAAA,EACxB;AAAA,EAEA,MAAA,CAAO,IAAA,EAAY,SAAA,EAA2B,IAAA,EAAgB;AAC5D,IAAA,IAAI,cAAA,GAAiB,IAAA;AACrB,IAAA,IAAI,OAAA;AACJ,IAAA,IAAI;AACF,MAAA,OAAA,GAAUC,2CAAA,CAAoC,KAAK,IAAI,CAAA;AAAA,IACzD,SAAS,CAAA,EAAG;AACV,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA,CAAKC,cAAA,CAAQ,CAAC,EAAE,OAAO,CAAA;AACnC,MAAA,IAAA,EAAK;AACL,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,OAAA,KAAY,KAAK,IAAA,EAAM;AACzB,MAAA,IAAA,EAAK;AACL,MAAA;AAAA,IACF;AAGA,IAAA,IAAA,CAAK,QAAA,EAAA;AACL,IAAA,IAAI,IAAA,CAAK,QAAA,GAAW,IAAA,CAAK,cAAA,EAAgB;AACvC,MAAA,IAAA,EAAK;AACL,MAAA,cAAA,GAAiB,KAAA;AAAA,IACnB;AAGA,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,cAAA,GACjB,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,GACnB,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA;AACvB,IAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAA,UAAA,EAAa,IAAA,CAAK,IAAI,CAAA,CAAE,CAAA;AAC1C,IAAA,OAAA,CAAQ,OAAO,CAAA,CACZ,KAAA;AAAA,MAAM,CAAA,CAAA,KACL,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,CAAA,kBAAA,EAAqB,KAAK,IAAI,CAAA,EAAA,EAAK,CAAA,CAAE,OAAO,CAAA,CAAE;AAAA,KACjE,CACC,QAAQ,MAAM;AACb,MAAA,IAAA,CAAK,QAAA,EAAA;AACL,MAAA,IAAI,cAAA,EAAgB;AAClB,QAAA,IAAA,EAAK;AAAA,MACP;AAAA,IACF,CAAC,CAAA;AAAA,EACL;AACF;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"openStackSwift.cjs.js","sources":["../../../src/stages/publish/openStackSwift.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 */\nimport { Entity, CompoundEntityRef } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport express from 'express';\nimport fs from 'fs-extra';\nimport JSON5 from 'json5';\nimport createLimiter from 'p-limit';\nimport path from 'node:path';\nimport { SwiftClient } from '@trendyol-js/openstack-swift-sdk';\nimport { NotFound } from '@trendyol-js/openstack-swift-sdk/lib/types';\nimport { Stream, Readable } from 'node:stream';\n\nimport {\n getFileTreeRecursively,\n getHeadersForFileExtension,\n lowerCaseEntityTripletInStoragePath,\n} from './helpers';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport { ForwardedError, toError } from '@backstage/errors';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nconst streamToBuffer = (stream: Stream | Readable): Promise<Buffer> => {\n return new Promise((resolve, reject) => {\n try {\n const chunks: any[] = [];\n stream.on('data', chunk => chunks.push(chunk));\n stream.on('error', reject);\n stream.on('end', () => resolve(Buffer.concat(chunks)));\n } catch (e) {\n throw new ForwardedError('Unable to parse the response data', e);\n }\n });\n};\n\nconst bufferToStream = (buffer: Buffer): Readable => {\n const stream = new Readable();\n stream.push(buffer);\n stream.push(null);\n return stream;\n};\n\nexport class OpenStackSwiftPublish implements PublisherBase {\n private readonly storageClient: SwiftClient;\n private readonly containerName: string;\n private readonly logger: LoggerService;\n\n constructor(options: {\n storageClient: SwiftClient;\n containerName: string;\n logger: LoggerService;\n }) {\n this.storageClient = options.storageClient;\n this.containerName = options.containerName;\n this.logger = options.logger;\n }\n\n static fromConfig(config: Config, logger: LoggerService): PublisherBase {\n let containerName = '';\n try {\n containerName = config.getString(\n 'techdocs.publisher.openStackSwift.containerName',\n );\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'openStackSwift' in your app config, \" +\n 'techdocs.publisher.openStackSwift.containerName is required.',\n );\n }\n\n const openStackSwiftConfig = config.getConfig(\n 'techdocs.publisher.openStackSwift',\n );\n\n const storageClient = new SwiftClient({\n authEndpoint: openStackSwiftConfig.getString('authUrl'),\n swiftEndpoint: openStackSwiftConfig.getString('swiftUrl'),\n credentialId: openStackSwiftConfig.getString('credentials.id'),\n secret: openStackSwiftConfig.getString('credentials.secret'),\n });\n\n return new OpenStackSwiftPublish({ storageClient, containerName, logger });\n }\n\n /*\n * Check if the defined container exists. Being able to connect means the configuration is good\n * and the storage client will work.\n */\n async getReadiness(): Promise<ReadinessResponse> {\n try {\n const container = await this.storageClient.getContainerMetadata(\n this.containerName,\n );\n\n if (!(container instanceof NotFound)) {\n this.logger.info(\n `Successfully connected to the OpenStack Swift container ${this.containerName}.`,\n );\n return {\n isAvailable: true,\n };\n }\n this.logger.error(\n `Could not retrieve metadata about the OpenStack Swift container ${this.containerName}. ` +\n 'Make sure the container exists. Also make sure that authentication is setup either by ' +\n 'explicitly defining credentials and region in techdocs.publisher.openStackSwift in app config or ' +\n 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',\n );\n return {\n isAvailable: false,\n };\n } catch (err) {\n this.logger.error(\n `from OpenStack client library: ${toError(err).message}`,\n );\n return {\n isAvailable: false,\n };\n }\n }\n\n /**\n * Upload all the files from the generated `directory` to the OpenStack Swift container.\n * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html\n */\n async publish({\n entity,\n directory,\n }: PublishRequest): Promise<PublishResponse> {\n try {\n const objects: string[] = [];\n\n // Note: OpenStack Swift manages creation of parent directories if they do not exist.\n // So collecting path of only the files is good enough.\n const allFilesToUpload = await getFileTreeRecursively(directory);\n const limiter = createLimiter(10);\n const uploadPromises: Array<Promise<unknown>> = [];\n for (const filePath of allFilesToUpload) {\n // Remove the absolute path prefix of the source directory\n // Path of all files to upload, relative to the root of the source directory\n // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png']\n const relativeFilePath = path.relative(directory, filePath);\n // Convert destination file path to a POSIX path for uploading.\n // Swift expects / as path separator and relativeFilePath will contain \\\\ on Windows.\n // https://docs.openstack.org/python-openstackclient/pike/cli/man/openstack.html\n const relativeFilePathPosix = relativeFilePath\n .split(path.sep)\n .join(path.posix.sep);\n\n // The / delimiter is intentional since it represents the cloud storage and not the local file system.\n const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;\n const destination = `${entityRootDir}/${relativeFilePathPosix}`; // Swift container file relative path\n objects.push(destination);\n\n // Rate limit the concurrent execution of file uploads to batches of 10 (per publish)\n const uploadFile = limiter(async () => {\n const fileBuffer = await fs.readFile(filePath);\n const stream = bufferToStream(fileBuffer);\n return this.storageClient.upload(\n this.containerName,\n destination,\n stream,\n );\n });\n uploadPromises.push(uploadFile);\n }\n await Promise.all(uploadPromises);\n this.logger.info(\n `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${allFilesToUpload.length}`,\n );\n return { objects };\n } catch (e) {\n const errorMessage = `Unable to upload file(s) to OpenStack Swift. ${e}`;\n this.logger.error(errorMessage);\n throw new Error(errorMessage);\n }\n }\n\n async fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n return await new Promise<TechDocsMetadata>(async (resolve, reject) => {\n const entityRootDir = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;\n\n const downloadResponse = await this.storageClient.download(\n this.containerName,\n `${entityRootDir}/techdocs_metadata.json`,\n );\n\n if (!(downloadResponse instanceof NotFound)) {\n const stream = downloadResponse.data;\n try {\n const techdocsMetadataJson = await streamToBuffer(stream);\n if (!techdocsMetadataJson) {\n throw new Error(\n `Unable to parse the techdocs metadata file ${entityRootDir}/techdocs_metadata.json.`,\n );\n }\n\n const techdocsMetadata = JSON5.parse(\n techdocsMetadataJson.toString('utf-8'),\n );\n\n resolve(techdocsMetadata);\n } catch (err) {\n const error = toError(err);\n this.logger.error(error.message);\n reject(error);\n }\n } else {\n reject({\n message: `TechDocs metadata fetch failed, The file /rootDir/${entityRootDir}/techdocs_metadata.json does not exist !`,\n });\n }\n });\n }\n\n /**\n * Express route middleware to serve static files on a route in techdocs-backend.\n */\n docsRouter(): express.Handler {\n return async (req, res) => {\n // Decode and trim the leading forward slash\n // filePath example - /default/Component/documented-component/index.html\n const filePath = decodeURI(req.path.replace(/^\\//, ''));\n\n // Files with different extensions (CSS, HTML) need to be served with different headers\n const fileExtension = path.extname(filePath);\n const responseHeaders = getHeadersForFileExtension(fileExtension);\n\n const downloadResponse = await this.storageClient.download(\n this.containerName,\n filePath,\n );\n\n if (!(downloadResponse instanceof NotFound)) {\n const stream = downloadResponse.data;\n\n try {\n // Inject response headers\n for (const [headerKey, headerValue] of Object.entries(\n responseHeaders,\n )) {\n res.setHeader(headerKey, headerValue);\n }\n\n res.send(await streamToBuffer(stream));\n } catch (err) {\n this.logger.warn(\n `TechDocs OpenStack swift router failed to serve content from container ${\n this.containerName\n } at path ${filePath}: ${toError(err).message}`,\n );\n res.status(404).send('File Not Found');\n }\n } else {\n this.logger.warn(\n `TechDocs OpenStack swift router failed to serve content from container ${this.containerName} at path ${filePath}: Not found`,\n );\n res.status(404).send('File Not Found');\n }\n };\n }\n\n /**\n * A helper function which checks if index.html of an Entity's docs site is available. This\n * can be used to verify if there are any pre-generated docs available to serve.\n */\n async hasDocsBeenGenerated(entity: Entity): Promise<boolean> {\n const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;\n try {\n const fileResponse = await this.storageClient.getMetadata(\n this.containerName,\n `${entityRootDir}/index.html`,\n );\n\n if (!(fileResponse instanceof NotFound)) {\n return true;\n }\n return false;\n } catch (err) {\n this.logger.warn(toError(err).message);\n return false;\n }\n }\n\n async migrateDocsCase({\n removeOriginal = false,\n concurrency = 25,\n }): Promise<void> {\n // Iterate through every file in the root of the publisher.\n const allObjects = await this.getAllObjectsFromContainer();\n const limiter = createLimiter(concurrency);\n await Promise.all(\n allObjects.map(f =>\n limiter(async file => {\n let newPath;\n try {\n newPath = lowerCaseEntityTripletInStoragePath(file);\n } catch (e) {\n this.logger.warn(toError(e).message);\n return;\n }\n\n // If all parts are already lowercase, ignore.\n if (file === newPath) {\n return;\n }\n\n try {\n this.logger.debug(`Migrating ${file} to ${newPath}`);\n await this.storageClient.copy(\n this.containerName,\n file,\n this.containerName,\n newPath,\n );\n if (removeOriginal) {\n await this.storageClient.delete(this.containerName, file);\n }\n } catch (e) {\n this.logger.warn(\n `Unable to migrate ${file}: ${toError(e).message}`,\n );\n }\n }, f),\n ),\n );\n }\n\n /**\n * Returns a list of all object keys from the configured container.\n */\n protected async getAllObjectsFromContainer(\n { prefix } = { prefix: '' },\n ): Promise<string[]> {\n let objects: string[] = [];\n const OSS_MAX_LIMIT = Math.pow(2, 31) - 1;\n\n const allObjects = await this.storageClient.list(\n this.containerName,\n prefix,\n OSS_MAX_LIMIT,\n );\n objects = allObjects.map((object: any) => object.name);\n\n return objects;\n }\n}\n"],"names":["ForwardedError","Readable","SwiftClient","NotFound","toError","getFileTreeRecursively","createLimiter","path","fs","JSON5","getHeadersForFileExtension","lowerCaseEntityTripletInStoragePath"],"mappings":"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"}
1
+ {"version":3,"file":"openStackSwift.cjs.js","sources":["../../../src/stages/publish/openStackSwift.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 */\nimport { Entity, CompoundEntityRef } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport express from 'express';\nimport fs from 'fs-extra';\nimport JSON5 from 'json5';\nimport createLimiter from 'p-limit';\nimport path from 'node:path';\nimport { SwiftClient } from '@trendyol-js/openstack-swift-sdk';\nimport { NotFound } from '@trendyol-js/openstack-swift-sdk/lib/types';\nimport { Stream, Readable } from 'node:stream';\n\nimport {\n getFileTreeRecursively,\n getHeadersForFileExtension,\n lowerCaseEntityTripletInStoragePath,\n} from './helpers';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport { ForwardedError, toError } from '@backstage/errors';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nconst streamToBuffer = (stream: Stream | Readable): Promise<Buffer> => {\n return new Promise((resolve, reject) => {\n try {\n const chunks: any[] = [];\n stream.on('data', chunk => chunks.push(chunk));\n stream.on('error', reject);\n stream.on('end', () => resolve(Buffer.concat(chunks)));\n } catch (e) {\n throw new ForwardedError('Unable to parse the response data', e);\n }\n });\n};\n\nconst bufferToStream = (buffer: Buffer): Readable => {\n const stream = new Readable();\n stream.push(buffer);\n stream.push(null);\n return stream;\n};\n\nexport class OpenStackSwiftPublish implements PublisherBase {\n private readonly storageClient: SwiftClient;\n private readonly containerName: string;\n private readonly logger: LoggerService;\n\n constructor(options: {\n storageClient: SwiftClient;\n containerName: string;\n logger: LoggerService;\n }) {\n this.storageClient = options.storageClient;\n this.containerName = options.containerName;\n this.logger = options.logger;\n }\n\n static fromConfig(config: Config, logger: LoggerService): PublisherBase {\n let containerName = '';\n try {\n containerName = config.getString(\n 'techdocs.publisher.openStackSwift.containerName',\n );\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'openStackSwift' in your app config, \" +\n 'techdocs.publisher.openStackSwift.containerName is required.',\n );\n }\n\n const openStackSwiftConfig = config.getConfig(\n 'techdocs.publisher.openStackSwift',\n );\n\n const storageClient = new SwiftClient({\n authEndpoint: openStackSwiftConfig.getString('authUrl'),\n swiftEndpoint: openStackSwiftConfig.getString('swiftUrl'),\n credentialId: openStackSwiftConfig.getString('credentials.id'),\n secret: openStackSwiftConfig.getString('credentials.secret'),\n });\n\n return new OpenStackSwiftPublish({ storageClient, containerName, logger });\n }\n\n /*\n * Check if the defined container exists. Being able to connect means the configuration is good\n * and the storage client will work.\n */\n async getReadiness(): Promise<ReadinessResponse> {\n try {\n const container = await this.storageClient.getContainerMetadata(\n this.containerName,\n );\n\n if (!(container instanceof NotFound)) {\n this.logger.info(\n `Successfully connected to the OpenStack Swift container ${this.containerName}.`,\n );\n return {\n isAvailable: true,\n };\n }\n this.logger.error(\n `Could not retrieve metadata about the OpenStack Swift container ${this.containerName}. ` +\n 'Make sure the container exists. Also make sure that authentication is setup either by ' +\n 'explicitly defining credentials and region in techdocs.publisher.openStackSwift in app config or ' +\n 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',\n );\n return {\n isAvailable: false,\n };\n } catch (err) {\n this.logger.error(\n `from OpenStack client library: ${toError(err).message}`,\n );\n return {\n isAvailable: false,\n };\n }\n }\n\n /**\n * Upload all the files from the generated `directory` to the OpenStack Swift container.\n * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html\n */\n async publish({\n entity,\n directory,\n }: PublishRequest): Promise<PublishResponse> {\n try {\n const objects: string[] = [];\n\n // Note: OpenStack Swift manages creation of parent directories if they do not exist.\n // So collecting path of only the files is good enough.\n const allFilesToUpload = await getFileTreeRecursively(directory);\n const limiter = createLimiter(10);\n const uploadPromises: Array<Promise<unknown>> = [];\n for (const filePath of allFilesToUpload) {\n // Remove the absolute path prefix of the source directory\n // Path of all files to upload, relative to the root of the source directory\n // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png']\n const relativeFilePath = path.relative(directory, filePath);\n // Convert destination file path to a POSIX path for uploading.\n // Swift expects / as path separator and relativeFilePath will contain \\\\ on Windows.\n // https://docs.openstack.org/python-openstackclient/pike/cli/man/openstack.html\n const relativeFilePathPosix = relativeFilePath\n .split(path.sep)\n .join(path.posix.sep);\n\n // The / delimiter is intentional since it represents the cloud storage and not the local file system.\n const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;\n const destination = `${entityRootDir}/${relativeFilePathPosix}`; // Swift container file relative path\n objects.push(destination);\n\n // Rate limit the concurrent execution of file uploads to batches of 10 (per publish)\n const uploadFile = limiter(async () => {\n const fileBuffer = await fs.readFile(filePath);\n const stream = bufferToStream(fileBuffer);\n return this.storageClient.upload(\n this.containerName,\n destination,\n stream,\n );\n });\n uploadPromises.push(uploadFile);\n }\n await Promise.all(uploadPromises);\n this.logger.info(\n `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${allFilesToUpload.length}`,\n );\n return { objects };\n } catch (e) {\n const errorMessage = `Unable to upload file(s) to OpenStack Swift. ${e}`;\n this.logger.error(errorMessage);\n throw new Error(errorMessage);\n }\n }\n\n async fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n return await new Promise<TechDocsMetadata>(async (resolve, reject) => {\n const entityRootDir = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;\n\n const downloadResponse = await this.storageClient.download(\n this.containerName,\n `${entityRootDir}/techdocs_metadata.json`,\n );\n\n if (!(downloadResponse instanceof NotFound)) {\n const stream = downloadResponse.data;\n try {\n const techdocsMetadataJson = await streamToBuffer(stream);\n if (!techdocsMetadataJson) {\n throw new Error(\n `Unable to parse the techdocs metadata file ${entityRootDir}/techdocs_metadata.json.`,\n );\n }\n\n const techdocsMetadata = JSON5.parse(\n techdocsMetadataJson.toString('utf-8'),\n );\n\n resolve(techdocsMetadata);\n } catch (err) {\n const error = toError(err);\n this.logger.error(error.message);\n reject(error);\n }\n } else {\n reject({\n message: `TechDocs metadata fetch failed, The file /rootDir/${entityRootDir}/techdocs_metadata.json does not exist !`,\n });\n }\n });\n }\n\n /**\n * Express route middleware to serve static files on a route in techdocs-backend.\n */\n docsRouter(): express.Handler {\n return async (req, res) => {\n // Decode and trim the leading forward slash\n // filePath example - /default/Component/documented-component/index.html\n const filePath = decodeURI(req.path.replace(/^\\//, ''));\n\n // Files with different extensions (CSS, HTML) need to be served with different headers\n const fileExtension = path.extname(filePath);\n const responseHeaders = getHeadersForFileExtension(fileExtension);\n\n const downloadResponse = await this.storageClient.download(\n this.containerName,\n filePath,\n );\n\n if (!(downloadResponse instanceof NotFound)) {\n const stream = downloadResponse.data;\n\n try {\n // Inject response headers\n for (const [headerKey, headerValue] of Object.entries(\n responseHeaders,\n )) {\n res.setHeader(headerKey, headerValue);\n }\n\n res.send(await streamToBuffer(stream));\n } catch (err) {\n this.logger.warn(\n `TechDocs OpenStack swift router failed to serve content from container ${\n this.containerName\n } at path ${filePath}: ${toError(err).message}`,\n );\n res.status(404).send('File Not Found');\n }\n } else {\n this.logger.warn(\n `TechDocs OpenStack swift router failed to serve content from container ${this.containerName} at path ${filePath}: Not found`,\n );\n res.status(404).send('File Not Found');\n }\n };\n }\n\n /**\n * A helper function which checks if index.html of an Entity's docs site is available. This\n * can be used to verify if there are any pre-generated docs available to serve.\n */\n async hasDocsBeenGenerated(entity: Entity): Promise<boolean> {\n const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;\n try {\n const fileResponse = await this.storageClient.getMetadata(\n this.containerName,\n `${entityRootDir}/index.html`,\n );\n\n if (!(fileResponse instanceof NotFound)) {\n return true;\n }\n return false;\n } catch (err) {\n this.logger.warn(toError(err).message);\n return false;\n }\n }\n\n async migrateDocsCase({\n removeOriginal = false,\n concurrency = 25,\n }): Promise<void> {\n // Iterate through every file in the root of the publisher.\n const allObjects = await this.getAllObjectsFromContainer();\n const limiter = createLimiter(concurrency);\n await Promise.all(\n allObjects.map(f =>\n limiter(async file => {\n let newPath;\n try {\n newPath = lowerCaseEntityTripletInStoragePath(file);\n } catch (e) {\n this.logger.warn(toError(e).message);\n return;\n }\n\n // If all parts are already lowercase, ignore.\n if (file === newPath) {\n return;\n }\n\n try {\n this.logger.debug(`Migrating ${file} to ${newPath}`);\n await this.storageClient.copy(\n this.containerName,\n file,\n this.containerName,\n newPath,\n );\n if (removeOriginal) {\n await this.storageClient.delete(this.containerName, file);\n }\n } catch (e) {\n this.logger.warn(\n `Unable to migrate ${file}: ${toError(e).message}`,\n );\n }\n }, f),\n ),\n );\n }\n\n /**\n * Returns a list of all object keys from the configured container.\n */\n protected async getAllObjectsFromContainer(\n { prefix } = { prefix: '' },\n ): Promise<string[]> {\n let objects: string[] = [];\n const OSS_MAX_LIMIT = Math.pow(2, 31) - 1;\n\n const allObjects = await this.storageClient.list(\n this.containerName,\n prefix,\n OSS_MAX_LIMIT,\n );\n objects = allObjects.map((object: any) => object.name);\n\n return objects;\n }\n}\n"],"names":["ForwardedError","Readable","SwiftClient","NotFound","toError","getFileTreeRecursively","createLimiter","path","fs","JSON5","getHeadersForFileExtension","lowerCaseEntityTripletInStoragePath"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"publish.cjs.js","sources":["../../../src/stages/publish/publish.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 { Config } from '@backstage/config';\nimport { AwsS3Publish } from './awsS3';\nimport { AzureBlobStoragePublish } from './azureBlobStorage';\nimport { GoogleGCSPublish } from './googleStorage';\nimport { LocalPublish } from './local';\nimport { OpenStackSwiftPublish } from './openStackSwift';\nimport {\n PublisherFactory,\n PublisherBase,\n PublisherType,\n PublisherBuilder,\n} from './types';\n\n/**\n * Factory class to create a TechDocs publisher based on defined publisher type in app config.\n * Uses `techdocs.publisher.type`.\n * @public\n */\nexport class Publisher implements PublisherBuilder {\n private publishers: Map<PublisherType | 'techdocs', PublisherBase> =\n new Map();\n\n register(type: PublisherType | 'techdocs', publisher: PublisherBase): void {\n this.publishers.set(type, publisher);\n }\n\n get(config: Config): PublisherBase {\n const publisherType = (config.getOptionalString(\n 'techdocs.publisher.type',\n ) ?? 'local') as PublisherType;\n\n if (!publisherType) {\n throw new Error('TechDocs publisher type not specified for the entity');\n }\n\n const publisher = this.publishers.get(publisherType);\n if (!publisher) {\n throw new Error(\n `TechDocs publisher '${publisherType}' is not registered`,\n );\n }\n\n return publisher;\n }\n\n /**\n * Returns a instance of TechDocs publisher\n * @param config - A Backstage configuration\n * @param options - Options for configuring the publisher factory\n */\n static async fromConfig(\n config: Config,\n options: PublisherFactory,\n ): Promise<PublisherBase> {\n const { logger, discovery, customPublisher } = options;\n\n const publishers = new Publisher();\n\n if (customPublisher) {\n publishers.register('techdocs', customPublisher);\n return customPublisher;\n }\n\n const publisherType = (config.getOptionalString(\n 'techdocs.publisher.type',\n ) ?? 'local') as PublisherType;\n\n switch (publisherType) {\n case 'googleGcs':\n logger.info('Creating Google Storage Bucket publisher for TechDocs');\n publishers.register(\n publisherType,\n GoogleGCSPublish.fromConfig(\n config,\n logger,\n options.publisherSettings?.googleGcs,\n ),\n );\n break;\n case 'awsS3':\n logger.info('Creating AWS S3 Bucket publisher for TechDocs');\n publishers.register(\n publisherType,\n await AwsS3Publish.fromConfig(config, logger),\n );\n break;\n case 'azureBlobStorage':\n logger.info(\n 'Creating Azure Blob Storage Container publisher for TechDocs',\n );\n publishers.register(\n publisherType,\n AzureBlobStoragePublish.fromConfig(config, logger),\n );\n break;\n case 'openStackSwift':\n logger.info(\n 'Creating OpenStack Swift Container publisher for TechDocs',\n );\n publishers.register(\n publisherType,\n OpenStackSwiftPublish.fromConfig(config, logger),\n );\n break;\n case 'local':\n logger.info('Creating Local publisher for TechDocs');\n publishers.register(\n publisherType,\n LocalPublish.fromConfig(config, logger, discovery),\n );\n break;\n default:\n logger.info('Creating Local publisher for TechDocs');\n publishers.register(\n publisherType,\n LocalPublish.fromConfig(config, logger, discovery),\n );\n }\n\n return publishers.get(config);\n }\n}\n"],"names":["GoogleGCSPublish","AwsS3Publish","AzureBlobStoragePublish","OpenStackSwiftPublish","LocalPublish"],"mappings":"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"}
1
+ {"version":3,"file":"publish.cjs.js","sources":["../../../src/stages/publish/publish.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 { Config } from '@backstage/config';\nimport { AwsS3Publish } from './awsS3';\nimport { AzureBlobStoragePublish } from './azureBlobStorage';\nimport { GoogleGCSPublish } from './googleStorage';\nimport { LocalPublish } from './local';\nimport { OpenStackSwiftPublish } from './openStackSwift';\nimport {\n PublisherFactory,\n PublisherBase,\n PublisherType,\n PublisherBuilder,\n} from './types';\n\n/**\n * Factory class to create a TechDocs publisher based on defined publisher type in app config.\n * Uses `techdocs.publisher.type`.\n * @public\n */\nexport class Publisher implements PublisherBuilder {\n private publishers: Map<PublisherType | 'techdocs', PublisherBase> =\n new Map();\n\n register(type: PublisherType | 'techdocs', publisher: PublisherBase): void {\n this.publishers.set(type, publisher);\n }\n\n get(config: Config): PublisherBase {\n const publisherType = (config.getOptionalString(\n 'techdocs.publisher.type',\n ) ?? 'local') as PublisherType;\n\n if (!publisherType) {\n throw new Error('TechDocs publisher type not specified for the entity');\n }\n\n const publisher = this.publishers.get(publisherType);\n if (!publisher) {\n throw new Error(\n `TechDocs publisher '${publisherType}' is not registered`,\n );\n }\n\n return publisher;\n }\n\n /**\n * Returns a instance of TechDocs publisher\n * @param config - A Backstage configuration\n * @param options - Options for configuring the publisher factory\n */\n static async fromConfig(\n config: Config,\n options: PublisherFactory,\n ): Promise<PublisherBase> {\n const { logger, discovery, customPublisher } = options;\n\n const publishers = new Publisher();\n\n if (customPublisher) {\n publishers.register('techdocs', customPublisher);\n return customPublisher;\n }\n\n const publisherType = (config.getOptionalString(\n 'techdocs.publisher.type',\n ) ?? 'local') as PublisherType;\n\n switch (publisherType) {\n case 'googleGcs':\n logger.info('Creating Google Storage Bucket publisher for TechDocs');\n publishers.register(\n publisherType,\n GoogleGCSPublish.fromConfig(\n config,\n logger,\n options.publisherSettings?.googleGcs,\n ),\n );\n break;\n case 'awsS3':\n logger.info('Creating AWS S3 Bucket publisher for TechDocs');\n publishers.register(\n publisherType,\n await AwsS3Publish.fromConfig(config, logger),\n );\n break;\n case 'azureBlobStorage':\n logger.info(\n 'Creating Azure Blob Storage Container publisher for TechDocs',\n );\n publishers.register(\n publisherType,\n AzureBlobStoragePublish.fromConfig(config, logger),\n );\n break;\n case 'openStackSwift':\n logger.info(\n 'Creating OpenStack Swift Container publisher for TechDocs',\n );\n publishers.register(\n publisherType,\n OpenStackSwiftPublish.fromConfig(config, logger),\n );\n break;\n case 'local':\n logger.info('Creating Local publisher for TechDocs');\n publishers.register(\n publisherType,\n LocalPublish.fromConfig(config, logger, discovery),\n );\n break;\n default:\n logger.info('Creating Local publisher for TechDocs');\n publishers.register(\n publisherType,\n LocalPublish.fromConfig(config, logger, discovery),\n );\n }\n\n return publishers.get(config);\n }\n}\n"],"names":["GoogleGCSPublish","AwsS3Publish","AzureBlobStoragePublish","OpenStackSwiftPublish","LocalPublish"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-techdocs-node",
3
- "version": "1.16.0-next.1",
3
+ "version": "2.0.1-next.0",
4
4
  "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli",
5
5
  "backstage": {
6
6
  "role": "node-library",
@@ -53,13 +53,13 @@
53
53
  "@aws-sdk/types": "^3.347.0",
54
54
  "@azure/identity": "^4.0.0",
55
55
  "@azure/storage-blob": "^12.5.0",
56
- "@backstage/backend-plugin-api": "1.10.1-next.1",
57
- "@backstage/catalog-model": "1.10.0",
58
- "@backstage/config": "1.3.9-next.0",
59
- "@backstage/errors": "1.3.1",
60
- "@backstage/integration": "2.1.2-next.1",
61
- "@backstage/integration-aws-node": "0.2.2-next.0",
62
- "@backstage/plugin-search-common": "1.2.25-next.0",
56
+ "@backstage/backend-plugin-api": "1.10.2-next.0",
57
+ "@backstage/catalog-model": "1.10.2-next.0",
58
+ "@backstage/config": "1.3.10-next.0",
59
+ "@backstage/errors": "1.3.2-next.0",
60
+ "@backstage/integration": "2.1.3-next.0",
61
+ "@backstage/integration-aws-node": "0.2.3-next.0",
62
+ "@backstage/plugin-search-common": "1.2.26-next.0",
63
63
  "@backstage/plugin-techdocs-common": "0.1.1",
64
64
  "@google-cloud/storage": "^7.0.0",
65
65
  "@smithy/node-http-handler": "^3.0.0",
@@ -70,7 +70,7 @@
70
70
  "fs-extra": "^11.2.0",
71
71
  "git-url-parse": "^15.0.0",
72
72
  "hpagent": "^1.2.0",
73
- "js-yaml": "^4.3.0",
73
+ "js-yaml": "^4.3.2",
74
74
  "json5": "^2.1.3",
75
75
  "mime-types": "^2.1.27",
76
76
  "p-limit": "^3.1.0",
@@ -78,8 +78,8 @@
78
78
  "winston": "^3.2.1"
79
79
  },
80
80
  "devDependencies": {
81
- "@backstage/backend-test-utils": "1.11.7-next.1",
82
- "@backstage/cli": "0.36.6-next.1",
81
+ "@backstage/backend-test-utils": "1.11.8-next.0",
82
+ "@backstage/cli": "0.36.7-next.0",
83
83
  "@types/fs-extra": "^11.0.0",
84
84
  "@types/js-yaml": "^4.0.0",
85
85
  "@types/mime-types": "^2.1.0",