Merge pull request #6113 from djamaile/master

fix: todo-plugin ignores images and files that are bigger than/equal 200k
This commit is contained in:
Patrik Oldsberg
2021-08-17 14:18:37 +02:00
committed by GitHub
8 changed files with 70 additions and 18 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-common': patch
---
Add an optional `info` parameter to the `readTree` filter option with a `size` property.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-todo-backend': patch
---
Ignore images and files that are larger than 200KB.
+1 -1
View File
@@ -593,7 +593,7 @@ export function useHotMemoize<T>(_module: NodeModule, valueFactory: () => T): T;
// src/cache/types.d.ts:34:5 - (ae-forgotten-export) The symbol "ClientOptions" needs to be exported by the entry point index.d.ts
// src/middleware/errorHandler.d.ts:17:26 - (tsdoc-malformed-html-name) Invalid HTML element: A space is not allowed here
// src/reading/AzureUrlReader.d.ts:9:9 - (ae-forgotten-export) The symbol "ReadTreeResponseFactory" needs to be exported by the entry point index.d.ts
// src/reading/types.d.ts:106:5 - (ae-forgotten-export) The symbol "ReadTreeResponseDirOptions" needs to be exported by the entry point index.d.ts
// src/reading/types.d.ts:108:5 - (ae-forgotten-export) The symbol "ReadTreeResponseDirOptions" needs to be exported by the entry point index.d.ts
// src/service/types.d.ts:12:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
// src/service/types.d.ts:22:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
// src/service/types.d.ts:30:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
@@ -43,7 +43,7 @@ export class TarArchiveResponse implements ReadTreeResponse {
private readonly subPath: string,
private readonly workDir: string,
public readonly etag: string,
private readonly filter?: (path: string) => boolean,
private readonly filter?: (path: string, info: { size: number }) => boolean,
) {
if (subPath) {
if (!subPath.endsWith('/')) {
@@ -92,7 +92,7 @@ export class TarArchiveResponse implements ReadTreeResponse {
const path = relativePath.slice(this.subPath.length);
if (this.filter) {
if (!this.filter(path)) {
if (!this.filter(path, { size: entry.remain })) {
entry.resume();
return;
}
@@ -155,7 +155,7 @@ export class TarArchiveResponse implements ReadTreeResponse {
tar.extract({
strip,
cwd: dir,
filter: path => {
filter: (path, stat) => {
// File path relative to the root extracted directory. Will remove the
// top level dir name from the path since its name is hard to predetermine.
const relativePath = stripFirstDirectoryFromPath(path);
@@ -164,7 +164,7 @@ export class TarArchiveResponse implements ReadTreeResponse {
}
if (this.filter) {
const innerPath = path.split('/').slice(strip).join('/');
return this.filter(innerPath);
return this.filter(innerPath, { size: stat.size });
}
return true;
},
@@ -37,7 +37,7 @@ export class ZipArchiveResponse implements ReadTreeResponse {
private readonly subPath: string,
private readonly workDir: string,
public readonly etag: string,
private readonly filter?: (path: string) => boolean,
private readonly filter?: (path: string, info: { size: number }) => boolean,
) {
if (subPath) {
if (!subPath.endsWith('/')) {
@@ -75,7 +75,11 @@ export class ZipArchiveResponse implements ReadTreeResponse {
}
}
if (this.filter) {
return this.filter(this.getInnerPath(entry.path));
return this.filter(this.getInnerPath(entry.path), {
size:
(entry.vars as { uncompressedSize?: number }).uncompressedSize ??
entry.vars.compressedSize,
});
}
return true;
}
+2 -2
View File
@@ -104,7 +104,7 @@ export type ReadTreeOptions = {
*
* If no filter is provided all files are extracted.
*/
filter?(path: string): boolean;
filter?(path: string, info?: { size: number }): boolean;
/**
* An etag can be provided to check whether readTree's response has changed from a previous execution.
@@ -164,7 +164,7 @@ export type FromArchiveOptions = {
// etag of the blob
etag: string;
// Filter passed on from the ReadTreeOptions
filter?: (path: string) => boolean;
filter?: (path: string, info?: { size: number }) => boolean;
};
export interface ReadTreeResponseFactory {
@@ -74,7 +74,13 @@ describe('TodoScmReader', () => {
],
};
await expect(todoReader.readTodos({ url })).resolves.toEqual(expected);
// These two reads should only result in a single call to readTree
await expect(
Promise.all([
todoReader.readTodos({ url }),
todoReader.readTodos({ url }),
]),
).resolves.toEqual([expected, expected]);
expect(reader.readTree).toHaveBeenCalledTimes(1);
expect(reader.readTree).toHaveBeenCalledWith(
@@ -27,6 +27,19 @@ import {
} from './types';
import { Config } from '@backstage/config';
import { createTodoParser } from './createTodoParser';
import path from 'path';
const excludedExtensions = [
'.png',
'.svg',
'.jpg',
'.jpeg',
'.gif',
'.raw',
'.lock',
'.ico',
];
const MAX_FILE_SIZE = 200000;
type Options = {
logger: Logger;
@@ -47,6 +60,7 @@ export class TodoScmReader implements TodoReader {
private readonly integrations: ScmIntegrations;
private readonly cache = new Map<string, CacheItem>();
private readonly inFlightReads = new Map<string, Promise<CacheItem>>();
static fromConfig(config: Config, options: Omit<Options, 'integrations'>) {
return new TodoScmReader({
@@ -63,16 +77,26 @@ export class TodoScmReader implements TodoReader {
}
async readTodos({ url }: ReadTodosOptions): Promise<ReadTodosResult> {
const inFlightRead = this.inFlightReads.get(url);
if (inFlightRead) {
return inFlightRead.then(read => read.result);
}
const cacheItem = this.cache.get(url);
try {
const newCacheItem = await this.doReadTodos({ url }, cacheItem?.etag);
this.cache.set(url, newCacheItem);
return newCacheItem.result;
} catch (error) {
const newRead = this.doReadTodos({ url }, cacheItem?.etag).catch(error => {
if (cacheItem && error.name === 'NotModifiedError') {
return cacheItem.result;
return cacheItem;
}
throw error;
});
this.inFlightReads.set(url, newRead);
try {
const newCacheItem = await newRead;
this.cache.set(url, newCacheItem);
return newCacheItem.result;
} finally {
this.inFlightReads.delete(url);
}
}
@@ -82,8 +106,16 @@ export class TodoScmReader implements TodoReader {
): Promise<CacheItem> {
const tree = await this.reader.readTree(url, {
etag,
filter(path) {
return !path.startsWith('.') && !path.includes('/.');
filter(filePath, info) {
const extname = path.extname(filePath);
if (info && info.size > MAX_FILE_SIZE) {
return false;
}
return (
!filePath.startsWith('.') &&
!filePath.includes('/.') &&
!excludedExtensions.includes(extname)
);
},
});