bananareporter 0.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.
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitlabIntegration = exports.GitlabConfig = void 0;
4
+ const base_1 = require("./base");
5
+ // import {Axios} from 'axios'
6
+ const common_1 = require("../util/common");
7
+ const zod_1 = require("zod");
8
+ const axios_1 = require("axios");
9
+ const dayjs = require("dayjs");
10
+ const logger_1 = require("../util/logger");
11
+ exports.GitlabConfig = zod_1.z.object({
12
+ committerUsername: zod_1.z.string().min(1),
13
+ userId: zod_1.z.number().optional(),
14
+ token: zod_1.z.string().min(1),
15
+ filters: zod_1.z.array(zod_1.z.object({
16
+ on: zod_1.z.string().min(1),
17
+ regex: zod_1.z.string().min(1),
18
+ })).nonempty().optional(),
19
+ domain: zod_1.z.string().optional().default('gitlab.com'),
20
+ apiVersion: zod_1.z.string().startsWith('v').optional().default('v4'),
21
+ protocol: zod_1.z.union([zod_1.z.literal('http'), zod_1.z.literal('https')]).optional().default('https'),
22
+ apiBasePath: zod_1.z.string().optional().default('api'),
23
+ // TODO should be extended from a base integration
24
+ from: common_1.zIsoString.optional(),
25
+ to: common_1.zIsoString.optional(),
26
+ delay: zod_1.z.number().min(0).optional(),
27
+ });
28
+ class GitlabIntegration extends base_1.IntegrationBase {
29
+ constructor(_rawConfig, bananaReporterConfig) {
30
+ var _a, _b, _c;
31
+ super(_rawConfig, bananaReporterConfig);
32
+ this.projectIdToDetails = {};
33
+ logger_1.logger.debug('gitlab integration');
34
+ this.config = exports.GitlabConfig.parse(_rawConfig);
35
+ logger_1.logger.debug('gitlab integration config', this.config);
36
+ this.bananaReporterConfig = bananaReporterConfig;
37
+ this.delayToUse = (_a = this.config.delay) !== null && _a !== void 0 ? _a : this.bananaReporterConfig.delay;
38
+ logger_1.logger.debug(`gitlab integration delayToUse ${this.delayToUse}`);
39
+ this.dateRange = {
40
+ from: (_b = this.config.from) !== null && _b !== void 0 ? _b : this.bananaReporterConfig.from,
41
+ to: (_c = this.config.to) !== null && _c !== void 0 ? _c : this.bananaReporterConfig.to,
42
+ };
43
+ logger_1.logger.debug('gitlab integration dateRange', this.dateRange);
44
+ const baseURL = `${this.config.protocol}://${this.config.domain}/${this.config.apiBasePath}/${this.config.apiVersion}`;
45
+ logger_1.logger.debug(`gitlab integration baseURL ${baseURL}`);
46
+ this.httpClient = axios_1.default.create({
47
+ baseURL,
48
+ headers: {
49
+ Authorization: `Bearer ${this.config.token}`,
50
+ },
51
+ timeout: 35000,
52
+ });
53
+ }
54
+ setup(options) {
55
+ for (const p of options.projectDetails) {
56
+ if (!(p.id in this.projectIdToDetails)) {
57
+ this.projectIdToDetails[p.id] = p;
58
+ }
59
+ }
60
+ }
61
+ async httpRequest(path, config) {
62
+ logger_1.logger.debug(`gitlab integration http request ${path}`, config);
63
+ const res = await this.httpClient(path, {
64
+ ...config,
65
+ });
66
+ return res.data;
67
+ }
68
+ /**
69
+ * events API needs the user id, retrieve it
70
+ * from the username provided
71
+ * @returns GitLab user id
72
+ */
73
+ async getUserId() {
74
+ if (typeof this.config.userId !== 'undefined') {
75
+ return this.config.userId;
76
+ }
77
+ const username = this.config.committerUsername;
78
+ const data = await this.httpRequest(`/users?username=${username}`);
79
+ if (data.length === 0) {
80
+ throw new Error(`unable to find the GitLab username ${username}`);
81
+ }
82
+ return data[0].id;
83
+ }
84
+ async fetchData() {
85
+ const userId = await this.getUserId();
86
+ logger_1.logger.debug(`gitlab integration userId ${userId}`);
87
+ let page = 1;
88
+ let eventList = [];
89
+ const projectIds = new Set();
90
+ while (page > 0) {
91
+ logger_1.logger.debug(`gitlab integration working on ${page}`);
92
+ /* eslint-disable no-await-in-loop */
93
+ const events = await this.getUserEvents(userId, {
94
+ from: this.dateRange.from,
95
+ to: this.dateRange.to,
96
+ page,
97
+ });
98
+ // no more data, break loop
99
+ if (events.length === 0) {
100
+ logger_1.logger.debug('gitlab integration no more events to process');
101
+ break;
102
+ }
103
+ for (const e of events) {
104
+ projectIds.add(e.project_id);
105
+ }
106
+ // eslint-disable-next-line unicorn/prefer-spread
107
+ eventList = eventList.concat(events);
108
+ logger_1.logger.debug(`gitlab integration eventList ${eventList.length} (added ${events.length} events)`);
109
+ page += 1;
110
+ await (0, common_1.delay)(this.delayToUse);
111
+ }
112
+ // get all project details
113
+ const projectDetails = await this.getProjects(userId, projectIds);
114
+ logger_1.logger.debug('gitlab integration projectDetails', projectDetails);
115
+ for (const p of projectDetails) {
116
+ this.projectIdToDetails[p.id] = p;
117
+ }
118
+ this.setup({
119
+ projectDetails,
120
+ });
121
+ // format data to common obj
122
+ let filteredData = eventList
123
+ .filter(e => e.action_name.startsWith('pushed'));
124
+ const projectsToRemove = new Set();
125
+ for (const filter of this.config.filters || []) {
126
+ const targetKey = filter.on;
127
+ if (targetKey.startsWith('$project.')) {
128
+ // custom logic for project matching
129
+ for (const projectId of Object.keys(this.projectIdToDetails)) {
130
+ const project = this.projectIdToDetails[Number(projectId)];
131
+ const targetProjectKey = targetKey.split('.').pop();
132
+ if (!(targetProjectKey in project)) {
133
+ const errorMsg = `while filtering the project ${project.name} (id ${project.id}) unable to find the key ${targetProjectKey} in the project's object (available keys: ${Object.keys(project).join(',')})`;
134
+ logger_1.logger.error(errorMsg);
135
+ throw new Error(errorMsg);
136
+ }
137
+ const regex = new RegExp(filter.regex);
138
+ if (!regex.test(project[targetProjectKey])) {
139
+ // false, remove
140
+ projectsToRemove.add(Number(projectId));
141
+ }
142
+ }
143
+ }
144
+ filteredData = filteredData.filter(e => {
145
+ const targetKey = filter.on;
146
+ // custom project is filtered later on
147
+ if (targetKey.startsWith('$project.')) {
148
+ return true;
149
+ }
150
+ const regex = new RegExp(filter.regex);
151
+ if (!(targetKey in e)) {
152
+ const errorMsg = `while filtering event id ${e.id} unable to find the key ${targetKey} in the event's object (available keys: ${Object.keys(e).join(',')})`;
153
+ logger_1.logger.error(errorMsg);
154
+ throw new Error(errorMsg);
155
+ }
156
+ return regex.test(e[targetKey]);
157
+ });
158
+ }
159
+ if (projectsToRemove.size > 0) {
160
+ filteredData = filteredData.filter(e => {
161
+ return !projectsToRemove.has(e.project_id);
162
+ });
163
+ }
164
+ const formattedData = filteredData.map(e => this.toBananaReporterObj(e));
165
+ return formattedData;
166
+ }
167
+ /**
168
+ * GitLab doesn't return project information in the events call,
169
+ * need to fetch all projects to enrich the events data
170
+ * @param userId identifier of gitlab user
171
+ * @param projectIds set of project ids found in events call
172
+ * @returns list of projects found
173
+ */
174
+ async getProjects(userId, projectIds) {
175
+ // get all user projects, if some projects are missing
176
+ // try to get the project via a direct GET call
177
+ const remainingProjectIdsToFetch = projectIds;
178
+ const projects = await this.httpRequest(`/users/${userId}/projects`);
179
+ for (const p of projects) {
180
+ remainingProjectIdsToFetch.delete(p.id);
181
+ }
182
+ const projectsLoaded = projects;
183
+ if (remainingProjectIdsToFetch.size > 0) {
184
+ // delay before starting to fetch other data
185
+ await (0, common_1.delay)(this.delayToUse);
186
+ // eslint-disable-next-line unicorn/prefer-spread
187
+ for (const projectId of Array.from(remainingProjectIdsToFetch)) {
188
+ try {
189
+ const project = await this.httpRequest(`/projects/${projectId}`);
190
+ if (project) {
191
+ remainingProjectIdsToFetch.delete(projectId);
192
+ projectsLoaded.push(project);
193
+ }
194
+ }
195
+ catch (error) {
196
+ logger_1.logger.debug(`gitlab integration unable to fetch details for project ID ${projectId}`, error);
197
+ }
198
+ await (0, common_1.delay)(this.delayToUse);
199
+ }
200
+ }
201
+ if (remainingProjectIdsToFetch.size > 0) {
202
+ logger_1.logger.warn(`unable to retrieve details for the following project ids: ${[...remainingProjectIdsToFetch].join(',')} make sure you have still access to these projects, the report won't include the project names`);
203
+ }
204
+ return projectsLoaded;
205
+ }
206
+ async getUserEvents(userId, options) {
207
+ var _a;
208
+ const fromDate = dayjs(options.from);
209
+ const toDate = dayjs(options.to);
210
+ // recalculate from - to according on GitLab API behaviour
211
+ const from = fromDate.subtract(1, 'day').format('YYYY-MM-DD');
212
+ const to = toDate.add(1, 'day').format('YYYY-MM-DD');
213
+ logger_1.logger.debug(`gitlab integration getUserEvents from ${from} to ${to}`);
214
+ const page = (_a = options.page) !== null && _a !== void 0 ? _a : 1;
215
+ const url = `/users/${userId}/events`;
216
+ const events = await this.httpRequest(url, {
217
+ params: {
218
+ after: from,
219
+ before: to,
220
+ sort: 'asc',
221
+ // eslint-disable-next-line camelcase
222
+ per_page: 100,
223
+ action: 'pushed',
224
+ page,
225
+ },
226
+ });
227
+ return events;
228
+ }
229
+ toBananaReporterObj(rawData) {
230
+ const projectId = rawData.project_id;
231
+ const projectDetails = this.projectIdToDetails[projectId];
232
+ const projectName = projectDetails === null || projectDetails === void 0 ? void 0 : projectDetails.path;
233
+ const description = `${rawData.push_data.commit_title} branch:${rawData.push_data.ref} git:${rawData.push_data.commit_to.slice(0, 7)}`;
234
+ return {
235
+ date: rawData.created_at,
236
+ username: rawData.author_username,
237
+ description,
238
+ projectId: String(projectId),
239
+ projectName,
240
+ type: GitlabIntegration.type,
241
+ __raw: this.bananaReporterConfig.includeRawObject ? rawData : undefined,
242
+ };
243
+ }
244
+ }
245
+ exports.GitlabIntegration = GitlabIntegration;
246
+ GitlabIntegration.type = base_1.SourceType.Enum.gitlab;
@@ -0,0 +1,51 @@
1
+ import { CommonBananaReporterObj, IntegrationBase } from './base';
2
+ import { BananaConfig } from '../core';
3
+ import { z } from 'zod';
4
+ import { Task } from '@lggruspe/todo-txt-parser';
5
+ export type TodoTxtConfig = z.infer<typeof TodoTxtConfig>;
6
+ export declare const TodoTxtConfig: z.ZodObject<{
7
+ file: z.ZodEffects<z.ZodDefault<z.ZodString>, string, string | undefined>;
8
+ filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
9
+ on: z.ZodString;
10
+ regex: z.ZodString;
11
+ }, "strip", z.ZodTypeAny, {
12
+ on: string;
13
+ regex: string;
14
+ }, {
15
+ on: string;
16
+ regex: string;
17
+ }>, "atleastone">>;
18
+ from: z.ZodOptional<z.ZodString>;
19
+ to: z.ZodOptional<z.ZodString>;
20
+ }, "strip", z.ZodTypeAny, {
21
+ from?: string | undefined;
22
+ to?: string | undefined;
23
+ filters?: [{
24
+ on: string;
25
+ regex: string;
26
+ }, ...{
27
+ on: string;
28
+ regex: string;
29
+ }[]] | undefined;
30
+ file: string;
31
+ }, {
32
+ file?: string | undefined;
33
+ from?: string | undefined;
34
+ to?: string | undefined;
35
+ filters?: [{
36
+ on: string;
37
+ regex: string;
38
+ }, ...{
39
+ on: string;
40
+ regex: string;
41
+ }[]] | undefined;
42
+ }>;
43
+ export declare class TodoTxtIntegration extends IntegrationBase {
44
+ static type: "todo.txt";
45
+ private config;
46
+ private dateRange;
47
+ private bananaReporterConfig;
48
+ constructor(_rawConfig: unknown, bananaReporterConfig: BananaConfig);
49
+ fetchData(): Promise<CommonBananaReporterObj[]>;
50
+ toBananaReporterObj(rawData: Task): CommonBananaReporterObj;
51
+ }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TodoTxtIntegration = exports.TodoTxtConfig = void 0;
4
+ const base_1 = require("./base");
5
+ // import {Axios} from 'axios'
6
+ const common_1 = require("../util/common");
7
+ const zod_1 = require("zod");
8
+ const dayjs = require("dayjs");
9
+ const logger_1 = require("../util/logger");
10
+ const objectPath = require("object-path");
11
+ const node_fs_1 = require("node:fs");
12
+ const todo_txt_parser_1 = require("@lggruspe/todo-txt-parser");
13
+ const path = require("path");
14
+ exports.TodoTxtConfig = zod_1.z.object({
15
+ // TODO support wildcard
16
+ file: zod_1.z.string().min(1).default('./todo.txt').refine(s => {
17
+ return path.resolve(s);
18
+ }),
19
+ filters: zod_1.z.array(zod_1.z.object({
20
+ on: zod_1.z.string().min(1),
21
+ regex: zod_1.z.string().min(1),
22
+ })).nonempty().optional(),
23
+ from: common_1.zIsoString.optional(),
24
+ to: common_1.zIsoString.optional(),
25
+ });
26
+ class TodoTxtIntegration extends base_1.IntegrationBase {
27
+ constructor(_rawConfig, bananaReporterConfig) {
28
+ var _a, _b;
29
+ super(_rawConfig, bananaReporterConfig);
30
+ logger_1.logger.debug('todo.txt integration');
31
+ this.config = exports.TodoTxtConfig.parse(_rawConfig);
32
+ logger_1.logger.debug('todo.txt integration config', this.config);
33
+ this.bananaReporterConfig = bananaReporterConfig;
34
+ this.dateRange = {
35
+ from: (_a = this.config.from) !== null && _a !== void 0 ? _a : this.bananaReporterConfig.from,
36
+ to: (_b = this.config.to) !== null && _b !== void 0 ? _b : this.bananaReporterConfig.to,
37
+ };
38
+ logger_1.logger.debug('todo.txt integration dateRange', this.dateRange);
39
+ if (!(0, node_fs_1.existsSync)(this.config.file)) {
40
+ const errorMsg = `Unable to find file ${this.config.file}`;
41
+ logger_1.logger.error(errorMsg);
42
+ throw new Error(errorMsg);
43
+ }
44
+ logger_1.logger.debug(`todo.txt file ${this.config.file} exists`);
45
+ }
46
+ async fetchData() {
47
+ // parse
48
+ const rawTodoTxt = (0, node_fs_1.readFileSync)(this.config.file).toString();
49
+ const parsedTodoTxtFile = (0, todo_txt_parser_1.parse)(rawTodoTxt);
50
+ const fromDate = dayjs(this.dateRange.from).subtract(1, 'minute');
51
+ const toDate = dayjs(this.dateRange.to).add(1, 'minute');
52
+ let filteredTasks = parsedTodoTxtFile.filter(t => {
53
+ const rawTaskDate = t.completion || t.creation;
54
+ // unknown task date, don't include it
55
+ if (typeof rawTaskDate === 'undefined') {
56
+ return false;
57
+ }
58
+ const taskDateObj = dayjs(rawTaskDate);
59
+ return (taskDateObj.isAfter(fromDate) &&
60
+ taskDateObj.isBefore(toDate));
61
+ });
62
+ // format data to common obj
63
+ for (const filter of this.config.filters || []) {
64
+ const targetKey = filter.on;
65
+ filteredTasks = filteredTasks.filter(t => {
66
+ const regex = new RegExp(filter.regex);
67
+ const value = objectPath.get(t, targetKey);
68
+ if (typeof value === 'undefined') {
69
+ const errorMsg = `while filtering "${t.description}" unable to find the key ${targetKey} in the task's object (available keys: ${Object.keys(t).join(',')})`;
70
+ logger_1.logger.error(errorMsg);
71
+ throw new Error(errorMsg);
72
+ }
73
+ return regex.test(value);
74
+ });
75
+ }
76
+ const formattedData = filteredTasks.map(e => this.toBananaReporterObj(e));
77
+ return formattedData;
78
+ }
79
+ toBananaReporterObj(rawData) {
80
+ var _a;
81
+ const projectName = (_a = rawData.projects) === null || _a === void 0 ? void 0 : _a.map(p => p.replace('+', '')).join(',');
82
+ const description = `${rawData.description}`;
83
+ return {
84
+ date: rawData.completion || rawData.creation || '',
85
+ description,
86
+ projectName,
87
+ type: TodoTxtIntegration.type,
88
+ __raw: this.bananaReporterConfig.includeRawObject ? rawData : undefined,
89
+ };
90
+ }
91
+ }
92
+ exports.TodoTxtIntegration = TodoTxtIntegration;
93
+ TodoTxtIntegration.type = base_1.SourceType.Enum['todo.txt'];
@@ -0,0 +1,4 @@
1
+ /// <reference types="node" />
2
+ import { z } from 'zod';
3
+ export declare const zIsoString: z.ZodString;
4
+ export declare function delay(ms: number): Promise<NodeJS.Timeout>;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.delay = exports.zIsoString = void 0;
4
+ const zod_1 = require("zod");
5
+ const ISO_DATE_REGEX = /\d{4}-[01]\d-[0-3]\d/;
6
+ // from https://github.com/colinhacks/zod/issues/482#issuecomment-1369800957
7
+ exports.zIsoString = zod_1.z
8
+ .string()
9
+ .regex(ISO_DATE_REGEX, 'date must be a valid ISO8601 date');
10
+ function delay(ms) {
11
+ // eslint-disable-next-line no-promise-executor-return
12
+ return new Promise(resolve => setTimeout(resolve, ms));
13
+ }
14
+ exports.delay = delay;
@@ -0,0 +1,2 @@
1
+ import winston = require('winston');
2
+ export declare const logger: winston.Logger;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logger = void 0;
4
+ const winston = require("winston");
5
+ exports.logger = winston.createLogger({
6
+ level: process.env.BANANAREPORTER_LOG_LEVEL || 'info',
7
+ format: winston.format.json(),
8
+ transports: [
9
+ new winston.transports.Console({
10
+ format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
11
+ }),
12
+ ],
13
+ });
@@ -0,0 +1,92 @@
1
+ {
2
+ "version": "0.1.0",
3
+ "commands": {
4
+ "run": {
5
+ "id": "run",
6
+ "description": "Run report",
7
+ "strict": true,
8
+ "pluginName": "bananareporter",
9
+ "pluginAlias": "bananareporter",
10
+ "pluginType": "core",
11
+ "aliases": [],
12
+ "examples": [
13
+ "$ banana-reporter run --from 2023-01-01 --to 2023-01-31\nreport with 138 entries saved to ./bananareporter.json\n"
14
+ ],
15
+ "flags": {
16
+ "config": {
17
+ "name": "config",
18
+ "type": "option",
19
+ "char": "c",
20
+ "description": "config file location, by default ~/.config/bananareporter/config.yaml",
21
+ "required": false,
22
+ "multiple": false
23
+ },
24
+ "from": {
25
+ "name": "from",
26
+ "type": "option",
27
+ "description": "from date (ISO8601)",
28
+ "required": false,
29
+ "helpValue": "2023-03-01",
30
+ "multiple": false
31
+ },
32
+ "to": {
33
+ "name": "to",
34
+ "type": "option",
35
+ "description": "to date (ISO8601)",
36
+ "required": false,
37
+ "helpValue": "2023-03-31",
38
+ "multiple": false,
39
+ "dependsOn": [
40
+ "from"
41
+ ]
42
+ },
43
+ "out": {
44
+ "name": "out",
45
+ "type": "option",
46
+ "char": "o",
47
+ "description": "file path to save the output",
48
+ "required": true,
49
+ "multiple": false,
50
+ "default": "./bananareporter.json",
51
+ "aliases": [
52
+ "output"
53
+ ]
54
+ },
55
+ "format": {
56
+ "name": "format",
57
+ "type": "option",
58
+ "description": "output file format",
59
+ "required": true,
60
+ "multiple": false,
61
+ "options": [
62
+ "json",
63
+ "jsonl",
64
+ "csv"
65
+ ],
66
+ "default": "json"
67
+ },
68
+ "delay": {
69
+ "name": "delay",
70
+ "type": "option",
71
+ "description": "global delay in millisecons between http requests",
72
+ "multiple": false,
73
+ "default": 300
74
+ },
75
+ "include-raw-object": {
76
+ "name": "include-raw-object",
77
+ "type": "boolean",
78
+ "description": "include raw object in json/jsonl reporter output",
79
+ "allowNo": false
80
+ },
81
+ "disable-welcome-emoji": {
82
+ "name": "disable-welcome-emoji",
83
+ "type": "boolean",
84
+ "description": "disable banana welcome emoji",
85
+ "hidden": true,
86
+ "allowNo": false
87
+ }
88
+ },
89
+ "args": {}
90
+ }
91
+ }
92
+ }
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "bananareporter",
3
+ "version": "0.1.0",
4
+ "description": "Easily generate a report from multiple sources",
5
+ "author": "nya1",
6
+ "bin": {
7
+ "bananareporter": "./bin/run"
8
+ },
9
+ "homepage": "https://github.com/nya1/bananareporter",
10
+ "license": "MIT",
11
+ "main": "dist/index.js",
12
+ "repository": "nya1/bananareporter",
13
+ "files": [
14
+ "/bin",
15
+ "/dist",
16
+ "/npm-shrinkwrap.json",
17
+ "/oclif.manifest.json"
18
+ ],
19
+ "dependencies": {
20
+ "@lggruspe/todo-txt-parser": "^1.1.1",
21
+ "@oclif/core": "^2",
22
+ "@oclif/plugin-help": "^5",
23
+ "axios": "^1.3.4",
24
+ "dayjs": "^1.11.7",
25
+ "js-yaml": "^4.1.0",
26
+ "lodash": "^4.17.21",
27
+ "object-path": "^0.11.8",
28
+ "papaparse": "^5.4.0",
29
+ "winston": "^3.8.2",
30
+ "zod": "^3.21.0"
31
+ },
32
+ "devDependencies": {
33
+ "@oclif/test": "^2.3.8",
34
+ "@types/chai": "^4",
35
+ "@types/js-yaml": "^4.0.5",
36
+ "@types/mocha": "^9.1.1",
37
+ "@types/node": "^18.14.6",
38
+ "@types/node-fetch": "^2.6.2",
39
+ "@types/object-path": "^0.11.1",
40
+ "@types/papaparse": "^5.3.7",
41
+ "chai": "^4",
42
+ "eslint": "^7.32.0",
43
+ "eslint-config-oclif": "^4",
44
+ "eslint-config-oclif-typescript": "^1.0.3",
45
+ "mocha": "^9",
46
+ "oclif": "^3",
47
+ "shx": "^0.3.4",
48
+ "ts-node": "^10.9.1",
49
+ "tslib": "^2.5.0",
50
+ "typescript": "^4.9.5"
51
+ },
52
+ "oclif": {
53
+ "bin": "bananareporter",
54
+ "dirname": "bananareporter",
55
+ "default": "run",
56
+ "commands": "./dist/commands",
57
+ "plugins": [
58
+ "@oclif/plugin-help"
59
+ ],
60
+ "topicSeparator": " ",
61
+ "topics": {
62
+ "run": {
63
+ "description": "Run reporter"
64
+ }
65
+ }
66
+ },
67
+ "scripts": {
68
+ "build": "shx rm -rf dist && tsc -b",
69
+ "lint": "eslint . --ext .ts --config .eslintrc",
70
+ "postpack": "shx rm -f oclif.manifest.json",
71
+ "posttest": "yarn lint",
72
+ "prepack": "yarn build && oclif manifest && oclif readme",
73
+ "test": "mocha --forbid-only \"test/**/*.test.ts\"",
74
+ "version": "oclif readme && git add README.md"
75
+ },
76
+ "engines": {
77
+ "node": ">=12.0.0"
78
+ },
79
+ "bugs": "https://github.com/nya1/bananareporter/issues",
80
+ "keywords": [
81
+ "report",
82
+ "banana",
83
+ "reporter",
84
+ "export",
85
+ "oclif"
86
+ ],
87
+ "types": "dist/index.d.ts"
88
+ }