@backstage/plugin-techdocs-node 0.0.0-nightly-20260904022803 → 0.0.0-nightly-20260918023200
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.
- package/CHANGELOG.md +40 -9
- package/dist/stages/generate/helpers.cjs.js +1 -0
- package/dist/stages/generate/helpers.cjs.js.map +1 -1
- package/dist/stages/generate/mkdocsPatchers.cjs.js +116 -3
- package/dist/stages/generate/mkdocsPatchers.cjs.js.map +1 -1
- package/dist/stages/generate/techdocs.cjs.js +9 -2
- package/dist/stages/generate/techdocs.cjs.js.map +1 -1
- package/dist/stages/publish/awsS3.cjs.js +3 -12
- package/dist/stages/publish/awsS3.cjs.js.map +1 -1
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
# @backstage/plugin-techdocs-node
|
|
2
2
|
|
|
3
|
-
## 0.0.0-nightly-
|
|
3
|
+
## 0.0.0-nightly-20260918023200
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
- @backstage/integration@0.0.0-nightly-20260918023200
|
|
9
|
+
- @backstage/integration-aws-node@0.0.0-nightly-20260918023200
|
|
10
|
+
- @backstage/backend-plugin-api@1.10.1
|
|
11
|
+
- @backstage/catalog-model@1.10.1
|
|
12
|
+
- @backstage/config@1.3.9
|
|
13
|
+
- @backstage/errors@1.3.1
|
|
14
|
+
- @backstage/plugin-search-common@1.2.25
|
|
15
|
+
- @backstage/plugin-techdocs-common@0.1.1
|
|
16
|
+
|
|
17
|
+
## 2.0.0
|
|
18
|
+
|
|
19
|
+
### Major Changes
|
|
20
|
+
|
|
21
|
+
- 62d1a6f: **BREAKING**: TechDocs generation now removes MkDocs plugins outside the built-in allowlist and logs a warning before continuing the build. To retain additional plugins, review and add them to `techdocs.generator.mkdocs.dangerouslyAllowAdditionalPlugins`, or supply them through `dangerouslyAllowAdditionalPlugins` when creating the generator directly. Plugins configured through `defaultPlugins` are also permitted. Generation stops if the MkDocs configuration cannot be read, parsed, or updated.
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
|
6
24
|
|
|
@@ -9,21 +27,34 @@
|
|
|
9
27
|
### Patch Changes
|
|
10
28
|
|
|
11
29
|
- 1711594: Fixed local TechDocs generation when temporary source paths resolve through filesystem aliases.
|
|
30
|
+
- c0ebaa7: chore(deps): bump `js-yaml` from 4.3.1 to 4.3.2
|
|
12
31
|
- e58d265: Updated TechDocs generation to reject source trees containing symlinks that resolve outside the source directory.
|
|
13
32
|
- d13ad29: Improved MkDocs configuration handling for additional templates.
|
|
33
|
+
- e582b56: Fixed AWS S3 publishing to paginate existing objects in order to properly remove all stale TechDocs files.
|
|
14
34
|
- 2233af2: Improved MkDocs configuration sanitization to cover additional nested configuration keys and theme options.
|
|
15
35
|
- 4441911: Improved MkDocs configuration file handling.
|
|
16
36
|
- 45cbd0a: Improved validation of MkDocs configuration values during TechDocs generation.
|
|
17
37
|
- 030331b: Improved file validation in the local TechDocs publisher during the publish step.
|
|
18
38
|
- Updated dependencies
|
|
19
|
-
- @backstage/integration@
|
|
20
|
-
- @backstage/
|
|
21
|
-
- @backstage/
|
|
22
|
-
- @backstage/backend-plugin-api@
|
|
23
|
-
- @backstage/
|
|
24
|
-
- @backstage/
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
- @backstage/integration@2.1.2
|
|
40
|
+
- @backstage/catalog-model@1.10.1
|
|
41
|
+
- @backstage/config@1.3.9
|
|
42
|
+
- @backstage/backend-plugin-api@1.10.1
|
|
43
|
+
- @backstage/integration-aws-node@0.2.2
|
|
44
|
+
- @backstage/plugin-search-common@1.2.25
|
|
45
|
+
|
|
46
|
+
## 1.16.0-next.1
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- 1711594: Fixed local TechDocs generation when temporary source paths resolve through filesystem aliases.
|
|
51
|
+
- e582b56: Fixed AWS S3 publishing to paginate existing objects in order to properly remove all stale TechDocs files.
|
|
52
|
+
- Updated dependencies
|
|
53
|
+
- @backstage/config@1.3.9-next.0
|
|
54
|
+
- @backstage/backend-plugin-api@1.10.1-next.1
|
|
55
|
+
- @backstage/integration@2.1.2-next.1
|
|
56
|
+
- @backstage/integration-aws-node@0.2.2-next.0
|
|
57
|
+
- @backstage/plugin-search-common@1.2.25-next.0
|
|
27
58
|
|
|
28
59
|
## 1.16.0-next.0
|
|
29
60
|
|
|
@@ -342,6 +342,7 @@ exports.ALLOWED_MKDOCS_KEYS = ALLOWED_MKDOCS_KEYS;
|
|
|
342
342
|
exports.ALLOWED_THEME_KEYS = ALLOWED_THEME_KEYS;
|
|
343
343
|
exports.DANGEROUS_EXTENSION_CONFIG_KEYS = DANGEROUS_EXTENSION_CONFIG_KEYS;
|
|
344
344
|
exports.MKDOCS_SCHEMA = MKDOCS_SCHEMA;
|
|
345
|
+
exports.UnknownTag = UnknownTag;
|
|
345
346
|
exports.createOrUpdateMetadata = createOrUpdateMetadata;
|
|
346
347
|
exports.generateMkdocsYml = generateMkdocsYml;
|
|
347
348
|
exports.getGeneratorKey = getGeneratorKey;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.cjs.js","sources":["../../../src/stages/generate/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 */\n\nimport { isChildPath, LoggerService } from '@backstage/backend-plugin-api';\nimport { Entity } from '@backstage/catalog-model';\nimport { ForwardedError, NotAllowedError, toError } from '@backstage/errors';\nimport { ScmIntegrationRegistry } from '@backstage/integration';\nimport { SpawnOptionsWithoutStdio, spawn } from 'node:child_process';\nimport fs from 'fs-extra';\nimport gitUrlParse from 'git-url-parse';\nimport yaml, { DEFAULT_SCHEMA, Type } from 'js-yaml';\nimport path, { resolve as resolvePath } from 'node:path';\nimport { PassThrough, Writable } from 'node:stream';\nimport { ParsedLocationAnnotation } from '../../helpers';\nimport { DefaultMkdocsContent, SupportedGeneratorKey } from './types';\nimport { getFileTreeRecursively } from '../publish/helpers';\n\n// TODO: Implement proper support for more generators.\nexport function getGeneratorKey(entity: Entity): SupportedGeneratorKey {\n if (!entity) {\n throw new Error('No entity provided');\n }\n\n return 'techdocs';\n}\n\nexport type RunCommandOptions = {\n /** command to run */\n command: string;\n /** arguments to pass the command */\n args: string[];\n /** options to pass to spawn */\n options: SpawnOptionsWithoutStdio;\n /** stream to capture stdout and stderr output */\n logStream?: Writable;\n};\n\n/**\n * Run a command in a sub-process, normally a shell command.\n */\nexport const runCommand = async ({\n command,\n args,\n options,\n logStream = new PassThrough(),\n}: RunCommandOptions) => {\n await new Promise<void>((resolve, reject) => {\n const process = spawn(command, args, options);\n\n process.stdout.on('data', stream => {\n logStream.write(stream);\n });\n\n process.stderr.on('data', stream => {\n logStream.write(stream);\n });\n\n process.on('error', error => {\n return reject(error);\n });\n\n process.on('close', code => {\n if (code !== 0) {\n return reject(`Command ${command} failed, exit code: ${code}`);\n }\n return resolve();\n });\n });\n};\n\n/**\n * Return the source url for MkDocs based on the backstage.io/techdocs-ref annotation.\n * Depending on the type of target, it can either return a repo_url, an edit_uri, both, or none.\n *\n * @param parsedLocationAnnotation - Object with location url and type\n * @param scmIntegrations - the scmIntegration to do url transformations\n * @param docsFolder - the configured docs folder in the mkdocs.yml (defaults to 'docs')\n * @returns the settings for the mkdocs.yml\n */\nexport const getRepoUrlFromLocationAnnotation = (\n parsedLocationAnnotation: ParsedLocationAnnotation,\n scmIntegrations: ScmIntegrationRegistry,\n docsFolder: string = 'docs',\n): { repo_url?: string; edit_uri?: string } => {\n const { type: locationType, target } = parsedLocationAnnotation;\n\n if (locationType === 'url') {\n const integration = scmIntegrations.byUrl(target);\n\n // We only support it for github, gitlab, bitbucketServer and harness for now as the edit_uri\n // is not properly supported for others yet.\n if (\n integration &&\n ['github', 'gitlab', 'bitbucketServer', 'harness'].includes(\n integration.type,\n )\n ) {\n // handle the case where a user manually writes url:https://github.com/backstage/backstage i.e. without /blob/...\n const { filepathtype } = gitUrlParse(target);\n if (filepathtype === '') {\n return { repo_url: target };\n }\n\n const sourceFolder = integration.resolveUrl({\n url: `./${docsFolder}`,\n base: target.endsWith('/') ? target : `${target}/`,\n });\n return {\n repo_url: target,\n edit_uri: integration.resolveEditUrl(sourceFolder),\n };\n }\n }\n\n return {};\n};\n\nconst ALLOWED_PYTHON_YAML_TAGS = new Set([\n 'tag:yaml.org,2002:python/name:materialx.emoji.twemoji',\n 'tag:yaml.org,2002:python/object.apply:materialx.emoji.to_svg',\n 'tag:yaml.org,2002:python/object/apply:pymdownx.slugs.slugify',\n]);\n\nclass UnknownTag {\n public readonly data: any;\n public readonly type?: string;\n\n constructor(data: any, type?: string) {\n if (\n type?.startsWith('tag:yaml.org,2002:python/') &&\n !ALLOWED_PYTHON_YAML_TAGS.has(type)\n ) {\n throw new Error(`Unsupported Python YAML tag '${type}'`);\n }\n\n this.data = data;\n this.type = type;\n }\n}\n\nexport const MKDOCS_SCHEMA = DEFAULT_SCHEMA.extend([\n new Type('', {\n kind: 'scalar',\n multi: true,\n representName: o => (o as UnknownTag).type,\n represent: o => (o as UnknownTag).data ?? '',\n instanceOf: UnknownTag,\n construct: (data: string, type?: string) => new UnknownTag(data, type),\n }),\n new Type('tag:', {\n kind: 'mapping',\n multi: true,\n representName: o => (o as UnknownTag).type,\n represent: o => (o as UnknownTag).data ?? '',\n instanceOf: UnknownTag,\n construct: (data: string, type?: string) => new UnknownTag(data, type),\n }),\n new Type('', {\n kind: 'sequence',\n multi: true,\n representName: o => (o as UnknownTag).type,\n represent: o => (o as UnknownTag).data ?? '',\n instanceOf: UnknownTag,\n construct: (data: string, type?: string) => new UnknownTag(data, type),\n }),\n]);\n\n/**\n * Generates a mkdocs.yml configuration file\n *\n * @param inputDir - base dir to where the mkdocs.yml file will be created\n * @param siteOptions - options for the site: `name` property will be used in mkdocs.yml for the\n * required `site_name` property, default value is \"Documentation Site\"\n */\nexport const generateMkdocsYml = async (\n inputDir: string,\n siteOptions?: { name?: string },\n) => {\n try {\n // TODO(awanlin): Use a provided default mkdocs.yml\n // from config or some specified location. If this is\n // not provided then fall back to generating bare\n // minimum mkdocs.yml file\n\n const mkdocsYmlPath = path.join(inputDir, 'mkdocs.yml');\n const defaultSiteName = siteOptions?.name ?? 'Documentation Site';\n const defaultMkdocsContent: DefaultMkdocsContent = {\n site_name: defaultSiteName,\n docs_dir: 'docs',\n plugins: ['techdocs-core'],\n };\n\n await fs.writeFile(\n mkdocsYmlPath,\n yaml.dump(defaultMkdocsContent, { schema: MKDOCS_SCHEMA }),\n );\n } catch (error) {\n throw new ForwardedError('Could not generate mkdocs.yml file', error);\n }\n};\n\n/**\n * Finds and loads the contents of an mkdocs.yml, mkdocs.yaml file, a file\n * with a specified name or an ad-hoc created file with minimal config.\n * @public\n *\n * @param inputDir - base dir to be searched for either an mkdocs.yml or mkdocs.yaml file.\n * @param options - name: default mkdocs site_name to be used with a ad hoc file default value is \"Documentation Site\"\n * mkdocsConfigFileName (optional): a non-default file name to be used as the config\n */\nexport const getMkdocsYml = async (\n inputDir: string,\n options?: { name?: string; mkdocsConfigFileName?: string },\n): Promise<{ path: string; content: string; configIsTemporary: boolean }> => {\n let mkdocsYmlPath: string;\n let mkdocsYmlFileString: string;\n try {\n if (options?.mkdocsConfigFileName) {\n mkdocsYmlPath = path.join(inputDir, options.mkdocsConfigFileName);\n if (!(await fs.pathExists(mkdocsYmlPath))) {\n throw new Error(`The specified file ${mkdocsYmlPath} does not exist`);\n }\n\n mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');\n return {\n path: mkdocsYmlPath,\n content: mkdocsYmlFileString,\n configIsTemporary: false,\n };\n }\n\n mkdocsYmlPath = path.join(inputDir, 'mkdocs.yaml');\n if (await fs.pathExists(mkdocsYmlPath)) {\n mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');\n return {\n path: mkdocsYmlPath,\n content: mkdocsYmlFileString,\n configIsTemporary: false,\n };\n }\n\n mkdocsYmlPath = path.join(inputDir, 'mkdocs.yml');\n if (await fs.pathExists(mkdocsYmlPath)) {\n mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');\n return {\n path: mkdocsYmlPath,\n content: mkdocsYmlFileString,\n configIsTemporary: false,\n };\n }\n\n // No mkdocs file, generate it\n await generateMkdocsYml(inputDir, options);\n mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');\n } catch (error) {\n throw new ForwardedError(\n 'Could not read MkDocs YAML config file mkdocs.yml or mkdocs.yaml or default for validation',\n error,\n );\n }\n\n return {\n path: mkdocsYmlPath,\n content: mkdocsYmlFileString,\n configIsTemporary: true,\n };\n};\n\n/**\n * Allowlist of MkDocs configuration keys supported by TechDocs.\n *\n * @see https://www.mkdocs.org/user-guide/configuration/\n */\nexport const ALLOWED_MKDOCS_KEYS = new Set([\n // Site information\n 'site_name',\n 'site_url',\n 'site_description',\n 'site_author',\n // Repository\n 'repo_url',\n 'repo_name',\n 'edit_uri',\n 'edit_uri_template',\n // Build directories\n 'docs_dir',\n 'site_dir',\n // Documentation layout\n 'nav',\n 'exclude_docs',\n 'not_in_nav',\n // Build settings\n 'theme',\n 'plugins',\n 'markdown_extensions',\n 'extra',\n 'extra_css',\n // Preview controls\n 'use_directory_urls',\n 'strict',\n 'dev_addr',\n 'watch',\n // Metadata\n 'copyright',\n 'remote_branch',\n 'remote_name',\n 'validation',\n // Deprecated\n 'google_analytics',\n]);\n\n/**\n * Denylist of configuration keys that must be stripped from extension\n * configurations nested within `markdown_extensions`.\n */\nexport const DANGEROUS_EXTENSION_CONFIG_KEYS = new Set(['plantuml_cmd']);\n\n/**\n * Allowlist of theme configuration keys supported by TechDocs.\n *\n * @see https://squidfunk.github.io/mkdocs-material/setup/\n */\nexport const ALLOWED_THEME_KEYS = new Set([\n 'name',\n 'font',\n 'icon',\n 'logo',\n 'favicon',\n 'language',\n 'direction',\n 'palette',\n 'features',\n]);\n\n/**\n * Validating mkdocs config file for incorrect/insecure values\n * Throws on invalid configs\n *\n * @param inputDir - base dir to be used as a docs_dir path validity check\n * @param mkdocsYmlFileString - The string contents of the loaded\n * mkdocs.yml or equivalent of a docs site\n * @returns the parsed docs_dir or undefined\n */\nexport const validateMkdocsYaml = async (\n inputDir: string,\n mkdocsYmlFileString: string,\n): Promise<string | undefined> => {\n const mkdocsYml = yaml.load(mkdocsYmlFileString, {\n schema: MKDOCS_SCHEMA,\n });\n\n if (mkdocsYml === null || typeof mkdocsYml !== 'object') {\n return undefined;\n }\n\n const parsedMkdocsYml: Record<string, any> = mkdocsYml;\n\n if (\n parsedMkdocsYml.docs_dir &&\n !isChildPath(inputDir, resolvePath(inputDir, parsedMkdocsYml.docs_dir))\n ) {\n throw new Error(\n `docs_dir configuration value in mkdocs can't be an absolute directory or start with ../ for security reasons.\n Use relative paths instead which are resolved relative to your mkdocs.yml file location.`,\n );\n }\n return parsedMkdocsYml.docs_dir;\n};\n\n/**\n * Validates that the input directory doesn't contain symlinks pointing outside\n * of it. This prevents path traversal attacks where malicious symlinks could be\n * used to read arbitrary files from the host filesystem.\n *\n * The whole input directory is checked rather than only the docs directory,\n * because MkDocs extensions can read files from anywhere in the input directory.\n *\n * @param inputDir - The input directory to validate (absolute path)\n */\nexport const validateInputDirectory = async (\n inputDir: string,\n): Promise<void> => {\n const entries = await fs.readdir(inputDir, {\n recursive: true,\n withFileTypes: true,\n });\n\n for (const entry of entries) {\n if (!entry.isSymbolicLink()) {\n continue;\n }\n\n const entryPath = path.join(entry.parentPath, entry.name);\n // isChildPath resolves both paths through realpath, so this also catches\n // relative, chained and dangling links\n if (!isChildPath(inputDir, entryPath)) {\n throw new NotAllowedError(\n `Path ${entryPath} is not allowed to refer to a location outside ${inputDir}`,\n );\n }\n }\n};\n\n/**\n * Update docs/index.md file before TechDocs generator uses it to generate docs site,\n * falling back to docs/README.md or README.md in case a default docs/index.md\n * is not provided.\n */\nexport const patchIndexPreBuild = async ({\n inputDir,\n logger,\n docsDir = 'docs',\n}: {\n inputDir: string;\n logger: LoggerService;\n docsDir?: string;\n}) => {\n const docsPath = path.join(inputDir, docsDir);\n const indexMdPath = path.join(docsPath, 'index.md');\n\n if (await fs.pathExists(indexMdPath)) {\n return;\n }\n logger.warn(`${path.join(docsDir, 'index.md')} not found.`);\n const fallbacks = [\n path.join(docsPath, 'README.md'),\n path.join(docsPath, 'readme.md'),\n path.join(inputDir, 'README.md'),\n path.join(inputDir, 'readme.md'),\n ];\n\n if (!isChildPath(inputDir, docsPath)) {\n throw new NotAllowedError(\n `Target path ${docsPath} is not allowed to refer to a location outside ${inputDir}`,\n );\n }\n await fs.ensureDir(docsPath);\n for (const filePath of fallbacks) {\n if (!isChildPath(inputDir, filePath)) {\n throw new NotAllowedError(\n `Source path ${filePath} is not allowed to refer to a location outside ${inputDir}`,\n );\n }\n try {\n await fs.copyFile(filePath, indexMdPath);\n return;\n } catch (error) {\n logger.warn(`${path.relative(inputDir, filePath)} not found.`);\n }\n }\n\n logger.warn(\n `Could not find any techdocs' index file. Please make sure at least one of ${[\n indexMdPath,\n ...fallbacks,\n ].join(' ')} exists.`,\n );\n};\n\n/**\n * Create or update the techdocs_metadata.json. Values initialized/updated are:\n * - The build_timestamp (now)\n * - The list of files generated\n *\n * @param techdocsMetadataPath - File path to techdocs_metadata.json\n */\nexport const createOrUpdateMetadata = async (\n techdocsMetadataPath: string,\n logger: LoggerService,\n): Promise<void> => {\n const techdocsMetadataDir = techdocsMetadataPath\n .split(path.sep)\n .slice(0, -1)\n .join(path.sep);\n // check if file exists, create if it does not.\n try {\n await fs.access(techdocsMetadataPath, fs.constants.F_OK);\n } catch (err) {\n // Bootstrap file with empty JSON\n await fs.writeJson(techdocsMetadataPath, JSON.parse('{}'));\n }\n // check if valid Json\n let json;\n try {\n json = await fs.readJson(techdocsMetadataPath);\n } catch (err) {\n const message = `Invalid JSON at ${techdocsMetadataPath} with error ${\n toError(err).message\n }`;\n logger.error(message);\n throw new Error(message);\n }\n\n json.build_timestamp = Date.now();\n\n // Get and write generated files to the metadata JSON. Each file string is in\n // a form appropriate for invalidating the associated object from cache.\n try {\n json.files = (await getFileTreeRecursively(techdocsMetadataDir)).map(file =>\n file.replace(`${techdocsMetadataDir}${path.sep}`, ''),\n );\n } catch (err) {\n json.files = [];\n logger.warn(\n `Unable to add files list to metadata: ${toError(err).message}`,\n );\n }\n\n await fs.writeJson(techdocsMetadataPath, json);\n return;\n};\n\n/**\n * Update the techdocs_metadata.json to add etag of the prepared tree (e.g. commit SHA or actual Etag of the resource).\n * This is helpful to check if a TechDocs site in storage has gone outdated, without maintaining an in-memory build info\n * per Backstage instance.\n *\n * @param techdocsMetadataPath - File path to techdocs_metadata.json\n * @param etag - The ETag to use\n */\nexport const storeEtagMetadata = async (\n techdocsMetadataPath: string,\n etag: string,\n): Promise<void> => {\n const json = await fs.readJson(techdocsMetadataPath);\n json.etag = etag;\n await fs.writeJson(techdocsMetadataPath, json);\n};\n"],"names":["PassThrough","spawn","gitUrlParse","DEFAULT_SCHEMA","Type","path","fs","yaml","ForwardedError","isChildPath","resolvePath","NotAllowedError","toError","getFileTreeRecursively"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"helpers.cjs.js","sources":["../../../src/stages/generate/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 */\n\nimport { isChildPath, LoggerService } from '@backstage/backend-plugin-api';\nimport { Entity } from '@backstage/catalog-model';\nimport { ForwardedError, NotAllowedError, toError } from '@backstage/errors';\nimport { ScmIntegrationRegistry } from '@backstage/integration';\nimport { SpawnOptionsWithoutStdio, spawn } from 'node:child_process';\nimport fs from 'fs-extra';\nimport gitUrlParse from 'git-url-parse';\nimport yaml, { DEFAULT_SCHEMA, Type } from 'js-yaml';\nimport path, { resolve as resolvePath } from 'node:path';\nimport { PassThrough, Writable } from 'node:stream';\nimport { ParsedLocationAnnotation } from '../../helpers';\nimport { DefaultMkdocsContent, SupportedGeneratorKey } from './types';\nimport { getFileTreeRecursively } from '../publish/helpers';\n\n// TODO: Implement proper support for more generators.\nexport function getGeneratorKey(entity: Entity): SupportedGeneratorKey {\n if (!entity) {\n throw new Error('No entity provided');\n }\n\n return 'techdocs';\n}\n\nexport type RunCommandOptions = {\n /** command to run */\n command: string;\n /** arguments to pass the command */\n args: string[];\n /** options to pass to spawn */\n options: SpawnOptionsWithoutStdio;\n /** stream to capture stdout and stderr output */\n logStream?: Writable;\n};\n\n/**\n * Run a command in a sub-process, normally a shell command.\n */\nexport const runCommand = async ({\n command,\n args,\n options,\n logStream = new PassThrough(),\n}: RunCommandOptions) => {\n await new Promise<void>((resolve, reject) => {\n const process = spawn(command, args, options);\n\n process.stdout.on('data', stream => {\n logStream.write(stream);\n });\n\n process.stderr.on('data', stream => {\n logStream.write(stream);\n });\n\n process.on('error', error => {\n return reject(error);\n });\n\n process.on('close', code => {\n if (code !== 0) {\n return reject(`Command ${command} failed, exit code: ${code}`);\n }\n return resolve();\n });\n });\n};\n\n/**\n * Return the source url for MkDocs based on the backstage.io/techdocs-ref annotation.\n * Depending on the type of target, it can either return a repo_url, an edit_uri, both, or none.\n *\n * @param parsedLocationAnnotation - Object with location url and type\n * @param scmIntegrations - the scmIntegration to do url transformations\n * @param docsFolder - the configured docs folder in the mkdocs.yml (defaults to 'docs')\n * @returns the settings for the mkdocs.yml\n */\nexport const getRepoUrlFromLocationAnnotation = (\n parsedLocationAnnotation: ParsedLocationAnnotation,\n scmIntegrations: ScmIntegrationRegistry,\n docsFolder: string = 'docs',\n): { repo_url?: string; edit_uri?: string } => {\n const { type: locationType, target } = parsedLocationAnnotation;\n\n if (locationType === 'url') {\n const integration = scmIntegrations.byUrl(target);\n\n // We only support it for github, gitlab, bitbucketServer and harness for now as the edit_uri\n // is not properly supported for others yet.\n if (\n integration &&\n ['github', 'gitlab', 'bitbucketServer', 'harness'].includes(\n integration.type,\n )\n ) {\n // handle the case where a user manually writes url:https://github.com/backstage/backstage i.e. without /blob/...\n const { filepathtype } = gitUrlParse(target);\n if (filepathtype === '') {\n return { repo_url: target };\n }\n\n const sourceFolder = integration.resolveUrl({\n url: `./${docsFolder}`,\n base: target.endsWith('/') ? target : `${target}/`,\n });\n return {\n repo_url: target,\n edit_uri: integration.resolveEditUrl(sourceFolder),\n };\n }\n }\n\n return {};\n};\n\nconst ALLOWED_PYTHON_YAML_TAGS = new Set([\n 'tag:yaml.org,2002:python/name:materialx.emoji.twemoji',\n 'tag:yaml.org,2002:python/object.apply:materialx.emoji.to_svg',\n 'tag:yaml.org,2002:python/object/apply:pymdownx.slugs.slugify',\n]);\n\nexport class UnknownTag {\n public readonly data: any;\n public readonly type?: string;\n\n constructor(data: any, type?: string) {\n if (\n type?.startsWith('tag:yaml.org,2002:python/') &&\n !ALLOWED_PYTHON_YAML_TAGS.has(type)\n ) {\n throw new Error(`Unsupported Python YAML tag '${type}'`);\n }\n\n this.data = data;\n this.type = type;\n }\n}\n\nexport const MKDOCS_SCHEMA = DEFAULT_SCHEMA.extend([\n new Type('', {\n kind: 'scalar',\n multi: true,\n representName: o => (o as UnknownTag).type,\n represent: o => (o as UnknownTag).data ?? '',\n instanceOf: UnknownTag,\n construct: (data: string, type?: string) => new UnknownTag(data, type),\n }),\n new Type('tag:', {\n kind: 'mapping',\n multi: true,\n representName: o => (o as UnknownTag).type,\n represent: o => (o as UnknownTag).data ?? '',\n instanceOf: UnknownTag,\n construct: (data: string, type?: string) => new UnknownTag(data, type),\n }),\n new Type('', {\n kind: 'sequence',\n multi: true,\n representName: o => (o as UnknownTag).type,\n represent: o => (o as UnknownTag).data ?? '',\n instanceOf: UnknownTag,\n construct: (data: string, type?: string) => new UnknownTag(data, type),\n }),\n]);\n\n/**\n * Generates a mkdocs.yml configuration file\n *\n * @param inputDir - base dir to where the mkdocs.yml file will be created\n * @param siteOptions - options for the site: `name` property will be used in mkdocs.yml for the\n * required `site_name` property, default value is \"Documentation Site\"\n */\nexport const generateMkdocsYml = async (\n inputDir: string,\n siteOptions?: { name?: string },\n) => {\n try {\n // TODO(awanlin): Use a provided default mkdocs.yml\n // from config or some specified location. If this is\n // not provided then fall back to generating bare\n // minimum mkdocs.yml file\n\n const mkdocsYmlPath = path.join(inputDir, 'mkdocs.yml');\n const defaultSiteName = siteOptions?.name ?? 'Documentation Site';\n const defaultMkdocsContent: DefaultMkdocsContent = {\n site_name: defaultSiteName,\n docs_dir: 'docs',\n plugins: ['techdocs-core'],\n };\n\n await fs.writeFile(\n mkdocsYmlPath,\n yaml.dump(defaultMkdocsContent, { schema: MKDOCS_SCHEMA }),\n );\n } catch (error) {\n throw new ForwardedError('Could not generate mkdocs.yml file', error);\n }\n};\n\n/**\n * Finds and loads the contents of an mkdocs.yml, mkdocs.yaml file, a file\n * with a specified name or an ad-hoc created file with minimal config.\n * @public\n *\n * @param inputDir - base dir to be searched for either an mkdocs.yml or mkdocs.yaml file.\n * @param options - name: default mkdocs site_name to be used with a ad hoc file default value is \"Documentation Site\"\n * mkdocsConfigFileName (optional): a non-default file name to be used as the config\n */\nexport const getMkdocsYml = async (\n inputDir: string,\n options?: { name?: string; mkdocsConfigFileName?: string },\n): Promise<{ path: string; content: string; configIsTemporary: boolean }> => {\n let mkdocsYmlPath: string;\n let mkdocsYmlFileString: string;\n try {\n if (options?.mkdocsConfigFileName) {\n mkdocsYmlPath = path.join(inputDir, options.mkdocsConfigFileName);\n if (!(await fs.pathExists(mkdocsYmlPath))) {\n throw new Error(`The specified file ${mkdocsYmlPath} does not exist`);\n }\n\n mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');\n return {\n path: mkdocsYmlPath,\n content: mkdocsYmlFileString,\n configIsTemporary: false,\n };\n }\n\n mkdocsYmlPath = path.join(inputDir, 'mkdocs.yaml');\n if (await fs.pathExists(mkdocsYmlPath)) {\n mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');\n return {\n path: mkdocsYmlPath,\n content: mkdocsYmlFileString,\n configIsTemporary: false,\n };\n }\n\n mkdocsYmlPath = path.join(inputDir, 'mkdocs.yml');\n if (await fs.pathExists(mkdocsYmlPath)) {\n mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');\n return {\n path: mkdocsYmlPath,\n content: mkdocsYmlFileString,\n configIsTemporary: false,\n };\n }\n\n // No mkdocs file, generate it\n await generateMkdocsYml(inputDir, options);\n mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');\n } catch (error) {\n throw new ForwardedError(\n 'Could not read MkDocs YAML config file mkdocs.yml or mkdocs.yaml or default for validation',\n error,\n );\n }\n\n return {\n path: mkdocsYmlPath,\n content: mkdocsYmlFileString,\n configIsTemporary: true,\n };\n};\n\n/**\n * Allowlist of MkDocs configuration keys supported by TechDocs.\n *\n * @see https://www.mkdocs.org/user-guide/configuration/\n */\nexport const ALLOWED_MKDOCS_KEYS = new Set([\n // Site information\n 'site_name',\n 'site_url',\n 'site_description',\n 'site_author',\n // Repository\n 'repo_url',\n 'repo_name',\n 'edit_uri',\n 'edit_uri_template',\n // Build directories\n 'docs_dir',\n 'site_dir',\n // Documentation layout\n 'nav',\n 'exclude_docs',\n 'not_in_nav',\n // Build settings\n 'theme',\n 'plugins',\n 'markdown_extensions',\n 'extra',\n 'extra_css',\n // Preview controls\n 'use_directory_urls',\n 'strict',\n 'dev_addr',\n 'watch',\n // Metadata\n 'copyright',\n 'remote_branch',\n 'remote_name',\n 'validation',\n // Deprecated\n 'google_analytics',\n]);\n\n/**\n * Denylist of configuration keys that must be stripped from extension\n * configurations nested within `markdown_extensions`.\n */\nexport const DANGEROUS_EXTENSION_CONFIG_KEYS = new Set(['plantuml_cmd']);\n\n/**\n * Allowlist of theme configuration keys supported by TechDocs.\n *\n * @see https://squidfunk.github.io/mkdocs-material/setup/\n */\nexport const ALLOWED_THEME_KEYS = new Set([\n 'name',\n 'font',\n 'icon',\n 'logo',\n 'favicon',\n 'language',\n 'direction',\n 'palette',\n 'features',\n]);\n\n/**\n * Validating mkdocs config file for incorrect/insecure values\n * Throws on invalid configs\n *\n * @param inputDir - base dir to be used as a docs_dir path validity check\n * @param mkdocsYmlFileString - The string contents of the loaded\n * mkdocs.yml or equivalent of a docs site\n * @returns the parsed docs_dir or undefined\n */\nexport const validateMkdocsYaml = async (\n inputDir: string,\n mkdocsYmlFileString: string,\n): Promise<string | undefined> => {\n const mkdocsYml = yaml.load(mkdocsYmlFileString, {\n schema: MKDOCS_SCHEMA,\n });\n\n if (mkdocsYml === null || typeof mkdocsYml !== 'object') {\n return undefined;\n }\n\n const parsedMkdocsYml: Record<string, any> = mkdocsYml;\n\n if (\n parsedMkdocsYml.docs_dir &&\n !isChildPath(inputDir, resolvePath(inputDir, parsedMkdocsYml.docs_dir))\n ) {\n throw new Error(\n `docs_dir configuration value in mkdocs can't be an absolute directory or start with ../ for security reasons.\n Use relative paths instead which are resolved relative to your mkdocs.yml file location.`,\n );\n }\n return parsedMkdocsYml.docs_dir;\n};\n\n/**\n * Validates that the input directory doesn't contain symlinks pointing outside\n * of it. This prevents path traversal attacks where malicious symlinks could be\n * used to read arbitrary files from the host filesystem.\n *\n * The whole input directory is checked rather than only the docs directory,\n * because MkDocs extensions can read files from anywhere in the input directory.\n *\n * @param inputDir - The input directory to validate (absolute path)\n */\nexport const validateInputDirectory = async (\n inputDir: string,\n): Promise<void> => {\n const entries = await fs.readdir(inputDir, {\n recursive: true,\n withFileTypes: true,\n });\n\n for (const entry of entries) {\n if (!entry.isSymbolicLink()) {\n continue;\n }\n\n const entryPath = path.join(entry.parentPath, entry.name);\n // isChildPath resolves both paths through realpath, so this also catches\n // relative, chained and dangling links\n if (!isChildPath(inputDir, entryPath)) {\n throw new NotAllowedError(\n `Path ${entryPath} is not allowed to refer to a location outside ${inputDir}`,\n );\n }\n }\n};\n\n/**\n * Update docs/index.md file before TechDocs generator uses it to generate docs site,\n * falling back to docs/README.md or README.md in case a default docs/index.md\n * is not provided.\n */\nexport const patchIndexPreBuild = async ({\n inputDir,\n logger,\n docsDir = 'docs',\n}: {\n inputDir: string;\n logger: LoggerService;\n docsDir?: string;\n}) => {\n const docsPath = path.join(inputDir, docsDir);\n const indexMdPath = path.join(docsPath, 'index.md');\n\n if (await fs.pathExists(indexMdPath)) {\n return;\n }\n logger.warn(`${path.join(docsDir, 'index.md')} not found.`);\n const fallbacks = [\n path.join(docsPath, 'README.md'),\n path.join(docsPath, 'readme.md'),\n path.join(inputDir, 'README.md'),\n path.join(inputDir, 'readme.md'),\n ];\n\n if (!isChildPath(inputDir, docsPath)) {\n throw new NotAllowedError(\n `Target path ${docsPath} is not allowed to refer to a location outside ${inputDir}`,\n );\n }\n await fs.ensureDir(docsPath);\n for (const filePath of fallbacks) {\n if (!isChildPath(inputDir, filePath)) {\n throw new NotAllowedError(\n `Source path ${filePath} is not allowed to refer to a location outside ${inputDir}`,\n );\n }\n try {\n await fs.copyFile(filePath, indexMdPath);\n return;\n } catch (error) {\n logger.warn(`${path.relative(inputDir, filePath)} not found.`);\n }\n }\n\n logger.warn(\n `Could not find any techdocs' index file. Please make sure at least one of ${[\n indexMdPath,\n ...fallbacks,\n ].join(' ')} exists.`,\n );\n};\n\n/**\n * Create or update the techdocs_metadata.json. Values initialized/updated are:\n * - The build_timestamp (now)\n * - The list of files generated\n *\n * @param techdocsMetadataPath - File path to techdocs_metadata.json\n */\nexport const createOrUpdateMetadata = async (\n techdocsMetadataPath: string,\n logger: LoggerService,\n): Promise<void> => {\n const techdocsMetadataDir = techdocsMetadataPath\n .split(path.sep)\n .slice(0, -1)\n .join(path.sep);\n // check if file exists, create if it does not.\n try {\n await fs.access(techdocsMetadataPath, fs.constants.F_OK);\n } catch (err) {\n // Bootstrap file with empty JSON\n await fs.writeJson(techdocsMetadataPath, JSON.parse('{}'));\n }\n // check if valid Json\n let json;\n try {\n json = await fs.readJson(techdocsMetadataPath);\n } catch (err) {\n const message = `Invalid JSON at ${techdocsMetadataPath} with error ${\n toError(err).message\n }`;\n logger.error(message);\n throw new Error(message);\n }\n\n json.build_timestamp = Date.now();\n\n // Get and write generated files to the metadata JSON. Each file string is in\n // a form appropriate for invalidating the associated object from cache.\n try {\n json.files = (await getFileTreeRecursively(techdocsMetadataDir)).map(file =>\n file.replace(`${techdocsMetadataDir}${path.sep}`, ''),\n );\n } catch (err) {\n json.files = [];\n logger.warn(\n `Unable to add files list to metadata: ${toError(err).message}`,\n );\n }\n\n await fs.writeJson(techdocsMetadataPath, json);\n return;\n};\n\n/**\n * Update the techdocs_metadata.json to add etag of the prepared tree (e.g. commit SHA or actual Etag of the resource).\n * This is helpful to check if a TechDocs site in storage has gone outdated, without maintaining an in-memory build info\n * per Backstage instance.\n *\n * @param techdocsMetadataPath - File path to techdocs_metadata.json\n * @param etag - The ETag to use\n */\nexport const storeEtagMetadata = async (\n techdocsMetadataPath: string,\n etag: string,\n): Promise<void> => {\n const json = await fs.readJson(techdocsMetadataPath);\n json.etag = etag;\n await fs.writeJson(techdocsMetadataPath, json);\n};\n"],"names":["PassThrough","spawn","gitUrlParse","DEFAULT_SCHEMA","Type","path","fs","yaml","ForwardedError","isChildPath","resolvePath","NotAllowedError","toError","getFileTreeRecursively"],"mappings":"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"}
|
|
@@ -12,13 +12,95 @@ var yaml__default = /*#__PURE__*/_interopDefaultCompat(yaml);
|
|
|
12
12
|
|
|
13
13
|
const MATERIAL_THEME = "material";
|
|
14
14
|
const PYMDOWNX_SNIPPETS_EXTENSION = "pymdownx.snippets";
|
|
15
|
+
const DEFAULT_ALLOWED_MKDOCS_PLUGINS = /* @__PURE__ */ new Set([
|
|
16
|
+
"techdocs-core",
|
|
17
|
+
"search",
|
|
18
|
+
"material/search",
|
|
19
|
+
"redirects",
|
|
20
|
+
"group",
|
|
21
|
+
"material/group"
|
|
22
|
+
]);
|
|
23
|
+
const MKDOCS_PLUGIN_GROUPS = /* @__PURE__ */ new Set(["group", "material/group"]);
|
|
15
24
|
function isPymdownxSnippetsExtension(extensionName) {
|
|
16
25
|
return extensionName === PYMDOWNX_SNIPPETS_EXTENSION || extensionName === `${PYMDOWNX_SNIPPETS_EXTENSION}:SnippetExtension`;
|
|
17
26
|
}
|
|
27
|
+
function isPlainObject(value) {
|
|
28
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof helpers.UnknownTag) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const prototype = Object.getPrototypeOf(value);
|
|
32
|
+
return prototype === Object.prototype || prototype === null;
|
|
33
|
+
}
|
|
34
|
+
function normalizeMkdocsPlugins(plugins) {
|
|
35
|
+
if (Array.isArray(plugins)) {
|
|
36
|
+
return plugins;
|
|
37
|
+
}
|
|
38
|
+
if (isPlainObject(plugins)) {
|
|
39
|
+
return Object.entries(plugins).map(([name, config]) => ({
|
|
40
|
+
[name]: config
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
return void 0;
|
|
44
|
+
}
|
|
45
|
+
function sanitizeMkdocsPlugins(plugins, allowedPlugins, removedPlugins) {
|
|
46
|
+
if (plugins instanceof helpers.UnknownTag) {
|
|
47
|
+
removedPlugins.add("dynamically configured plugin name");
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
const normalizedPlugins = normalizeMkdocsPlugins(plugins);
|
|
51
|
+
if (!normalizedPlugins) {
|
|
52
|
+
removedPlugins.add("malformed plugin declaration");
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
const sanitizedPlugins = [];
|
|
56
|
+
for (const plugin of normalizedPlugins) {
|
|
57
|
+
if (plugin instanceof helpers.UnknownTag) {
|
|
58
|
+
removedPlugins.add("dynamically configured plugin name");
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
let pluginName;
|
|
62
|
+
if (typeof plugin === "string" && plugin.length > 0) {
|
|
63
|
+
pluginName = plugin;
|
|
64
|
+
} else if (isPlainObject(plugin) && Object.keys(plugin).length === 1) {
|
|
65
|
+
[pluginName] = Object.keys(plugin);
|
|
66
|
+
const config = plugin[pluginName];
|
|
67
|
+
if (config !== null && !isPlainObject(config)) {
|
|
68
|
+
removedPlugins.add("malformed plugin declaration");
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
removedPlugins.add("malformed plugin declaration");
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (!allowedPlugins.has(pluginName)) {
|
|
76
|
+
removedPlugins.add(pluginName);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
let sanitizedPlugin = plugin;
|
|
80
|
+
if (isPlainObject(plugin)) {
|
|
81
|
+
const config = plugin[pluginName];
|
|
82
|
+
if (MKDOCS_PLUGIN_GROUPS.has(pluginName) && isPlainObject(config)) {
|
|
83
|
+
sanitizedPlugin = {
|
|
84
|
+
...plugin,
|
|
85
|
+
[pluginName]: {
|
|
86
|
+
...config,
|
|
87
|
+
plugins: sanitizeMkdocsPlugins(
|
|
88
|
+
config.plugins,
|
|
89
|
+
allowedPlugins,
|
|
90
|
+
removedPlugins
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
sanitizedPlugins.push(sanitizedPlugin);
|
|
97
|
+
}
|
|
98
|
+
return sanitizedPlugins;
|
|
99
|
+
}
|
|
18
100
|
function isThemeObject(theme) {
|
|
19
101
|
return typeof theme === "object" && theme !== null && !Array.isArray(theme);
|
|
20
102
|
}
|
|
21
|
-
const patchMkdocsFile = async (mkdocsYmlPath, logger, updateAction) => {
|
|
103
|
+
const patchMkdocsFile = async (mkdocsYmlPath, logger, updateAction, options) => {
|
|
22
104
|
let didEdit = false;
|
|
23
105
|
let mkdocsYmlFileString;
|
|
24
106
|
try {
|
|
@@ -27,6 +109,9 @@ const patchMkdocsFile = async (mkdocsYmlPath, logger, updateAction) => {
|
|
|
27
109
|
logger.warn(
|
|
28
110
|
`Could not read MkDocs YAML config file ${mkdocsYmlPath} before running the generator: ${errors.toError(error).message}`
|
|
29
111
|
);
|
|
112
|
+
if (options?.failOnError) {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
30
115
|
return;
|
|
31
116
|
}
|
|
32
117
|
let mkdocsYml;
|
|
@@ -39,6 +124,9 @@ const patchMkdocsFile = async (mkdocsYmlPath, logger, updateAction) => {
|
|
|
39
124
|
logger.warn(
|
|
40
125
|
`Error in parsing YAML at ${mkdocsYmlPath} before running the generator. ${errors.toError(error).message}`
|
|
41
126
|
);
|
|
127
|
+
if (options?.failOnError) {
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
42
130
|
return;
|
|
43
131
|
}
|
|
44
132
|
didEdit = updateAction(mkdocsYml);
|
|
@@ -54,9 +142,15 @@ const patchMkdocsFile = async (mkdocsYmlPath, logger, updateAction) => {
|
|
|
54
142
|
logger.warn(
|
|
55
143
|
`Could not write to ${mkdocsYmlPath} after updating it before running the generator. ${errors.toError(error).message}`
|
|
56
144
|
);
|
|
145
|
+
if (options?.failOnError) {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
57
148
|
return;
|
|
58
149
|
}
|
|
59
150
|
};
|
|
151
|
+
const patchMkdocsFileOrThrow = async (mkdocsYmlPath, logger, updateAction) => patchMkdocsFile(mkdocsYmlPath, logger, updateAction, {
|
|
152
|
+
failOnError: true
|
|
153
|
+
});
|
|
60
154
|
const patchMkdocsYmlPreBuild = async (mkdocsYmlPath, logger, parsedLocationAnnotation, scmIntegrations) => {
|
|
61
155
|
await patchMkdocsFile(mkdocsYmlPath, logger, (mkdocsYml) => {
|
|
62
156
|
if (!("repo_url" in mkdocsYml) || !("edit_uri" in mkdocsYml)) {
|
|
@@ -119,8 +213,8 @@ const patchMkdocsYmlWithFontDisabled = async (mkdocsYmlPath, logger) => {
|
|
|
119
213
|
return false;
|
|
120
214
|
});
|
|
121
215
|
};
|
|
122
|
-
const sanitizeMkdocsYml = async (mkdocsYmlPath, logger, additionalAllowedKeys) => {
|
|
123
|
-
await
|
|
216
|
+
const sanitizeMkdocsYml = async (mkdocsYmlPath, logger, additionalAllowedKeys, additionalAllowedPlugins) => {
|
|
217
|
+
await patchMkdocsFileOrThrow(mkdocsYmlPath, logger, (mkdocsYml) => {
|
|
124
218
|
const allowedKeys = new Set(helpers.ALLOWED_MKDOCS_KEYS);
|
|
125
219
|
if (additionalAllowedKeys && additionalAllowedKeys.length > 0) {
|
|
126
220
|
logger.warn(
|
|
@@ -200,6 +294,25 @@ const sanitizeMkdocsYml = async (mkdocsYmlPath, logger, additionalAllowedKeys) =
|
|
|
200
294
|
);
|
|
201
295
|
}
|
|
202
296
|
}
|
|
297
|
+
if ("plugins" in sanitized) {
|
|
298
|
+
const allowedPlugins = new Set(DEFAULT_ALLOWED_MKDOCS_PLUGINS);
|
|
299
|
+
additionalAllowedPlugins?.forEach((plugin) => allowedPlugins.add(plugin));
|
|
300
|
+
const removedPlugins = /* @__PURE__ */ new Set();
|
|
301
|
+
sanitized.plugins = sanitizeMkdocsPlugins(
|
|
302
|
+
sanitized.plugins,
|
|
303
|
+
allowedPlugins,
|
|
304
|
+
removedPlugins
|
|
305
|
+
);
|
|
306
|
+
if (removedPlugins.size > 0) {
|
|
307
|
+
logger.warn(
|
|
308
|
+
`Removed unsupported MkDocs plugins from mkdocs.yml: ${Array.from(
|
|
309
|
+
removedPlugins
|
|
310
|
+
).join(
|
|
311
|
+
", "
|
|
312
|
+
)}. To allow additional plugins, configure 'techdocs.generator.mkdocs.dangerouslyAllowAdditionalPlugins' in your Backstage app-config. When using the TechDocs CLI, use '--defaultPlugin' instead.`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
203
316
|
for (const key of Object.keys(mkdocsYml)) {
|
|
204
317
|
delete mkdocsYml[key];
|
|
205
318
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mkdocsPatchers.cjs.js","sources":["../../../src/stages/generate/mkdocsPatchers.ts"],"sourcesContent":["/*\n * Copyright 2022 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 fs from 'fs-extra';\nimport yaml from 'js-yaml';\nimport { ParsedLocationAnnotation } from '../../helpers';\nimport {\n ALLOWED_MKDOCS_KEYS,\n ALLOWED_THEME_KEYS,\n DANGEROUS_EXTENSION_CONFIG_KEYS,\n getRepoUrlFromLocationAnnotation,\n MKDOCS_SCHEMA,\n} from './helpers';\nimport { toError } from '@backstage/errors';\nimport { ScmIntegrationRegistry } from '@backstage/integration';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nconst MATERIAL_THEME = 'material';\nconst PYMDOWNX_SNIPPETS_EXTENSION = 'pymdownx.snippets';\n\nfunction isPymdownxSnippetsExtension(extensionName: string): boolean {\n return (\n extensionName === PYMDOWNX_SNIPPETS_EXTENSION ||\n extensionName === `${PYMDOWNX_SNIPPETS_EXTENSION}:SnippetExtension`\n );\n}\n\ntype MkDocsThemeObject = {\n name?: string;\n font?: boolean;\n};\n\nfunction isThemeObject(theme: unknown): theme is MkDocsThemeObject {\n return typeof theme === 'object' && theme !== null && !Array.isArray(theme);\n}\n\ntype MkDocsObject = {\n plugins?: string[];\n docs_dir: string;\n repo_url?: string;\n edit_uri?: string;\n theme?: MkDocsThemeObject;\n};\n\nconst patchMkdocsFile = async (\n mkdocsYmlPath: string,\n logger: LoggerService,\n updateAction: (mkdocsYml: MkDocsObject) => boolean,\n) => {\n // We only want to override the mkdocs.yml if it has actually changed. This is relevant if\n // used with a 'dir' location on the file system as this would permanently update the file.\n let didEdit = false;\n\n let mkdocsYmlFileString;\n try {\n mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');\n } catch (error) {\n logger.warn(\n `Could not read MkDocs YAML config file ${mkdocsYmlPath} before running the generator: ${\n toError(error).message\n }`,\n );\n return;\n }\n\n let mkdocsYml: any;\n try {\n mkdocsYml = yaml.load(mkdocsYmlFileString, { schema: MKDOCS_SCHEMA });\n\n // mkdocsYml should be an object type after successful parsing.\n // But based on its type definition, it can also be a string or undefined, which we don't want.\n if (typeof mkdocsYml === 'string' || typeof mkdocsYml === 'undefined') {\n throw new Error('Bad YAML format.');\n }\n } catch (error) {\n logger.warn(\n `Error in parsing YAML at ${mkdocsYmlPath} before running the generator. ${\n toError(error).message\n }`,\n );\n return;\n }\n\n didEdit = updateAction(mkdocsYml);\n\n try {\n if (didEdit) {\n await fs.writeFile(\n mkdocsYmlPath,\n yaml.dump(mkdocsYml, { schema: MKDOCS_SCHEMA }),\n 'utf8',\n );\n }\n } catch (error) {\n logger.warn(\n `Could not write to ${mkdocsYmlPath} after updating it before running the generator. ${\n toError(error).message\n }`,\n );\n return;\n }\n};\n\n/**\n * Update the mkdocs.yml file before TechDocs generator uses it to generate docs site.\n *\n * List of tasks:\n * - Add repo_url or edit_uri if it does not exists\n * If mkdocs.yml has a repo_url, the generated docs site gets an Edit button on the pages by default.\n * If repo_url is missing in mkdocs.yml, we will use techdocs annotation of the entity to possibly get\n * the repository URL.\n *\n * This function will not throw an error since this is not critical to the whole TechDocs pipeline.\n * Instead it will log warnings if there are any errors in reading, parsing or writing YAML.\n *\n * @param mkdocsYmlPath - Absolute path to mkdocs.yml or equivalent of a docs site\n * @param logger - A logger instance\n * @param parsedLocationAnnotation - Object with location url and type\n * @param scmIntegrations - the scmIntegration to do url transformations\n */\nexport const patchMkdocsYmlPreBuild = async (\n mkdocsYmlPath: string,\n logger: LoggerService,\n parsedLocationAnnotation: ParsedLocationAnnotation,\n scmIntegrations: ScmIntegrationRegistry,\n) => {\n await patchMkdocsFile(mkdocsYmlPath, logger, mkdocsYml => {\n if (!('repo_url' in mkdocsYml) || !('edit_uri' in mkdocsYml)) {\n // Add edit_uri and/or repo_url to mkdocs.yml if it is missing.\n // This will enable the Page edit button generated by MkDocs.\n // If the either has been set, keep the original value\n const result = getRepoUrlFromLocationAnnotation(\n parsedLocationAnnotation,\n scmIntegrations,\n mkdocsYml.docs_dir,\n );\n\n if (result.repo_url || result.edit_uri) {\n mkdocsYml.repo_url = mkdocsYml.repo_url || result.repo_url;\n mkdocsYml.edit_uri = mkdocsYml.edit_uri || result.edit_uri;\n\n logger.info(\n `Set ${JSON.stringify(\n result,\n )}. You can disable this feature by manually setting 'repo_url' or 'edit_uri' according to the MkDocs documentation at https://www.mkdocs.org/user-guide/configuration/#repo_url`,\n );\n return true;\n }\n }\n return false;\n });\n};\n\n/**\n * Update the mkdocs.yml file before TechDocs generator uses it to generate docs site.\n *\n * List of tasks:\n * - Add all provided default plugins\n *\n * This function will not throw an error since this is not critical to the whole TechDocs pipeline.\n * Instead it will log warnings if there are any errors in reading, parsing or writing YAML.\n *\n * @param mkdocsYmlPath - Absolute path to mkdocs.yml or equivalent of a docs site\n * @param logger - A logger instance\n * @param defaultPlugins - List of default mkdocs plugins\n */\nexport const patchMkdocsYmlWithPlugins = async (\n mkdocsYmlPath: string,\n logger: LoggerService,\n defaultPlugins: string[] = ['techdocs-core'],\n) => {\n await patchMkdocsFile(mkdocsYmlPath, logger, mkdocsYml => {\n // Modify mkdocs.yaml to contain the required default plugins.\n // If no plugins are defined we can just return the defaults.\n if (!('plugins' in mkdocsYml)) {\n mkdocsYml.plugins = defaultPlugins;\n return true;\n }\n\n // Otherwise, check each default plugin and include it if necessary.\n let changesMade = false;\n\n defaultPlugins.forEach(dp => {\n // if the plugin isn't there as a string, and isn't there as an object (which may itself contain extra config)\n // then we need to add it\n if (\n !(\n mkdocsYml.plugins!.includes(dp) ||\n mkdocsYml.plugins!.some(p => p.hasOwnProperty(dp))\n )\n ) {\n mkdocsYml.plugins = [...new Set([...mkdocsYml.plugins!, dp])];\n changesMade = true;\n }\n });\n\n return changesMade;\n });\n};\n\n/**\n * Disable external font download for the material theme.\n * @param mkdocsYmlPath - Absolute path to mkdocs.yml or equivalent of a docs site\n * @param logger\n */\nexport const patchMkdocsYmlWithFontDisabled = async (\n mkdocsYmlPath: string,\n logger: LoggerService,\n) => {\n await patchMkdocsFile(mkdocsYmlPath, logger, mkdocsYml => {\n if (!('theme' in mkdocsYml)) {\n // No theme section exists, create it with font disabled\n mkdocsYml.theme = {\n name: MATERIAL_THEME,\n font: false,\n };\n return true;\n }\n\n const theme = mkdocsYml.theme;\n if (isThemeObject(theme)) {\n // Theme section exists. Only modify it when the configured theme is Material\n if (theme.name === MATERIAL_THEME && !('font' in theme)) {\n theme.font = false;\n return true;\n }\n if (theme.name !== MATERIAL_THEME) {\n logger.debug(\n 'mkdocs.yml theme is not \"material\"; skipping font disabling patch',\n );\n }\n }\n\n return false;\n });\n};\n\n/**\n * Sanitize mkdocs.yml by keeping only allowed configuration keys.\n *\n * TechDocs only supports a subset of MkDocs configuration options.\n * This function reconstructs the config with only allowed keys,\n * discarding everything else. This approach ensures that any unknown\n * or potentially dangerous configuration options are not passed to MkDocs.\n *\n * The file is always rewritten to ensure YAML features like merge keys\n * and anchors are resolved into plain configuration.\n *\n * @param mkdocsYmlPath - Absolute path to mkdocs.yml or equivalent of a docs site\n * @param logger - A logger instance\n * @param additionalAllowedKeys - Optional array of additional keys to allow beyond the default allowlist\n */\nexport const sanitizeMkdocsYml = async (\n mkdocsYmlPath: string,\n logger: LoggerService,\n additionalAllowedKeys?: string[],\n) => {\n await patchMkdocsFile(mkdocsYmlPath, logger, mkdocsYml => {\n // Combine default allowed keys with additional keys\n const allowedKeys = new Set(ALLOWED_MKDOCS_KEYS);\n if (additionalAllowedKeys && additionalAllowedKeys.length > 0) {\n logger.warn(\n `DANGEROUS: Allowing additional MkDocs configuration keys beyond the default safe allowlist: ${additionalAllowedKeys.join(\n ', ',\n )}. This may introduce security vulnerabilities. Only use in trusted environments.`,\n );\n additionalAllowedKeys.forEach(key => allowedKeys.add(key));\n }\n\n // Identify keys that will be removed for logging\n const removedKeys = Object.keys(mkdocsYml).filter(\n key => !allowedKeys.has(key),\n );\n\n if (removedKeys.length > 0) {\n logger.warn(\n `Removed the following unsupported configuration keys from mkdocs.yml: ${removedKeys.join(\n ', ',\n )}. ` +\n `TechDocs only supports a subset of MkDocs configuration options.`,\n );\n }\n\n // Build a new object with only allowed keys\n const sanitized: Record<string, unknown> = {};\n for (const key of allowedKeys) {\n if (key in mkdocsYml) {\n sanitized[key] = (mkdocsYml as Record<string, unknown>)[key];\n }\n }\n\n // Sanitize markdown_extensions\n const extensions = sanitized.markdown_extensions;\n if (Array.isArray(extensions)) {\n const removedEntries: string[] = [];\n\n sanitized.markdown_extensions = extensions.filter(ext => {\n if (typeof ext === 'string') {\n if (isPymdownxSnippetsExtension(ext)) {\n return true;\n }\n\n if (ext.includes(':')) {\n removedEntries.push(ext);\n return false;\n }\n return true;\n }\n\n if (!ext || typeof ext !== 'object' || Array.isArray(ext)) {\n return true;\n }\n\n // Check every key, not just the first, so that a multi-key mapping\n // cannot smuggle a dangerous name past the filter.\n const extensionEntries = Object.entries(ext as Record<string, unknown>);\n const dangerousNames = extensionEntries\n .map(([extensionName]) => extensionName)\n .filter(\n extensionName =>\n extensionName.includes(':') &&\n !isPymdownxSnippetsExtension(extensionName),\n );\n if (dangerousNames.length > 0) {\n removedEntries.push(...dangerousNames);\n return false;\n }\n\n for (const [extensionName, extensionConfig] of extensionEntries) {\n if (!isPymdownxSnippetsExtension(extensionName)) {\n continue;\n }\n\n if (\n extensionConfig !== null &&\n (typeof extensionConfig !== 'object' ||\n Object.keys(extensionConfig).length > 0)\n ) {\n removedEntries.push(`${extensionName} configuration`);\n }\n Object.assign(ext, { [extensionName]: {} });\n }\n\n // Strip dangerous keys from the extension's own configuration.\n for (const extConfig of Object.values(ext as Record<string, unknown>)) {\n if (\n extConfig &&\n typeof extConfig === 'object' &&\n !Array.isArray(extConfig)\n ) {\n for (const dangerousKey of DANGEROUS_EXTENSION_CONFIG_KEYS) {\n if (dangerousKey in extConfig) {\n delete (extConfig as Record<string, unknown>)[dangerousKey];\n removedEntries.push(dangerousKey);\n }\n }\n }\n }\n\n return true;\n });\n\n if (removedEntries.length > 0) {\n logger.warn(\n `Removed the following dangerous entries from markdown_extensions in mkdocs.yml: ${removedEntries.join(\n ', ',\n )}.`,\n );\n }\n }\n\n // Clear the original object and copy sanitized values back\n for (const key of Object.keys(mkdocsYml)) {\n delete (mkdocsYml as Record<string, unknown>)[key];\n }\n Object.assign(mkdocsYml, sanitized);\n\n // Sanitize theme sub-keys\n const theme = (mkdocsYml as Record<string, unknown>).theme;\n if (isThemeObject(theme)) {\n const removedThemeKeys = Object.keys(theme).filter(\n key => !ALLOWED_THEME_KEYS.has(key),\n );\n if (removedThemeKeys.length > 0) {\n for (const key of removedThemeKeys) {\n delete (theme as Record<string, unknown>)[key];\n }\n logger.warn(\n `Removed the following unsupported keys from theme configuration in mkdocs.yml: ${removedThemeKeys.join(\n ', ',\n )}.`,\n );\n }\n }\n\n // Always rewrite to ensure clean YAML output (resolves merge keys, anchors, etc.)\n return true;\n });\n};\n"],"names":["fs","toError","yaml","MKDOCS_SCHEMA","getRepoUrlFromLocationAnnotation","ALLOWED_MKDOCS_KEYS","DANGEROUS_EXTENSION_CONFIG_KEYS","ALLOWED_THEME_KEYS"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mkdocsPatchers.cjs.js","sources":["../../../src/stages/generate/mkdocsPatchers.ts"],"sourcesContent":["/*\n * Copyright 2022 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 fs from 'fs-extra';\nimport yaml from 'js-yaml';\nimport { ParsedLocationAnnotation } from '../../helpers';\nimport {\n ALLOWED_MKDOCS_KEYS,\n ALLOWED_THEME_KEYS,\n DANGEROUS_EXTENSION_CONFIG_KEYS,\n getRepoUrlFromLocationAnnotation,\n MKDOCS_SCHEMA,\n UnknownTag,\n} from './helpers';\nimport { toError } from '@backstage/errors';\nimport { ScmIntegrationRegistry } from '@backstage/integration';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nconst MATERIAL_THEME = 'material';\nconst PYMDOWNX_SNIPPETS_EXTENSION = 'pymdownx.snippets';\n\nconst DEFAULT_ALLOWED_MKDOCS_PLUGINS = new Set([\n 'techdocs-core',\n 'search',\n 'material/search',\n 'redirects',\n 'group',\n 'material/group',\n]);\nconst MKDOCS_PLUGIN_GROUPS = new Set(['group', 'material/group']);\n\nfunction isPymdownxSnippetsExtension(extensionName: string): boolean {\n return (\n extensionName === PYMDOWNX_SNIPPETS_EXTENSION ||\n extensionName === `${PYMDOWNX_SNIPPETS_EXTENSION}:SnippetExtension`\n );\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value) ||\n value instanceof UnknownTag\n ) {\n return false;\n }\n\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction normalizeMkdocsPlugins(plugins: unknown): unknown[] | undefined {\n if (Array.isArray(plugins)) {\n return plugins;\n }\n if (isPlainObject(plugins)) {\n return Object.entries(plugins).map(([name, config]) => ({\n [name]: config,\n }));\n }\n return undefined;\n}\n\nfunction sanitizeMkdocsPlugins(\n plugins: unknown,\n allowedPlugins: Set<string>,\n removedPlugins: Set<string>,\n): unknown[] {\n if (plugins instanceof UnknownTag) {\n removedPlugins.add('dynamically configured plugin name');\n return [];\n }\n\n const normalizedPlugins = normalizeMkdocsPlugins(plugins);\n if (!normalizedPlugins) {\n removedPlugins.add('malformed plugin declaration');\n return [];\n }\n\n const sanitizedPlugins: unknown[] = [];\n for (const plugin of normalizedPlugins) {\n if (plugin instanceof UnknownTag) {\n removedPlugins.add('dynamically configured plugin name');\n continue;\n }\n\n let pluginName: string;\n if (typeof plugin === 'string' && plugin.length > 0) {\n pluginName = plugin;\n } else if (isPlainObject(plugin) && Object.keys(plugin).length === 1) {\n [pluginName] = Object.keys(plugin);\n const config = plugin[pluginName];\n if (config !== null && !isPlainObject(config)) {\n removedPlugins.add('malformed plugin declaration');\n continue;\n }\n } else {\n removedPlugins.add('malformed plugin declaration');\n continue;\n }\n\n if (!allowedPlugins.has(pluginName)) {\n removedPlugins.add(pluginName);\n continue;\n }\n\n let sanitizedPlugin = plugin;\n if (isPlainObject(plugin)) {\n const config = plugin[pluginName];\n if (MKDOCS_PLUGIN_GROUPS.has(pluginName) && isPlainObject(config)) {\n sanitizedPlugin = {\n ...plugin,\n [pluginName]: {\n ...config,\n plugins: sanitizeMkdocsPlugins(\n config.plugins,\n allowedPlugins,\n removedPlugins,\n ),\n },\n };\n }\n }\n\n sanitizedPlugins.push(sanitizedPlugin);\n }\n\n return sanitizedPlugins;\n}\n\ntype MkDocsThemeObject = {\n name?: string;\n font?: boolean;\n};\n\nfunction isThemeObject(theme: unknown): theme is MkDocsThemeObject {\n return typeof theme === 'object' && theme !== null && !Array.isArray(theme);\n}\n\ntype MkDocsObject = {\n plugins?: string[];\n docs_dir: string;\n repo_url?: string;\n edit_uri?: string;\n theme?: MkDocsThemeObject;\n};\n\nconst patchMkdocsFile = async (\n mkdocsYmlPath: string,\n logger: LoggerService,\n updateAction: (mkdocsYml: MkDocsObject) => boolean,\n options?: { failOnError?: boolean },\n) => {\n // We only want to override the mkdocs.yml if it has actually changed. This is relevant if\n // used with a 'dir' location on the file system as this would permanently update the file.\n let didEdit = false;\n\n let mkdocsYmlFileString;\n try {\n mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');\n } catch (error) {\n logger.warn(\n `Could not read MkDocs YAML config file ${mkdocsYmlPath} before running the generator: ${\n toError(error).message\n }`,\n );\n if (options?.failOnError) {\n throw error;\n }\n return;\n }\n\n let mkdocsYml: any;\n try {\n mkdocsYml = yaml.load(mkdocsYmlFileString, { schema: MKDOCS_SCHEMA });\n\n // mkdocsYml should be an object type after successful parsing.\n // But based on its type definition, it can also be a string or undefined, which we don't want.\n if (typeof mkdocsYml === 'string' || typeof mkdocsYml === 'undefined') {\n throw new Error('Bad YAML format.');\n }\n } catch (error) {\n logger.warn(\n `Error in parsing YAML at ${mkdocsYmlPath} before running the generator. ${\n toError(error).message\n }`,\n );\n if (options?.failOnError) {\n throw error;\n }\n return;\n }\n\n didEdit = updateAction(mkdocsYml);\n\n try {\n if (didEdit) {\n await fs.writeFile(\n mkdocsYmlPath,\n yaml.dump(mkdocsYml, { schema: MKDOCS_SCHEMA }),\n 'utf8',\n );\n }\n } catch (error) {\n logger.warn(\n `Could not write to ${mkdocsYmlPath} after updating it before running the generator. ${\n toError(error).message\n }`,\n );\n if (options?.failOnError) {\n throw error;\n }\n return;\n }\n};\n\nconst patchMkdocsFileOrThrow = async (\n mkdocsYmlPath: string,\n logger: LoggerService,\n updateAction: (mkdocsYml: MkDocsObject) => boolean,\n) =>\n patchMkdocsFile(mkdocsYmlPath, logger, updateAction, {\n failOnError: true,\n });\n\n/**\n * Update the mkdocs.yml file before TechDocs generator uses it to generate docs site.\n *\n * List of tasks:\n * - Add repo_url or edit_uri if it does not exists\n * If mkdocs.yml has a repo_url, the generated docs site gets an Edit button on the pages by default.\n * If repo_url is missing in mkdocs.yml, we will use techdocs annotation of the entity to possibly get\n * the repository URL.\n *\n * This function will not throw an error since this is not critical to the whole TechDocs pipeline.\n * Instead it will log warnings if there are any errors in reading, parsing or writing YAML.\n *\n * @param mkdocsYmlPath - Absolute path to mkdocs.yml or equivalent of a docs site\n * @param logger - A logger instance\n * @param parsedLocationAnnotation - Object with location url and type\n * @param scmIntegrations - the scmIntegration to do url transformations\n */\nexport const patchMkdocsYmlPreBuild = async (\n mkdocsYmlPath: string,\n logger: LoggerService,\n parsedLocationAnnotation: ParsedLocationAnnotation,\n scmIntegrations: ScmIntegrationRegistry,\n) => {\n await patchMkdocsFile(mkdocsYmlPath, logger, mkdocsYml => {\n if (!('repo_url' in mkdocsYml) || !('edit_uri' in mkdocsYml)) {\n // Add edit_uri and/or repo_url to mkdocs.yml if it is missing.\n // This will enable the Page edit button generated by MkDocs.\n // If the either has been set, keep the original value\n const result = getRepoUrlFromLocationAnnotation(\n parsedLocationAnnotation,\n scmIntegrations,\n mkdocsYml.docs_dir,\n );\n\n if (result.repo_url || result.edit_uri) {\n mkdocsYml.repo_url = mkdocsYml.repo_url || result.repo_url;\n mkdocsYml.edit_uri = mkdocsYml.edit_uri || result.edit_uri;\n\n logger.info(\n `Set ${JSON.stringify(\n result,\n )}. You can disable this feature by manually setting 'repo_url' or 'edit_uri' according to the MkDocs documentation at https://www.mkdocs.org/user-guide/configuration/#repo_url`,\n );\n return true;\n }\n }\n return false;\n });\n};\n\n/**\n * Update the mkdocs.yml file before TechDocs generator uses it to generate docs site.\n *\n * List of tasks:\n * - Add all provided default plugins\n *\n * This function will not throw an error since this is not critical to the whole TechDocs pipeline.\n * Instead it will log warnings if there are any errors in reading, parsing or writing YAML.\n *\n * @param mkdocsYmlPath - Absolute path to mkdocs.yml or equivalent of a docs site\n * @param logger - A logger instance\n * @param defaultPlugins - List of default mkdocs plugins\n */\nexport const patchMkdocsYmlWithPlugins = async (\n mkdocsYmlPath: string,\n logger: LoggerService,\n defaultPlugins: string[] = ['techdocs-core'],\n) => {\n await patchMkdocsFile(mkdocsYmlPath, logger, mkdocsYml => {\n // Modify mkdocs.yaml to contain the required default plugins.\n // If no plugins are defined we can just return the defaults.\n if (!('plugins' in mkdocsYml)) {\n mkdocsYml.plugins = defaultPlugins;\n return true;\n }\n\n // Otherwise, check each default plugin and include it if necessary.\n let changesMade = false;\n\n defaultPlugins.forEach(dp => {\n // if the plugin isn't there as a string, and isn't there as an object (which may itself contain extra config)\n // then we need to add it\n if (\n !(\n mkdocsYml.plugins!.includes(dp) ||\n mkdocsYml.plugins!.some(p => p.hasOwnProperty(dp))\n )\n ) {\n mkdocsYml.plugins = [...new Set([...mkdocsYml.plugins!, dp])];\n changesMade = true;\n }\n });\n\n return changesMade;\n });\n};\n\n/**\n * Disable external font download for the material theme.\n * @param mkdocsYmlPath - Absolute path to mkdocs.yml or equivalent of a docs site\n * @param logger\n */\nexport const patchMkdocsYmlWithFontDisabled = async (\n mkdocsYmlPath: string,\n logger: LoggerService,\n) => {\n await patchMkdocsFile(mkdocsYmlPath, logger, mkdocsYml => {\n if (!('theme' in mkdocsYml)) {\n // No theme section exists, create it with font disabled\n mkdocsYml.theme = {\n name: MATERIAL_THEME,\n font: false,\n };\n return true;\n }\n\n const theme = mkdocsYml.theme;\n if (isThemeObject(theme)) {\n // Theme section exists. Only modify it when the configured theme is Material\n if (theme.name === MATERIAL_THEME && !('font' in theme)) {\n theme.font = false;\n return true;\n }\n if (theme.name !== MATERIAL_THEME) {\n logger.debug(\n 'mkdocs.yml theme is not \"material\"; skipping font disabling patch',\n );\n }\n }\n\n return false;\n });\n};\n\n/**\n * Sanitize mkdocs.yml by keeping only allowed configuration keys.\n *\n * TechDocs only supports a subset of MkDocs configuration options.\n * This function reconstructs the config with only allowed keys,\n * discarding everything else. This approach ensures that any unknown\n * or potentially dangerous configuration options are not passed to MkDocs.\n *\n * The file is always rewritten to ensure YAML features like merge keys\n * and anchors are resolved into plain configuration.\n *\n * @param mkdocsYmlPath - Absolute path to mkdocs.yml or equivalent of a docs site\n * @param logger - A logger instance\n * @param additionalAllowedKeys - Optional array of additional keys to allow beyond the default allowlist\n * @param additionalAllowedPlugins - Optional array of additional plugins to allow beyond the default set\n */\nexport const sanitizeMkdocsYml = async (\n mkdocsYmlPath: string,\n logger: LoggerService,\n additionalAllowedKeys?: string[],\n additionalAllowedPlugins?: string[],\n) => {\n await patchMkdocsFileOrThrow(mkdocsYmlPath, logger, mkdocsYml => {\n // Combine default allowed keys with additional keys\n const allowedKeys = new Set(ALLOWED_MKDOCS_KEYS);\n if (additionalAllowedKeys && additionalAllowedKeys.length > 0) {\n logger.warn(\n `DANGEROUS: Allowing additional MkDocs configuration keys beyond the default safe allowlist: ${additionalAllowedKeys.join(\n ', ',\n )}. This may introduce security vulnerabilities. Only use in trusted environments.`,\n );\n additionalAllowedKeys.forEach(key => allowedKeys.add(key));\n }\n\n // Identify keys that will be removed for logging\n const removedKeys = Object.keys(mkdocsYml).filter(\n key => !allowedKeys.has(key),\n );\n\n if (removedKeys.length > 0) {\n logger.warn(\n `Removed the following unsupported configuration keys from mkdocs.yml: ${removedKeys.join(\n ', ',\n )}. ` +\n `TechDocs only supports a subset of MkDocs configuration options.`,\n );\n }\n\n // Build a new object with only allowed keys\n const sanitized: Record<string, unknown> = {};\n for (const key of allowedKeys) {\n if (key in mkdocsYml) {\n sanitized[key] = (mkdocsYml as Record<string, unknown>)[key];\n }\n }\n\n // Sanitize markdown_extensions\n const extensions = sanitized.markdown_extensions;\n if (Array.isArray(extensions)) {\n const removedEntries: string[] = [];\n\n sanitized.markdown_extensions = extensions.filter(ext => {\n if (typeof ext === 'string') {\n if (isPymdownxSnippetsExtension(ext)) {\n return true;\n }\n\n if (ext.includes(':')) {\n removedEntries.push(ext);\n return false;\n }\n return true;\n }\n\n if (!ext || typeof ext !== 'object' || Array.isArray(ext)) {\n return true;\n }\n\n // Check every key, not just the first, so that a multi-key mapping\n // cannot smuggle a dangerous name past the filter.\n const extensionEntries = Object.entries(ext as Record<string, unknown>);\n const dangerousNames = extensionEntries\n .map(([extensionName]) => extensionName)\n .filter(\n extensionName =>\n extensionName.includes(':') &&\n !isPymdownxSnippetsExtension(extensionName),\n );\n if (dangerousNames.length > 0) {\n removedEntries.push(...dangerousNames);\n return false;\n }\n\n for (const [extensionName, extensionConfig] of extensionEntries) {\n if (!isPymdownxSnippetsExtension(extensionName)) {\n continue;\n }\n\n if (\n extensionConfig !== null &&\n (typeof extensionConfig !== 'object' ||\n Object.keys(extensionConfig).length > 0)\n ) {\n removedEntries.push(`${extensionName} configuration`);\n }\n Object.assign(ext, { [extensionName]: {} });\n }\n\n // Strip dangerous keys from the extension's own configuration.\n for (const extConfig of Object.values(ext as Record<string, unknown>)) {\n if (\n extConfig &&\n typeof extConfig === 'object' &&\n !Array.isArray(extConfig)\n ) {\n for (const dangerousKey of DANGEROUS_EXTENSION_CONFIG_KEYS) {\n if (dangerousKey in extConfig) {\n delete (extConfig as Record<string, unknown>)[dangerousKey];\n removedEntries.push(dangerousKey);\n }\n }\n }\n }\n\n return true;\n });\n\n if (removedEntries.length > 0) {\n logger.warn(\n `Removed the following dangerous entries from markdown_extensions in mkdocs.yml: ${removedEntries.join(\n ', ',\n )}.`,\n );\n }\n }\n\n if ('plugins' in sanitized) {\n const allowedPlugins = new Set(DEFAULT_ALLOWED_MKDOCS_PLUGINS);\n additionalAllowedPlugins?.forEach(plugin => allowedPlugins.add(plugin));\n\n const removedPlugins = new Set<string>();\n sanitized.plugins = sanitizeMkdocsPlugins(\n sanitized.plugins,\n allowedPlugins,\n removedPlugins,\n );\n\n if (removedPlugins.size > 0) {\n logger.warn(\n `Removed unsupported MkDocs plugins from mkdocs.yml: ${Array.from(\n removedPlugins,\n ).join(\n ', ',\n )}. To allow additional plugins, configure 'techdocs.generator.mkdocs.dangerouslyAllowAdditionalPlugins' in your Backstage app-config. When using the TechDocs CLI, use '--defaultPlugin' instead.`,\n );\n }\n }\n\n // Clear the original object and copy sanitized values back\n for (const key of Object.keys(mkdocsYml)) {\n delete (mkdocsYml as Record<string, unknown>)[key];\n }\n Object.assign(mkdocsYml, sanitized);\n\n // Sanitize theme sub-keys\n const theme = (mkdocsYml as Record<string, unknown>).theme;\n if (isThemeObject(theme)) {\n const removedThemeKeys = Object.keys(theme).filter(\n key => !ALLOWED_THEME_KEYS.has(key),\n );\n if (removedThemeKeys.length > 0) {\n for (const key of removedThemeKeys) {\n delete (theme as Record<string, unknown>)[key];\n }\n logger.warn(\n `Removed the following unsupported keys from theme configuration in mkdocs.yml: ${removedThemeKeys.join(\n ', ',\n )}.`,\n );\n }\n }\n\n // Always rewrite to ensure clean YAML output (resolves merge keys, anchors, etc.)\n return true;\n });\n};\n"],"names":["UnknownTag","fs","toError","yaml","MKDOCS_SCHEMA","getRepoUrlFromLocationAnnotation","ALLOWED_MKDOCS_KEYS","DANGEROUS_EXTENSION_CONFIG_KEYS","ALLOWED_THEME_KEYS"],"mappings":"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"}
|
|
@@ -71,10 +71,15 @@ class TechdocsGenerator {
|
|
|
71
71
|
);
|
|
72
72
|
}
|
|
73
73
|
const docsDir = await helpers.validateMkdocsYaml(inputDir, content);
|
|
74
|
+
const defaultPlugins = [...this.options.defaultPlugins ?? []];
|
|
74
75
|
await mkdocsPatchers.sanitizeMkdocsYml(
|
|
75
76
|
mkdocsYmlPath,
|
|
76
77
|
childLogger,
|
|
77
|
-
this.options.dangerouslyAllowAdditionalKeys
|
|
78
|
+
this.options.dangerouslyAllowAdditionalKeys,
|
|
79
|
+
[
|
|
80
|
+
...this.options.dangerouslyAllowAdditionalPlugins ?? [],
|
|
81
|
+
...defaultPlugins
|
|
82
|
+
]
|
|
78
83
|
);
|
|
79
84
|
if (parsedLocationAnnotation) {
|
|
80
85
|
await mkdocsPatchers.patchMkdocsYmlPreBuild(
|
|
@@ -88,7 +93,6 @@ class TechdocsGenerator {
|
|
|
88
93
|
await helpers.patchIndexPreBuild({ inputDir, logger: childLogger, docsDir });
|
|
89
94
|
}
|
|
90
95
|
await helpers.validateInputDirectory(inputDir);
|
|
91
|
-
const defaultPlugins = this.options.defaultPlugins ?? [];
|
|
92
96
|
if (!this.options.omitTechdocsCoreMkdocsPlugin && !defaultPlugins.includes("techdocs-core")) {
|
|
93
97
|
defaultPlugins.push("techdocs-core");
|
|
94
98
|
}
|
|
@@ -202,6 +206,9 @@ function readGeneratorConfig(config, logger) {
|
|
|
202
206
|
dangerouslyAllowAdditionalKeys: config.getOptionalStringArray(
|
|
203
207
|
"techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys"
|
|
204
208
|
),
|
|
209
|
+
dangerouslyAllowAdditionalPlugins: config.getOptionalStringArray(
|
|
210
|
+
"techdocs.generator.mkdocs.dangerouslyAllowAdditionalPlugins"
|
|
211
|
+
),
|
|
205
212
|
disableExternalFonts: config.getOptionalBoolean(
|
|
206
213
|
"techdocs.generator.mkdocs.disableExternalFonts"
|
|
207
214
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"techdocs.cjs.js","sources":["../../../src/stages/generate/techdocs.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 fs from 'fs-extra';\nimport path from 'node:path';\nimport {\n ScmIntegrationRegistry,\n ScmIntegrations,\n} from '@backstage/integration';\nimport {\n createOrUpdateMetadata,\n getMkdocsYml,\n patchIndexPreBuild,\n runCommand,\n storeEtagMetadata,\n validateInputDirectory,\n validateMkdocsYaml,\n} from './helpers';\n\nimport {\n patchMkdocsYmlPreBuild,\n patchMkdocsYmlWithFontDisabled,\n patchMkdocsYmlWithPlugins,\n sanitizeMkdocsYml,\n} from './mkdocsPatchers';\nimport {\n ContainerRunnerPullOptions,\n GeneratorBase,\n GeneratorConfig,\n GeneratorOptions,\n GeneratorRunInType,\n GeneratorRunOptions,\n} from './types';\nimport { ForwardedError } from '@backstage/errors';\nimport { DockerContainerRunner } from './DockerContainerRunner';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { TechDocsContainerRunner } from './types';\n\n/**\n * Generates documentation files\n * @public\n */\nexport class TechdocsGenerator implements GeneratorBase {\n /**\n * The default docker image (and version) used to generate content. Public\n * and static so that techdocs-node consumers can use the same version.\n *\n * See {@link https://hub.docker.com/r/spotify/techdocs/tags} for list of available versions.\n */\n public static readonly defaultDockerImage = 'spotify/techdocs:v1.2.8';\n private readonly logger: LoggerService;\n private readonly containerRunner?: TechDocsContainerRunner;\n private readonly options: GeneratorConfig;\n private readonly scmIntegrations: ScmIntegrationRegistry;\n\n /**\n * Returns a instance of TechDocs generator\n * @param config - A Backstage configuration\n * @param options - Options to configure the generator\n */\n static fromConfig(config: Config, options: GeneratorOptions) {\n const { containerRunner, logger } = options;\n const scmIntegrations = ScmIntegrations.fromConfig(config);\n return new TechdocsGenerator({\n logger,\n containerRunner,\n config,\n scmIntegrations,\n });\n }\n\n constructor(options: {\n logger: LoggerService;\n containerRunner?: TechDocsContainerRunner;\n config: Config;\n scmIntegrations: ScmIntegrationRegistry;\n }) {\n this.logger = options.logger;\n this.options = readGeneratorConfig(options.config, options.logger);\n this.containerRunner = options.containerRunner;\n this.scmIntegrations = options.scmIntegrations;\n }\n\n /** {@inheritDoc GeneratorBase.run} */\n public async run(options: GeneratorRunOptions): Promise<void> {\n const {\n inputDir,\n outputDir,\n parsedLocationAnnotation,\n etag,\n logger: childLogger,\n logStream,\n siteOptions,\n runAsDefaultUser,\n } = options;\n\n // Do some updates to mkdocs.yml before generating docs e.g. adding repo_url\n const { path: mkdocsYmlPath, content } = await getMkdocsYml(\n inputDir,\n siteOptions,\n );\n\n // Warn if both config files exist — MkDocs and Backstage resolve them in\n // different order, so a second file would be silently ignored by one side.\n const alternateConfigName =\n path.basename(mkdocsYmlPath) === 'mkdocs.yaml'\n ? 'mkdocs.yml'\n : 'mkdocs.yaml';\n if (await fs.pathExists(path.join(inputDir, alternateConfigName))) {\n childLogger.warn(\n `Both mkdocs.yml and mkdocs.yaml found in ${inputDir}; using ${path.basename(\n mkdocsYmlPath,\n )}. The other file will be ignored.`,\n );\n }\n\n // validate the docs_dir first\n const docsDir = await validateMkdocsYaml(inputDir, content);\n\n // Remove unsupported configuration keys\n await sanitizeMkdocsYml(\n mkdocsYmlPath,\n childLogger,\n this.options.dangerouslyAllowAdditionalKeys,\n );\n\n if (parsedLocationAnnotation) {\n await patchMkdocsYmlPreBuild(\n mkdocsYmlPath,\n childLogger,\n parsedLocationAnnotation,\n this.scmIntegrations,\n );\n }\n\n if (this.options.legacyCopyReadmeMdToIndexMd) {\n await patchIndexPreBuild({ inputDir, logger: childLogger, docsDir });\n }\n\n // Validate that no symlinks in the input directory point outside it. MkDocs\n // extensions can access files throughout the input directory, not just docs_dir.\n await validateInputDirectory(inputDir);\n\n // patch the list of mkdocs plugins\n const defaultPlugins = this.options.defaultPlugins ?? [];\n\n if (\n !this.options.omitTechdocsCoreMkdocsPlugin &&\n !defaultPlugins.includes('techdocs-core')\n ) {\n defaultPlugins.push('techdocs-core');\n }\n\n await patchMkdocsYmlWithPlugins(mkdocsYmlPath, childLogger, defaultPlugins);\n if (this.options.disableExternalFonts) {\n await patchMkdocsYmlWithFontDisabled(mkdocsYmlPath, childLogger);\n }\n\n // Directories to bind on container\n const mountDirs = {\n [inputDir]: '/input',\n [outputDir]: '/output',\n };\n\n try {\n switch (this.options.runIn) {\n case 'local':\n await runCommand({\n command: 'mkdocs',\n args: [\n 'build',\n '-f',\n path.relative(inputDir, mkdocsYmlPath),\n '-d',\n outputDir,\n '-v',\n ],\n options: {\n cwd: inputDir,\n },\n logStream,\n });\n childLogger.info(\n `Successfully generated docs from ${inputDir} into ${outputDir} using local mkdocs`,\n );\n break;\n case 'docker': {\n const containerRunner =\n this.containerRunner || new DockerContainerRunner();\n await containerRunner.runContainer({\n imageName:\n this.options.dockerImage ?? TechdocsGenerator.defaultDockerImage,\n args: [\n 'build',\n '-f',\n `/input/${path.basename(mkdocsYmlPath)}`,\n '-d',\n '/output',\n ],\n logStream,\n mountDirs,\n workingDir: '/input',\n // Set the home directory inside the container as something that applications can\n // write to, otherwise they will just fail trying to write to /\n envVars: { HOME: '/tmp' },\n pullImage: this.options.pullImage,\n pullOptions: this.options.pullOptions,\n defaultUser: runAsDefaultUser,\n });\n childLogger.info(\n `Successfully generated docs from ${inputDir} into ${outputDir} using techdocs-container`,\n );\n break;\n }\n default:\n throw new Error(\n `Invalid config value \"${this.options.runIn}\" provided in 'techdocs.generators.techdocs'.`,\n );\n }\n } catch (error) {\n this.logger.debug(\n `Failed to generate docs from ${inputDir} into ${outputDir}`,\n );\n throw new ForwardedError(\n `Failed to generate docs from ${inputDir} into ${outputDir}`,\n error,\n );\n }\n\n /**\n * Post Generate steps\n */\n\n // Add build timestamp and files to techdocs_metadata.json\n // Creates techdocs_metadata.json if file does not exist.\n await createOrUpdateMetadata(\n path.join(outputDir, 'techdocs_metadata.json'),\n childLogger,\n );\n\n // Add etag of the prepared tree to techdocs_metadata.json\n // Assumes that the file already exists.\n if (etag) {\n await storeEtagMetadata(\n path.join(outputDir, 'techdocs_metadata.json'),\n etag,\n );\n }\n }\n}\n\nexport function readGeneratorConfig(\n config: Config,\n logger: LoggerService,\n): GeneratorConfig {\n const legacyGeneratorType = config.getOptionalString(\n 'techdocs.generators.techdocs',\n ) as GeneratorRunInType;\n\n if (legacyGeneratorType) {\n logger.warn(\n `The 'techdocs.generators.techdocs' configuration key is deprecated and will be removed in the future. Please use 'techdocs.generator' instead. ` +\n `See here https://backstage.io/docs/features/techdocs/configuration`,\n );\n }\n\n return {\n runIn:\n legacyGeneratorType ??\n config.getOptionalString('techdocs.generator.runIn') ??\n 'docker',\n dockerImage: config.getOptionalString('techdocs.generator.dockerImage'),\n pullImage: config.getOptionalBoolean('techdocs.generator.pullImage'),\n pullOptions: config.getOptional<ContainerRunnerPullOptions>(\n 'techdocs.generator.pullOptions',\n ),\n omitTechdocsCoreMkdocsPlugin: config.getOptionalBoolean(\n 'techdocs.generator.mkdocs.omitTechdocsCorePlugin',\n ),\n legacyCopyReadmeMdToIndexMd: config.getOptionalBoolean(\n 'techdocs.generator.mkdocs.legacyCopyReadmeMdToIndexMd',\n ),\n defaultPlugins: config.getOptionalStringArray(\n 'techdocs.generator.mkdocs.defaultPlugins',\n ),\n dangerouslyAllowAdditionalKeys: config.getOptionalStringArray(\n 'techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys',\n ),\n disableExternalFonts: config.getOptionalBoolean(\n 'techdocs.generator.mkdocs.disableExternalFonts',\n ),\n };\n}\n"],"names":["ScmIntegrations","getMkdocsYml","path","fs","validateMkdocsYaml","sanitizeMkdocsYml","patchMkdocsYmlPreBuild","patchIndexPreBuild","validateInputDirectory","patchMkdocsYmlWithPlugins","patchMkdocsYmlWithFontDisabled","runCommand","DockerContainerRunner","ForwardedError","createOrUpdateMetadata","storeEtagMetadata"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"techdocs.cjs.js","sources":["../../../src/stages/generate/techdocs.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 fs from 'fs-extra';\nimport path from 'node:path';\nimport {\n ScmIntegrationRegistry,\n ScmIntegrations,\n} from '@backstage/integration';\nimport {\n createOrUpdateMetadata,\n getMkdocsYml,\n patchIndexPreBuild,\n runCommand,\n storeEtagMetadata,\n validateInputDirectory,\n validateMkdocsYaml,\n} from './helpers';\n\nimport {\n patchMkdocsYmlPreBuild,\n patchMkdocsYmlWithFontDisabled,\n patchMkdocsYmlWithPlugins,\n sanitizeMkdocsYml,\n} from './mkdocsPatchers';\nimport {\n ContainerRunnerPullOptions,\n GeneratorBase,\n GeneratorConfig,\n GeneratorOptions,\n GeneratorRunInType,\n GeneratorRunOptions,\n} from './types';\nimport { ForwardedError } from '@backstage/errors';\nimport { DockerContainerRunner } from './DockerContainerRunner';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { TechDocsContainerRunner } from './types';\n\n/**\n * Generates documentation files\n * @public\n */\nexport class TechdocsGenerator implements GeneratorBase {\n /**\n * The default docker image (and version) used to generate content. Public\n * and static so that techdocs-node consumers can use the same version.\n *\n * See {@link https://hub.docker.com/r/spotify/techdocs/tags} for list of available versions.\n */\n public static readonly defaultDockerImage = 'spotify/techdocs:v1.2.8';\n private readonly logger: LoggerService;\n private readonly containerRunner?: TechDocsContainerRunner;\n private readonly options: GeneratorConfig;\n private readonly scmIntegrations: ScmIntegrationRegistry;\n\n /**\n * Returns a instance of TechDocs generator\n * @param config - A Backstage configuration\n * @param options - Options to configure the generator\n */\n static fromConfig(config: Config, options: GeneratorOptions) {\n const { containerRunner, logger } = options;\n const scmIntegrations = ScmIntegrations.fromConfig(config);\n return new TechdocsGenerator({\n logger,\n containerRunner,\n config,\n scmIntegrations,\n });\n }\n\n constructor(options: {\n logger: LoggerService;\n containerRunner?: TechDocsContainerRunner;\n config: Config;\n scmIntegrations: ScmIntegrationRegistry;\n }) {\n this.logger = options.logger;\n this.options = readGeneratorConfig(options.config, options.logger);\n this.containerRunner = options.containerRunner;\n this.scmIntegrations = options.scmIntegrations;\n }\n\n /** {@inheritDoc GeneratorBase.run} */\n public async run(options: GeneratorRunOptions): Promise<void> {\n const {\n inputDir,\n outputDir,\n parsedLocationAnnotation,\n etag,\n logger: childLogger,\n logStream,\n siteOptions,\n runAsDefaultUser,\n } = options;\n\n // Do some updates to mkdocs.yml before generating docs e.g. adding repo_url\n const { path: mkdocsYmlPath, content } = await getMkdocsYml(\n inputDir,\n siteOptions,\n );\n\n // Warn if both config files exist — MkDocs and Backstage resolve them in\n // different order, so a second file would be silently ignored by one side.\n const alternateConfigName =\n path.basename(mkdocsYmlPath) === 'mkdocs.yaml'\n ? 'mkdocs.yml'\n : 'mkdocs.yaml';\n if (await fs.pathExists(path.join(inputDir, alternateConfigName))) {\n childLogger.warn(\n `Both mkdocs.yml and mkdocs.yaml found in ${inputDir}; using ${path.basename(\n mkdocsYmlPath,\n )}. The other file will be ignored.`,\n );\n }\n\n // validate the docs_dir first\n const docsDir = await validateMkdocsYaml(inputDir, content);\n\n const defaultPlugins = [...(this.options.defaultPlugins ?? [])];\n\n // Remove unsupported configuration keys and plugins\n await sanitizeMkdocsYml(\n mkdocsYmlPath,\n childLogger,\n this.options.dangerouslyAllowAdditionalKeys,\n [\n ...(this.options.dangerouslyAllowAdditionalPlugins ?? []),\n ...defaultPlugins,\n ],\n );\n\n if (parsedLocationAnnotation) {\n await patchMkdocsYmlPreBuild(\n mkdocsYmlPath,\n childLogger,\n parsedLocationAnnotation,\n this.scmIntegrations,\n );\n }\n\n if (this.options.legacyCopyReadmeMdToIndexMd) {\n await patchIndexPreBuild({ inputDir, logger: childLogger, docsDir });\n }\n\n // Validate that no symlinks in the input directory point outside it. MkDocs\n // extensions can access files throughout the input directory, not just docs_dir.\n await validateInputDirectory(inputDir);\n\n // patch the list of mkdocs plugins\n if (\n !this.options.omitTechdocsCoreMkdocsPlugin &&\n !defaultPlugins.includes('techdocs-core')\n ) {\n defaultPlugins.push('techdocs-core');\n }\n\n await patchMkdocsYmlWithPlugins(mkdocsYmlPath, childLogger, defaultPlugins);\n if (this.options.disableExternalFonts) {\n await patchMkdocsYmlWithFontDisabled(mkdocsYmlPath, childLogger);\n }\n\n // Directories to bind on container\n const mountDirs = {\n [inputDir]: '/input',\n [outputDir]: '/output',\n };\n\n try {\n switch (this.options.runIn) {\n case 'local':\n await runCommand({\n command: 'mkdocs',\n args: [\n 'build',\n '-f',\n path.relative(inputDir, mkdocsYmlPath),\n '-d',\n outputDir,\n '-v',\n ],\n options: {\n cwd: inputDir,\n },\n logStream,\n });\n childLogger.info(\n `Successfully generated docs from ${inputDir} into ${outputDir} using local mkdocs`,\n );\n break;\n case 'docker': {\n const containerRunner =\n this.containerRunner || new DockerContainerRunner();\n await containerRunner.runContainer({\n imageName:\n this.options.dockerImage ?? TechdocsGenerator.defaultDockerImage,\n args: [\n 'build',\n '-f',\n `/input/${path.basename(mkdocsYmlPath)}`,\n '-d',\n '/output',\n ],\n logStream,\n mountDirs,\n workingDir: '/input',\n // Set the home directory inside the container as something that applications can\n // write to, otherwise they will just fail trying to write to /\n envVars: { HOME: '/tmp' },\n pullImage: this.options.pullImage,\n pullOptions: this.options.pullOptions,\n defaultUser: runAsDefaultUser,\n });\n childLogger.info(\n `Successfully generated docs from ${inputDir} into ${outputDir} using techdocs-container`,\n );\n break;\n }\n default:\n throw new Error(\n `Invalid config value \"${this.options.runIn}\" provided in 'techdocs.generators.techdocs'.`,\n );\n }\n } catch (error) {\n this.logger.debug(\n `Failed to generate docs from ${inputDir} into ${outputDir}`,\n );\n throw new ForwardedError(\n `Failed to generate docs from ${inputDir} into ${outputDir}`,\n error,\n );\n }\n\n /**\n * Post Generate steps\n */\n\n // Add build timestamp and files to techdocs_metadata.json\n // Creates techdocs_metadata.json if file does not exist.\n await createOrUpdateMetadata(\n path.join(outputDir, 'techdocs_metadata.json'),\n childLogger,\n );\n\n // Add etag of the prepared tree to techdocs_metadata.json\n // Assumes that the file already exists.\n if (etag) {\n await storeEtagMetadata(\n path.join(outputDir, 'techdocs_metadata.json'),\n etag,\n );\n }\n }\n}\n\nexport function readGeneratorConfig(\n config: Config,\n logger: LoggerService,\n): GeneratorConfig {\n const legacyGeneratorType = config.getOptionalString(\n 'techdocs.generators.techdocs',\n ) as GeneratorRunInType;\n\n if (legacyGeneratorType) {\n logger.warn(\n `The 'techdocs.generators.techdocs' configuration key is deprecated and will be removed in the future. Please use 'techdocs.generator' instead. ` +\n `See here https://backstage.io/docs/features/techdocs/configuration`,\n );\n }\n\n return {\n runIn:\n legacyGeneratorType ??\n config.getOptionalString('techdocs.generator.runIn') ??\n 'docker',\n dockerImage: config.getOptionalString('techdocs.generator.dockerImage'),\n pullImage: config.getOptionalBoolean('techdocs.generator.pullImage'),\n pullOptions: config.getOptional<ContainerRunnerPullOptions>(\n 'techdocs.generator.pullOptions',\n ),\n omitTechdocsCoreMkdocsPlugin: config.getOptionalBoolean(\n 'techdocs.generator.mkdocs.omitTechdocsCorePlugin',\n ),\n legacyCopyReadmeMdToIndexMd: config.getOptionalBoolean(\n 'techdocs.generator.mkdocs.legacyCopyReadmeMdToIndexMd',\n ),\n defaultPlugins: config.getOptionalStringArray(\n 'techdocs.generator.mkdocs.defaultPlugins',\n ),\n dangerouslyAllowAdditionalKeys: config.getOptionalStringArray(\n 'techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys',\n ),\n dangerouslyAllowAdditionalPlugins: config.getOptionalStringArray(\n 'techdocs.generator.mkdocs.dangerouslyAllowAdditionalPlugins',\n ),\n disableExternalFonts: config.getOptionalBoolean(\n 'techdocs.generator.mkdocs.disableExternalFonts',\n ),\n };\n}\n"],"names":["ScmIntegrations","getMkdocsYml","path","fs","validateMkdocsYaml","sanitizeMkdocsYml","patchMkdocsYmlPreBuild","patchIndexPreBuild","validateInputDirectory","patchMkdocsYmlWithPlugins","patchMkdocsYmlWithFontDisabled","runCommand","DockerContainerRunner","ForwardedError","createOrUpdateMetadata","storeEtagMetadata"],"mappings":"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"}
|
|
@@ -314,18 +314,9 @@ class AwsS3Publish {
|
|
|
314
314
|
useLegacyPathCasing,
|
|
315
315
|
bucketRootPath
|
|
316
316
|
);
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
Bucket: this.bucketName,
|
|
321
|
-
Prefix: remoteFolder
|
|
322
|
-
});
|
|
323
|
-
return this.storageClient.send(listCommand);
|
|
324
|
-
},
|
|
325
|
-
"ListObjects",
|
|
326
|
-
this.maxAttempts
|
|
327
|
-
);
|
|
328
|
-
existingFiles = (response.Contents || []).map((f) => f.Key || "").filter((f) => !!f);
|
|
317
|
+
existingFiles = await this.getAllObjectsFromBucket({
|
|
318
|
+
prefix: remoteFolder
|
|
319
|
+
});
|
|
329
320
|
} catch (e) {
|
|
330
321
|
this.logger.error(
|
|
331
322
|
`Unable to list files for Entity ${entity.metadata.name}: ${errors.toError(e).message}`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"awsS3.cjs.js","sources":["../../../src/stages/publish/awsS3.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 { ForwardedError, toError } from '@backstage/errors';\n\n// Maximum size in bytes for a single upload part (5MB)\nconst MAX_SINGLE_UPLOAD_BYTES = 5 * 1024 * 1024;\n\nimport {\n AwsCredentialsManager,\n DefaultAwsCredentialsManager,\n} from '@backstage/integration-aws-node';\nimport {\n GetObjectCommand,\n CopyObjectCommand,\n DeleteObjectCommand,\n HeadBucketCommand,\n HeadObjectCommand,\n PutObjectCommand,\n PutObjectCommandInput,\n ListObjectsV2CommandOutput,\n ListObjectsV2Command,\n S3Client,\n S3ServiceException,\n} from '@aws-sdk/client-s3';\nimport { fromTemporaryCredentials } from '@aws-sdk/credential-providers';\nimport { NodeHttpHandler } from '@smithy/node-http-handler';\nimport { Upload } from '@aws-sdk/lib-storage';\nimport { AwsCredentialIdentityProvider } from '@aws-sdk/types';\nimport { HttpsProxyAgent } from 'hpagent';\nimport express from 'express';\nimport fs from 'fs-extra';\nimport JSON5 from 'json5';\nimport createLimiter from 'p-limit';\nimport path from 'node:path';\nimport { Readable } from 'node:stream';\nimport {\n bulkStorageOperation,\n getCloudPathForLocalPath,\n getFileTreeRecursively,\n getHeadersForFileExtension,\n getStaleFiles,\n isValidContentPath,\n lowerCaseEntityTriplet,\n lowerCaseEntityTripletInStoragePath,\n normalizeExternalStorageRootPath,\n} from './helpers';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { AwsS3Integration, ScmIntegrations } from '@backstage/integration';\n\nconst streamToBuffer = (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', (e: Error) =>\n reject(new ForwardedError('Unable to read stream', e)),\n );\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\nexport class AwsS3Publish implements PublisherBase {\n public readonly storageClient: S3Client;\n private readonly bucketName: string;\n private readonly legacyPathCasing: boolean;\n private readonly logger: LoggerService;\n private readonly bucketRootPath: string;\n private readonly sse?: 'aws:kms' | 'AES256';\n private readonly maxAttempts: number;\n\n constructor(options: {\n storageClient: S3Client;\n bucketName: string;\n legacyPathCasing: boolean;\n logger: LoggerService;\n bucketRootPath: string;\n sse?: 'aws:kms' | 'AES256';\n maxAttempts: number;\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 this.sse = options.sse;\n this.maxAttempts = options.maxAttempts;\n }\n\n static async fromConfig(\n config: Config,\n logger: LoggerService,\n ): Promise<PublisherBase> {\n let bucketName = '';\n try {\n bucketName = config.getString('techdocs.publisher.awsS3.bucketName');\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'awsS3' in your app config, \" +\n 'techdocs.publisher.awsS3.bucketName is required.',\n );\n }\n\n const bucketRootPath = normalizeExternalStorageRootPath(\n config.getOptionalString('techdocs.publisher.awsS3.bucketRootPath') || '',\n );\n\n const sse = config.getOptionalString('techdocs.publisher.awsS3.sse') as\n | 'aws:kms'\n | 'AES256'\n | undefined;\n\n // AWS Region is an optional config. If missing, default AWS env variable AWS_REGION\n // or AWS shared credentials file at ~/.aws/credentials will be used.\n const region = config.getOptionalString('techdocs.publisher.awsS3.region');\n\n // Credentials can optionally be configured by specifying the AWS account ID, which will retrieve credentials\n // for the account from the 'aws' section of the app config.\n // Credentials can also optionally be directly configured in the techdocs awsS3 config, but this method is\n // deprecated.\n // If no credentials are configured, the AWS SDK V3's default credential chain will be used.\n const accountId = config.getOptionalString(\n 'techdocs.publisher.awsS3.accountId',\n );\n const credentialsConfig = config.getOptionalConfig(\n 'techdocs.publisher.awsS3.credentials',\n );\n\n const credsManager = DefaultAwsCredentialsManager.fromConfig(config);\n\n const scmIntegrations = ScmIntegrations.fromConfig(config);\n const awsS3Integrations = scmIntegrations.awsS3.list();\n\n const sdkCredentialProvider = await AwsS3Publish.buildCredentials(\n credsManager,\n logger,\n awsS3Integrations,\n accountId,\n credentialsConfig,\n region,\n );\n\n // AWS endpoint is an optional config. If missing, the default endpoint is built from\n // the configured region.\n const endpoint = config.getOptionalString(\n 'techdocs.publisher.awsS3.endpoint',\n );\n\n // AWS HTTPS proxy is an optional config. If missing, no proxy is used\n const httpsProxy = config.getOptionalString(\n 'techdocs.publisher.awsS3.httpsProxy',\n );\n\n // AWS forcePathStyle is an optional config. If missing, it defaults to false. Needs to be enabled for cases\n // where endpoint url points to locally hosted S3 compatible storage like Localstack\n const forcePathStyle = config.getOptionalBoolean(\n 'techdocs.publisher.awsS3.s3ForcePathStyle',\n );\n\n // AWS MAX ATTEMPTS is an optional config. If missing, default value of 5 is used\n const maxAttempts = config.getOptionalNumber(\n 'techdocs.publisher.awsS3.maxAttempts',\n );\n\n const storageClient = new S3Client({\n customUserAgent: 'backstage-aws-techdocs-s3-publisher',\n credentialDefaultProvider: () => sdkCredentialProvider,\n ...(region && { region }),\n ...(endpoint && { endpoint }),\n ...(forcePathStyle && { forcePathStyle }),\n // Enhanced retry configuration for better reliability\n maxAttempts: maxAttempts || 5,\n retryMode: 'adaptive',\n // Enhanced connection settings for large file uploads\n requestHandler: new NodeHttpHandler({\n ...(httpsProxy && {\n httpsAgent: new HttpsProxyAgent({ proxy: httpsProxy }),\n }),\n connectionTimeout: 60000,\n socketTimeout: 120000,\n }),\n });\n\n const legacyPathCasing =\n config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n ) || false;\n\n return new AwsS3Publish({\n storageClient,\n bucketName,\n bucketRootPath,\n legacyPathCasing,\n logger,\n sse,\n maxAttempts: maxAttempts || 5,\n });\n }\n\n private static buildStaticCredentials(\n accessKeyId: string,\n secretAccessKey: string,\n ): AwsCredentialIdentityProvider {\n return async () => {\n return Promise.resolve({\n accessKeyId,\n secretAccessKey,\n });\n };\n }\n\n private static async buildCredentials(\n credsManager: AwsCredentialsManager,\n logger: LoggerService,\n awsS3Integrations: AwsS3Integration[],\n accountId?: string,\n credentialsConfig?: Config,\n region?: string,\n ): Promise<AwsCredentialIdentityProvider> {\n // Pull credentials for the specified account ID from the 'aws' config section\n if (accountId) {\n return (await credsManager.getCredentialProvider({ accountId }))\n .sdkCredentialProvider;\n }\n\n const explicitCredentials = await AwsS3Publish.getExplicitCredentials({\n credsManager,\n credentialsConfig,\n awsS3Integrations,\n logger,\n });\n\n const roleArn = credentialsConfig?.getOptionalString('roleArn');\n if (roleArn) {\n return fromTemporaryCredentials({\n masterCredentials: explicitCredentials,\n params: {\n RoleSessionName: 'backstage-aws-techdocs-s3-publisher',\n RoleArn: roleArn,\n },\n clientConfig: { region },\n });\n }\n\n return explicitCredentials;\n }\n /**\n * Custom retry wrapper for S3 operations with detailed error handling.\n */\n public async retryOperation<TOutput>(\n operation: () => Promise<TOutput>,\n operationName: string,\n maxAttempts: number = 3,\n shouldRetry: (\n error: S3ServiceException,\n ) => boolean = this.defaultShouldRetry.bind(this),\n ): Promise<TOutput> {\n for (let attempt = 1; attempt < maxAttempts; attempt++) {\n try {\n return await operation();\n } catch (error) {\n const e = error as S3ServiceException;\n if (!shouldRetry(e)) {\n this.logger.error(`${operationName} failed: ${e.message}`);\n throw e;\n }\n\n this.logger.warn(`${operationName} failed, retrying...`, {\n attempt,\n maxAttempts,\n error: e.message,\n errorCode: e.name,\n httpStatusCode: e.$metadata?.httpStatusCode,\n });\n\n // Enhanced exponential backoff with jitter\n const baseDelay = operationName.startsWith('Upload-') ? 2000 : 1000;\n const backoffDelay = Math.min(\n baseDelay * Math.pow(2, attempt - 1),\n 30000,\n );\n const jitter = Math.random() * 1000;\n await new Promise(resolve =>\n setTimeout(resolve, backoffDelay + jitter),\n );\n }\n }\n return await operation();\n }\n\n /**\n * Determines if an S3 operation should be retried based on the error details.\n */\n private defaultShouldRetry(error: S3ServiceException): boolean {\n const httpStatusCode = error.$metadata?.httpStatusCode;\n const errorCode = error.name;\n\n // Truly transient errors that should always be retried\n const transientErrors = [\n 'NetworkingError',\n 'TimeoutError',\n 'ConnectionError',\n 'RequestTimeout',\n 'ServiceUnavailable',\n 'SlowDown',\n 'ThrottlingException',\n ];\n\n // Server errors are always considered transient\n if (httpStatusCode && httpStatusCode >= 500) {\n return true;\n }\n\n // Specific 4xx errors that are known to be transient\n if (httpStatusCode && httpStatusCode >= 400 && httpStatusCode < 500) {\n const retriable4xxErrors = [\n 'RequestTimeout',\n 'RequestTimeoutException',\n 'PriorRequestNotComplete',\n ];\n return retriable4xxErrors.includes(errorCode);\n }\n\n // Check against known transient errors\n return transientErrors.some(\n retriableError =>\n errorCode === retriableError || error.message.includes(retriableError),\n );\n }\n\n private static async getExplicitCredentials({\n credentialsConfig,\n awsS3Integrations,\n credsManager,\n logger,\n }: {\n credentialsConfig?: Config;\n awsS3Integrations: AwsS3Integration[];\n credsManager: AwsCredentialsManager;\n logger: LoggerService;\n }): Promise<AwsCredentialIdentityProvider> {\n const accessKeyId = credentialsConfig?.getOptionalString('accessKeyId');\n const secretAccessKey =\n credentialsConfig?.getOptionalString('secretAccessKey');\n\n if (accessKeyId && secretAccessKey) {\n return AwsS3Publish.buildStaticCredentials(accessKeyId, secretAccessKey);\n }\n\n if (awsS3Integrations.length > 0) {\n if (awsS3Integrations.length === 1) {\n const singleAwsS3IntegrationConfig = awsS3Integrations[0].config;\n\n const singleAwsS3IntegrationAccessKeyId =\n singleAwsS3IntegrationConfig.accessKeyId;\n\n const singleAwsS3IntegrationSecretAccessKey =\n singleAwsS3IntegrationConfig.secretAccessKey;\n\n if (\n singleAwsS3IntegrationAccessKeyId &&\n singleAwsS3IntegrationSecretAccessKey\n ) {\n return AwsS3Publish.buildStaticCredentials(\n singleAwsS3IntegrationAccessKeyId,\n singleAwsS3IntegrationSecretAccessKey,\n );\n }\n } else {\n if (accessKeyId) {\n const targetAwsS3IntegrationConfig = awsS3Integrations.find(\n c => c.config.accessKeyId === accessKeyId,\n );\n\n if (!targetAwsS3IntegrationConfig) {\n logger.warn(\n `No AWS S3 integration config under integrations.awsS3 found for access key id ${accessKeyId}.`,\n );\n }\n const targetAwsS3IntegrationAccessKeyId =\n targetAwsS3IntegrationConfig?.config.accessKeyId;\n const targetAwsS3IntegrationSecretAccessKey =\n targetAwsS3IntegrationConfig?.config.secretAccessKey;\n if (\n targetAwsS3IntegrationAccessKeyId &&\n targetAwsS3IntegrationSecretAccessKey\n ) {\n return AwsS3Publish.buildStaticCredentials(\n targetAwsS3IntegrationAccessKeyId,\n targetAwsS3IntegrationSecretAccessKey,\n );\n }\n }\n }\n }\n return (await credsManager.getCredentialProvider()).sdkCredentialProvider;\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.send(\n new HeadBucketCommand({ Bucket: this.bucketName }),\n );\n\n this.logger.info(\n `Successfully connected to the AWS S3 bucket ${this.bucketName}.`,\n );\n\n return { isAvailable: true };\n } catch (error) {\n this.logger.error(\n `Could not retrieve metadata about the AWS S3 bucket ${this.bucketName}. ` +\n 'Make sure the bucket exists. Also make sure that authentication is setup either by ' +\n 'explicitly defining credentials and region in techdocs.publisher.awsS3 in app config or ' +\n 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',\n );\n this.logger.error(\n `from AWS client library`,\n error instanceof Error ? error : new Error(String(error)),\n );\n return {\n isAvailable: false,\n };\n }\n }\n /**\n * Upload all the files from the generated `directory` to the S3 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 bucketRootPath = this.bucketRootPath;\n const sse = this.sse;\n\n // Track timing for performance monitoring\n const publishStartTime = Date.now();\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 const response = await this.retryOperation(\n async () => {\n const listCommand = new ListObjectsV2Command({\n Bucket: this.bucketName,\n Prefix: remoteFolder,\n });\n return this.storageClient.send(listCommand);\n },\n 'ListObjects',\n this.maxAttempts,\n );\n existingFiles = (response.Contents || [])\n .map(f => f.Key || '')\n .filter(f => !!f);\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 s3Key = getCloudPathForLocalPath(\n entity,\n relativeFilePath,\n useLegacyPathCasing,\n bucketRootPath,\n );\n // Create params without the Body because the body must be the\n // actual file contents (Buffer or Readable), not the path string.\n // For multipart uploads we attach a Readable stream to avoid\n // buffering large files in memory. For simple uploads we attach\n // a Buffer read from disk.\n const params: PutObjectCommandInput = {\n Bucket: this.bucketName,\n Key: s3Key,\n ...(sse && { ServerSideEncryption: sse }),\n };\n\n objects.push(params.Key!);\n // Get file stats before upload\n const stats = await fs.stat(absoluteFilePath);\n const fileSizeInBytes = stats.size;\n\n // Check if this is a large file that requires multipart upload\n if (fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES) {\n // Try multipart upload for large files\n try {\n // Create stream and Upload inside retry closure so stream is\n // recreated on each retry attempt (streams are consumable once).\n await this.retryOperation(\n () => {\n // Create a fresh stream on each attempt\n const fileStream = fs.createReadStream(absoluteFilePath);\n const uploadParams = { ...params, Body: fileStream };\n\n const upload = new Upload({\n client: this.storageClient,\n params: uploadParams,\n partSize: MAX_SINGLE_UPLOAD_BYTES,\n queueSize: 3,\n leavePartsOnError: false,\n });\n return upload.done();\n },\n `Upload-${params.Key}`,\n this.maxAttempts,\n );\n return;\n } catch (multipartError) {\n const s3Error = multipartError as any;\n const errorName = s3Error?.name || 'Unknown';\n\n // For specific multipart errors, attempt simple upload fallback\n if (errorName === 'InvalidPart' || errorName === 'NoSuchUpload') {\n this.logger.warn(\n `Multipart upload failed for ${params.Key}, attempting simple upload fallback.`,\n );\n } else {\n // Non-recoverable multipart error, throw it\n this.logger.error(\n `Multipart upload failed for ${params.Key}: ${\n multipartError instanceof Error\n ? multipartError.message\n : String(multipartError)\n }`,\n );\n throw multipartError;\n }\n }\n }\n\n // Use simple upload for small files or as fallback from multipart\n try {\n const fileContent = await fs.readFile(absoluteFilePath);\n const putParams = { ...params, Body: fileContent };\n await this.retryOperation(\n () => this.storageClient.send(new PutObjectCommand(putParams)),\n `Upload-${params.Key}`,\n this.maxAttempts,\n );\n\n if (fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES) {\n this.logger.info(\n `Simple upload fallback succeeded for ${params.Key}`,\n );\n }\n } catch (error) {\n this.logger.error(\n `Upload failed for ${params.Key}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n throw error;\n }\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 AWS S3. ${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 this.retryOperation(\n async () => {\n const deleteCommand = new DeleteObjectCommand({\n Bucket: this.bucketName,\n Key: relativeFilePath,\n });\n return this.storageClient.send(deleteCommand);\n },\n 'DeleteObject',\n this.maxAttempts,\n );\n },\n staleFiles,\n { concurrencyLimit: 10 },\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 AWS S3. ${error}`;\n this.logger.error(errorMessage);\n }\n const publishEndTime = Date.now();\n const publishDurationMs = publishEndTime - publishStartTime;\n this.logger.info(\n `Successfully published ${objects.length} files for ${\n entity.metadata.name\n } in ${Math.round(publishDurationMs / 1000)}s`,\n );\n return { objects };\n }\n\n async fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n try {\n return await new Promise<TechDocsMetadata>(async (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 throw new Error(`Metadata Not Found`);\n }\n\n try {\n const resp = await this.retryOperation(\n async () => {\n const getCommand = new GetObjectCommand({\n Bucket: this.bucketName,\n Key: `${entityRootDir}/techdocs_metadata.json`,\n });\n return this.storageClient.send(getCommand);\n },\n 'GetTechDocsMetadata',\n this.maxAttempts,\n );\n\n const techdocsMetadataJson = await streamToBuffer(\n resp.Body as Readable,\n );\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 this.logger.error(toError(err).message);\n reject(new Error(toError(err).message));\n }\n });\n } catch (e) {\n throw new ForwardedError('TechDocs metadata fetch failed', e);\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 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 try {\n const resp = await this.storageClient.send(\n new GetObjectCommand({ Bucket: this.bucketName, Key: filePath }),\n );\n\n // Inject response headers\n for (const [headerKey, headerValue] of Object.entries(\n responseHeaders,\n )) {\n res.setHeader(headerKey, headerValue);\n }\n\n (resp.Body as Readable)\n .on('error', err => {\n this.logger.warn(\n `TechDocs S3 router failed to serve static files from bucket ${this.bucketName} at key ${filePath}: ${err.message}`,\n );\n if (!res.headersSent) {\n res.status(404).send('File Not Found');\n } else {\n res.destroy();\n }\n })\n .pipe(res);\n } catch (err) {\n this.logger.warn(\n `TechDocs S3 router failed to serve static files from bucket ${\n this.bucketName\n } at key ${filePath}: ${toError(err).message}`,\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 try {\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 return Promise.resolve(false);\n }\n\n await this.storageClient.send(\n new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: `${entityRootDir}/index.html`,\n }),\n );\n return Promise.resolve(true);\n } catch (e) {\n return Promise.resolve(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.getAllObjectsFromBucket();\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}`);\n await this.storageClient.send(\n new CopyObjectCommand({\n Bucket: this.bucketName,\n CopySource: [this.bucketName, file].join('/'),\n Key: newPath,\n }),\n );\n\n if (removeOriginal) {\n await this.storageClient.send(\n new DeleteObjectCommand({\n Bucket: this.bucketName,\n Key: file,\n }),\n );\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 bucket.\n */\n protected async getAllObjectsFromBucket(\n { prefix } = { prefix: '' },\n ): Promise<string[]> {\n const objects: string[] = [];\n let nextContinuation: string | undefined;\n let allObjects: ListObjectsV2CommandOutput;\n // Iterate through every file in the root of the publisher.\n do {\n const currentToken = nextContinuation;\n allObjects = await this.retryOperation(\n async () => {\n const listCommand = new ListObjectsV2Command({\n Bucket: this.bucketName,\n ContinuationToken: currentToken,\n ...(prefix ? { Prefix: prefix } : {}),\n });\n return this.storageClient.send(listCommand);\n },\n 'GetAllObjects',\n this.maxAttempts,\n );\n objects.push(\n ...(allObjects.Contents || []).map(f => f.Key || '').filter(f => !!f),\n );\n nextContinuation = allObjects.NextContinuationToken;\n } while (nextContinuation);\n\n return objects;\n }\n}\n"],"names":["ForwardedError","normalizeExternalStorageRootPath","DefaultAwsCredentialsManager","ScmIntegrations","S3Client","NodeHttpHandler","HttpsProxyAgent","fromTemporaryCredentials","HeadBucketCommand","getCloudPathForLocalPath","ListObjectsV2Command","toError","getFileTreeRecursively","bulkStorageOperation","path","fs","Upload","PutObjectCommand","getStaleFiles","DeleteObjectCommand","lowerCaseEntityTriplet","isValidContentPath","GetObjectCommand","JSON5","lowerCaseEntityTripletInStoragePath","getHeadersForFileExtension","HeadObjectCommand","createLimiter","CopyObjectCommand"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"awsS3.cjs.js","sources":["../../../src/stages/publish/awsS3.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 { ForwardedError, toError } from '@backstage/errors';\n\n// Maximum size in bytes for a single upload part (5MB)\nconst MAX_SINGLE_UPLOAD_BYTES = 5 * 1024 * 1024;\n\nimport {\n AwsCredentialsManager,\n DefaultAwsCredentialsManager,\n} from '@backstage/integration-aws-node';\nimport {\n GetObjectCommand,\n CopyObjectCommand,\n DeleteObjectCommand,\n HeadBucketCommand,\n HeadObjectCommand,\n PutObjectCommand,\n PutObjectCommandInput,\n ListObjectsV2CommandOutput,\n ListObjectsV2Command,\n S3Client,\n S3ServiceException,\n} from '@aws-sdk/client-s3';\nimport { fromTemporaryCredentials } from '@aws-sdk/credential-providers';\nimport { NodeHttpHandler } from '@smithy/node-http-handler';\nimport { Upload } from '@aws-sdk/lib-storage';\nimport { AwsCredentialIdentityProvider } from '@aws-sdk/types';\nimport { HttpsProxyAgent } from 'hpagent';\nimport express from 'express';\nimport fs from 'fs-extra';\nimport JSON5 from 'json5';\nimport createLimiter from 'p-limit';\nimport path from 'node:path';\nimport { Readable } from 'node:stream';\nimport {\n bulkStorageOperation,\n getCloudPathForLocalPath,\n getFileTreeRecursively,\n getHeadersForFileExtension,\n getStaleFiles,\n isValidContentPath,\n lowerCaseEntityTriplet,\n lowerCaseEntityTripletInStoragePath,\n normalizeExternalStorageRootPath,\n} from './helpers';\nimport {\n PublisherBase,\n PublishRequest,\n PublishResponse,\n ReadinessResponse,\n TechDocsMetadata,\n} from './types';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { AwsS3Integration, ScmIntegrations } from '@backstage/integration';\n\nconst streamToBuffer = (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', (e: Error) =>\n reject(new ForwardedError('Unable to read stream', e)),\n );\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\nexport class AwsS3Publish implements PublisherBase {\n public readonly storageClient: S3Client;\n private readonly bucketName: string;\n private readonly legacyPathCasing: boolean;\n private readonly logger: LoggerService;\n private readonly bucketRootPath: string;\n private readonly sse?: 'aws:kms' | 'AES256';\n private readonly maxAttempts: number;\n\n constructor(options: {\n storageClient: S3Client;\n bucketName: string;\n legacyPathCasing: boolean;\n logger: LoggerService;\n bucketRootPath: string;\n sse?: 'aws:kms' | 'AES256';\n maxAttempts: number;\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 this.sse = options.sse;\n this.maxAttempts = options.maxAttempts;\n }\n\n static async fromConfig(\n config: Config,\n logger: LoggerService,\n ): Promise<PublisherBase> {\n let bucketName = '';\n try {\n bucketName = config.getString('techdocs.publisher.awsS3.bucketName');\n } catch (error) {\n throw new Error(\n \"Since techdocs.publisher.type is set to 'awsS3' in your app config, \" +\n 'techdocs.publisher.awsS3.bucketName is required.',\n );\n }\n\n const bucketRootPath = normalizeExternalStorageRootPath(\n config.getOptionalString('techdocs.publisher.awsS3.bucketRootPath') || '',\n );\n\n const sse = config.getOptionalString('techdocs.publisher.awsS3.sse') as\n | 'aws:kms'\n | 'AES256'\n | undefined;\n\n // AWS Region is an optional config. If missing, default AWS env variable AWS_REGION\n // or AWS shared credentials file at ~/.aws/credentials will be used.\n const region = config.getOptionalString('techdocs.publisher.awsS3.region');\n\n // Credentials can optionally be configured by specifying the AWS account ID, which will retrieve credentials\n // for the account from the 'aws' section of the app config.\n // Credentials can also optionally be directly configured in the techdocs awsS3 config, but this method is\n // deprecated.\n // If no credentials are configured, the AWS SDK V3's default credential chain will be used.\n const accountId = config.getOptionalString(\n 'techdocs.publisher.awsS3.accountId',\n );\n const credentialsConfig = config.getOptionalConfig(\n 'techdocs.publisher.awsS3.credentials',\n );\n\n const credsManager = DefaultAwsCredentialsManager.fromConfig(config);\n\n const scmIntegrations = ScmIntegrations.fromConfig(config);\n const awsS3Integrations = scmIntegrations.awsS3.list();\n\n const sdkCredentialProvider = await AwsS3Publish.buildCredentials(\n credsManager,\n logger,\n awsS3Integrations,\n accountId,\n credentialsConfig,\n region,\n );\n\n // AWS endpoint is an optional config. If missing, the default endpoint is built from\n // the configured region.\n const endpoint = config.getOptionalString(\n 'techdocs.publisher.awsS3.endpoint',\n );\n\n // AWS HTTPS proxy is an optional config. If missing, no proxy is used\n const httpsProxy = config.getOptionalString(\n 'techdocs.publisher.awsS3.httpsProxy',\n );\n\n // AWS forcePathStyle is an optional config. If missing, it defaults to false. Needs to be enabled for cases\n // where endpoint url points to locally hosted S3 compatible storage like Localstack\n const forcePathStyle = config.getOptionalBoolean(\n 'techdocs.publisher.awsS3.s3ForcePathStyle',\n );\n\n // AWS MAX ATTEMPTS is an optional config. If missing, default value of 5 is used\n const maxAttempts = config.getOptionalNumber(\n 'techdocs.publisher.awsS3.maxAttempts',\n );\n\n const storageClient = new S3Client({\n customUserAgent: 'backstage-aws-techdocs-s3-publisher',\n credentialDefaultProvider: () => sdkCredentialProvider,\n ...(region && { region }),\n ...(endpoint && { endpoint }),\n ...(forcePathStyle && { forcePathStyle }),\n // Enhanced retry configuration for better reliability\n maxAttempts: maxAttempts || 5,\n retryMode: 'adaptive',\n // Enhanced connection settings for large file uploads\n requestHandler: new NodeHttpHandler({\n ...(httpsProxy && {\n httpsAgent: new HttpsProxyAgent({ proxy: httpsProxy }),\n }),\n connectionTimeout: 60000,\n socketTimeout: 120000,\n }),\n });\n\n const legacyPathCasing =\n config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n ) || false;\n\n return new AwsS3Publish({\n storageClient,\n bucketName,\n bucketRootPath,\n legacyPathCasing,\n logger,\n sse,\n maxAttempts: maxAttempts || 5,\n });\n }\n\n private static buildStaticCredentials(\n accessKeyId: string,\n secretAccessKey: string,\n ): AwsCredentialIdentityProvider {\n return async () => {\n return Promise.resolve({\n accessKeyId,\n secretAccessKey,\n });\n };\n }\n\n private static async buildCredentials(\n credsManager: AwsCredentialsManager,\n logger: LoggerService,\n awsS3Integrations: AwsS3Integration[],\n accountId?: string,\n credentialsConfig?: Config,\n region?: string,\n ): Promise<AwsCredentialIdentityProvider> {\n // Pull credentials for the specified account ID from the 'aws' config section\n if (accountId) {\n return (await credsManager.getCredentialProvider({ accountId }))\n .sdkCredentialProvider;\n }\n\n const explicitCredentials = await AwsS3Publish.getExplicitCredentials({\n credsManager,\n credentialsConfig,\n awsS3Integrations,\n logger,\n });\n\n const roleArn = credentialsConfig?.getOptionalString('roleArn');\n if (roleArn) {\n return fromTemporaryCredentials({\n masterCredentials: explicitCredentials,\n params: {\n RoleSessionName: 'backstage-aws-techdocs-s3-publisher',\n RoleArn: roleArn,\n },\n clientConfig: { region },\n });\n }\n\n return explicitCredentials;\n }\n /**\n * Custom retry wrapper for S3 operations with detailed error handling.\n */\n public async retryOperation<TOutput>(\n operation: () => Promise<TOutput>,\n operationName: string,\n maxAttempts: number = 3,\n shouldRetry: (\n error: S3ServiceException,\n ) => boolean = this.defaultShouldRetry.bind(this),\n ): Promise<TOutput> {\n for (let attempt = 1; attempt < maxAttempts; attempt++) {\n try {\n return await operation();\n } catch (error) {\n const e = error as S3ServiceException;\n if (!shouldRetry(e)) {\n this.logger.error(`${operationName} failed: ${e.message}`);\n throw e;\n }\n\n this.logger.warn(`${operationName} failed, retrying...`, {\n attempt,\n maxAttempts,\n error: e.message,\n errorCode: e.name,\n httpStatusCode: e.$metadata?.httpStatusCode,\n });\n\n // Enhanced exponential backoff with jitter\n const baseDelay = operationName.startsWith('Upload-') ? 2000 : 1000;\n const backoffDelay = Math.min(\n baseDelay * Math.pow(2, attempt - 1),\n 30000,\n );\n const jitter = Math.random() * 1000;\n await new Promise(resolve =>\n setTimeout(resolve, backoffDelay + jitter),\n );\n }\n }\n return await operation();\n }\n\n /**\n * Determines if an S3 operation should be retried based on the error details.\n */\n private defaultShouldRetry(error: S3ServiceException): boolean {\n const httpStatusCode = error.$metadata?.httpStatusCode;\n const errorCode = error.name;\n\n // Truly transient errors that should always be retried\n const transientErrors = [\n 'NetworkingError',\n 'TimeoutError',\n 'ConnectionError',\n 'RequestTimeout',\n 'ServiceUnavailable',\n 'SlowDown',\n 'ThrottlingException',\n ];\n\n // Server errors are always considered transient\n if (httpStatusCode && httpStatusCode >= 500) {\n return true;\n }\n\n // Specific 4xx errors that are known to be transient\n if (httpStatusCode && httpStatusCode >= 400 && httpStatusCode < 500) {\n const retriable4xxErrors = [\n 'RequestTimeout',\n 'RequestTimeoutException',\n 'PriorRequestNotComplete',\n ];\n return retriable4xxErrors.includes(errorCode);\n }\n\n // Check against known transient errors\n return transientErrors.some(\n retriableError =>\n errorCode === retriableError || error.message.includes(retriableError),\n );\n }\n\n private static async getExplicitCredentials({\n credentialsConfig,\n awsS3Integrations,\n credsManager,\n logger,\n }: {\n credentialsConfig?: Config;\n awsS3Integrations: AwsS3Integration[];\n credsManager: AwsCredentialsManager;\n logger: LoggerService;\n }): Promise<AwsCredentialIdentityProvider> {\n const accessKeyId = credentialsConfig?.getOptionalString('accessKeyId');\n const secretAccessKey =\n credentialsConfig?.getOptionalString('secretAccessKey');\n\n if (accessKeyId && secretAccessKey) {\n return AwsS3Publish.buildStaticCredentials(accessKeyId, secretAccessKey);\n }\n\n if (awsS3Integrations.length > 0) {\n if (awsS3Integrations.length === 1) {\n const singleAwsS3IntegrationConfig = awsS3Integrations[0].config;\n\n const singleAwsS3IntegrationAccessKeyId =\n singleAwsS3IntegrationConfig.accessKeyId;\n\n const singleAwsS3IntegrationSecretAccessKey =\n singleAwsS3IntegrationConfig.secretAccessKey;\n\n if (\n singleAwsS3IntegrationAccessKeyId &&\n singleAwsS3IntegrationSecretAccessKey\n ) {\n return AwsS3Publish.buildStaticCredentials(\n singleAwsS3IntegrationAccessKeyId,\n singleAwsS3IntegrationSecretAccessKey,\n );\n }\n } else {\n if (accessKeyId) {\n const targetAwsS3IntegrationConfig = awsS3Integrations.find(\n c => c.config.accessKeyId === accessKeyId,\n );\n\n if (!targetAwsS3IntegrationConfig) {\n logger.warn(\n `No AWS S3 integration config under integrations.awsS3 found for access key id ${accessKeyId}.`,\n );\n }\n const targetAwsS3IntegrationAccessKeyId =\n targetAwsS3IntegrationConfig?.config.accessKeyId;\n const targetAwsS3IntegrationSecretAccessKey =\n targetAwsS3IntegrationConfig?.config.secretAccessKey;\n if (\n targetAwsS3IntegrationAccessKeyId &&\n targetAwsS3IntegrationSecretAccessKey\n ) {\n return AwsS3Publish.buildStaticCredentials(\n targetAwsS3IntegrationAccessKeyId,\n targetAwsS3IntegrationSecretAccessKey,\n );\n }\n }\n }\n }\n return (await credsManager.getCredentialProvider()).sdkCredentialProvider;\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.send(\n new HeadBucketCommand({ Bucket: this.bucketName }),\n );\n\n this.logger.info(\n `Successfully connected to the AWS S3 bucket ${this.bucketName}.`,\n );\n\n return { isAvailable: true };\n } catch (error) {\n this.logger.error(\n `Could not retrieve metadata about the AWS S3 bucket ${this.bucketName}. ` +\n 'Make sure the bucket exists. Also make sure that authentication is setup either by ' +\n 'explicitly defining credentials and region in techdocs.publisher.awsS3 in app config or ' +\n 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',\n );\n this.logger.error(\n `from AWS client library`,\n error instanceof Error ? error : new Error(String(error)),\n );\n return {\n isAvailable: false,\n };\n }\n }\n /**\n * Upload all the files from the generated `directory` to the S3 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 bucketRootPath = this.bucketRootPath;\n const sse = this.sse;\n\n // Track timing for performance monitoring\n const publishStartTime = Date.now();\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.getAllObjectsFromBucket({\n prefix: remoteFolder,\n });\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 s3Key = getCloudPathForLocalPath(\n entity,\n relativeFilePath,\n useLegacyPathCasing,\n bucketRootPath,\n );\n // Create params without the Body because the body must be the\n // actual file contents (Buffer or Readable), not the path string.\n // For multipart uploads we attach a Readable stream to avoid\n // buffering large files in memory. For simple uploads we attach\n // a Buffer read from disk.\n const params: PutObjectCommandInput = {\n Bucket: this.bucketName,\n Key: s3Key,\n ...(sse && { ServerSideEncryption: sse }),\n };\n\n objects.push(params.Key!);\n // Get file stats before upload\n const stats = await fs.stat(absoluteFilePath);\n const fileSizeInBytes = stats.size;\n\n // Check if this is a large file that requires multipart upload\n if (fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES) {\n // Try multipart upload for large files\n try {\n // Create stream and Upload inside retry closure so stream is\n // recreated on each retry attempt (streams are consumable once).\n await this.retryOperation(\n () => {\n // Create a fresh stream on each attempt\n const fileStream = fs.createReadStream(absoluteFilePath);\n const uploadParams = { ...params, Body: fileStream };\n\n const upload = new Upload({\n client: this.storageClient,\n params: uploadParams,\n partSize: MAX_SINGLE_UPLOAD_BYTES,\n queueSize: 3,\n leavePartsOnError: false,\n });\n return upload.done();\n },\n `Upload-${params.Key}`,\n this.maxAttempts,\n );\n return;\n } catch (multipartError) {\n const s3Error = multipartError as any;\n const errorName = s3Error?.name || 'Unknown';\n\n // For specific multipart errors, attempt simple upload fallback\n if (errorName === 'InvalidPart' || errorName === 'NoSuchUpload') {\n this.logger.warn(\n `Multipart upload failed for ${params.Key}, attempting simple upload fallback.`,\n );\n } else {\n // Non-recoverable multipart error, throw it\n this.logger.error(\n `Multipart upload failed for ${params.Key}: ${\n multipartError instanceof Error\n ? multipartError.message\n : String(multipartError)\n }`,\n );\n throw multipartError;\n }\n }\n }\n\n // Use simple upload for small files or as fallback from multipart\n try {\n const fileContent = await fs.readFile(absoluteFilePath);\n const putParams = { ...params, Body: fileContent };\n await this.retryOperation(\n () => this.storageClient.send(new PutObjectCommand(putParams)),\n `Upload-${params.Key}`,\n this.maxAttempts,\n );\n\n if (fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES) {\n this.logger.info(\n `Simple upload fallback succeeded for ${params.Key}`,\n );\n }\n } catch (error) {\n this.logger.error(\n `Upload failed for ${params.Key}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n throw error;\n }\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 AWS S3. ${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 this.retryOperation(\n async () => {\n const deleteCommand = new DeleteObjectCommand({\n Bucket: this.bucketName,\n Key: relativeFilePath,\n });\n return this.storageClient.send(deleteCommand);\n },\n 'DeleteObject',\n this.maxAttempts,\n );\n },\n staleFiles,\n { concurrencyLimit: 10 },\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 AWS S3. ${error}`;\n this.logger.error(errorMessage);\n }\n const publishEndTime = Date.now();\n const publishDurationMs = publishEndTime - publishStartTime;\n this.logger.info(\n `Successfully published ${objects.length} files for ${\n entity.metadata.name\n } in ${Math.round(publishDurationMs / 1000)}s`,\n );\n return { objects };\n }\n\n async fetchTechDocsMetadata(\n entityName: CompoundEntityRef,\n ): Promise<TechDocsMetadata> {\n try {\n return await new Promise<TechDocsMetadata>(async (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 throw new Error(`Metadata Not Found`);\n }\n\n try {\n const resp = await this.retryOperation(\n async () => {\n const getCommand = new GetObjectCommand({\n Bucket: this.bucketName,\n Key: `${entityRootDir}/techdocs_metadata.json`,\n });\n return this.storageClient.send(getCommand);\n },\n 'GetTechDocsMetadata',\n this.maxAttempts,\n );\n\n const techdocsMetadataJson = await streamToBuffer(\n resp.Body as Readable,\n );\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 this.logger.error(toError(err).message);\n reject(new Error(toError(err).message));\n }\n });\n } catch (e) {\n throw new ForwardedError('TechDocs metadata fetch failed', e);\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 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 try {\n const resp = await this.storageClient.send(\n new GetObjectCommand({ Bucket: this.bucketName, Key: filePath }),\n );\n\n // Inject response headers\n for (const [headerKey, headerValue] of Object.entries(\n responseHeaders,\n )) {\n res.setHeader(headerKey, headerValue);\n }\n\n (resp.Body as Readable)\n .on('error', err => {\n this.logger.warn(\n `TechDocs S3 router failed to serve static files from bucket ${this.bucketName} at key ${filePath}: ${err.message}`,\n );\n if (!res.headersSent) {\n res.status(404).send('File Not Found');\n } else {\n res.destroy();\n }\n })\n .pipe(res);\n } catch (err) {\n this.logger.warn(\n `TechDocs S3 router failed to serve static files from bucket ${\n this.bucketName\n } at key ${filePath}: ${toError(err).message}`,\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 try {\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 return Promise.resolve(false);\n }\n\n await this.storageClient.send(\n new HeadObjectCommand({\n Bucket: this.bucketName,\n Key: `${entityRootDir}/index.html`,\n }),\n );\n return Promise.resolve(true);\n } catch (e) {\n return Promise.resolve(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.getAllObjectsFromBucket();\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}`);\n await this.storageClient.send(\n new CopyObjectCommand({\n Bucket: this.bucketName,\n CopySource: [this.bucketName, file].join('/'),\n Key: newPath,\n }),\n );\n\n if (removeOriginal) {\n await this.storageClient.send(\n new DeleteObjectCommand({\n Bucket: this.bucketName,\n Key: file,\n }),\n );\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 bucket.\n */\n protected async getAllObjectsFromBucket(\n { prefix } = { prefix: '' },\n ): Promise<string[]> {\n const objects: string[] = [];\n let nextContinuation: string | undefined;\n let allObjects: ListObjectsV2CommandOutput;\n // Iterate through every file in the root of the publisher.\n do {\n const currentToken = nextContinuation;\n allObjects = await this.retryOperation(\n async () => {\n const listCommand = new ListObjectsV2Command({\n Bucket: this.bucketName,\n ContinuationToken: currentToken,\n ...(prefix ? { Prefix: prefix } : {}),\n });\n return this.storageClient.send(listCommand);\n },\n 'GetAllObjects',\n this.maxAttempts,\n );\n objects.push(\n ...(allObjects.Contents || []).map(f => f.Key || '').filter(f => !!f),\n );\n nextContinuation = allObjects.NextContinuationToken;\n } while (nextContinuation);\n\n return objects;\n }\n}\n"],"names":["ForwardedError","normalizeExternalStorageRootPath","DefaultAwsCredentialsManager","ScmIntegrations","S3Client","NodeHttpHandler","HttpsProxyAgent","fromTemporaryCredentials","HeadBucketCommand","getCloudPathForLocalPath","toError","getFileTreeRecursively","bulkStorageOperation","path","fs","Upload","PutObjectCommand","getStaleFiles","DeleteObjectCommand","lowerCaseEntityTriplet","isValidContentPath","GetObjectCommand","JSON5","lowerCaseEntityTripletInStoragePath","getHeadersForFileExtension","HeadObjectCommand","createLimiter","CopyObjectCommand","ListObjectsV2Command"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-techdocs-node",
|
|
3
|
-
"version": "0.0.0-nightly-
|
|
3
|
+
"version": "0.0.0-nightly-20260918023200",
|
|
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": "
|
|
57
|
-
"@backstage/catalog-model": "1.10.
|
|
58
|
-
"@backstage/config": "1.3.
|
|
56
|
+
"@backstage/backend-plugin-api": "1.10.1",
|
|
57
|
+
"@backstage/catalog-model": "1.10.1",
|
|
58
|
+
"@backstage/config": "1.3.9",
|
|
59
59
|
"@backstage/errors": "1.3.1",
|
|
60
|
-
"@backstage/integration": "0.0.0-nightly-
|
|
61
|
-
"@backstage/integration-aws-node": "0.
|
|
62
|
-
"@backstage/plugin-search-common": "1.2.
|
|
60
|
+
"@backstage/integration": "0.0.0-nightly-20260918023200",
|
|
61
|
+
"@backstage/integration-aws-node": "0.0.0-nightly-20260918023200",
|
|
62
|
+
"@backstage/plugin-search-common": "1.2.25",
|
|
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.
|
|
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": "0.0.0-nightly-
|
|
82
|
-
"@backstage/cli": "0.0.0-nightly-
|
|
81
|
+
"@backstage/backend-test-utils": "0.0.0-nightly-20260918023200",
|
|
82
|
+
"@backstage/cli": "0.0.0-nightly-20260918023200",
|
|
83
83
|
"@types/fs-extra": "^11.0.0",
|
|
84
84
|
"@types/js-yaml": "^4.0.0",
|
|
85
85
|
"@types/mime-types": "^2.1.0",
|