diff --git a/.env.example b/.env.example index 05893d58..4a592944 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,7 @@ SWARM_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX CHANNEL_SANDBOX=CXXXXXXXX CHANNEL_ESOLANG=CXXXXXXXX CHANNEL_PROCON=CXXXXXXXX +CHANNEL_SCRAPBOX=CXXXXXXXX CHANNEL_RANDOM=CXXXXXXXX USER_TSGBOT=UXXXXXXXX GITHUB_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx @@ -14,6 +15,7 @@ GOOGLE_APPLICATION_CREDENTIALS=google_application_credentials.json FIREBASE_ENDPOINT=https://hakata-shi.firebaseio.com ACCUWEATHER_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx SCRAPBOX_SID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +SCRAPBOX_PROJECT_NAME=xxxxxxxxx GITHUB_WEBHOOK_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx SIGNING_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx PORT=21864 diff --git a/index.js b/index.js index 87760ee7..35ddcadc 100644 --- a/index.js +++ b/index.js @@ -31,6 +31,7 @@ const allBots = [ // ...(word2vecInstalled ? ['vocabwar'] : []), 'ricochet-robots', 'scrapbox', + 'scrapbox/mute', 'slack-log', 'welcome', 'deploy', diff --git a/lib/fastify.test.ts b/lib/fastify.test.ts new file mode 100644 index 00000000..614633b9 --- /dev/null +++ b/lib/fastify.test.ts @@ -0,0 +1,24 @@ +import plugin from 'fastify-plugin'; +import { fastifyDevConstructor } from './fastify'; +import { FastifyInstance } from 'fastify'; + +describe('fastifyDevConstructor', () => { + it('throws error when error occures during request', () => { + const fastify: FastifyInstance = fastifyDevConstructor(); + const msg = 'Dummy error.'; + const path = '/path/to/somewhere'; + fastify.register(plugin((fastify, opts, next) => { + fastify.get(path, (req) => { + throw Error(msg); + }) + next(); + })) + expect( + fastify.inject({ + method: 'GET', + url: path, + payload: {something: 'hoge'}, + }) + ).rejects.toThrow(msg); + }) +}); diff --git a/lib/fastify.ts b/lib/fastify.ts new file mode 100644 index 00000000..21444624 --- /dev/null +++ b/lib/fastify.ts @@ -0,0 +1,34 @@ +import fastifyConstructor, { FastifyInstance } from 'fastify'; + +/** + * 単体テストに適した設定がなされたfastifyインスタンスを生成する + * + * @param opts fastifyConstructor に渡す引数 + * @example + * import slack from '../lib/slackMock.js'; + * import {fastifyDevConstructor} from '../lib/fastify'; + * import {server} from './index'; + * + * const fastify = fastifyDevConstructor(); + * fastify.register(server(slack)); + */ + +export const fastifyDevConstructor = (opts?: Parameters[0]): FastifyInstance => { + // TODO: support generics of fastifyConstructor + /* + * Setting the return type to ReturnType causes typeerror + * because type Server is not compatible with type Http2SecureServer + * Maybe because of not handling the generics of fastifyConstructor + */ + + const fastify = fastifyConstructor({ logger: true , ...opts }); + + /** + * デフォルトのエラーハンドラはエラーをログに出力して握り潰すため, + * 単体テストでexpectの失敗などによる例外をJestが検知することができない + * 発生した例外は全て再送出するように設定 + */ + fastify.setErrorHandler((err) => { throw err; }); + + return fastify; +}; diff --git a/lib/scrapbox.test.ts b/lib/scrapbox.test.ts new file mode 100644 index 00000000..95f0e96c --- /dev/null +++ b/lib/scrapbox.test.ts @@ -0,0 +1,184 @@ +const defaultProjectName = 'default_proj'; +process.env.SCRAPBOX_PROJECT_NAME = defaultProjectName; +const defaultToken = 'default_token'; +process.env.SCRAPBOX_SID = defaultToken; + +jest.mock('axios'); +import _axios from 'axios'; +const axios = _axios as jest.Mocked; + +import { isPageOfProject, fetchScrapboxUrl, Page, pageUrlRegExp } from './scrapbox'; + +beforeEach(() => { + axios.get.mockReset(); +}) + +describe('fetchScrapboxUrl', () => { + const data = {dummy: 'data'}; + const url = 'dummy_url'; + const token = 'dummy_token'; + + beforeEach(() => { + axios.get.mockResolvedValueOnce({ data }); + }); + + it('fetches given URL with given token', async () => { + const res = await fetchScrapboxUrl({ url, token }); + expect(res).toEqual(data); + expect(axios.get.mock.calls.length).toBe(1); + expect(axios.get.mock.calls[0][0]).toBe(url); + expect(axios.get.mock.calls[0][1]!.headers.Cookie).toContain(token); + }); + + it('uses default token if not specified', async () => { + await fetchScrapboxUrl({ url }); + expect(axios.get.mock.calls[0][1]!.headers.Cookie).toContain(defaultToken); + }); +}); + +describe('pageUrlRegExp', () => { + const projectName = 'proj'; + const titleLc = 'タイトル'; + const hash = 'hash'; + + it('parses URL without hash', () => { + const match = `https://scrapbox.io/${projectName}/${titleLc}`.match(pageUrlRegExp); + expect(match).not.toBeNull(); + expect(match!.groups).toMatchObject({ projectName, titleLc }); + }); + + it('parses URL with hash', () => { + const match = `https://scrapbox.io/${projectName}/${titleLc}#${hash}`.match(pageUrlRegExp); + expect(match).not.toBeNull(); + expect(match!.groups).toMatchObject({ projectName, titleLc, hash }); + }); +}); + +describe('isPageOfProject', () => { + const projectName = 'proj'; + const projectName2 = 'proj2'; + const titleLc = 'タイトル'; + + it('returns true for Scrapbox URL of specified project', () => { + const url = `https://scrapbox.io/${projectName}/${titleLc}`; + expect(isPageOfProject({ url, projectName })).toBe(true); + }); + + it('returns false for Scrapbox URL of specified project', () => { + const url = `https://scrapbox.io/${projectName2}/${titleLc}`; + expect(isPageOfProject({ url, projectName })).toBe(false); + }); + + it('returns false for strings not Scrapbox URL', () => { + const url = 'hoge'; + expect(isPageOfProject({ url, projectName })).toBe(false); + }); + + test.each([ + {projectName: defaultProjectName, expected: true}, + {projectName: projectName, expected: false} + ])('uses projectName specified in envvar when not specified #%#', ({ projectName, expected }) => { + const url = `https://scrapbox.io/${projectName}/${titleLc}`; + expect(isPageOfProject({ url })).toBe(expected); + }); +}); + +describe('Page', () => { + const projectName = 'proj'; + const titleLc = 'タイトル'; + const encodedTitleLc = encodeURIComponent(titleLc); + const hash = 'hash'; + const token = 'token'; + + describe('constructor', () => { + const assertProperties = (page: Page): void => { + expect(page.token).toBe(token); + expect(page.projectName).toBe(projectName); + expect(page.titleLc).toBe(titleLc); + expect(page.encodedTitleLc).toBe(encodedTitleLc); + expect(page.hash).toBe(hash); + }; + + const generateURL = ({ projectName, titleLc, hash }: { projectName: string; titleLc: string; hash: string }) => + `https://scrapbox.io/${projectName}/${titleLc}#${hash}`; + + it('handles unencoded titleLc', () => { + assertProperties(new Page({ titleLc, projectName, hash, isEncoded: false, token })); + }); + + it('handles encoded titleLc', () => { + assertProperties(new Page({ titleLc: encodedTitleLc, projectName, hash, isEncoded: true, token })); + }); + + it('assumes unencoded titleLc to be unencoded', () => { + assertProperties(new Page({ titleLc, projectName, hash, isEncoded: undefined, token })); + }); + + it('assumes encoded titleLc to be encoded', () => { + assertProperties(new Page({ titleLc: encodedTitleLc, projectName, hash, isEncoded: undefined, token })); + }); + + + it('handles unencoded URL', () => { + const url = generateURL({ projectName, titleLc, hash }); + assertProperties(new Page({ url, isEncoded: false, token })); + }); + + it('handles encoded URL', () => { + const url = generateURL({ projectName, titleLc: encodedTitleLc, hash }); + assertProperties(new Page({ url, isEncoded: true, token })); + }); + + it('assumes unencoded URL to be unencoded', () => { + const url = generateURL({ projectName, titleLc, hash }); + assertProperties(new Page({ url, isEncoded: undefined, token })); + }); + + it('assumes encoded URL to be encoded', () => { + const url = generateURL({ projectName, titleLc: encodedTitleLc, hash }); + assertProperties(new Page({ url, isEncoded: undefined, token })); + }); + it('handles encoded URL', () => { + const url = generateURL({ projectName, titleLc: encodedTitleLc, hash }); + assertProperties(new Page({ url, isEncoded: undefined, token })); + }); + + it('handles missing parameters when specified titleLc', () => { + const page = new Page({ titleLc }); + expect(page.projectName).toBe(defaultProjectName); + expect(page.token).toBe(defaultToken); + expect(page.hash).toBeUndefined(); + }); + + it('handles missing parameters when specified URL', () => { + const page = new Page({ titleLc }); + expect(page.token).toBe(defaultToken); + }); + + it('throws error on invalid URL', () => { + expect(() => new Page({ url: 'hoge' })).toThrow(); + }) + }); + + describe('methods', () => { + let page: Page | null = null; + beforeEach(() => { + page = new Page({ titleLc, projectName, hash, token }); + }); + + test('.url is correct', () => { + expect(page!.url).toBe(`https://scrapbox.io/${projectName}/${encodedTitleLc}#${hash}`); + }); + + test('.infoUrl is correct', () => { + expect(page!.infoUrl).toBe(`https://scrapbox.io/api/pages/${projectName}/${encodedTitleLc}`); + }); + + test('.fetchInfo() fetches from correct URL', async () => { + axios.get.mockResolvedValueOnce({ data: {} }); + await page!.fetchInfo(); + expect(axios.get.mock.calls.length).toBe(1); + expect(axios.get.mock.calls[0][0]/* url of 0th call */).toBe(page!.infoUrl); + }); + }); +}); diff --git a/lib/scrapbox.ts b/lib/scrapbox.ts new file mode 100644 index 00000000..cc91658e --- /dev/null +++ b/lib/scrapbox.ts @@ -0,0 +1,203 @@ +import axios from 'axios'; + +export const tsgProjectName = process.env.SCRAPBOX_PROJECT_NAME!; +export const tsgScrapboxToken = process.env.SCRAPBOX_SID!; + +/** + * ScrapboxのURLにマッチする正規表現 + * Groups: { projectName, titleLc, hash } + */ +export const pageUrlRegExp = /^https?:\/\/scrapbox\.io\/(?.+?)\/(?.+?)(?:#(?.*))?$/; + +/** + * URLが指定したプロジェクトのURLか判定 + */ +export const isPageOfProject = ({ url, projectName = tsgProjectName }: { url: string, projectName?: string }) => { + const match = url.match(pageUrlRegExp); + return match !== null && match.groups!.projectName === projectName; +}; + +/** + * ScrapboxのURLをトークンをつけてGETリクエスト + */ +export const fetchScrapboxUrl = async ({ url, token = tsgScrapboxToken }: { url: string; token?: string }): Promise => { + // TODO: support axios config + return (await axios.get( + url, + { headers: { Cookie: `connect.sid=${token}` } }, + )).data; +} + +export interface User { + id: string; + name: string; + displayName: string; + photo: string; +} + +export interface Line { + id: string; + text: string; + userId: string; + created: number; + updated: number; +} + +export interface Link { + id: string; + title: string; + titleLc: string; + image?: string; + descriptions: string[]; + linksLc: string[]; + updated: number; + accessed: number; +} + +/** + * Scrapbox APIのページ情報の返り値 + */ +export interface PageInfo { + id: string; + title: string; + image?: string; + descriptions: string[]; + user: User; + pin: number; + views: number; + linked: number; + commitId: string; + created: number; + updated: number; + accessed: number; + snapshotCreated?: number; + persistent: boolean; + lines: Line[]; + links: string[]; + icons: { + [key: string]: number; + }; + relatedPages: { + links1hop: Link[]; + links2hop: Link[]; + icons1hop: unknown[]; // could not find a page that has icons1hop + } + collaborators: User[]; + lastAccessed: number; +} + + +const decodeIfNeeded = ({ str, isEncoded = undefined }: { str: string; isEncoded?: boolean }): { str: string; isEncoded?: boolean } => { + if (isEncoded === true || isEncoded === undefined) { + try { + str = decodeURIComponent(str); + } catch (err) { + // str is not a valid encoded string + if (isEncoded === true) throw err; + isEncoded = false; + } + } + return { str, isEncoded }; +} + +const encodeIfNeeded = ({ str, isEncoded = undefined }: { str: string; isEncoded?: boolean }): { str: string; isEncoded: boolean } => { + if (isEncoded === undefined) { + isEncoded = false; + try { + if (decodeURIComponent(str) !== str) { + isEncoded = true; + } + } catch { + // str is not a valid encoded string + } + } + return { str: isEncoded ? str: encodeURIComponent(str), isEncoded }; +} + +const parsePageUrl = (url: string): { titleLc: string; projectName: string; hash: string } => { + const match = url.match(pageUrlRegExp); + if (match) { + const { titleLc, projectName, hash } = match.groups!; + return { titleLc, projectName, hash }; + } else { + throw Error(`Invalid Scrapbox URL was given: ${url}`); + } +}; + +/** + * Scrapboxの記事 + */ +export class Page { + /** Scrapbox SID */ + token: string; + + /** Scrapbox プロジェクト名 */ + projectName: string; + + /** URIエンコードされたtitleLc */ + encodedTitleLc: string; + + /** URIエンコードされていないtitleLc */ + titleLc: string + + /** URL末尾のhash */ + hash?: string; + + /** + * Scrapboxの記事 + * + * URLあるいはtitleLc, [プロジェクト名, hash]を指定可能 + * + * @param args.token - Scrapbox SID + * @param args.isEncoded - URLのtitleLc部分がURIエンコード済みかどうか. デフォルトではurlまたはtitleLcから判断. + * @param args.url - Scrapbox 記事のURL + * @param args.titleLc - URL上の記事タイトル. スペースが_に変換されるなど,表示上のタイトルとは異なる場合がある. + * @param args.projectName - Scrapboxプロジェクト名. デフォルトでは環境変数を用いる + * @param hash - URL末尾のhash + */ + constructor(args: { + token?: string; + isEncoded?: boolean; + } & ({ + url: string; + } | { + titleLc: string; + projectName?: string + hash?: string + })) { + this.token = args.token ?? tsgScrapboxToken; + const { isEncoded: isEncodedGiven } = args; + const { titleLc, projectName, hash } = + 'titleLc' in args ? args : + 'url' in args ? parsePageUrl(args.url) : + args as never; // exhaustive check + // TODO: remove `as never` + // args should be but is not never here because of a bug of TypeScript (#37039) + const { str: encodedTitleLc, isEncoded } = encodeIfNeeded({ str: titleLc, isEncoded: isEncodedGiven }); + this.projectName = projectName ?? tsgProjectName; + this.encodedTitleLc = encodedTitleLc; + this.titleLc = decodeIfNeeded({ str: titleLc, isEncoded }).str; + this.hash = hash; + } + + /** + * Scrapbox記事のURL + */ + get url(): string { + return `https://scrapbox.io/${this.projectName}/${this.encodedTitleLc}${this.hash? `#${this.hash}` : ''}` + } + + /** + * ページ情報APIのURL + */ + get infoUrl(): string { + return `https://scrapbox.io/api/pages/${this.projectName}/${this.encodedTitleLc}`; + } + + /** + * ページ情報をAPIから取得 + */ + async fetchInfo(): Promise { + return fetchScrapboxUrl({ url: this.infoUrl, token: this.token }); + } +} diff --git a/package-lock.json b/package-lock.json index 43559dbb..b9fbfbae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -617,6 +617,12 @@ "resolve-from": "^5.0.0" }, "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -627,6 +633,24 @@ "path-exists": "^4.0.0" } }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -759,6 +783,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-escapes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", @@ -916,6 +949,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -993,6 +1035,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -1091,6 +1142,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -1199,6 +1259,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -1299,6 +1368,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -1418,6 +1496,15 @@ "chalk": "^3.0.0" }, "dependencies": { + "@types/yargs": { + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.4.tgz", + "integrity": "sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -1578,29 +1665,6 @@ "yargs": "^6.6.0" }, "dependencies": { - "@types/yargs": { - "version": "13.0.8", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz", - "integrity": "sha512-XAvHLwG7UQ+8M4caKIH0ZozIOYay5fQkAgyIXegXT9jPtdIGdhga+sUEdAr1CiG46aB+c64xQEYyEzlwWVTNzA==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -1609,35 +1673,6 @@ "ms": "2.0.0" } }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - } - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" - }, "yargs": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", @@ -1657,14 +1692,6 @@ "y18n": "^3.2.1", "yargs-parser": "^4.2.0" } - }, - "yargs-parser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", - "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", - "requires": { - "camelcase": "^3.0.0" - } } } }, @@ -2076,18 +2103,17 @@ } }, "@types/yargs": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", - "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", - "dev": true, + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", + "integrity": "sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ==", "requires": { "@types/yargs-parser": "*" } }, "@types/yargs-parser": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.1.0.tgz", + "integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==" }, "@typescript-eslint/experimental-utils": { "version": "2.13.0", @@ -2521,85 +2547,6 @@ "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.2.tgz", - "integrity": "sha512-YoKuru3Lyoy7yVTBSH2j7UxTqe/je3dWAruC0sHvZX1GNd5zX8SSLvQqEgO9b3Ex8IW+goFI9arEEsFIbulhOw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true - }, - "string.prototype.trimleft": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", - "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string.prototype.trimright": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", - "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - } } }, "arrify": { @@ -2746,6 +2693,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -3345,9 +3301,9 @@ } }, "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" }, "camelcase-keys": { "version": "2.1.0", @@ -3763,48 +3719,13 @@ } }, "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - } + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" } }, "clone": { @@ -5597,9 +5518,9 @@ } }, "resolve": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", - "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -5670,78 +5591,41 @@ "requires": { "ms": "2.0.0" } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + } + } + }, + "eslint-plugin-array-plural": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-array-plural/-/eslint-plugin-array-plural-1.2.0.tgz", + "integrity": "sha512-+G7QzDAjSei6s0+eXfQNY8YRwxw3oRTf0UVJerZY/bXmtEsj7LWb0B9UVUHVGSmuxSaLLBAbYRXAgzIqi2Nm/g==", + "dev": true, + "requires": { + "inflected": "^2.0.4", + "no-case": "^3.0.3", + "requireindex": "^1.2.0" + }, + "dependencies": { + "lower-case": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", + "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "tslib": "^1.10.0" } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "no-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz", + "integrity": "sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==", "dev": true, "requires": { - "find-up": "^2.1.0" + "lower-case": "^2.0.1", + "tslib": "^1.10.0" } } } }, - "eslint-plugin-array-plural": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-array-plural/-/eslint-plugin-array-plural-1.1.0.tgz", - "integrity": "sha512-VUcuCtz1+tIt4z9Rk+rsisben66pGshIvnW8u8bWjKgKkuUS+jhzPHqMDgicVXPjyt6K54CDC9cwtMbKPA/Fkw==", - "dev": true, - "requires": { - "inflected": "^2.0.2", - "no-case": "^2.3.1", - "requireindex": "^1.1.0" - } - }, "eslint-plugin-es": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-2.0.0.tgz", @@ -5767,9 +5651,9 @@ "dev": true }, "eslint-plugin-import": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.0.tgz", - "integrity": "sha512-NK42oA0mUc8Ngn4kONOPsPB1XhbUvNHqF+g307dPV28aknPoiNnKLFd9em4nkswwepdF5ouieqv5Th/63U7YJQ==", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz", + "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==", "dev": true, "requires": { "array-includes": "^3.0.3", @@ -5826,46 +5710,6 @@ "strip-bom": "^3.0.0" } }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, "path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", @@ -5904,6 +5748,61 @@ } } }, + "eslint-plugin-jest": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.6.0.tgz", + "integrity": "sha512-GH8AhcFXspOLqak7fqnddLXEJsrFyvgO8Bm60SexvKSn1+3rWYESnCiWUOCUcBTprNSDSE4CtAZdM4EyV6gPPw==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "^2.5.0", + "micromatch": "^4.0.2" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, "eslint-plugin-mysticatea": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/eslint-plugin-mysticatea/-/eslint-plugin-mysticatea-4.2.4.tgz", @@ -5980,11 +5879,12 @@ } }, "eslint-plugin-vue": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-6.1.2.tgz", - "integrity": "sha512-M75oAB+2a/LNkLKRbeEaS07EjzjIUaV7/hYoHAfRFeeF8ZMmCbahUn8nQLsLP85mkar24+zDU3QW2iT1JRsACw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-6.2.1.tgz", + "integrity": "sha512-MiIDOotoWseIfLIfGeDzF6sDvHkVvGd2JgkvjyHtN3q4RoxdAXrAMuI3SXTOKatljgacKwpNAYShmcKZa4yZzw==", "dev": true, "requires": { + "natural-compare": "^1.4.0", "semver": "^5.6.0", "vue-eslint-parser": "^7.0.0" } @@ -6196,6 +6096,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -7430,9 +7339,9 @@ "dev": true }, "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, "get-func-name": { "version": "2.0.0", @@ -8083,6 +7992,51 @@ "requires": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + } } }, "imurmurhash": { @@ -8788,6 +8742,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -8884,6 +8847,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -9062,6 +9034,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -9285,6 +9266,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -9393,6 +9383,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -9470,6 +9469,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -9559,6 +9567,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -9720,6 +9737,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -9818,6 +9844,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -9924,6 +9959,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -10052,6 +10096,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -10168,6 +10221,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -10257,6 +10319,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -10332,6 +10403,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -10423,6 +10503,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -10526,6 +10615,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -10629,6 +10727,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -10759,6 +10866,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -10837,6 +10953,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -10853,6 +10978,12 @@ "color-convert": "^2.0.1" } }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -10945,6 +11076,15 @@ "chalk": "^3.0.0" } }, + "@types/yargs": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz", + "integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, "ansi-escapes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", @@ -11450,12 +11590,21 @@ } }, "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } } }, "lodash": { @@ -13104,12 +13253,29 @@ } }, "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "^2.2.0" + "p-limit": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } } }, "p-queue": { @@ -13393,29 +13559,22 @@ } }, "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "find-up": "^4.0.0" + "find-up": "^2.1.0" }, "dependencies": { "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "^2.0.0" } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true } } }, @@ -14172,9 +14331,9 @@ "dev": true }, "react-is": { - "version": "16.12.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", - "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==", + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", + "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==", "dev": true } } @@ -15020,9 +15179,9 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, "require-relative": { "version": "0.8.7", @@ -17045,6 +17204,24 @@ "resolve": "1.x", "semver": "^5.5", "yargs-parser": "^16.1.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "yargs-parser": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-16.1.0.tgz", + "integrity": "sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } } }, "ts-node": { @@ -17169,9 +17346,9 @@ } }, "typescript": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.2.tgz", - "integrity": "sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ==" + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==" }, "typpy": { "version": "2.3.11", @@ -17744,9 +17921,9 @@ } }, "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" }, "which-pm-runs": { "version": "1.0.0", @@ -17877,70 +18054,12 @@ "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" }, "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - } + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" } }, "wrappy": { @@ -18006,9 +18125,9 @@ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" }, "yallist": { "version": "3.1.1", @@ -18038,6 +18157,43 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -18052,6 +18208,11 @@ "path-exists": "^4.0.0" } }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -18078,6 +18239,11 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -18095,16 +18261,44 @@ "requires": { "ansi-regex": "^5.0.0" } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yargs-parser": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-16.1.0.tgz", + "integrity": "sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, "yargs-parser": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-16.1.0.tgz", - "integrity": "sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "camelcase": "^3.0.0" } }, "yauzl": { diff --git a/package.json b/package.json index 10f4fccb..7e880ef4 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "byline": "^5.0.0", "csv-parse": "^4.8.2", "eslint": "^6.7.2", + "eslint-plugin-jest": "^23.6.0", "eslint-plugin-react": "^7.17.0", "jest": "^25.1.0", "lolex": "^5.1.1", diff --git a/scrapbox/.eslintrc.json b/scrapbox/.eslintrc.json index e5638929..1d677067 100644 --- a/scrapbox/.eslintrc.json +++ b/scrapbox/.eslintrc.json @@ -1,8 +1,17 @@ { - "extends": "@hakatashi", + "extends": ["plugin:jest/recommended", "@hakatashi"], "parser": "@typescript-eslint/parser", + "plugins": [ + "jest" + ], "rules": { "no-console": "off", - "camelcase": "off" + "camelcase": "off", + "valid-jsdoc": [ + "error", { + "requireParamType": false, + "requireReturnType": false + } + ] } } diff --git a/scrapbox/index.test.ts b/scrapbox/index.test.ts index ef0c0760..63cbb451 100644 --- a/scrapbox/index.test.ts +++ b/scrapbox/index.test.ts @@ -1,23 +1,35 @@ -jest.mock('axios'); - -import scrapbox from './index'; -// @ts-ignore -import Slack from '../lib/slackMock.js'; -import axios from 'axios'; import qs from 'querystring'; - +import axios from 'axios'; // @ts-ignore -axios.response = {data: {title: 'hoge', descriptions: ['fuga', 'piyo']}}; +import Slack from '../lib/slackMock.js'; let slack: Slack = null; -beforeEach(async () => { - slack = new Slack(); - process.env.CHANNEL_SANDBOX = slack.fakeChannel; - await scrapbox(slack); -}); +const projectName = 'PROJECTNAME'; +process.env.SCRAPBOX_PROJECT_NAME = projectName; + + +// eslint-disable-next-line import/first, import/imports-first, import/order +import {Page, PageInfo} from '../lib/scrapbox'; +// eslint-disable-next-line import/first, import/imports-first +import scrapbox from './index'; + +describe('unfurl', () => { + let fetchInfoSpy: jest.SpyInstance> | null = null; + + beforeEach(async () => { + fetchInfoSpy = jest.spyOn(Page.prototype, 'fetchInfo') + .mockImplementation(() => Promise.resolve({title: 'hoge', descriptions: ['fuga', 'piyo']} as PageInfo)); + // cast this because it is too demanding to completely write down all properties + slack = new Slack(); + process.env.CHANNEL_SANDBOX = slack.fakeChannel; + await scrapbox(slack); + }); + + afterEach(() => { + fetchInfoSpy!.mockRestore(); + }); -describe('scrapbox', () => { it('respond to slack hook of scrapbox unfurling', async () => { const done = new Promise((resolve) => { // @ts-ignore @@ -25,13 +37,12 @@ describe('scrapbox', () => { if (url === 'https://slack.com/api/chat.unfurl') { const parsed = qs.parse(data); const unfurls = JSON.parse(Array.isArray(parsed.unfurls) ? parsed.unfurls[0] : parsed.unfurls); - expect(unfurls['https://scrapbox.io/tsg/hoge']).toBeTruthy(); - expect(unfurls['https://scrapbox.io/tsg/hoge'].text).toBe('fuga\npiyo'); + expect(unfurls[`https://scrapbox.io/${projectName}/hoge`]).toBeTruthy(); + expect(unfurls[`https://scrapbox.io/${projectName}/hoge`].text).toBe('fuga\npiyo'); resolve(); return Promise.resolve({data: {ok: true}}); } - // @ts-ignore - return Promise.resolve(axios.response); + throw Error(`axios-mock: unknown URL: ${url}`); }); }); @@ -44,7 +55,7 @@ describe('scrapbox', () => { links: [ { domain: 'scrapbox.io', - url: 'https://scrapbox.io/tsg/hoge', + url: `https://scrapbox.io/${projectName}/hoge`, }, ], }); diff --git a/scrapbox/index.ts b/scrapbox/index.ts index 96ff948c..2457f829 100644 --- a/scrapbox/index.ts +++ b/scrapbox/index.ts @@ -1,12 +1,10 @@ +import qs from 'querystring'; +import {LinkUnfurls} from '@slack/client'; import axios from 'axios'; // @ts-ignore import logger from '../lib/logger.js'; -import {LinkUnfurls} from '@slack/client'; +import {Page, tsgProjectName} from '../lib/scrapbox'; import type {SlackInterface} from '../lib/slack'; -import qs from 'querystring'; - -const getScrapboxUrl = (pageName: string) => `https://scrapbox.io/api/pages/tsg/${pageName}`; - export default async ({rtmClient: rtm, webClient: slack, eventClient: event}: SlackInterface) => { event.on('link_shared', async (e: any) => { @@ -16,18 +14,16 @@ export default async ({rtmClient: rtm, webClient: slack, eventClient: event}: Sl const unfurls: LinkUnfurls = {}; for (const link of links) { const {url} = link; - if (!(/^https?:\/\/scrapbox.io\/tsg\/.+/).test(url)) { + let page: Page | null = null; + try { + page = new Page({url}); + } catch { continue; } - let pageName = url.replace(/^https?:\/\/scrapbox.io\/tsg\/(.+)$/, '$1'); - try { - if (decodeURI(pageName) === pageName) { - pageName = encodeURI(pageName); - } - } catch {} - const scrapboxUrl = getScrapboxUrl(pageName); - const response = await axios.get(scrapboxUrl, {headers: {Cookie: `connect.sid=${process.env.SCRAPBOX_SID}`}}); - const {data} = response; + if (page.projectName !== tsgProjectName) { + continue; + } + const data = await page.fetchInfo(); unfurls[url] = { title: data.title, diff --git a/scrapbox/mute.test.ts b/scrapbox/mute.test.ts new file mode 100644 index 00000000..68b3b8fa --- /dev/null +++ b/scrapbox/mute.test.ts @@ -0,0 +1,186 @@ +import EventEmitter from 'events'; +import {MessageAttachment} from '@slack/client'; +import {WebClient} from '@slack/web-api'; +import {flatten, set, sum} from 'lodash'; +import {fastifyDevConstructor} from '../lib/fastify'; +// @ts-ignore +import Slack from '../lib/slackMock.js'; + +const projectName = 'PROJECTNAME'; +process.env.SCRAPBOX_PROJECT_NAME = projectName; + +// eslint-disable-next-line import/first, import/imports-first, import/order +import {Page, PageInfo} from '../lib/scrapbox'; +// eslint-disable-next-line import/first, import/imports-first +import {maskAttachments, reconstructAttachments, server, muteTag, splitAttachments} from './mute'; +import { FastifyInstance } from 'fastify'; + +class FakeAttachmentGenerator { + i: number = 0; + + j: number = 0; + + get(kind: 'text' | 'img'): MessageAttachment { + let a: MessageAttachment & { [key: string]: any } | null = null; + switch (kind) { + case 'text': { + const text = `page ${this.i}`; + a = { + title: `タイトル ${this.i}`, + title_link: `https://scrapbox.io/${projectName}/${encodeURIComponent(`タイトル_${this.i}`)}#hash_${this.i}`, + text, + rawText: text, + mrkdwn_in: ['text' as const], + author_name: `user ${this.i}`, + image_url: `https://example.com/image_${this.i}.png`, + thumb_url: `https://example.com/thumb_${this.i}.png`, + }; + ++this.i; + break; + } + case 'img': { + a = { + image_url: `https://example.com/image_${this.i}_${this.j}.png`, + }; + ++this.j; + break; + } + default: { + a = kind; + } + } + return a; + } + + reset() { + this.i = 0; + this.j = 0; + } +} + +const waitEvent = (eventEmitter: EventEmitter, event: string): Promise => new Promise((resolve) => { + eventEmitter.once(event, (args) => { + resolve(args); + }); +}); + +describe('mute notification', () => { + describe('splitAttachments', () => { + it('splits attachments to each pages', () => { + const gen = new FakeAttachmentGenerator(); + const attachments = (['text', 'img', 'img', 'text', 'text', 'img'] as const).map((s) => gen.get(s)); + gen.reset(); + // eslint-disable-next-line array-plural/array-plural + const expected = [{ + text: gen.get('text'), + images: [gen.get('img'), gen.get('img')], + }, { + text: gen.get('text'), + images: [], + }, { + text: gen.get('text'), + images: [gen.get('img')], + }]; + const actual = splitAttachments(attachments); + expect(actual).toMatchObject(expected); + for (const {images} of actual) { + for (const attachment of images) { + expect(attachment.text).toBe(''); + } + } + }); + }); + + describe('maskAttachments', () => { + it('conceals values of notification', () => { + const gen = new FakeAttachmentGenerator(); + const notification = { + text: gen.get('text'), + images: [gen.get('img'), gen.get('img')], + }; + const attachments = maskAttachments(notification); + expect(attachments.length).toBe(1); + const [attachment] = attachments; + expect(attachment.text).toContain('ミュート'); + const unchanged = ['title', 'title_link', 'mrkdwn_in', 'author_name'] as const; + const nulled = ['image_url', 'thumb_url'] as const; + for (const key of unchanged) { + expect(attachment[key]).toEqual(notification.text[key]); + } + for (const key of nulled) { + expect(attachment[key]).toBeNull(); + } + }); + }); + + describe('reconstructAttachments', () => { + it('restores original attachment parsed by splitAttachments', () => { + const gen = new FakeAttachmentGenerator(); + const attachments = [gen.get('text'), gen.get('img'), gen.get('img')]; + const [notification] = splitAttachments(attachments); + const res = reconstructAttachments(notification); + expect(res).toMatchObject(attachments); + }); + }); + + describe('server', () => { + const fakeChannel = 'CSCRAPBOX'; + let fastify: FastifyInstance | null = null; + let slack: Slack | null = null; + + beforeAll(() => { + process.env.CHANNEL_SCRAPBOX = fakeChannel; + }); + + beforeEach(() => { + slack = new Slack(); + fastify = fastifyDevConstructor(); + fastify.register(server(slack)); + }); + + it(`mutes pages with ${muteTag} tag`, async () => { + // eslint-disable-next-line array-plural/array-plural + const isMuted = [true, false]; + const fetchInfoSpy = jest.spyOn(Page.prototype, 'fetchInfo').mockImplementation( + () => Promise.resolve(set( + {}, + ['relatedPages', 'links1hop'], + isMuted.map((b, i) => ({b, i})).filter(({b}) => b).map(({i}) => ({titleLc: `タイトル_${i}`})), + ) as PageInfo), + // cast this because it is too demanding to completely write down all properties + ); + const gen = new FakeAttachmentGenerator(); + + const separatedAttachments = [[ + gen.get('text'), + gen.get('img'), + gen.get('img'), + ], [ + gen.get('text'), + gen.get('img'), + ]]; + + const args = { + text: `New lines on `, + mrkdwn: true, + username: 'Scrapbox', + attachments: flatten(separatedAttachments), + }; + const messagePromise = waitEvent[0]>(slack, 'chat.postMessage'); + await fastify.inject({ + method: 'POST', + url: '/hooks/scrapbox', + payload: args, + }); + + const {channel, text, attachments: resultAttachment} = await messagePromise; + expect(channel).toBe(fakeChannel); + expect(text).toBe(args.text); + expect(resultAttachment.length).toBe( + sum(separatedAttachments.map((a, i) => isMuted[i] ? 1 : a.length)), + ); + + fetchInfoSpy!.mockRestore(); + }); + }); +}); diff --git a/scrapbox/mute.ts b/scrapbox/mute.ts new file mode 100644 index 00000000..8d5d0e45 --- /dev/null +++ b/scrapbox/mute.ts @@ -0,0 +1,105 @@ +import {WebClient, RTMClient, MessageAttachment} from '@slack/client'; +import plugin from 'fastify-plugin'; +import {flatten, zip} from 'lodash'; +// @ts-ignore +import {Page, pageUrlRegExp} from '../lib/scrapbox'; + +interface SlackInterface { + rtmClient: RTMClient, + webClient: WebClient, + eventClient: any, +} + +/** + * 1つのScrapbox記事に関する通知を表すオブジェクト + * @property text: 文章の更新についてのattachment + * @property images: 添付画像のattachment[] + */ +interface ScrapboxPageNotification { + text: MessageAttachment, + images: MessageAttachment[], +} + +/** + * Scrapboxからの通知attachments全体を記事ごとに分け,通知オブジェクトに変換 + * @param attachments: 複数の記事に関するattachments + * @returns 通知オブジェクトの配列 + */ +export const splitAttachments = (attachments: MessageAttachment[]): ScrapboxPageNotification[] => { + const pageIndices = attachments + .map(({title_link}, i) => ({url: title_link, i})) + .filter(({url}) => pageUrlRegExp.test(url)) + .map(({i}) => i); + const pageRange = zip(pageIndices, pageIndices.concat([attachments.length]).slice(1)); + return pageRange.map(([i, j]) => ({ + text: attachments[i], + images: attachments.slice(i + 1, j).map((a) => ({text: '', ...a})), + })); +}; + +/** + * ミュートしたい記事に対し,隠したい情報を消したattachmentsを生成 + * 文章の更新は一部を隠した上で返し,画像の更新は全て消す + * @param notification ミュートしたい記事の通知オブジェクト + * @return ミュート済みのattachments + */ +export const maskAttachments = (notification: ScrapboxPageNotification): MessageAttachment[] => { + const dummyText = 'この記事の更新通知はミュートされています。'; + return [{ + ...notification.text, + text: dummyText, + fallback: dummyText, + image_url: null, + thumb_url: null, + }]; +}; + +/** + * 記事の通知オブジェクトをそのままattachments形式に変換 + * @param notification 変換する記事の通知オブジェクト + * @return 変換されたattachments + */ +export const reconstructAttachments = (notification: ScrapboxPageNotification): MessageAttachment[] => [notification.text, ...notification.images]; + +export const muteTag = '##ミュート'; +const getMutedList = async (): Promise> => { + const muteTagPage = new Page({titleLc: muteTag, isEncoded: false}); + return new Set((await muteTagPage.fetchInfo()).relatedPages.links1hop.map(({titleLc}) => titleLc)); +}; + +interface SlackIncomingWebhookRequest { + text: string; + mrkdwn?: boolean; + username?: string; + attachments: MessageAttachment[]; +} + +/** + * Scrapboxからの更新通知 (Incoming Webhook形式) を受け取り,ミュート処理をしてSlackに投稿する + */ +// for developers: 実際に動かすのは手間がかかるので,fastify.inject などで動作確認するのがおすすめです。mute.test.ts 参照 + +// eslint-disable-next-line node/no-unsupported-features, node/no-unsupported-features/es-syntax +export const server = ({webClient: slack}: SlackInterface) => plugin((fastify, opts, next) => { + fastify.post('/hooks/scrapbox', async (req) => { + const mutedList = await getMutedList(); + const attachments = flatten( + splitAttachments(req.body.attachments).map( + (notification) => mutedList.has(new Page({url: notification.text.title_link}).titleLc) + ? maskAttachments(notification) + : reconstructAttachments(notification), + ), + ); + await slack.chat.postMessage( + { + channel: process.env.CHANNEL_SCRAPBOX, + icon_emoji: ':scrapbox:', + ...req.body, + attachments, + }, + ); + return ''; + }); + + next(); +}); diff --git a/welcome/index.ts b/welcome/index.ts index e2139b0d..835c7d14 100644 --- a/welcome/index.ts +++ b/welcome/index.ts @@ -1,15 +1,14 @@ import axios from 'axios'; +import {WebClient} from '@slack/client'; // @ts-ignore import logger from '../lib/logger.js'; -const welcomeScrapboxUrl = `https://scrapbox.io/api/pages/tsg/welcome`; - -import {WebClient} from '@slack/client'; +import {Page} from '../lib/scrapbox'; import type {SlackInterface} from '../lib/slack'; async function postWelcomeMessage(slack: WebClient, channel: string) { - const {data} = await axios.get(welcomeScrapboxUrl, {headers: {Cookie: `connect.sid=${process.env.SCRAPBOX_SID}`}}); - const text = data.lines.map(({text}: {text: string}) => text).slice(1).join('\n'); + const welcomePage = new Page({ titleLc: 'welcome', isEncoded: false }); + const text = (await welcomePage.fetchInfo()).lines.map(({text}: {text: string}) => text).slice(1).join('\n'); return slack.chat.postMessage({ channel,