@backstage/plugin-techdocs-node 2.0.3-next.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @backstage/plugin-techdocs-node
2
2
 
3
+ ## 2.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4d4f987: Add engine abstraction layer for multi-engine TechDocs support. Introduces `--engine` flag, engine config map, generator registry with `backstage.io/techdocs-engine` annotation support, `engine` field in `techdocs_metadata.json`, `serve:raw` command, and live-reload adapter interface. MkDocs remains the default with no behavior change.
8
+
9
+ The built-in generator is now registered under `mkdocs`; the previous `techdocs` key is kept as an alias, so existing `generators.register('techdocs', ...)` calls continue to work. The CLI's `--engine` flag takes precedence over the `backstage.io/techdocs-engine` annotation for all commands, and `serve:mkdocs` and the `--mkdocs-parameter-*` flags now print deprecation warnings.
10
+
11
+ ### Patch Changes
12
+
13
+ - 9fc5387: Improved handling of mapping-style Markdown extension configuration.
14
+ - 0fd5fe5: Fixed TechDocs allowing `custom_icons` paths in `mkdocs.yml` that resolve outside the documentation input directory. A `custom_icons` option with such a path is now removed from the configuration with a warning.
15
+ - 8a37336: Fixed TechDocs generation rejecting `mkdocs.yml` files that use the emoji indexes and generators or the `pymdownx.superfences` custom fence formats documented by mkdocs-material, pymdown-extensions and mkdocs-mermaid2.
16
+ - 76ac6ed: Updated dependency `js-yaml` to `^5.0.0`.
17
+ - Updated dependencies
18
+ - @backstage/integration@2.1.3
19
+ - @backstage/backend-plugin-api@1.11.0
20
+ - @backstage/config@1.3.10
21
+ - @backstage/errors@1.3.2
22
+ - @backstage/plugin-techdocs-common@0.1.2
23
+ - @backstage/catalog-model@1.10.2
24
+ - @backstage/integration-aws-node@0.2.3
25
+ - @backstage/plugin-search-common@1.2.26
26
+
3
27
  ## 2.0.3-next.2
4
28
 
