From 0b6e15745d7ea895f28c41ca06f917d2c462cc84 Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 02:41:50 -0500 Subject: [PATCH 01/16] test: replace jest with vitest --- jest.config.js | 9 --------- package.json | 8 +++----- tests/utils.spec.ts | 1 + 3 files changed, 4 insertions(+), 14 deletions(-) delete mode 100644 jest.config.js diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index ad261cf..0000000 --- a/jest.config.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - preset: 'ts-jest', - rootDir: __dirname, - moduleNameMapper: { - '^src/(.*)$': '/src/$1' - }, - testMatch: ['/tests/**/*.spec.ts'], - testPathIgnorePatterns: ['/node_modules/'] -} diff --git a/package.json b/package.json index 6678892..47298de 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "start": "ts-node src/index.ts", "lint": "prettier --check --write --parser typescript \"{src,test}/**/*.ts\"", "lint:fail": "prettier --check --parser typescript \"{src,test}/**/*.ts\"", - "jest": "jest", - "test": "yarn lint && yarn jest" + "vitest": "vitest", + "test": "yarn lint && vitest" }, "dependencies": { "@octokit/rest": "^18.3.0", @@ -19,9 +19,7 @@ "typescript": "^4.2.2" }, "devDependencies": { - "@types/jest": "^26.0.20", - "jest": "^26.6.3", "prettier": "^2.2.1", - "ts-jest": "^26.5.2" + "vitest": "^0.18.1" } } diff --git a/tests/utils.spec.ts b/tests/utils.spec.ts index 484da8a..3725240 100644 --- a/tests/utils.spec.ts +++ b/tests/utils.spec.ts @@ -1,4 +1,5 @@ import * as Utils from 'src/utils' +import { describe, it, expect } from 'vitest' describe('utils', () => { const https = 'https://github.com/vuejs/vuejs.org' From add0f5ca546d5a19da199e54fb0a21ad1227eedf Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 14:50:31 -0500 Subject: [PATCH 02/16] chore: add @vitest/ui --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 47298de..c9c97b4 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "lint": "prettier --check --write --parser typescript \"{src,test}/**/*.ts\"", "lint:fail": "prettier --check --parser typescript \"{src,test}/**/*.ts\"", "vitest": "vitest", - "test": "yarn lint && vitest" + "test": "yarn lint && vitest", + "test:ui": "yarn lint && vitest --ui" }, "dependencies": { "@octokit/rest": "^18.3.0", @@ -19,6 +20,7 @@ "typescript": "^4.2.2" }, "devDependencies": { + "@vitest/ui": "^0.18.1", "prettier": "^2.2.1", "vitest": "^0.18.1" } From 1c00902f1c5f7c52504abf6ac741094181322b0e Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 14:50:44 -0500 Subject: [PATCH 03/16] chore: configure vitest --- vitest.config.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 vitest.config.ts diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..74dc67e --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + reporters: 'verbose', + }, +}) \ No newline at end of file From eb976cef685a4548e272ba354032ae129cb679f4 Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 21:01:27 -0500 Subject: [PATCH 04/16] chore: get input from @actions/core instead of env --- action.yml | 11 ----------- package.json | 1 + src/index.ts | 29 +++++++++++------------------ 3 files changed, 12 insertions(+), 29 deletions(-) diff --git a/action.yml b/action.yml index 29369d9..0eb7870 100644 --- a/action.yml +++ b/action.yml @@ -43,17 +43,6 @@ runs: run: ${{ github.action_path }}/scripts/checkout.sh shell: bash - name: Run Ryu-Cho - env: - ACCESS_TOKEN: ${{ inputs.access-token }} - USER_NAME: ${{ inputs.username }} - EMAIL: ${{ inputs.email }} - UPSTREAM_REPO: ${{ inputs.upstream-repo }} - UPSTREAM_REPO_BRANCH: ${{ inputs.upstream-repo-branch }} - HEAD_REPO: ${{ inputs.head-repo }} - HEAD_REPO_BRANCH: ${{ inputs.head-repo-branch }} - TRACK_FROM: ${{ inputs.track-from }} - PATH_STARTS_WITH: ${{ inputs.path-starts-with }} - WORKFLOW_NAME: ${{ inputs.workflow-name }} run: | cd ryu-cho yarn install diff --git a/package.json b/package.json index c9c97b4..cb5685d 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "test:ui": "yarn lint && vitest --ui" }, "dependencies": { + "@actions/core": "^1.9.0", "@octokit/rest": "^18.3.0", "@types/node": "^14.14.31", "@types/shelljs": "^0.8.8", diff --git a/src/index.ts b/src/index.ts index a9df293..9c166ac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,25 +1,18 @@ -import { assert } from './utils' import { createConfig } from './config' import { RyuCho } from './ryu-cho' - -assert(!!process.env.ACCESS_TOKEN, '`accessToken` is required.') -assert(!!process.env.USER_NAME, '`userName` is required.') -assert(!!process.env.EMAIL, '`email` is required.') -assert(!!process.env.UPSTREAM_REPO, '`upstreamRepo` is required.') -assert(!!process.env.HEAD_REPO, '`headRepo` is required.') -assert(!!process.env.TRACK_FROM, '`trackFrom` is required.') +import core from '@actions/core' const config = createConfig({ - accessToken: process.env.ACCESS_TOKEN!, - userName: process.env.USER_NAME!, - email: process.env.EMAIL!, - upstreamRepo: process.env.UPSTREAM_REPO!, - upstreamRepoBranch: process.env.UPSTREAM_REPO_BRANCH, - headRepo: process.env.HEAD_REPO!, - headRepoBranch: process.env.HEAD_REPO_BRANCH, - workflowName: process.env.WORKFLOW_NAME, - trackFrom: process.env.TRACK_FROM!, - pathStartsWith: process.env.PATH_STARTS_WITH + accessToken: core.getInput('access-token', { required: true }), + userName: core.getInput('username', { required: true }), + email: core.getInput('email', { required: true }), + upstreamRepo: core.getInput('upstream-repo', { required: true }), + upstreamRepoBranch: core.getInput('upstream-repo-branch', { required: true }), + headRepo: core.getInput('head-repo', { required: true }), + headRepoBranch: core.getInput('head-repo-branch'), + workflowName: core.getInput('workflow-name'), + trackFrom: core.getInput('track-from', { required: true }), + pathStartsWith: core.getInput('path-starts-with'), }) const ryuCho = new RyuCho(config) From 8eef1a5c049289c1e49def201934ab21c01939b1 Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 21:13:37 -0500 Subject: [PATCH 05/16] chore: assert index.ts is running in GitHub Action --- src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.ts b/src/index.ts index 9c166ac..83e8ec1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,10 @@ +import { assert } from './utils' import { createConfig } from './config' import { RyuCho } from './ryu-cho' import core from '@actions/core' +assert(typeof core !== 'undefined', `core is undefined, which probably means you're not running in a GitHub Action`) + const config = createConfig({ accessToken: core.getInput('access-token', { required: true }), userName: core.getInput('username', { required: true }), From 44446e484714e3e2c326412ddb0b6d46267138b3 Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 02:29:33 -0500 Subject: [PATCH 06/16] feat: enable tracking multiple paths --- package.json | 2 ++ src/config.ts | 10 ++++++++++ src/index.ts | 1 + src/ryu-cho.ts | 29 +++++++++++++++++++++++++---- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index cb5685d..cb9a0b6 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@types/node": "^14.14.31", "@types/shelljs": "^0.8.8", "colors": "^1.4.0", + "minimatch": "^5.1.0", "queue": "^6.0.2", "rss-parser": "^3.12.0", "shelljs": "^0.8.4", @@ -22,6 +23,7 @@ }, "devDependencies": { "@vitest/ui": "^0.18.1", + "@types/minimatch": "^3.0.5", "prettier": "^2.2.1", "vitest": "^0.18.1" } diff --git a/src/config.ts b/src/config.ts index a3a3ce8..0d32d68 100644 --- a/src/config.ts +++ b/src/config.ts @@ -82,8 +82,15 @@ export interface UserConfig { * path will be not tracked. * * @example 'docs/' + * @deprecated Use `paths` instead. */ pathStartsWith?: string + + /** + * File paths to track (glob patterns). If this option is set, commits + * not containing the paths will not be tracked. + */ + paths: string[] } export interface Config { @@ -92,7 +99,9 @@ export interface Config { accessToken: string workflowName: string trackFrom: string + /** @deprecated Use `paths` instead. */ pathStartsWith?: string + paths: string[] remote: { upstream: Remote @@ -115,6 +124,7 @@ export function createConfig(config: UserConfig): Config { workflowName: config.workflowName ?? 'ryu-cho', trackFrom: config.trackFrom, pathStartsWith: config.pathStartsWith, + paths: config.paths, remote: { upstream: { diff --git a/src/index.ts b/src/index.ts index 83e8ec1..5f64e66 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ const config = createConfig({ workflowName: core.getInput('workflow-name'), trackFrom: core.getInput('track-from', { required: true }), pathStartsWith: core.getInput('path-starts-with'), + paths: core.getMultilineInput('paths'), }) const ryuCho = new RyuCho(config) diff --git a/src/ryu-cho.ts b/src/ryu-cho.ts index 28e9b0c..336d6bf 100644 --- a/src/ryu-cho.ts +++ b/src/ryu-cho.ts @@ -3,6 +3,7 @@ import { Config, Remote } from './config' import { Rss } from './rss' import { GitHub } from './github' import { Repository } from './repository' +import minimatch from 'minimatch' interface Feed { link: string @@ -98,15 +99,35 @@ export class RyuCho { } protected async containsValidFile(feed: Feed, hash: string) { - if (!this.config.pathStartsWith) { + if (!this.config.pathStartsWith && !this.config.paths) { return true } const res = await this.github.getCommit(this.head, hash) - return res.data.files!.some((file) => { - return file.filename!.startsWith(this.config.pathStartsWith!) - }) + let hasValidFile = false + + if (this.config.pathStartsWith) { + log('W', 'pathStartsWith is deprecated. Use paths instead.') + + hasValidFile = res.data.files!.some((file) => { + return file.filename!.startsWith(this.config.pathStartsWith!) + }) + } + + if (this.config.paths?.length) { + const findFile = (filename: string) => { + return this.config.paths.some((pattern) => { + return minimatch(filename, pattern, { partial: true } as any) + }) + } + + hasValidFile = res.data.files!.some((file) => { + return findFile(file.filename!) + }) + } + + return hasValidFile } protected async createIssueIfNot(feed: Feed, hash: string) { From 63fad6b371293af1e372308c51baf7e74ac6b925 Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 02:42:30 -0500 Subject: [PATCH 07/16] test: test config.paths --- src/ryu-cho.ts | 3 +- tests/ryo-cho.spec.ts | 84 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 tests/ryo-cho.spec.ts diff --git a/src/ryu-cho.ts b/src/ryu-cho.ts index 336d6bf..255a79b 100644 --- a/src/ryu-cho.ts +++ b/src/ryu-cho.ts @@ -5,7 +5,7 @@ import { GitHub } from './github' import { Repository } from './repository' import minimatch from 'minimatch' -interface Feed { +export interface Feed { link: string title: string contentSnippet: string @@ -24,7 +24,6 @@ export class RyuCho { this.config = config this.upstream = config.remote.upstream this.head = config.remote.head - this.rss = new Rss() this.github = new GitHub(config.accessToken) diff --git a/tests/ryo-cho.spec.ts b/tests/ryo-cho.spec.ts new file mode 100644 index 0000000..6aeca8d --- /dev/null +++ b/tests/ryo-cho.spec.ts @@ -0,0 +1,84 @@ +import { RyuCho, type Feed } from '../src/ryu-cho' +import type { Config } from '../src/config' +import * as GitHub from '../src/github' +import { describe, it, expect, vi } from 'vitest' + +vi.mock('../src/github') + +const DEFAULT_CONFIG: Config = { + userName: '', + email: '', + accessToken: '', + workflowName: '', + trackFrom: '', + pathStartsWith: '', + paths: [], + + remote: { + upstream: { + url: '', + owner: '', + name: '', + branch: '', + }, + head: { + url: '', + owner: '', + name: '', + branch: '', + } + } +} + +const DEFAULT_FEED: Feed = { + isoDate: '', + link: '', + title: '', + contentSnippet: '', +} + +type Mutable = { + -readonly [P in keyof T]: T[P]; +} + +type MutableGitHub = Mutable + +function makeRyuCho(config: Partial, filenames: string[]) { + class TestRyuCho extends RyuCho { + public containsValidFile(feed: Feed, hash: string): Promise { + return super.containsValidFile(feed, hash) + } + } + (GitHub as MutableGitHub).GitHub = vi.fn(() => ({ + getCommit() { + return Promise.resolve({ + data: { + files: filenames.map(n => ({ filename: n })) + } + }) + } + })) as unknown as typeof GitHub.GitHub + + const ryuCho = new TestRyuCho({ + ...DEFAULT_CONFIG, + ...config, + }) + + return ryuCho +} + +describe('RyuCho', () => { + it('config.paths[]', () => { + const paths = ['/docs/**/*.md'] + const filenames = [ + '/docs/guide/index.md', + '/docs/team.md', + '/CONTRIBUTING.md', + '/README.md', + ] + const ryuCho = makeRyuCho({ paths }, filenames) + + const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') + expect(hasValidFile).resolves.toBe(true) + }) +}) \ No newline at end of file From faee2b3b5c7ac468caac14abbf724d3c5703a8c4 Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 14:49:28 -0500 Subject: [PATCH 08/16] fix: match full path instead of partial --- src/ryu-cho.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ryu-cho.ts b/src/ryu-cho.ts index 255a79b..d611c8e 100644 --- a/src/ryu-cho.ts +++ b/src/ryu-cho.ts @@ -117,7 +117,7 @@ export class RyuCho { if (this.config.paths?.length) { const findFile = (filename: string) => { return this.config.paths.some((pattern) => { - return minimatch(filename, pattern, { partial: true } as any) + return minimatch(filename, pattern) }) } From ee124a8490948be5d6eb73572b95fea08e971131 Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 14:50:15 -0500 Subject: [PATCH 09/16] test: test config.paths[] --- tests/ryo-cho.spec.ts | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/tests/ryo-cho.spec.ts b/tests/ryo-cho.spec.ts index 6aeca8d..557ef33 100644 --- a/tests/ryo-cho.spec.ts +++ b/tests/ryo-cho.spec.ts @@ -68,12 +68,11 @@ function makeRyuCho(config: Partial, filenames: string[]) { } describe('RyuCho', () => { - it('config.paths[]', () => { + it('containsValidFile matches single config.paths[]', () => { const paths = ['/docs/**/*.md'] const filenames = [ '/docs/guide/index.md', '/docs/team.md', - '/CONTRIBUTING.md', '/README.md', ] const ryuCho = makeRyuCho({ paths }, filenames) @@ -81,4 +80,44 @@ describe('RyuCho', () => { const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') expect(hasValidFile).resolves.toBe(true) }) + + it('containsValidFile does not match any config.paths[]', () => { + const paths = ['/docs/**/*.md'] + const filenames = [ + '/docs/guide/index.txt', + '/docs/team.txt', + '/README.md', + ] + const ryuCho = makeRyuCho({ paths }, filenames) + + const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') + expect(hasValidFile).resolves.toBe(false) + }) + + it('containsValidFile matches multiple config.paths[]', () => { + const paths = ['/docs/**/*.md', '/README.md'] + const filenames = [ + '/docs/guide/index.md', + '/docs/team.md', + '/README.md', + ] + const ryuCho = makeRyuCho({ paths }, filenames) + + const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') + expect(hasValidFile).resolves.toBe(true) + }) + + it('containsValidFile excludes specified config.paths[]', () => { + // match all *.md files except README.md + const paths = ['**/!(README).md'] + const filenames = [ + '/docs/guide/index.txt', + '/docs/team.txt', + '/README.md', + ] + const ryuCho = makeRyuCho({ paths }, filenames) + + const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') + expect(hasValidFile).resolves.toBe(false) + }) }) \ No newline at end of file From ec3fef94b097b39e9d82caa02dd544ef307d4822 Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 16:42:15 -0500 Subject: [PATCH 10/16] feat: enable exclusions --- src/config.ts | 7 +++++++ src/ryu-cho.ts | 9 ++++++++- tests/ryo-cho.spec.ts | 13 +++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index 0d32d68..026e0b9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -91,6 +91,11 @@ export interface UserConfig { * not containing the paths will not be tracked. */ paths: string[] + + /** + * File paths to exclude (glob patterns). + */ + excludes: string[] } export interface Config { @@ -102,6 +107,7 @@ export interface Config { /** @deprecated Use `paths` instead. */ pathStartsWith?: string paths: string[] + excludes: string[] remote: { upstream: Remote @@ -125,6 +131,7 @@ export function createConfig(config: UserConfig): Config { trackFrom: config.trackFrom, pathStartsWith: config.pathStartsWith, paths: config.paths, + excludes: config.excludes, remote: { upstream: { diff --git a/src/ryu-cho.ts b/src/ryu-cho.ts index d611c8e..f80aa62 100644 --- a/src/ryu-cho.ts +++ b/src/ryu-cho.ts @@ -116,8 +116,15 @@ export class RyuCho { if (this.config.paths?.length) { const findFile = (filename: string) => { + const { excludes } = this.config return this.config.paths.some((pattern) => { - return minimatch(filename, pattern) + let matched = minimatch(filename, pattern) + if (excludes?.length) { + matched &&= !excludes.some((exclude) => { + return minimatch(filename, exclude, { nonegate: true }) + }) + } + return matched }) } diff --git a/tests/ryo-cho.spec.ts b/tests/ryo-cho.spec.ts index 557ef33..76095ca 100644 --- a/tests/ryo-cho.spec.ts +++ b/tests/ryo-cho.spec.ts @@ -13,6 +13,7 @@ const DEFAULT_CONFIG: Config = { trackFrom: '', pathStartsWith: '', paths: [], + excludes: [], remote: { upstream: { @@ -120,4 +121,16 @@ describe('RyuCho', () => { const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') expect(hasValidFile).resolves.toBe(false) }) + + it('containsValidFile excludes specified config.excludes[]', () => { + const paths = ['**/*.md'] + const filenames = [ + '/README.md', + ] + const excludes = ['/README.md'] + const ryuCho = makeRyuCho({ paths, excludes }, filenames) + + const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') + expect(hasValidFile).resolves.toBe(false) + }) }) \ No newline at end of file From 2b9bdedb047cb6f68443a6cccf0cbbcb7e0f99a9 Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 16:46:16 -0500 Subject: [PATCH 11/16] refactor: rename config.paths to config.includes --- src/config.ts | 10 +++++----- src/index.ts | 2 +- src/ryu-cho.ts | 6 +++--- tests/ryo-cho.spec.ts | 30 +++++++++++++++--------------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/config.ts b/src/config.ts index 026e0b9..1775fa2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -82,7 +82,7 @@ export interface UserConfig { * path will be not tracked. * * @example 'docs/' - * @deprecated Use `paths` instead. + * @deprecated Use `includes` instead. */ pathStartsWith?: string @@ -90,7 +90,7 @@ export interface UserConfig { * File paths to track (glob patterns). If this option is set, commits * not containing the paths will not be tracked. */ - paths: string[] + includes: string[] /** * File paths to exclude (glob patterns). @@ -104,9 +104,9 @@ export interface Config { accessToken: string workflowName: string trackFrom: string - /** @deprecated Use `paths` instead. */ + /** @deprecated Use `includes` instead. */ pathStartsWith?: string - paths: string[] + includes: string[] excludes: string[] remote: { @@ -130,7 +130,7 @@ export function createConfig(config: UserConfig): Config { workflowName: config.workflowName ?? 'ryu-cho', trackFrom: config.trackFrom, pathStartsWith: config.pathStartsWith, - paths: config.paths, + includes: config.includes, excludes: config.excludes, remote: { diff --git a/src/index.ts b/src/index.ts index 5f64e66..e505399 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ const config = createConfig({ workflowName: core.getInput('workflow-name'), trackFrom: core.getInput('track-from', { required: true }), pathStartsWith: core.getInput('path-starts-with'), - paths: core.getMultilineInput('paths'), + includes: core.getMultilineInput('includes'), }) const ryuCho = new RyuCho(config) diff --git a/src/ryu-cho.ts b/src/ryu-cho.ts index f80aa62..03f0fd1 100644 --- a/src/ryu-cho.ts +++ b/src/ryu-cho.ts @@ -98,7 +98,7 @@ export class RyuCho { } protected async containsValidFile(feed: Feed, hash: string) { - if (!this.config.pathStartsWith && !this.config.paths) { + if (!this.config.pathStartsWith && !this.config.includes) { return true } @@ -114,10 +114,10 @@ export class RyuCho { }) } - if (this.config.paths?.length) { + if (this.config.includes?.length) { const findFile = (filename: string) => { const { excludes } = this.config - return this.config.paths.some((pattern) => { + return this.config.includes.some((pattern) => { let matched = minimatch(filename, pattern) if (excludes?.length) { matched &&= !excludes.some((exclude) => { diff --git a/tests/ryo-cho.spec.ts b/tests/ryo-cho.spec.ts index 76095ca..7f8401a 100644 --- a/tests/ryo-cho.spec.ts +++ b/tests/ryo-cho.spec.ts @@ -12,7 +12,7 @@ const DEFAULT_CONFIG: Config = { workflowName: '', trackFrom: '', pathStartsWith: '', - paths: [], + includes: [], excludes: [], remote: { @@ -69,66 +69,66 @@ function makeRyuCho(config: Partial, filenames: string[]) { } describe('RyuCho', () => { - it('containsValidFile matches single config.paths[]', () => { - const paths = ['/docs/**/*.md'] + it('containsValidFile matches single config.includes[]', () => { + const includes = ['/docs/**/*.md'] const filenames = [ '/docs/guide/index.md', '/docs/team.md', '/README.md', ] - const ryuCho = makeRyuCho({ paths }, filenames) + const ryuCho = makeRyuCho({ includes }, filenames) const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') expect(hasValidFile).resolves.toBe(true) }) - it('containsValidFile does not match any config.paths[]', () => { - const paths = ['/docs/**/*.md'] + it('containsValidFile does not match any config.includes[]', () => { + const includes = ['/docs/**/*.md'] const filenames = [ '/docs/guide/index.txt', '/docs/team.txt', '/README.md', ] - const ryuCho = makeRyuCho({ paths }, filenames) + const ryuCho = makeRyuCho({ includes }, filenames) const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') expect(hasValidFile).resolves.toBe(false) }) - it('containsValidFile matches multiple config.paths[]', () => { - const paths = ['/docs/**/*.md', '/README.md'] + it('containsValidFile matches multiple config.includes[]', () => { + const includes = ['/docs/**/*.md', '/README.md'] const filenames = [ '/docs/guide/index.md', '/docs/team.md', '/README.md', ] - const ryuCho = makeRyuCho({ paths }, filenames) + const ryuCho = makeRyuCho({ includes }, filenames) const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') expect(hasValidFile).resolves.toBe(true) }) - it('containsValidFile excludes specified config.paths[]', () => { + it('containsValidFile excludes specified config.includes[]', () => { // match all *.md files except README.md - const paths = ['**/!(README).md'] + const includes = ['**/!(README).md'] const filenames = [ '/docs/guide/index.txt', '/docs/team.txt', '/README.md', ] - const ryuCho = makeRyuCho({ paths }, filenames) + const ryuCho = makeRyuCho({ includes }, filenames) const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') expect(hasValidFile).resolves.toBe(false) }) it('containsValidFile excludes specified config.excludes[]', () => { - const paths = ['**/*.md'] + const includes = ['**/*.md'] const filenames = [ '/README.md', ] const excludes = ['/README.md'] - const ryuCho = makeRyuCho({ paths, excludes }, filenames) + const ryuCho = makeRyuCho({ includes, excludes }, filenames) const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') expect(hasValidFile).resolves.toBe(false) From 892e52f370500655b32777b2afbc29b18bb1bf95 Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 20:57:52 -0500 Subject: [PATCH 12/16] feat: add config.includes[] (2) --- README.md | 4 ++++ action.yml | 5 ++++- src/ryu-cho.ts | 15 +++------------ 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8bd9d1a..0874d30 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,12 @@ jobs: # File path to track. In this example, Ryu-Cho will only track # commits that modified files under `docs` folder. Optional. + # (deprecated: use `includes` instead) path-starts-with: docs/ + # File paths to track (glob patterns). Optional. + includes: [] + # GitHub workflow name that runs Ryu-Cho. This is required since # Ryu-Cho determines the last run by looking into last workflow # run timestamp. Optional. Defaults to `ryu-cho`. diff --git a/action.yml b/action.yml index 0eb7870..2ab1c5f 100644 --- a/action.yml +++ b/action.yml @@ -29,7 +29,10 @@ inputs: description: 'The git commit to track from. e.g. 4ed8b2f83a2f149734f3c5ecb6438309bd85a9e5' required: true path-starts-with: - description: 'File path to track. e.g `docs/`' + description: 'File path to track. e.g `docs/` (deprecated: use `includes` instead)' + required: false + includes: + description: 'File paths to track (glob patterns)' required: false workflow-name: description: 'GitHub workflow name that executes Ryu Cho action. Defaults to `ryu-cho`' diff --git a/src/ryu-cho.ts b/src/ryu-cho.ts index 03f0fd1..ba30cca 100644 --- a/src/ryu-cho.ts +++ b/src/ryu-cho.ts @@ -115,21 +115,12 @@ export class RyuCho { } if (this.config.includes?.length) { - const findFile = (filename: string) => { - const { excludes } = this.config - return this.config.includes.some((pattern) => { - let matched = minimatch(filename, pattern) - if (excludes?.length) { - matched &&= !excludes.some((exclude) => { - return minimatch(filename, exclude, { nonegate: true }) - }) - } - return matched - }) + const isFileIncluded = (filename: string) => { + return this.config.includes.some((pattern) => minimatch(filename, pattern)) } hasValidFile = res.data.files!.some((file) => { - return findFile(file.filename!) + return isFileIncluded(file.filename!) }) } From 51445835b9a9ee8efedbd2e49e439d70283a10a3 Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 20:59:11 -0500 Subject: [PATCH 13/16] feat: add config.excludes[] (2) --- README.md | 3 +++ action.yml | 3 +++ src/index.ts | 1 + src/ryu-cho.ts | 12 +++++++++++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0874d30..7ea8268 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,9 @@ jobs: # File paths to track (glob patterns). Optional. includes: [] + # File paths to exclude from tracking (glob patterns). Optional. + excludes: [] + # GitHub workflow name that runs Ryu-Cho. This is required since # Ryu-Cho determines the last run by looking into last workflow # run timestamp. Optional. Defaults to `ryu-cho`. diff --git a/action.yml b/action.yml index 2ab1c5f..a018fa8 100644 --- a/action.yml +++ b/action.yml @@ -34,6 +34,9 @@ inputs: includes: description: 'File paths to track (glob patterns)' required: false + excludes: + description: 'File paths to exclude from tracking (glob patterns)' + required: false workflow-name: description: 'GitHub workflow name that executes Ryu Cho action. Defaults to `ryu-cho`' required: false diff --git a/src/index.ts b/src/index.ts index e505399..93bf828 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ const config = createConfig({ trackFrom: core.getInput('track-from', { required: true }), pathStartsWith: core.getInput('path-starts-with'), includes: core.getMultilineInput('includes'), + excludes: core.getMultilineInput('excludes'), }) const ryuCho = new RyuCho(config) diff --git a/src/ryu-cho.ts b/src/ryu-cho.ts index ba30cca..59b6199 100644 --- a/src/ryu-cho.ts +++ b/src/ryu-cho.ts @@ -98,7 +98,7 @@ export class RyuCho { } protected async containsValidFile(feed: Feed, hash: string) { - if (!this.config.pathStartsWith && !this.config.includes) { + if (!this.config.pathStartsWith && !this.config.includes && !this.config.excludes) { return true } @@ -124,6 +124,16 @@ export class RyuCho { }) } + if (this.config.excludes?.length) { + const isFileExcluded = (filename: string) => { + return this.config.excludes.some((pattern) => minimatch(filename, pattern)) + } + + hasValidFile = res.data.files!.some((file) => { + return !isFileExcluded(file.filename!) + }) + } + return hasValidFile } From 6a7a8a9caf832c842f0aa82cc1c2b873d81a4a87 Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 21:23:53 -0500 Subject: [PATCH 14/16] chore: switch from minimatch to micromatch --- package.json | 4 ++-- src/ryu-cho.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index cb9a0b6..a01848d 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "@types/node": "^14.14.31", "@types/shelljs": "^0.8.8", "colors": "^1.4.0", - "minimatch": "^5.1.0", + "micromatch": "^4.0.5", "queue": "^6.0.2", "rss-parser": "^3.12.0", "shelljs": "^0.8.4", @@ -22,8 +22,8 @@ "typescript": "^4.2.2" }, "devDependencies": { + "@types/micromatch": "^4.0.2", "@vitest/ui": "^0.18.1", - "@types/minimatch": "^3.0.5", "prettier": "^2.2.1", "vitest": "^0.18.1" } diff --git a/src/ryu-cho.ts b/src/ryu-cho.ts index 59b6199..254d995 100644 --- a/src/ryu-cho.ts +++ b/src/ryu-cho.ts @@ -3,7 +3,7 @@ import { Config, Remote } from './config' import { Rss } from './rss' import { GitHub } from './github' import { Repository } from './repository' -import minimatch from 'minimatch' +import micromatch from 'micromatch' export interface Feed { link: string @@ -116,7 +116,7 @@ export class RyuCho { if (this.config.includes?.length) { const isFileIncluded = (filename: string) => { - return this.config.includes.some((pattern) => minimatch(filename, pattern)) + return micromatch.isMatch(filename, this.config.includes) } hasValidFile = res.data.files!.some((file) => { @@ -126,7 +126,7 @@ export class RyuCho { if (this.config.excludes?.length) { const isFileExcluded = (filename: string) => { - return this.config.excludes.some((pattern) => minimatch(filename, pattern)) + return micromatch.isMatch(filename, this.config.excludes) } hasValidFile = res.data.files!.some((file) => { From 1640fc945ec0436ce964af4d136c4c4531c9478a Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 23:46:54 -0500 Subject: [PATCH 15/16] test: await all expected promises --- tests/ryo-cho.spec.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/ryo-cho.spec.ts b/tests/ryo-cho.spec.ts index 7f8401a..b97bcfe 100644 --- a/tests/ryo-cho.spec.ts +++ b/tests/ryo-cho.spec.ts @@ -69,7 +69,7 @@ function makeRyuCho(config: Partial, filenames: string[]) { } describe('RyuCho', () => { - it('containsValidFile matches single config.includes[]', () => { + it('containsValidFile matches single config.includes[]', async () => { const includes = ['/docs/**/*.md'] const filenames = [ '/docs/guide/index.md', @@ -79,10 +79,10 @@ describe('RyuCho', () => { const ryuCho = makeRyuCho({ includes }, filenames) const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') - expect(hasValidFile).resolves.toBe(true) + await expect(hasValidFile).resolves.toBe(true) }) - it('containsValidFile does not match any config.includes[]', () => { + it('containsValidFile does not match any config.includes[]', async () => { const includes = ['/docs/**/*.md'] const filenames = [ '/docs/guide/index.txt', @@ -92,10 +92,10 @@ describe('RyuCho', () => { const ryuCho = makeRyuCho({ includes }, filenames) const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') - expect(hasValidFile).resolves.toBe(false) + await expect(hasValidFile).resolves.toBe(false) }) - it('containsValidFile matches multiple config.includes[]', () => { + it('containsValidFile matches multiple config.includes[]', async () => { const includes = ['/docs/**/*.md', '/README.md'] const filenames = [ '/docs/guide/index.md', @@ -105,10 +105,10 @@ describe('RyuCho', () => { const ryuCho = makeRyuCho({ includes }, filenames) const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') - expect(hasValidFile).resolves.toBe(true) + await expect(hasValidFile).resolves.toBe(true) }) - it('containsValidFile excludes specified config.includes[]', () => { + it('containsValidFile excludes specified config.includes[]', async () => { // match all *.md files except README.md const includes = ['**/!(README).md'] const filenames = [ @@ -119,10 +119,10 @@ describe('RyuCho', () => { const ryuCho = makeRyuCho({ includes }, filenames) const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') - expect(hasValidFile).resolves.toBe(false) + await expect(hasValidFile).resolves.toBe(false) }) - it('containsValidFile excludes specified config.excludes[]', () => { + it('containsValidFile excludes specified config.excludes[]', async () => { const includes = ['**/*.md'] const filenames = [ '/README.md', @@ -131,6 +131,6 @@ describe('RyuCho', () => { const ryuCho = makeRyuCho({ includes, excludes }, filenames) const hasValidFile = ryuCho.containsValidFile(DEFAULT_FEED, 'hash') - expect(hasValidFile).resolves.toBe(false) + await expect(hasValidFile).resolves.toBe(false) }) }) \ No newline at end of file From 58077be1ffb47cc09948c2abdd066039543b7af1 Mon Sep 17 00:00:00 2001 From: Tony Trinh Date: Thu, 21 Jul 2022 23:50:29 -0500 Subject: [PATCH 16/16] chore: use `includes` in deprecation warning --- src/ryu-cho.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ryu-cho.ts b/src/ryu-cho.ts index 254d995..feab4c1 100644 --- a/src/ryu-cho.ts +++ b/src/ryu-cho.ts @@ -107,7 +107,7 @@ export class RyuCho { let hasValidFile = false if (this.config.pathStartsWith) { - log('W', 'pathStartsWith is deprecated. Use paths instead.') + log('W', '`path-starts-with` is deprecated. Use `includes` instead.') hasValidFile = res.data.files!.some((file) => { return file.filename!.startsWith(this.config.pathStartsWith!)