todo-backend: implement TodoScmReader
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -25,9 +25,10 @@ import { PluginEnvironment } from '../types';
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
reader,
|
||||
config,
|
||||
discovery,
|
||||
}: PluginEnvironment): Promise<Router> {
|
||||
const todoReader = new TodoScmReader({
|
||||
const todoReader = TodoScmReader.fromConfig(config, {
|
||||
logger,
|
||||
reader,
|
||||
});
|
||||
|
||||
@@ -22,18 +22,20 @@
|
||||
"@backstage/catalog-client": "^0.3.6",
|
||||
"@backstage/catalog-model": "^0.7.3",
|
||||
"@backstage/config": "^0.1.3",
|
||||
"@backstage/integration": "^0.5.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^3.0.3",
|
||||
"leasot": "^11.5.0",
|
||||
"winston": "^3.2.1",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.3",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"supertest": "^4.0.2",
|
||||
"msw": "^0.21.2"
|
||||
"msw": "^0.21.2",
|
||||
"supertest": "^4.0.2"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -14,27 +14,105 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { extname } from 'path';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { Logger } from 'winston';
|
||||
import { ReadTodosOptions, ReadTodosResult, TodoReader } from './types';
|
||||
import { parse } from 'leasot';
|
||||
import {
|
||||
ReadTodosOptions,
|
||||
ReadTodosResult,
|
||||
TodoItem,
|
||||
TodoReader,
|
||||
} from './types';
|
||||
import { Config } from '@backstage/config';
|
||||
|
||||
type TodoParser = (ctx: {
|
||||
content: string;
|
||||
path: string;
|
||||
}) => (TodoItem & { line: number })[];
|
||||
|
||||
type Options = {
|
||||
logger: Logger;
|
||||
reader: UrlReader;
|
||||
parser?: TodoParser;
|
||||
};
|
||||
|
||||
const defaultTodoParser: TodoParser = ({ content, path }) => {
|
||||
try {
|
||||
const comments = parse(content, {
|
||||
extension: extname(path),
|
||||
});
|
||||
|
||||
return comments.map(comment => ({
|
||||
text: comment.text,
|
||||
author: comment.ref,
|
||||
line: comment.line,
|
||||
}));
|
||||
} catch /* ignore unsupported extensions */ {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export class TodoScmReader implements TodoReader {
|
||||
private readonly logger: Logger;
|
||||
private readonly reader: UrlReader;
|
||||
private readonly parser: TodoParser;
|
||||
private readonly integrations: ScmIntegrations;
|
||||
|
||||
constructor(options: Options) {
|
||||
static fromConfig(config: Config, options: Options) {
|
||||
return new TodoScmReader(options, ScmIntegrations.fromConfig(config));
|
||||
}
|
||||
|
||||
private constructor(options: Options, integrations: ScmIntegrations) {
|
||||
this.logger = options.logger;
|
||||
this.reader = options.reader;
|
||||
this.parser = options.parser ?? defaultTodoParser;
|
||||
this.integrations = integrations;
|
||||
}
|
||||
|
||||
async readTodos({ url }: ReadTodosOptions): Promise<ReadTodosResult> {
|
||||
return {
|
||||
items: [{ text: 'My mock todo', viewUrl: url }],
|
||||
};
|
||||
const tree = await this.reader.readTree(url, {
|
||||
filter(path) {
|
||||
return !path.startsWith('.yarn');
|
||||
},
|
||||
});
|
||||
|
||||
const files = await tree.files();
|
||||
this.logger.info(`Read ${files.length} files from ${url}`);
|
||||
|
||||
const todos = new Array<TodoItem>();
|
||||
for (const file of files) {
|
||||
const content = await file.content();
|
||||
try {
|
||||
const items = this.parser({
|
||||
path: file.path,
|
||||
content: content.toString('utf8'),
|
||||
});
|
||||
const viewUrl = this.integrations.resolveUrl({
|
||||
url: file.path,
|
||||
base: url,
|
||||
});
|
||||
|
||||
let editUrl: string | undefined = this.integrations.resolveEditUrl(
|
||||
viewUrl,
|
||||
);
|
||||
if (editUrl === viewUrl) {
|
||||
editUrl = undefined;
|
||||
}
|
||||
|
||||
todos.push(
|
||||
...items.map(item => ({
|
||||
...item,
|
||||
editUrl,
|
||||
viewUrl: item.line ? `${viewUrl}#L${item.line}` : viewUrl,
|
||||
})),
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to parse TODO in ${url}, ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
return { items: todos };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10092,7 +10092,7 @@ commander@^5.0.0, commander@^5.1.0:
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
|
||||
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
|
||||
|
||||
commander@^6.1.0:
|
||||
commander@^6.1.0, commander@^6.2.1:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
|
||||
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
|
||||
@@ -12212,6 +12212,11 @@ envinfo@^7.7.4:
|
||||
resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz#c6311cdd38a0e86808c1c9343f667e4267c4a320"
|
||||
integrity sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ==
|
||||
|
||||
eol@^0.9.1:
|
||||
version "0.9.1"
|
||||
resolved "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz#f701912f504074be35c6117a5c4ade49cd547acd"
|
||||
integrity sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==
|
||||
|
||||
err-code@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9"
|
||||
@@ -13890,6 +13895,11 @@ get-stdin@^6.0.0:
|
||||
resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
|
||||
integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
|
||||
|
||||
get-stdin@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53"
|
||||
integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==
|
||||
|
||||
get-stream@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
|
||||
@@ -16881,6 +16891,11 @@ json-to-pretty-yaml@^1.2.2:
|
||||
remedial "^1.0.7"
|
||||
remove-trailing-spaces "^1.0.6"
|
||||
|
||||
json2xml@^0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.npmjs.org/json2xml/-/json2xml-0.1.3.tgz#9ae7c220bedd7c66a668e26f7ac182f6704eca21"
|
||||
integrity sha1-mufCIL7dfGamaOJvesGC9nBOyiE=
|
||||
|
||||
json3@^3.3.2:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
|
||||
@@ -17295,6 +17310,23 @@ ldapjs@^2.2.0:
|
||||
vasync "^2.2.0"
|
||||
verror "^1.8.1"
|
||||
|
||||
leasot@^11.5.0:
|
||||
version "11.5.0"
|
||||
resolved "https://registry.npmjs.org/leasot/-/leasot-11.5.0.tgz#a99eb4479618c9d2ea442a32ee006e5b9da4844d"
|
||||
integrity sha512-L08QKlmofYIRs5gfOmhOtbEJUu6U/zFGvYpboPq34yAHQ2Oc/QznOw62noe29yRJLiV/XnIDS8vO2um1e1sikA==
|
||||
dependencies:
|
||||
async "^3.2.0"
|
||||
chalk "^4.1.0"
|
||||
commander "^6.2.1"
|
||||
eol "^0.9.1"
|
||||
get-stdin "^8.0.0"
|
||||
globby "^11.0.1"
|
||||
json2xml "^0.1.3"
|
||||
lodash "^4.17.20"
|
||||
log-symbols "^4.0.0"
|
||||
strip-ansi "^6.0.0"
|
||||
text-table "^0.2.0"
|
||||
|
||||
left-pad@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
|
||||
|
||||
Reference in New Issue
Block a user