5
29
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"extensions.cjs.js","sources":["../src/extensions.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { createExtensionPoint } from '@backstage/backend-plugin-api';\nimport { DocsBuildStrategy } from './techdocsTypes';\nimport {\n PreparerBase,\n PublisherBase,\n PublisherType,\n RemoteProtocol,\n TechdocsGenerator,\n} from './stages';\nimport * as winston from 'winston';\nimport { PublisherSettings } from './stages/publish/types';\n\n/**\n * Extension point type for configuring TechDocs builds.\n *\n * @public\n */\nexport interface TechdocsBuildsExtensionPoint {\n setBuildStrategy(buildStrategy: DocsBuildStrategy): void;\n setBuildLogTransport(transport: winston.transport): void;\n}\n\n/**\n * Extension point for configuring TechDocs builds.\n *\n * @public\n */\nexport const techdocsBuildsExtensionPoint =\n createExtensionPoint<TechdocsBuildsExtensionPoint>({\n id: 'techdocs.builds',\n });\n\n/**\n * Extension point type for configuring a custom TechDocs generator\n *\n * @public\n */\nexport interface TechdocsGeneratorExtensionPoint {\n setTechdocsGenerator(generator: TechdocsGenerator): void;\n}\n\n/**\n * Extension point for configuring a custom TechDocs generator\n *\n * @public\n */\nexport const techdocsGeneratorExtensionPoint =\n createExtensionPoint<TechdocsGeneratorExtensionPoint>({\n id: 'techdocs.generator',\n });\n\n/**\n * Extension point type for configuring a custom TechDocs preparer\n *\n * @public\n */\nexport interface TechdocsPreparerExtensionPoint {\n registerPreparer(protocol: RemoteProtocol, preparer: PreparerBase): void;\n}\n\n/**\n * Extension point for configuring a custom TechDocs preparer\n *\n * @public\n */\nexport const techdocsPreparerExtensionPoint =\n createExtensionPoint<TechdocsPreparerExtensionPoint>({\n id: 'techdocs.preparer',\n });\n\n/**\n * Extension point type for configuring a custom TechDocs publisher\n *\n * @public\n */\nexport interface TechdocsPublisherExtensionPoint {\n registerPublisher(type: PublisherType, publisher: PublisherBase): void;\n registerPublisherSettings<T extends keyof PublisherSettings>(\n publisher: T,\n settings: PublisherSettings[T],\n ): void;\n}\n\n/**\n * Extension point for configuring a custom TechDocs publisher\n *\n * @public\n */\nexport const techdocsPublisherExtensionPoint =\n createExtensionPoint<TechdocsPublisherExtensionPoint>({\n id: 'techdocs.publisher',\n });\n"],"names":["createExtensionPoint"],"mappings":";;;;AA0CO,MAAM,+BACXA,qCAAA,CAAmD;AAAA,EACjD,EAAA,EAAI;AACN,CAAC;AAgBI,MAAM,kCACXA,qCAAA,CAAsD;AAAA,EACpD,EAAA,EAAI;AACN,CAAC;AAgBI,MAAM,iCACXA,qCAAA,CAAqD;AAAA,EACnD,EAAA,EAAI;AACN,CAAC;AAoBI,MAAM,kCACXA,qCAAA,CAAsD;AAAA,EACpD,EAAA,EAAI;AACN,CAAC;;;;;"}
1
+ {"version":3,"file":"extensions.cjs.js","sources":["../src/extensions.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { createExtensionPoint } from '@backstage/backend-plugin-api';\nimport { DocsBuildStrategy } from './techdocsTypes';\nimport {\n GeneratorBase,\n PreparerBase,\n PublisherBase,\n PublisherType,\n RemoteProtocol,\n SupportedGeneratorKey,\n} from './stages';\nimport * as winston from 'winston';\nimport { PublisherSettings } from './stages/publish/types';\n\n/**\n * Extension point type for configuring TechDocs builds.\n *\n * @public\n */\nexport interface TechdocsBuildsExtensionPoint {\n setBuildStrategy(buildStrategy: DocsBuildStrategy): void;\n setBuildLogTransport(transport: winston.transport): void;\n}\n\n/**\n * Extension point for configuring TechDocs builds.\n *\n * @public\n */\nexport const techdocsBuildsExtensionPoint =\n createExtensionPoint<TechdocsBuildsExtensionPoint>({\n id: 'techdocs.builds',\n });\n\n/**\n * Extension point type for configuring TechDocs generators.\n *\n * Use `registerGenerator` to add an engine-specific generator from a backend\n * module (e.g. `plugin-techdocs-backend-module-zensical`). The key corresponds\n * to the `backstage.io/techdocs-engine` entity annotation value.\n *\n * `setTechdocsGenerator` is kept for backwards compatibility — it replaces the\n * default MkDocs generator.\n *\n * @public\n */\nexport interface TechdocsGeneratorExtensionPoint {\n setTechdocsGenerator(generator: GeneratorBase): void;\n registerGenerator(key: SupportedGeneratorKey, generator: GeneratorBase): void;\n}\n\n/**\n * Extension point for configuring a custom TechDocs generator\n *\n * @public\n */\nexport const techdocsGeneratorExtensionPoint =\n createExtensionPoint<TechdocsGeneratorExtensionPoint>({\n id: 'techdocs.generator',\n });\n\n/**\n * Extension point type for configuring a custom TechDocs preparer\n *\n * @public\n */\nexport interface TechdocsPreparerExtensionPoint {\n registerPreparer(protocol: RemoteProtocol, preparer: PreparerBase): void;\n}\n\n/**\n * Extension point for configuring a custom TechDocs preparer\n *\n * @public\n */\nexport const techdocsPreparerExtensionPoint =\n createExtensionPoint<TechdocsPreparerExtensionPoint>({\n id: 'techdocs.preparer',\n });\n\n/**\n * Extension point type for configuring a custom TechDocs publisher\n *\n * @public\n */\nexport interface TechdocsPublisherExtensionPoint {\n registerPublisher(type: PublisherType, publisher: PublisherBase): void;\n registerPublisherSettings<T extends keyof PublisherSettings>(\n publisher: T,\n settings: PublisherSettings[T],\n ): void;\n}\n\n/**\n * Extension point for configuring a custom TechDocs publisher\n *\n * @public\n */\nexport const techdocsPublisherExtensionPoint =\n createExtensionPoint<TechdocsPublisherExtensionPoint>({\n id: 'techdocs.publisher',\n });\n"],"names":["createExtensionPoint"],"mappings":";;;;AA2CO,MAAM,+BACXA,qCAAA,CAAmD;AAAA,EACjD,EAAA,EAAI;AACN,CAAC;AAwBI,MAAM,kCACXA,qCAAA,CAAsD;AAAA,EACpD,EAAA,EAAI;AACN,CAAC;AAgBI,MAAM,iCACXA,qCAAA,CAAqD;AAAA,EACnD,EAAA,EAAI;AACN,CAAC;AAoBI,MAAM,kCACXA,qCAAA,CAAsD;AAAA,EACpD,EAAA,EAAI;AACN,CAAC;;;;;"}
package/dist/index.cjs.js CHANGED
@@ -6,6 +6,7 @@ var url = require('./stages/prepare/url.cjs.js');
6
6
  var preparers = require('./stages/prepare/preparers.cjs.js');
7
7
  var publish = require('./stages/publish/publish.cjs.js');
8
8
  var helpers = require('./helpers.cjs.js');
9
+ var techdocsTypes = require('./techdocsTypes.cjs.js');
9
10
  var extensions = require('./extensions.cjs.js');
10
11
  var generators = require('./stages/generate/generators.cjs.js');
11
12
  var techdocs = require('./stages/generate/techdocs.cjs.js');
@@ -22,6 +23,7 @@ exports.getDocFilesFromRepository = helpers.getDocFilesFromRepository;
22
23
  exports.getLocationForEntity = helpers.getLocationForEntity;
23
24
  exports.parseReferenceAnnotation = helpers.parseReferenceAnnotation;
24
25
  exports.transformDirLocation = helpers.transformDirLocation;
