@backstage/plugin-techdocs-node 1.12.11-next.1 → 1.12.11

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,42 @@
1
1
  # @backstage/plugin-techdocs-node
2
2
 
3
+ ## 1.12.11
4
+
5
+ ### Patch Changes
6
+
7
+ - f715f5c: Move `TechdocsContainerRunner` from `publish` to `generate`.
8
+ - 4417dd4: Fix typo and unify TechDocs casing in doc strings
9
+ - 57da51f: Add support for mapping custom tags in the techdocs yaml parser that validates the mkdocs.yaml file
10
+ - c2b63ab: Updated dependency `supertest` to `^7.0.0`.
11
+ - 3606843: Internal fixes to match `testcontainers` update
12
+ - 33ebb28: As the `@backstage/backend-common` package is deprecated, we have updated the `techdocs-node` package to stop depending on it.
13
+ - Updated dependencies
14
+ - @backstage/backend-plugin-api@1.0.0
15
+ - @backstage/catalog-model@1.7.0
16
+ - @backstage/integration@1.15.0
17
+ - @backstage/config@1.2.0
18
+ - @backstage/errors@1.2.4
19
+ - @backstage/integration-aws-node@0.1.12
20
+ - @backstage/plugin-search-common@1.2.14
21
+ - @backstage/plugin-techdocs-common@0.1.0
22
+
23
+ ## 1.12.11-next.2
24
+
25
+ ### Patch Changes
26
+
27
+ - 57da51f: Add support for mapping custom tags in the techdocs yaml parser that validates the mkdocs.yaml file
28
+ - c2b63ab: Updated dependency `supertest` to `^7.0.0`.
29
+ - 3606843: Internal fixes to match `testcontainers` update
30
+ - Updated dependencies
31
+ - @backstage/backend-plugin-api@1.0.0-next.2
32
+ - @backstage/integration@1.15.0-next.0
33
+ - @backstage/catalog-model@1.6.0
34
+ - @backstage/config@1.2.0
35
+ - @backstage/errors@1.2.4
36
+ - @backstage/integration-aws-node@0.1.12
37
+ - @backstage/plugin-search-common@1.2.14
38
+ - @backstage/plugin-techdocs-common@0.1.0
39
+
3
40
  ## 1.12.11-next.1
4
41
 
5
42
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -47,7 +47,29 @@ var os__default = /*#__PURE__*/_interopDefaultCompat(os);
47
47
 
48
48
  const getContentTypeForExtension = (ext) => {
49
49
  const defaultContentType = "text/plain; charset=utf-8";
50
- if (ext.match(/htm|xml|svg/i)) {
50
+ const excludedTypes = [
51
+ "text/html",
52
+ "text/xml",
53
+ "image/svg+xml",
54
+ "text/xsl",
55
+ "application/vnd.wap.xhtml+xml",
56
+ "multipart/x-mixed-replace",
57
+ "text/rdf",
58
+ "application/mathml+xml",
59
+ "application/octet-stream",
60
+ "application/rdf+xml",
61
+ "application/xhtml+xml",
62
+ "application/xml",
63
+ "text/cache-manifest",
64
+ "text/vtt"
65
+ ];
66
+ if (ext.match(
67
+ /htm|xml|svg|appcache|manifest|mathml|owl|rdf|rng|vtt|xht|xsd|xsl/i
68
+ )) {
69
+ return defaultContentType;
70
+ }
71
+ const contentType = mime__default.default.lookup(ext);
72
+ if (contentType && excludedTypes.includes(contentType)) {
51
73
  return defaultContentType;
52
74
  }
53
75
  return mime__default.default.contentType(ext) || defaultContentType;
@@ -126,6 +148,15 @@ const bulkStorageOperation = async (operation, args, { concurrencyLimit } = { co
126
148
  const limiter = createLimiter__default.default(concurrencyLimit);
127
149
  await Promise.all(args.map((arg) => limiter(operation, arg)));
128
150
  };
151
+ const isValidContentPath = (bucketRoot, contentPath) => {
152
+ const relativePath = path__default.default.posix.relative(bucketRoot, contentPath);
153
+ if (relativePath === "") {
154
+ return true;
155
+ }
156
+ const outsideBase = relativePath.startsWith("..");
157
+ const differentDrive = path__default.default.posix.isAbsolute(relativePath);
158
+ return !outsideBase && !differentDrive;
159
+ };
129
160
 
130
161
  function getGeneratorKey(entity) {
131
162
  if (!entity) {
@@ -196,6 +227,14 @@ const MKDOCS_SCHEMA = yaml.DEFAULT_SCHEMA.extend([
196
227
  instanceOf: UnknownTag,
197
228
  construct: (data, type) => new UnknownTag(data, type)
198
229
  }),
230
+ new yaml.Type("tag:", {
231
+ kind: "mapping",
232
+ multi: true,
233
+ representName: (o) => o.type,
234
+ represent: (o) => o.data ?? "",
235
+ instanceOf: UnknownTag,
236
+ construct: (data, type) => new UnknownTag(data, type)
237
+ }),
199
238
  new yaml.Type("", {
200
239
  kind: "sequence",
201
240
  multi: true,
@@ -466,9 +505,15 @@ class DockerContainerRunner {
466
505
  this.dockerClient.pull(imageName, {}, (err, stream) => {
467
506
  if (err) {
468
507
  reject(err);
469
- return;
508
+ } else if (!stream) {
509
+ reject(
510
+ new Error(
511
+ "Unexpeected error: no stream returned from Docker while pulling image"
512
+ )
513
+ );
514
+ } else {
515
+ pipeline(stream, logStream, { end: false }).then(resolve).catch(reject);
470
516
  }
471
- pipeline(stream, logStream, { end: false }).then(resolve).catch(reject);
472
517
  });
473
518
  });
474
519
  }
@@ -485,7 +530,7 @@ class DockerContainerRunner {
485
530
  const realHostDir = await fs__default.default.realpath(hostDir);
486
531
  Binds.push(`${realHostDir}:${containerDir}`);
487
532
  }
488
- const Env = [];
533
+ const Env = new Array();
489
534
  for (const [key, value] of Object.entries(envVars)) {
490
535
  Env.push(`${key}=${value}`);
491
536
  }
@@ -1150,6 +1195,12 @@ class AwsS3Publish {
1150
1195
  const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;
1151
1196
  const entityDir = this.legacyPathCasing ? entityTriplet : lowerCaseEntityTriplet(entityTriplet);
1152
1197
  const entityRootDir = path__default.default.posix.join(this.bucketRootPath, entityDir);
1198
+ if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {
1199
+ this.logger.error(
1200
+ `Invalid content path found while fetching TechDocs metadata: ${entityRootDir}`
1201
+ );
1202
+ throw new Error(`Metadata Not Found`);
1203
+ }
1153
1204
  try {
1154
1205
  const resp = await this.storageClient.send(
1155
1206
  new clientS3.GetObjectCommand({
@@ -1187,6 +1238,13 @@ class AwsS3Publish {
1187
1238
  const decodedUri = decodeURI(req.path.replace(/^\//, ""));
1188
1239
  const filePathNoRoot = this.legacyPathCasing ? decodedUri : lowerCaseEntityTripletInStoragePath(decodedUri);
1189
1240
  const filePath = path__default.default.posix.join(this.bucketRootPath, filePathNoRoot);
1241
+ if (!isValidContentPath(this.bucketRootPath, filePath)) {
1242
+ this.logger.error(
1243
+ `Attempted to fetch TechDocs content for a file outside of the bucket root: ${filePathNoRoot}`
1244
+ );
1245
+ res.status(404).send("File Not Found");
1246
+ return;
1247
+ }
1190
1248
  const fileExtension = path__default.default.extname(filePath);
1191
1249
  const responseHeaders = getHeadersForFileExtension(fileExtension);
1192
1250
  try {
@@ -1217,6 +1275,12 @@ class AwsS3Publish {
1217
1275
  const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;
1218
1276
  const entityDir = this.legacyPathCasing ? entityTriplet : lowerCaseEntityTriplet(entityTriplet);
1219
1277
  const entityRootDir = path__default.default.posix.join(this.bucketRootPath, entityDir);
1278
+ if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {
1279
+ this.logger.error(
1280
+ `Invalid content path found while checking if docs have been generated: ${entityRootDir}`
1281
+ );
1282
+ return Promise.resolve(false);
1283
+ }
1220
1284
  await this.storageClient.send(
1221
1285
  new clientS3.HeadObjectCommand({
1222
1286
  Bucket: this.bucketName,
@@ -1829,6 +1893,12 @@ class GoogleGCSPublish {
1829
1893
  const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;
1830
1894
  const entityDir = this.legacyPathCasing ? entityTriplet : lowerCaseEntityTriplet(entityTriplet);
1831
1895
  const entityRootDir = path__default.default.posix.join(this.bucketRootPath, entityDir);
1896
+ if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {
1897
+ this.logger.error(
1898
+ `Invalid content path found while fetching TechDocs metadata: ${entityRootDir}`
1899
+ );
1900
+ reject(new Error(`Metadata Not Found`));
1901
+ }
1832
1902
  const fileStreamChunks = [];
1833
1903
  this.storageClient.bucket(this.bucketName).file(`${entityRootDir}/techdocs_metadata.json`).createReadStream().on("error", (err) => {
1834
1904
  this.logger.error(err.message);
@@ -1849,6 +1919,13 @@ class GoogleGCSPublish {
1849
1919
  const decodedUri = decodeURI(req.path.replace(/^\//, ""));
1850
1920
  const filePathNoRoot = this.legacyPathCasing ? decodedUri : lowerCaseEntityTripletInStoragePath(decodedUri);
1851
1921
  const filePath = path__default.default.posix.join(this.bucketRootPath, filePathNoRoot);
1922
+ if (!isValidContentPath(this.bucketRootPath, filePath)) {
1923
+ this.logger.error(
1924
+ `Attempted to fetch TechDocs content for a file outside of the bucket root: ${filePathNoRoot}`
1925
+ );
1926
+ res.status(404).send("File Not Found");
1927
+ return;
1928
+ }
1852
1929
  const fileExtension = path__default.default.extname(filePath);
1853
1930
  const responseHeaders = getHeadersForFileExtension(fileExtension);
1854
1931
  this.storageClient.bucket(this.bucketName).file(filePath).createReadStream().on("pipe", () => {
@@ -1874,6 +1951,12 @@ class GoogleGCSPublish {
1874
1951
  const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;
1875
1952
  const entityDir = this.legacyPathCasing ? entityTriplet : lowerCaseEntityTriplet(entityTriplet);
1876
1953
  const entityRootDir = path__default.default.posix.join(this.bucketRootPath, entityDir);
1954
+ if (!isValidContentPath(this.bucketRootPath, entityRootDir)) {
1955
+ this.logger.error(
1956
+ `Invalid content path found while checking if docs have been generated: ${entityRootDir}`
1957
+ );
1958
+ resolve(false);
1959
+ }
1877
1960
  this.storageClient.bucket(this.bucketName).file(`${entityRootDir}/index.html`).exists().then((response) => {
1878
1961
  resolve(response[0]);
1879
1962
  }).catch(() => {