26
+ exports.TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY = techdocsTypes.TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY;
25
27
  exports.techdocsBuildsExtensionPoint = extensions.techdocsBuildsExtensionPoint;
26
28
  exports.techdocsGeneratorExtensionPoint = extensions.techdocsGeneratorExtensionPoint;
27
29
  exports.techdocsPreparerExtensionPoint = extensions.techdocsPreparerExtensionPoint;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.d.ts CHANGED
@@ -273,6 +273,7 @@ declare class TechdocsGenerator implements GeneratorBase {
273
273
  */
274
274
  declare class Generators implements GeneratorBuilder {
275
275
  private generatorMap;
276
+ private defaultEngine;
276
277
  /**
277
278
  * Returns a generators instance containing a generator for TechDocs
278
279
  * @param config - A Backstage configuration
@@ -281,7 +282,7 @@ declare class Generators implements GeneratorBuilder {
281
282
  static fromConfig(config: Config, options: {
282
283
  logger: LoggerService;
283
284
  containerRunner?: TechDocsContainerRunner;
284
- customGenerator?: TechdocsGenerator;
285
+ customGenerator?: GeneratorBase;
285
286
  }): Promise<GeneratorBuilder>;
286
287
  /**
287
288
  * Register a generator in the generators collection
@@ -587,6 +588,12 @@ interface DocsBuildStrategy {
587
588
  entity: Entity;
588
589
  }): Promise<boolean>;
589
590
  }
591
+ /**
592
+ * A catalog annotation key indicating which TechDoc Generation Engine to use
593
+ *
594
+ * @public
595
+ */
596
+ declare const TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY = "backstage.io/techdocs-engine";
590
597
 
591
598
  /**
592
599
  * Extension point type for configuring TechDocs builds.
@@ -604,12 +611,20 @@ interface TechdocsBuildsExtensionPoint {
604
611
  */
605
612
  declare const techdocsBuildsExtensionPoint: _backstage_backend_plugin_api.ExtensionPoint<TechdocsBuildsExtensionPoint>;
606
613
  /**
607
- * Extension point type for configuring a custom TechDocs generator
614
+ * Extension point type for configuring TechDocs generators.
615
+ *
616
+ * Use `registerGenerator` to add an engine-specific generator from a backend
617
+ * module (e.g. `plugin-techdocs-backend-module-zensical`). The key corresponds
618
+ * to the `backstage.io/techdocs-engine` entity annotation value.
619
+ *
620
+ * `setTechdocsGenerator` is kept for backwards compatibility — it replaces the
621
+ * default MkDocs generator.
608
622
  *
609
623
  * @public
610
624
  */
611
625
  interface TechdocsGeneratorExtensionPoint {
612
- setTechdocsGenerator(generator: TechdocsGenerator): void;
626
+ setTechdocsGenerator(generator: GeneratorBase): void;
627
+ registerGenerator(key: SupportedGeneratorKey, generator: GeneratorBase): void;
613
628
  }
614
629
  /**
615
630
  * Extension point for configuring a custom TechDocs generator
@@ -647,5 +662,5 @@ interface TechdocsPublisherExtensionPoint {
647
662
  */
648
663
  declare const techdocsPublisherExtensionPoint: _backstage_backend_plugin_api.ExtensionPoint<TechdocsPublisherExtensionPoint>;
649
664
 
650
- export { DirectoryPreparer, Generators, Preparers, Publisher, TechdocsGenerator, UrlPreparer, getDocFilesFromRepository, getLocationForEntity, getMkDocsYml, getMkdocsYml, parseReferenceAnnotation, techdocsBuildsExtensionPoint, techdocsGeneratorExtensionPoint, techdocsPreparerExtensionPoint, techdocsPublisherExtensionPoint, transformDirLocation };
665
+ export { DirectoryPreparer, Generators, Preparers, Publisher, TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY, TechdocsGenerator, UrlPreparer, getDocFilesFromRepository, getLocationForEntity, getMkDocsYml, getMkdocsYml, parseReferenceAnnotation, techdocsBuildsExtensionPoint, techdocsGeneratorExtensionPoint, techdocsPreparerExtensionPoint, techdocsPublisherExtensionPoint, transformDirLocation };
651
666
  export type { ContainerRunnerPullOptions, DocsBuildStrategy, ETag, GeneratorBase, GeneratorBuilder, GeneratorOptions, GeneratorRunOptions, MigrateRequest, ParsedLocationAnnotation, PreparerBase, PreparerBuilder, PreparerConfig, PreparerOptions, PreparerResponse, PublishRequest, PublishResponse, PublisherBase, PublisherBuilder, PublisherFactory, PublisherSettings, PublisherType, ReadinessResponse, RemoteProtocol, SupportedGeneratorKey, TechDocsContainerRunner, TechDocsDocument, TechDocsMetadata, TechdocsBuildsExtensionPoint, TechdocsGeneratorExtensionPoint, TechdocsPreparerExtensionPoint, TechdocsPublisherExtensionPoint };
@@ -1,10 +1,14 @@
1
1
  'use strict';
2
2
 
3
- var helpers = require('./helpers.cjs.js');
4
3
  var techdocs = require('./techdocs.cjs.js');
4
+ var techdocsTypes = require('../../techdocsTypes.cjs.js');
5
5
 
6
+ function normalizeGeneratorKey(key) {
7
+ return key === "techdocs" ? "mkdocs" : key;
8
+ }
6
9
  class Generators {
7
10
  generatorMap = /* @__PURE__ */ new Map();
11
+ defaultEngine = "mkdocs";
8
12
  /**
9
13
  * Returns a generators instance containing a generator for TechDocs
10
14
  * @param config - A Backstage configuration
@@ -12,8 +16,11 @@ class Generators {
12
16
  */
13
17
  static async fromConfig(config, options) {
14
18
  const generators = new Generators();
15
- const techdocsGenerator = options.customGenerator ?? techdocs.TechdocsGenerator.fromConfig(config, options);
16
- generators.register("techdocs", techdocsGenerator);
19
+ const defaultEngine = config.getOptionalString("techdocs.generator.defaultEngine") ?? "mkdocs";
20
+ generators.defaultEngine = defaultEngine;
21
+ options.logger.info(`TechDocs generator defaultEngine: ${defaultEngine}`);
22
+ const mkdocsGenerator = options.customGenerator ?? techdocs.TechdocsGenerator.fromConfig(config, options);
23
+ generators.register("mkdocs", mkdocsGenerator);
17
24
  return generators;
18
25
  }
19
26
  /**
@@ -22,17 +29,26 @@ class Generators {
22
29
  * @param generator - The generator instance to register
23
30
  */
24
31
  register(generatorKey, generator) {
25
- this.generatorMap.set(generatorKey, generator);
32
+ this.generatorMap.set(normalizeGeneratorKey(generatorKey), generator);
26
33
  }
27
34
  /**
28
35
  * Returns the generator for a given TechDocs entity
29
36
  * @param entity - A TechDocs entity instance
30
37
  */
31
38
  get(entity) {
32
- const generatorKey = helpers.getGeneratorKey(entity);
39
+ if (!entity) {
40
+ throw new Error("No entity provided");
41
+ }
42
+ const annotatedEngine = entity.metadata?.annotations?.[techdocsTypes.TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY];
43
+ const generatorKey = normalizeGeneratorKey(
44
+ annotatedEngine ?? this.defaultEngine
45
+ );
33
46
  const generator = this.generatorMap.get(generatorKey);
34
47
  if (!generator) {
35
- throw new Error(`No generator registered for entity: "${generatorKey}"`);
48
+ const source = annotatedEngine ? `the ${techdocsTypes.TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY} annotation` : "techdocs.generator.defaultEngine";
49
+ throw new Error(
50
+ `No TechDocs generator registered for engine "${generatorKey}" (from ${source}). Registered engines: ${[...this.generatorMap.keys()].join(", ")}`
51
+ );
36
52
  }
37
53
  return generator;
38
54
  }
@@ -1 +1 @@
1
- {"version":3,"file":"generators.cjs.js","sources":["../../../src/stages/generate/generators.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 { Entity } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { getGeneratorKey } from './helpers';\nimport { TechdocsGenerator } from './techdocs';\nimport {\n GeneratorBase,\n GeneratorBuilder,\n SupportedGeneratorKey,\n} from './types';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { TechDocsContainerRunner } from './types';\n\n/**\n * Collection of docs generators\n * @public\n */\nexport class Generators implements GeneratorBuilder {\n private generatorMap = new Map<SupportedGeneratorKey, GeneratorBase>();\n\n /**\n * Returns a generators instance containing a generator for TechDocs\n * @param config - A Backstage configuration\n * @param options - Options to configure the TechDocs generator\n */\n static async fromConfig(\n config: Config,\n options: {\n logger: LoggerService;\n containerRunner?: TechDocsContainerRunner;\n customGenerator?: TechdocsGenerator;\n },\n ): Promise<GeneratorBuilder> {\n const generators = new Generators();\n\n const techdocsGenerator =\n options.customGenerator ?? TechdocsGenerator.fromConfig(config, options);\n generators.register('techdocs', techdocsGenerator);\n\n return generators;\n }\n\n /**\n * Register a generator in the generators collection\n * @param generatorKey - Unique identifier for the generator\n * @param generator - The generator instance to register\n */\n register(generatorKey: SupportedGeneratorKey, generator: GeneratorBase) {\n this.generatorMap.set(generatorKey, generator);\n }\n\n /**\n * Returns the generator for a given TechDocs entity\n * @param entity - A TechDocs entity instance\n */\n get(entity: Entity): GeneratorBase {\n const generatorKey = getGeneratorKey(entity);\n const generator = this.generatorMap.get(generatorKey);\n\n if (!generator) {\n throw new Error(`No generator registered for entity: \"${generatorKey}\"`);\n }\n\n return generator;\n }\n}\n"],"names":["TechdocsGenerator","getGeneratorKey"],"mappings":";;;;;AAgCO,MAAM,UAAA,CAAuC;AAAA,EAC1C,YAAA,uBAAmB,GAAA,EAA0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOrE,aAAa,UAAA,CACX,MAAA,EACA,OAAA,EAK2B;AAC3B,IAAA,MAAM,UAAA,GAAa,IAAI,UAAA,EAAW;AAElC,IAAA,MAAM,oBACJ,OAAA,CAAQ,eAAA,IAAmBA,0BAAA,CAAkB,UAAA,CAAW,QAAQ,OAAO,CAAA;AACzE,IAAA,UAAA,CAAW,QAAA,CAAS,YAAY,iBAAiB,CAAA;AAEjD,IAAA,OAAO,UAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAA,CAAS,cAAqC,SAAA,EAA0B;AACtE,IAAA,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,YAAA,EAAc,SAAS,CAAA;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,MAAA,EAA+B;AACjC,IAAA,MAAM,YAAA,GAAeC,wBAAgB,MAAM,CAAA;AAC3C,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,YAAY,CAAA;AAEpD,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,qCAAA,EAAwC,YAAY,CAAA,CAAA,CAAG,CAAA;AAAA,IACzE;AAEA,IAAA,OAAO,SAAA;AAAA,EACT;AACF;;"}
1
+ {"version":3,"file":"generators.cjs.js","sources":["../../../src/stages/generate/generators.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 { Entity } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { TechdocsGenerator } from './techdocs';\nimport {\n GeneratorBase,\n GeneratorBuilder,\n SupportedGeneratorKey,\n} from './types';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { TechDocsContainerRunner } from './types';\nimport { TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY } from '../../techdocsTypes';\n\n/**\n * 'techdocs' was the generator key before engines were introduced, and is\n * kept as an alias of 'mkdocs' so that existing registrations keep working.\n */\nfunction normalizeGeneratorKey(key: SupportedGeneratorKey): string {\n return key === 'techdocs' ? 'mkdocs' : key;\n}\n\n/**\n * Collection of docs generators\n * @public\n */\nexport class Generators implements GeneratorBuilder {\n private generatorMap = new Map<SupportedGeneratorKey, GeneratorBase>();\n private defaultEngine: SupportedGeneratorKey = 'mkdocs';\n\n /**\n * Returns a generators instance containing a generator for TechDocs\n * @param config - A Backstage configuration\n * @param options - Options to configure the TechDocs generator\n */\n static async fromConfig(\n config: Config,\n options: {\n logger: LoggerService;\n containerRunner?: TechDocsContainerRunner;\n customGenerator?: GeneratorBase;\n },\n ): Promise<GeneratorBuilder> {\n const generators = new Generators();\n const defaultEngine =\n config.getOptionalString('techdocs.generator.defaultEngine') ?? 'mkdocs';\n\n generators.defaultEngine = defaultEngine;\n\n options.logger.info(`TechDocs generator defaultEngine: ${defaultEngine}`);\n\n const mkdocsGenerator =\n options.customGenerator ?? TechdocsGenerator.fromConfig(config, options);\n generators.register('mkdocs', mkdocsGenerator);\n\n return generators;\n }\n\n /**\n * Register a generator in the generators collection\n * @param generatorKey - Unique identifier for the generator\n * @param generator - The generator instance to register\n */\n register(generatorKey: SupportedGeneratorKey, generator: GeneratorBase) {\n this.generatorMap.set(normalizeGeneratorKey(generatorKey), generator);\n }\n\n /**\n * Returns the generator for a given TechDocs entity\n * @param entity - A TechDocs entity instance\n */\n get(entity: Entity): GeneratorBase {\n if (!entity) {\n throw new Error('No entity provided');\n }\n\n const annotatedEngine =\n entity.metadata?.annotations?.[TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY];\n const generatorKey = normalizeGeneratorKey(\n annotatedEngine ?? this.defaultEngine,\n );\n const generator = this.generatorMap.get(generatorKey);\n\n if (!generator) {\n const source = annotatedEngine\n ? `the ${TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY} annotation`\n : 'techdocs.generator.defaultEngine';\n throw new Error(\n `No TechDocs generator registered for engine \"${generatorKey}\" (from ${source}). ` +\n `Registered engines: ${[...this.generatorMap.keys()].join(', ')}`,\n );\n }\n\n return generator;\n }\n}\n"],"names":["TechdocsGenerator","TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY"],"mappings":";;;;;AAgCA,SAAS,sBAAsB,GAAA,EAAoC;AACjE,EAAA,OAAO,GAAA,KAAQ,aAAa,QAAA,GAAW,GAAA;AACzC;AAMO,MAAM,UAAA,CAAuC;AAAA,EAC1C,YAAA,uBAAmB,GAAA,EAA0C;AAAA,EAC7D,aAAA,GAAuC,QAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO/C,aAAa,UAAA,CACX,MAAA,EACA,OAAA,EAK2B;AAC3B,IAAA,MAAM,UAAA,GAAa,IAAI,UAAA,EAAW;AAClC,IAAA,MAAM,aAAA,GACJ,MAAA,CAAO,iBAAA,CAAkB,kCAAkC,CAAA,IAAK,QAAA;AAElE,IAAA,UAAA,CAAW,aAAA,GAAgB,aAAA;AAE3B,IAAA,OAAA,CAAQ,MAAA,CAAO,IAAA,CAAK,CAAA,kCAAA,EAAqC,aAAa,CAAA,CAAE,CAAA;AAExE,IAAA,MAAM,kBACJ,OAAA,CAAQ,eAAA,IAAmBA,0BAAA,CAAkB,UAAA,CAAW,QAAQ,OAAO,CAAA;AACzE,IAAA,UAAA,CAAW,QAAA,CAAS,UAAU,eAAe,CAAA;AAE7C,IAAA,OAAO,UAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAA,CAAS,cAAqC,SAAA,EAA0B;AACtE,IAAA,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,qBAAA,CAAsB,YAAY,GAAG,SAAS,CAAA;AAAA,EACtE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,MAAA,EAA+B;AACjC,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,MAAM,IAAI,MAAM,oBAAoB,CAAA;AAAA,IACtC;AAEA,IAAA,MAAM,eAAA,GACJ,MAAA,CAAO,QAAA,EAAU,WAAA,GAAcC,oDAAsC,CAAA;AACvE,IAAA,MAAM,YAAA,GAAe,qBAAA;AAAA,MACnB,mBAAmB,IAAA,CAAK;AAAA,KAC1B;AACA,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,YAAY,CAAA;AAEpD,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,MAAM,MAAA,GAAS,eAAA,GACX,CAAA,IAAA,EAAOA,oDAAsC,CAAA,WAAA,CAAA,GAC7C,kCAAA;AACJ,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,6CAAA,EAAgD,YAAY,CAAA,QAAA,EAAW,MAAM,0BACpD,CAAC,GAAG,IAAA,CAAK,YAAA,CAAa,IAAA,EAAM,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,OACnE;AAAA,IACF;AAEA,IAAA,OAAO,SAAA;AAAA,EACT;AACF;;"}
@@ -35,12 +35,6 @@ var gitUrlParse__default = /*#__PURE__*/_interopDefaultCompat(gitUrlParse);
35
35
  var yaml__namespace = /*#__PURE__*/_interopNamespaceCompat(yaml);
36
36
  var path__default = /*#__PURE__*/_interopDefaultCompat(path);
37
37
 
38
- function getGeneratorKey(entity) {
39
- if (!entity) {
40
- throw new Error("No entity provided");
41
- }
42
- return "techdocs";
43
- }
44
38
  const runCommand = async ({
45
39
  command,
46
40
  args,
@@ -345,7 +339,7 @@ const patchIndexPreBuild = async ({
345
339
  ].join(" ")} exists.`
346
340
  );
347
341
  };
348
- const createOrUpdateMetadata = async (techdocsMetadataPath, logger) => {
342
+ const createOrUpdateMetadata = async (techdocsMetadataPath, logger, engine) => {
349
343
  const techdocsMetadataDir = techdocsMetadataPath.split(path__default.default.sep).slice(0, -1).join(path__default.default.sep);
350
344
  try {
351
345
  await fs__default.default.access(techdocsMetadataPath, fs__default.default.constants.F_OK);
@@ -361,6 +355,9 @@ const createOrUpdateMetadata = async (techdocsMetadataPath, logger) => {
361
355
  throw new Error(message);
362
356
  }
363
357
  json.build_timestamp = Date.now();
358
+ {
359
+ json.engine = engine;
360
+ }
364
361
  try {
365
362
  json.files = (await helpers.getFileTreeRecursively(techdocsMetadataDir)).map(
366
363
  (file) => file.replace(`${techdocsMetadataDir}${path__default.default.sep}`, "")
@@ -387,7 +384,6 @@ exports.MKDOCS_SCHEMA = MKDOCS_SCHEMA;
387
384
  exports.UnknownTag = UnknownTag;
388
385
  exports.createOrUpdateMetadata = createOrUpdateMetadata;
389
386
  exports.generateMkdocsYml = generateMkdocsYml;
390
- exports.getGeneratorKey = getGeneratorKey;
391
387
  exports.getMkdocsYml = getMkdocsYml;
392
388
  exports.getRepoUrlFromLocationAnnotation = getRepoUrlFromLocationAnnotation;
393
389
  exports.patchIndexPreBuild = patchIndexPreBuild;
@@ -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 * as yaml from 'js-yaml';\nimport {\n defineMappingTag,\n defineScalarTag,\n defineSequenceTag,\n mapTag,\n YAML11_SCHEMA,\n} 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\n// Listed exactly rather than by a `pymdownx.` prefix: resolving one of these\n// tags imports the module it names, and a prefix would accept any module-level\n// attribute of any importable submodule.\nconst ALLOWED_PYTHON_YAML_TAGS = new Set([\n // Emoji indexes; mkdocs-material moved its own from materialx to\n // material.extensions in 9.4.\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.emojione',\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.gemoji',\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.twemoji',\n 'tag:yaml.org,2002:python/name:materialx.emoji.twemoji',\n 'tag:yaml.org,2002:python/name:material.extensions.emoji.twemoji',\n // Emoji generators\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.to_alt',\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.to_png',\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.to_png_sprite',\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.to_svg',\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.to_svg_sprite',\n 'tag:yaml.org,2002:python/name:materialx.emoji.to_svg',\n 'tag:yaml.org,2002:python/name:material.extensions.emoji.to_svg',\n // Custom fence formats, used for Mermaid diagrams. The mermaid2 ones come\n // from mkdocs-mermaid2, which mkdocs-techdocs-core does not bundle.\n 'tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format',\n 'tag:yaml.org,2002:python/name:pymdownx.superfences.fence_div_format',\n 'tag:yaml.org,2002:python/name:mermaid2.fence_mermaid',\n 'tag:yaml.org,2002:python/name:mermaid2.fence_mermaid_custom',\n // Slug factory for toc and pymdownx.tabbed\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\n/** Preserve YAML merge keys and MkDocs-specific tags while inspecting config. */\nexport const MKDOCS_SCHEMA = YAML11_SCHEMA.withTags(\n defineScalarTag('', {\n matchByTagPrefix: true,\n resolve: (data, _isExplicit, tagName) => new UnknownTag(data, tagName),\n identify: data =>\n data instanceof UnknownTag && typeof data.data === 'string',\n represent: data => data.data,\n representTagName: data => data.type,\n }),\n defineSequenceTag<UnknownTag>('', {\n matchByTagPrefix: true,\n create: tagName => new UnknownTag([], tagName),\n addItem: (carrier, item) => {\n carrier.data.push(item);\n },\n identify: data => data instanceof UnknownTag && Array.isArray(data.data),\n represent: data => data.data,\n representTagName: data => data.type,\n }),\n defineMappingTag<UnknownTag>('tag:', {\n matchByTagPrefix: true,\n create: tagName => new UnknownTag({}, tagName),\n addPair: (carrier, key, value) => mapTag.addPair(carrier.data, key, value),\n has: (carrier, key) => mapTag.has(carrier.data, key),\n keys: result => mapTag.keys(result.data),\n get: (result, key) => mapTag.get(result.data, key),\n identify: data =>\n data instanceof UnknownTag &&\n typeof data.data === 'object' &&\n data.data !== null &&\n !Array.isArray(data.data),\n represent: data => new Map(Object.entries(data.data)),\n representTagName: data => 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","YAML11_SCHEMA","defineScalarTag","defineSequenceTag","defineMappingTag","mapTag","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 { 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 * as yaml from 'js-yaml';\nimport {\n defineMappingTag,\n defineScalarTag,\n defineSequenceTag,\n mapTag,\n YAML11_SCHEMA,\n} from 'js-yaml';\nimport path, { resolve as resolvePath } from 'node:path';\nimport { PassThrough, Writable } from 'node:stream';\nimport { ParsedLocationAnnotation } from '../../helpers';\nimport { DefaultMkdocsContent } from './types';\nimport { getFileTreeRecursively } from '../publish/helpers';\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\n// Listed exactly rather than by a `pymdownx.` prefix: resolving one of these\n// tags imports the module it names, and a prefix would accept any module-level\n// attribute of any importable submodule.\nconst ALLOWED_PYTHON_YAML_TAGS = new Set([\n // Emoji indexes; mkdocs-material moved its own from materialx to\n // material.extensions in 9.4.\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.emojione',\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.gemoji',\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.twemoji',\n 'tag:yaml.org,2002:python/name:materialx.emoji.twemoji',\n 'tag:yaml.org,2002:python/name:material.extensions.emoji.twemoji',\n // Emoji generators\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.to_alt',\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.to_png',\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.to_png_sprite',\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.to_svg',\n 'tag:yaml.org,2002:python/name:pymdownx.emoji.to_svg_sprite',\n 'tag:yaml.org,2002:python/name:materialx.emoji.to_svg',\n 'tag:yaml.org,2002:python/name:material.extensions.emoji.to_svg',\n // Custom fence formats, used for Mermaid diagrams. The mermaid2 ones come\n // from mkdocs-mermaid2, which mkdocs-techdocs-core does not bundle.\n 'tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format',\n 'tag:yaml.org,2002:python/name:pymdownx.superfences.fence_div_format',\n 'tag:yaml.org,2002:python/name:mermaid2.fence_mermaid',\n 'tag:yaml.org,2002:python/name:mermaid2.fence_mermaid_custom',\n // Slug factory for toc and pymdownx.tabbed\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\n/** Preserve YAML merge keys and MkDocs-specific tags while inspecting config. */\nexport const MKDOCS_SCHEMA = YAML11_SCHEMA.withTags(\n defineScalarTag('', {\n matchByTagPrefix: true,\n resolve: (data, _isExplicit, tagName) => new UnknownTag(data, tagName),\n identify: data =>\n data instanceof UnknownTag && typeof data.data === 'string',\n represent: data => data.data,\n representTagName: data => data.type,\n }),\n defineSequenceTag<UnknownTag>('', {\n matchByTagPrefix: true,\n create: tagName => new UnknownTag([], tagName),\n addItem: (carrier, item) => {\n carrier.data.push(item);\n },\n identify: data => data instanceof UnknownTag && Array.isArray(data.data),\n represent: data => data.data,\n representTagName: data => data.type,\n }),\n defineMappingTag<UnknownTag>('tag:', {\n matchByTagPrefix: true,\n create: tagName => new UnknownTag({}, tagName),\n addPair: (carrier, key, value) => mapTag.addPair(carrier.data, key, value),\n has: (carrier, key) => mapTag.has(carrier.data, key),\n keys: result => mapTag.keys(result.data),\n get: (result, key) => mapTag.get(result.data, key),\n identify: data =>\n data instanceof UnknownTag &&\n typeof data.data === 'object' &&\n data.data !== null &&\n !Array.isArray(data.data),\n represent: data => new Map(Object.entries(data.data)),\n representTagName: data => 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 engine?: string,\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 if (engine) {\n json.engine = engine;\n }\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","YAML11_SCHEMA","defineScalarTag","defineSequenceTag","defineMappingTag","mapTag","path","fs","yaml","ForwardedError","isChildPath","resolvePath","NotAllowedError","toError","getFileTreeRecursively"],"mappings":"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"}
@@ -168,7 +168,8 @@ class TechdocsGenerator {
168
168
  }
169
169
  await helpers.createOrUpdateMetadata(
170
170
  path__default.default.join(outputDir, "techdocs_metadata.json"),
171
- childLogger
171
+ childLogger,
172
+ "mkdocs"
172
173
  );
173
174
  if (etag) {
174
175
  await helpers.storeEtagMetadata(
@@ -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 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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"}
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 'mkdocs',\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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"}
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+
3
+ const TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY = "backstage.io/techdocs-engine";
4
+
5
+ exports.TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY = TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY;
6
+ //# sourceMappingURL=techdocsTypes.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"techdocsTypes.cjs.js","sources":["../src/techdocsTypes.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity } from '@backstage/catalog-model';\nimport { IndexableDocument } from '@backstage/plugin-search-common';\n\n/**\n * TechDocs indexable document interface\n * @public\n */\nexport interface TechDocsDocument extends IndexableDocument {\n /**\n * Entity kind\n */\n kind: string;\n /**\n * Entity metadata namespace\n */\n namespace: string;\n /**\n * Entity metadata name\n */\n name: string;\n /**\n * Entity lifecycle\n */\n lifecycle: string;\n /**\n * Entity owner\n */\n owner: string;\n /**\n * Entity path\n */\n path: string;\n}\n\n/**\n * A strategy for when to build TechDocs locally, and when to skip building TechDocs (allowing for an external build)\n *\n * @public\n */\nexport interface DocsBuildStrategy {\n shouldBuild(params: { entity: Entity }): Promise<boolean>;\n}\n\n/**\n * A catalog annotation key indicating which TechDoc Generation Engine to use\n *\n * @public\n */\nexport const TECHDOCS_ENGINE_CATALOG_ANNOTATION_KEY =\n 'backstage.io/techdocs-engine';\n"],"names":[],"mappings":";;AAgEO,MAAM,sCAAA,GACX;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-techdocs-node",
3
- "version": "2.0.3-next.2",
3
+ "version": "2.1.0",
4
4
  "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli",
5
5
  "backstage": {
6
6
  "role": "node-library",
@@ -53,14 +53,14 @@
53
53
  "@aws-sdk/types": "^3.347.0",
54
54
  "@azure/identity": "^4.0.0",
55
55
  "@azure/storage-blob": "^12.5.0",
56
- "@backstage/backend-plugin-api": "1.11.0-next.1",
57
- "@backstage/catalog-model": "1.10.2-next.0",
58
- "@backstage/config": "1.3.10-next.0",
59
- "@backstage/errors": "1.3.2-next.0",
60
- "@backstage/integration": "2.1.3-next.1",
61
- "@backstage/integration-aws-node": "0.2.3-next.0",
62
- "@backstage/plugin-search-common": "1.2.26-next.0",
63
- "@backstage/plugin-techdocs-common": "0.1.2-next.0",
56
+ "@backstage/backend-plugin-api": "^1.11.0",
57
+ "@backstage/catalog-model": "^1.10.2",
58
+ "@backstage/config": "^1.3.10",
59
+ "@backstage/errors": "^1.3.2",
60
+ "@backstage/integration": "^2.1.3",
61
+ "@backstage/integration-aws-node": "^0.2.3",
62
+ "@backstage/plugin-search-common": "^1.2.26",
63
+ "@backstage/plugin-techdocs-common": "^0.1.2",
64
64
  "@google-cloud/storage": "^7.0.0",
65
65
  "@smithy/node-http-handler": "^3.0.0",
66
66
  "@trendyol-js/openstack-swift-sdk": "^0.0.7",
@@ -78,8 +78,8 @@
78
78
  "winston": "^3.2.1"
79
79
  },
80
80
  "devDependencies": {
81
- "@backstage/backend-test-utils": "1.11.8-next.1",
82
- "@backstage/cli": "0.36.7-next.0",
81
+ "@backstage/backend-test-utils": "^1.11.8",
82
+ "@backstage/cli": "^0.36.7",
83
83
  "@types/fs-extra": "^11.0.0",
84
84
  "@types/js-yaml": "^4.0.0",
85
85
  "@types/mime-types": "^2.1.0",