├── .commitlintrc ├── .editorconfig ├── .eslintrc ├── .github ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── card-labeler.yml ├── config.yml ├── labeler.yml ├── no-response.yml ├── pr-labeler.yml ├── pull_request_template.md ├── release-drafter.yml ├── stale.yml ├── workflow-settings.json └── workflows │ ├── add-release-tag.yml │ ├── add-test-tag.yml │ ├── check-warnings.yml │ ├── ci.yml │ ├── demo.yml │ ├── issue-opened.yml │ ├── pr-opened.yml │ ├── pr-updated.yml │ ├── project-card-moved.yml │ ├── release.yml │ ├── toc.yml │ └── update-dependencies.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc ├── .releasegarc ├── LICENSE ├── README.md ├── action.yml ├── docs ├── accesstoken.png ├── apikey.png ├── img.png └── img_1.png ├── package.json ├── rollup.config.mjs ├── src ├── main.ts ├── process.test.ts ├── process.ts └── setup.ts ├── tsconfig.json ├── vite.config.ts ├── yarn-error.log └── yarn.lock /.commitlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@commitlint/config-conventional" 4 | ] 5 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 2 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended", 5 | "plugin:@typescript-eslint/eslint-recommended" 6 | ], 7 | "plugins": [ 8 | "@typescript-eslint", 9 | "import" 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "sourceType": "module", 14 | "ecmaVersion": 2018 15 | }, 16 | "env": { 17 | "node": true, 18 | "jest": true, 19 | "es6": true, 20 | "browser": true 21 | }, 22 | "settings": { 23 | "react": { 24 | "version": "latest" 25 | } 26 | }, 27 | "rules": { 28 | "camelcase": [ 29 | "error", 30 | { 31 | "properties": "always" 32 | } 33 | ], 34 | "quotes": [ 35 | "error", 36 | "single", 37 | "avoid-escape" 38 | ], 39 | "key-spacing": [ 40 | "error", 41 | { 42 | "singleLine": { 43 | "beforeColon": false, 44 | "afterColon": true 45 | }, 46 | "multiLine": { 47 | "beforeColon": false, 48 | "afterColon": true 49 | } 50 | } 51 | ], 52 | "eqeqeq": "error", 53 | "block-scoped-var": "error", 54 | "complexity": [ 55 | "error", 56 | { 57 | "maximum": 20 58 | } 59 | ], 60 | "default-case": "error", 61 | "dot-location": [ 62 | "error", 63 | "property" 64 | ], 65 | "guard-for-in": "error", 66 | "no-eval": "error", 67 | "block-spacing": "error", 68 | "brace-style": "error", 69 | "comma-spacing": [ 70 | "error", 71 | { 72 | "before": false, 73 | "after": true 74 | } 75 | ], 76 | "indent": [ 77 | "error", 78 | 2, 79 | { 80 | "SwitchCase": 1 81 | } 82 | ], 83 | "space-before-function-paren": [ 84 | "error", 85 | "never" 86 | ], 87 | "space-before-blocks": "error", 88 | "prefer-const": "error", 89 | "no-var": "error", 90 | "arrow-body-style": "off", 91 | "arrow-spacing": "error", 92 | "strict": [ 93 | "error" 94 | ], 95 | "no-warning-comments": [ 96 | "warn", 97 | { 98 | "terms": [ 99 | "todo", 100 | "fixme", 101 | "hack" 102 | ], 103 | "location": "anywhere" 104 | } 105 | ], 106 | "semi": [ 107 | "error" 108 | ], 109 | "sort-imports": 0, 110 | "import/order": [2, { 111 | "groups": ["type", "builtin", "external", "internal", "parent", "sibling", "index", "object"], 112 | "alphabetize": { "order": "asc", "caseInsensitive": true } 113 | }], 114 | "@typescript-eslint/no-non-null-assertion": "off" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @azu 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at technote.space@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | [issues]: https://github.com/azu/rss-to-twitter/issues 3 | [fork]: https://github.com/azu/rss-to-twitter/fork 4 | [pr]: https://github.com/azu/rss-to-twitter/compare 5 | [eslint]: https://eslint.org/ 6 | [jest]: https://jestjs.io/ 7 | [code-of-conduct]: CODE_OF_CONDUCT.md 8 | 9 | When contributing to this repository, please first discuss the change you wish to make via [issue][issues] with the owners of this repository before making a change. 10 | 11 | Please note we have a [Contributor Code of Conduct][code-of-conduct], please follow it in all your interactions with the project. 12 | 13 | ## Submitting a pull request 14 | 15 | 1. [Fork][fork] and clone the repository 16 | 1. Make sure the tests pass on your machine: `yarn test`, which contains 17 | - [`ESLint`][eslint] 18 | - [`Jest`][jest] 19 | 1. Create a new branch: `git checkout -b my-branch-name` 20 | 1. Make your change, add tests, and make sure the tests still pass. 21 | 1. Push to your fork and [submit a pull request][pr]. 22 | 1. Pat your self on the back and wait for your pull request to be reviewed and merged. 23 | 24 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 25 | - Write and update tests. 26 | - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 27 | - Write a [good commit message](https://github.com/erlang/otp/wiki/writing-good-commit-messages). 28 | 29 | Work in Progress pull request are also welcome to get feedback early on, or if there is something blocked you. 30 | 31 | ## Resources 32 | 33 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 34 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 35 | - [GitHub Help](https://help.github.com) 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: 'azu' 7 | 8 | --- 9 | 10 | ## Describe the bug: バグの概要 11 | 12 | 13 | 14 | ## To Reproduce: 再現手順 15 | Steps to reproduce the behavior: 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | ## Expected behavior: 期待する動作 22 | 23 | 24 | 25 | ## Screenshots: スクリーンショット 26 | 27 | 28 | 29 | ## Operating environment: バグが発生した環境 30 | - Version of software 31 | - OS: [e.g. Windows10] 32 | - Browser: [e.g. Chrome, Safari] 33 | - etc. 34 | 35 | ## Additional context: 補足 36 | 37 | 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: 'azu' 7 | 8 | --- 9 | 10 | ## Please describe your suggestion: 提案の概要 11 | 12 | 13 | 14 | ## Describe the solution you'd like: 考えうる解決方法 15 | 16 | 17 | 18 | ## Describe alternatives you've considered: 考えうる代替案 19 | 20 | 21 | 22 | ## Additional context: 補足 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/card-labeler.yml: -------------------------------------------------------------------------------- 1 | Backlog: 2 | 'In progress': 3 | - 'Status: In Progress' 4 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | # Configuration for request-info - https://github.com/behaviorbot/request-info 2 | 3 | # *Required* Comment to reply with 4 | requestInfoReplyComment: > 5 | :clap: We would appreciate it if you could provide us with more info about this issue/pr! 6 | 7 | # *OPTIONAL* default titles to check against for lack of descriptiveness 8 | # MUST BE ALL LOWERCASE 9 | requestInfoDefaultTitles: 10 | - update readme.md 11 | - updates 12 | - update 13 | 14 | # *OPTIONAL* Label to be added to Issues and Pull Requests with insufficient information given 15 | requestInfoLabelToAdd: "Status: More Information Needed" 16 | 17 | 18 | 19 | 20 | # Configuration for welcome - https://github.com/behaviorbot/welcome 21 | 22 | # Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome 23 | 24 | # Comment to be posted to on first time issues 25 | newIssueWelcomeComment: > 26 | :raised_hands: Thanks for opening your first issue here! Be sure to follow the issue template! 27 | 28 | # Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome 29 | 30 | # Comment to be posted to on PRs from first time contributors in your repository 31 | newPRWelcomeComment: > 32 | :raised_hands: Thanks for opening this pull request! Please check out our contributing guidelines. 33 | 34 | # Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge 35 | 36 | # Comment to be posted to on pull requests merged by a first time user 37 | firstPRMergeComment: > 38 | :tada: Congrats on merging your first pull request! We here at behaviorbot are proud of you! 39 | 40 | 41 | 42 | # Configuration for todo - https://github.com/jasonetco/todo 43 | todo: 44 | - label: "Type: Todo" -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | javascript: 2 | - '**/*.js' 3 | typescript: 4 | - '**/*.ts' 5 | php: 6 | - '**/*.php' 7 | python: 8 | - '**/*.py' 9 | cpp: 10 | - '**/*.cpp' 11 | - '**/*.cxx' 12 | - '**/*.cc' 13 | - '**/*.cp' 14 | 15 | 'Type: Testing': 16 | - '**/tests/*' 17 | - '**/test/*' 18 | - '**/__tests__/*' 19 | 20 | 'Type: Documentation': 21 | - '**/*.md' 22 | 23 | 'Type: CI/CD': 24 | - '.github/workflows/*.yml' 25 | - '.circleci/*' 26 | - '.travis.yml' 27 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 14 5 | # Label requiring a response 6 | responseRequiredLabel: "Status: More Information Needed" 7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 8 | closeComment: > 9 | This issue has been automatically closed because there has been no response 10 | to our request for more information from the original author. With only the 11 | information that is currently in the issue, we don't have enough information 12 | to take action. Please reach out if you have or find the answers we need so 13 | that we can investigate further. 14 | -------------------------------------------------------------------------------- /.github/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | 'Type: Feature': ['feature/*', 'feat/*'] 2 | 'Type: Bug': fix/* 3 | 'Type: Maintenance': ['patch/*', 'chore/*'] 4 | 'Type: Release': release/* 5 | 'Type: Refactoring': ['refactor/*', 'refactoring/*'] 6 | 'Type: Documentation': ['docs/*', 'doc/*'] 7 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description: 概要 2 | 3 | 4 | 5 | ## Changes: 変更内容 6 | 7 | 8 | 9 | 10 | 11 | 12 | ## Expected Impact: 影響範囲 13 | 14 | 15 | 16 | ## Operating Requirements: 動作要件 17 | 18 | 19 | 20 | ## Additional context: 補足 21 | 22 | 23 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Config for https://github.com/apps/release-drafter 2 | name-template: 'v$NEXT_PATCH_VERSION' 3 | tag-template: 'v$NEXT_PATCH_VERSION' 4 | categories: 5 | - title: ':rocket: Features' 6 | labels: 7 | - 'Type: Feature' 8 | - 'Type: Refactoring' 9 | - title: ':bug: Bug Fixes' 10 | labels: 11 | - 'Type: Bug' 12 | - 'Type: Security' 13 | - title: ':wrench: Maintenance' 14 | labels: 15 | - 'Type: Maintenance' 16 | - 'Type: CI/CD' 17 | - title: ':green_book: Docs' 18 | labels: 19 | - 'Type: Documentation' 20 | - title: ':white_check_mark: Tested' 21 | labels: 22 | - 'Type: Testing' 23 | - title: ':sparkles: All Changes' 24 | labels: 25 | - 'Type: Release' 26 | exclude-labels: 27 | - 'dependencies' 28 | template: | 29 | ## What’s Changed 30 | 31 | $CHANGES 32 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 180 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 30 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - "Priority: Critical" 8 | - "Type: Security" 9 | # Label to use when marking an issue as stale 10 | staleLabel: "Status: Abandoned" 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false -------------------------------------------------------------------------------- /.github/workflow-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "EXCLUDE_MESSAGES": [ 3 | "update package version", 4 | "update packages", 5 | "update wp version", 6 | "trigger workflow", 7 | "update TOC" 8 | ], 9 | "PROJECT": "Backlog", 10 | "ISSUE_COLUMN": "To do", 11 | "PR_COLUMN": "In progress", 12 | "PR_BODY_TITLE": "## Changes", 13 | "TOC_FOLDING": "1", 14 | "TOC_MAX_HEADER_LEVEL": "3", 15 | "TOC_TITLE": "Details", 16 | "TOC_CREATE_PR": "true", 17 | "TOC_TARGET_PATHS": "README*.md", 18 | "BRANCH_PREFIX": "release/", 19 | "ANNOTATION_EXCLUDE_PATTERNS": [ 20 | ">> warning ", 21 | ">> hint: ", 22 | "Cloning into", 23 | "has unmet peer dependency", 24 | "has incorrect peer dependency", 25 | "Using version", 26 | "ci-helper", 27 | "tests/bootstrap.php" 28 | ], 29 | "CHANGE_TEMPLATE": "- [ ] ${TITLE} (#${NUMBER}) @${AUTHOR}" 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/add-release-tag.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: 4 | - master 5 | - main 6 | - develop/v* 7 | types: [closed] 8 | 9 | permissions: 10 | contents: write 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | 15 | name: Add release tag 16 | 17 | jobs: 18 | tag: 19 | name: Add release tag 20 | runs-on: ubuntu-latest 21 | timeout-minutes: 3 22 | if: github.event.pull_request.merged == true && github.event.pull_request.head.user.id == github.event.pull_request.base.user.id && startsWith(github.head_ref, 'release/') 23 | steps: 24 | - uses: technote-space/load-config-action@v1 25 | with: 26 | CONFIG_FILENAME: workflow-settings.json, workflow-details.json 27 | IGNORE_WARNING: 'true' 28 | - name: Get version 29 | uses: technote-space/get-next-version-action@v1 30 | with: 31 | EXCLUDE_MESSAGES: ${{ env.EXCLUDE_MESSAGES }} 32 | if: "! startsWith(github.head_ref, 'release/v')" 33 | - name: Get version 34 | run: echo "NEXT_VERSION=${HEAD_REF#release/}" >> $GITHUB_ENV 35 | env: 36 | HEAD_REF: ${{ github.head_ref }} 37 | if: startsWith(github.head_ref, 'release/v') 38 | - uses: actions/github-script@v3 39 | with: 40 | github-token: ${{ secrets.ACCESS_TOKEN }} 41 | script: | 42 | github.git.createRef({ 43 | owner: context.repo.owner, 44 | repo: context.repo.repo, 45 | ref: `refs/tags/${process.env.NEXT_VERSION}`, 46 | sha: context.sha 47 | }) 48 | if: env.NEXT_VERSION 49 | - uses: actions/github-script@v3 50 | with: 51 | github-token: ${{ secrets.ACCESS_TOKEN }} 52 | script: | 53 | github.git.createRef({ 54 | owner: context.repo.owner, 55 | repo: context.repo.repo, 56 | ref: `refs/heads/release/next-${process.env.NEXT_VERSION}`, 57 | sha: context.sha 58 | }) 59 | if: env.NEXT_VERSION 60 | -------------------------------------------------------------------------------- /.github/workflows/add-test-tag.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | types: [synchronize] 4 | 5 | permissions: 6 | contents: write 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | 11 | name: Add test tag 12 | 13 | jobs: 14 | tag: 15 | name: Add test tag 16 | runs-on: ubuntu-latest 17 | timeout-minutes: 3 18 | if: github.event.pull_request.head.user.id == github.event.pull_request.base.user.id && startsWith(github.head_ref, 'release/') 19 | steps: 20 | - uses: technote-space/load-config-action@v1 21 | with: 22 | CONFIG_FILENAME: workflow-settings.json, workflow-details.json 23 | IGNORE_WARNING: 'true' 24 | - uses: actions/checkout@v3 25 | - uses: technote-space/get-git-comment-action@v1 26 | - name: Get version 27 | uses: technote-space/get-next-version-action@v1 28 | with: 29 | EXCLUDE_MESSAGES: ${{ env.EXCLUDE_MESSAGES }} 30 | if: "! startsWith(github.head_ref, 'release/v') && (contains(env.COMMIT_MESSAGE, 'chore: update dependencies') || contains(env.COMMIT_MESSAGE, 'chore: update npm dependencies'))" 31 | - name: Get version 32 | run: echo "NEXT_VERSION=${HEAD_REF#release/}" >> $GITHUB_ENV 33 | env: 34 | HEAD_REF: ${{ github.head_ref }} 35 | if: "startsWith(github.head_ref, 'release/v') && (contains(env.COMMIT_MESSAGE, 'chore: update dependencies') || contains(env.COMMIT_MESSAGE, 'chore: update npm dependencies'))" 36 | - name: Get tag name 37 | run: echo "TAG_NAME=${NEXT_VERSION}.${RUN_ID}" >> $GITHUB_ENV 38 | env: 39 | HEAD_REF: ${{ github.head_ref }} 40 | RUN_ID: ${{ github.run_id }} 41 | if: env.NEXT_VERSION 42 | - uses: actions/github-script@v3 43 | with: 44 | github-token: ${{ secrets.ACCESS_TOKEN }} 45 | script: | 46 | github.git.createRef({ 47 | owner: context.repo.owner, 48 | repo: context.repo.repo, 49 | ref: `refs/tags/test/${process.env.TAG_NAME}`, 50 | sha: context.payload.pull_request.head.sha 51 | }) 52 | if: env.TAG_NAME 53 | -------------------------------------------------------------------------------- /.github/workflows/check-warnings.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_run: 3 | workflows: 4 | - CI 5 | - Sync workflows 6 | - Update dependencies 7 | - Broken Link Check 8 | types: 9 | - completed 10 | 11 | name: Check Warnings 12 | 13 | jobs: 14 | annotations: 15 | name: Annotations 16 | runs-on: ubuntu-latest 17 | timeout-minutes: 3 18 | steps: 19 | - uses: technote-space/load-config-action@v1 20 | with: 21 | CONFIG_FILENAME: workflow-settings.json, workflow-details.json 22 | IGNORE_WARNING: 'true' 23 | - uses: technote-space/download-annotations-action@v2 24 | id: annotations 25 | with: 26 | TARGET_RUN_ID: ${{ github.event.workflow_run.id }} 27 | INCLUDE_LEVELS: warning 28 | EXCLUDE_MESSAGE_PATTERNS: ${{ env.ANNOTATION_EXCLUDE_PATTERNS }} 29 | - name: Build attachments 30 | run: | 31 | arr1='[{"fields":[{"title":"repo","value":"","short":true},{"title":"action","value":"<${{ github.event.workflow_run.html_url }}|summary>","short":true}]}]' 32 | arr2=$(echo '${{ steps.annotations.outputs.messages }}' | jq -c 'map({"color":"warning","text":"```\(.)```"})') 33 | echo "SLACK_ATTACHMENTS=$(jq --argjson arr1 "$arr1" --argjson arr2 "$arr2" -nc '$arr1 + $arr2')" >> $GITHUB_ENV 34 | if: steps.annotations.outputs.number > 0 35 | - uses: 8398a7/action-slack@v3 36 | with: 37 | status: custom 38 | fields: repo 39 | custom_payload: | 40 | { 41 | text: "Warning annotations", 42 | attachments: ${{ env.SLACK_ATTACHMENTS }} 43 | } 44 | env: 45 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 46 | if: steps.annotations.outputs.number > 0 && env.SLACK_WEBHOOK_URL 47 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | 6 | name: CI 7 | jobs: 8 | eslint: 9 | name: ESLint 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 5 12 | permissions: 13 | contents: read 14 | env: 15 | LINT: 1 16 | steps: 17 | - name: Set running flag 18 | run: echo "RUNNING=1" >> $GITHUB_ENV 19 | - uses: actions/checkout@v3 20 | - uses: technote-space/get-git-comment-action@v1 21 | - uses: technote-space/get-diff-action@v6 22 | with: 23 | PATTERNS: +(src|__tests__)/**/*.+(js|ts) 24 | FILES: | 25 | yarn.lock 26 | .eslintrc 27 | if: "! contains(env.COMMIT_MESSAGE, '[skip ci]') && ! contains(env.COMMIT_MESSAGE, '[ci skip]')" 28 | - name: Set running flag 29 | run: echo "RUNNING=" >> $GITHUB_ENV 30 | if: "! env.GIT_DIFF" 31 | 32 | - uses: actions/setup-node@v3 33 | with: 34 | node-version: 16 35 | cache: yarn 36 | if: env.RUNNING 37 | - name: Install Package dependencies 38 | run: yarn install 39 | if: env.RUNNING 40 | - name: Check code style 41 | run: yarn eslint ${{ env.GIT_DIFF_FILTERED }} 42 | if: env.RUNNING && !env.MATCHED_FILES 43 | - name: Check code style 44 | run: yarn lint 45 | if: env.RUNNING && env.MATCHED_FILES 46 | 47 | cover: 48 | name: Coverage 49 | needs: eslint 50 | runs-on: ${{matrix.os}} 51 | timeout-minutes: 10 52 | strategy: 53 | matrix: 54 | os: [ubuntu-20.04, ubuntu-22.04, ubuntu-latest, macos-latest] 55 | steps: 56 | - name: Set running flag 57 | run: echo "RUNNING=1" >> $GITHUB_ENV 58 | - uses: actions/checkout@v3 59 | - uses: technote-space/get-git-comment-action@v1 60 | - uses: technote-space/get-diff-action@v6 61 | with: 62 | PATTERNS: +(src|__tests__)/**/*.+(js|ts|snap) 63 | FILES: | 64 | yarn.lock 65 | jest.config.js 66 | vite.config.ts 67 | if: "! contains(env.COMMIT_MESSAGE, '[skip ci]') && ! contains(env.COMMIT_MESSAGE, '[ci skip]')" 68 | - name: Set running flag 69 | run: echo "RUNNING=" >> $GITHUB_ENV 70 | if: "! env.GIT_DIFF" 71 | - name: Set running flag 72 | if: "matrix.os == 'ubuntu-latest' && ! startsWith(github.ref, 'refs/tags/') && github.event.base_ref == format('refs/heads/{0}', github.event.repository.default_branch)" 73 | run: echo "RUNNING=1" >> $GITHUB_ENV 74 | - name: Set running flag 75 | if: "matrix.os == 'ubuntu-latest' && ! startsWith(github.ref, 'refs/tags/') && startsWith(github.base_ref, 'refs/heads/develop/v')" 76 | run: echo "RUNNING=1" >> $GITHUB_ENV 77 | - name: Set running flag 78 | if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/v') 79 | run: echo "RUNNING=1" >> $GITHUB_ENV 80 | - name: Set running flag 81 | run: | 82 | if [[ ! -f package.json ]] || ! < package.json jq -r '.scripts | keys[]' | grep -qe '^cover$'; then 83 | echo "RUNNING=" >> $GITHUB_ENV 84 | fi 85 | 86 | - uses: actions/setup-node@v3 87 | with: 88 | node-version: 16 89 | cache: yarn 90 | if: env.RUNNING 91 | - name: Install Package dependencies 92 | run: yarn install 93 | if: env.RUNNING 94 | - name: Run tests 95 | run: yarn cover 96 | if: env.RUNNING 97 | - name: Codecov 98 | run: | 99 | if [ -n "$CODECOV_TOKEN" ]; then 100 | curl -s https://codecov.io/bash | bash -s -- -t $CODECOV_TOKEN -f $COVERAGE_FILE 101 | fi 102 | env: 103 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 104 | COVERAGE_FILE: ./coverage/lcov.info 105 | if: env.RUNNING && matrix.os == 'ubuntu-latest' 106 | # 107 | # release: 108 | # name: Release GitHub Actions 109 | # needs: cover 110 | # runs-on: ubuntu-latest 111 | # timeout-minutes: 5 112 | # if: startsWith(github.ref, 'refs/tags/') 113 | # permissions: 114 | # contents: write 115 | # steps: 116 | # - uses: actions/checkout@v3 117 | # - uses: actions/setup-node@v3 118 | # with: 119 | # node-version: 16 120 | # cache: yarn 121 | # 122 | # - uses: technote-space/load-config-action@v1 123 | # with: 124 | # CONFIG_FILENAME: workflow-settings.json, workflow-details.json 125 | # IGNORE_WARNING: 'true' 126 | # - name: Release GitHub Actions 127 | # uses: technote-space/release-github-actions@v8 128 | # with: 129 | # OUTPUT_BUILD_INFO_FILENAME: build.json 130 | # TEST_TAG_PREFIX: test/ 131 | # ORIGINAL_TAG_PREFIX: original/ 132 | # CLEAN_TEST_TAG: true 133 | # DELETE_NODE_MODULES: ${{ env.RELEASE_GA_DELETE_NODE_MODULES }} 134 | # 135 | # package: 136 | # name: Publish Package 137 | # needs: cover 138 | # runs-on: ubuntu-latest 139 | # timeout-minutes: 5 140 | # if: startsWith(github.ref, 'refs/tags/v') 141 | # permissions: 142 | # contents: read 143 | # strategy: 144 | # matrix: 145 | # target: ['npm', 'gpr'] 146 | # steps: 147 | # - name: Set running flag 148 | # run: echo "RUNNING=1" >> $GITHUB_ENV 149 | # - name: Set running flag 150 | # run: | 151 | # if [ -z "$NPM_AUTH_TOKEN" ]; then 152 | # echo "RUNNING=" >> $GITHUB_ENV 153 | # fi 154 | # env: 155 | # NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 156 | # - uses: actions/checkout@v3 157 | # if: env.RUNNING 158 | # - name: Check package version 159 | # uses: technote-space/package-version-check-action@v1 160 | # with: 161 | # COMMIT_DISABLED: 1 162 | # if: env.RUNNING 163 | # - name: Set running flag 164 | # run: npx can-npm-publish || echo "RUNNING=" >> $GITHUB_ENV 165 | # if: env.RUNNING && matrix.target == 'npm' 166 | # - name: Set running flag 167 | # run: | 168 | # LATEST=`npm view . version` 2> /dev/null || : 169 | # CURRENT=`cat package.json | jq -r .version` 170 | # if [ "$LATEST" = "$CURRENT" ]; then 171 | # echo "RUNNING=" >> $GITHUB_ENV 172 | # fi 173 | # env: 174 | # NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 175 | # if: env.RUNNING && matrix.target == 'gpr' 176 | # 177 | # - name: Setup Node.js 178 | # uses: actions/setup-node@v3 179 | # with: 180 | # node-version: 16 181 | # registry-url: https://registry.npmjs.org/ 182 | # cache: yarn 183 | # if: env.RUNNING && matrix.target == 'npm' 184 | # - name: Setup Node.js 185 | # uses: actions/setup-node@v3 186 | # with: 187 | # node-version: 16 188 | # registry-url: https://npm.pkg.github.com 189 | # cache: yarn 190 | # if: env.RUNNING && matrix.target == 'gpr' 191 | # - name: Install Package dependencies 192 | # run: yarn install 193 | # if: env.RUNNING 194 | # - name: Build 195 | # run: yarn build 196 | # if: env.RUNNING 197 | # - name: Publish 198 | # run: | 199 | # npm config set //registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN 200 | # npm publish 201 | # env: 202 | # NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 203 | # if: env.RUNNING && matrix.target == 'npm' 204 | # - name: Publish 205 | # run: | 206 | # npm publish 207 | # env: 208 | # NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 209 | # if: env.RUNNING && matrix.target == 'gpr' 210 | 211 | # publishRelease: 212 | # name: Create Release 213 | # needs: [release, package] 214 | # runs-on: ubuntu-latest 215 | # timeout-minutes: 5 216 | # permissions: 217 | # contents: write 218 | # steps: 219 | # - name: Get version 220 | # run: echo "TAG_NAME=${HEAD_REF#refs/tags/}" >> $GITHUB_ENV 221 | # env: 222 | # HEAD_REF: ${{ github.ref }} 223 | # - name: Create Release 224 | # id: drafter 225 | # uses: technote-space/release-drafter@v6 226 | # with: 227 | # GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 228 | # DRAFT: false 229 | # NAME: ${{ env.TAG_NAME }} 230 | # TAG: ${{ env.TAG_NAME }} 231 | # - uses: 8398a7/action-slack@v3 232 | # with: 233 | # status: ${{ job.status }} 234 | # text: ${{ format('<{0}>', steps.drafter.outputs.html_url) }} 235 | # env: 236 | # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 237 | # if: success() && env.SLACK_WEBHOOK_URL 238 | 239 | # slack: 240 | # name: Slack 241 | # needs: publishRelease 242 | # runs-on: ubuntu-latest 243 | # timeout-minutes: 3 244 | # if: always() 245 | # steps: 246 | # - uses: technote-space/workflow-conclusion-action@v3 247 | # - uses: 8398a7/action-slack@v3 248 | # with: 249 | # status: failure 250 | # env: 251 | # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 252 | # if: env.WORKFLOW_CONCLUSION == 'failure' && env.SLACK_WEBHOOK_URL 253 | -------------------------------------------------------------------------------- /.github/workflows/demo.yml: -------------------------------------------------------------------------------- 1 | name: demo 2 | on: 3 | schedule: 4 | # every 15 minutes 5 | - cron: "*/15 * * * *" 6 | workflow_dispatch: 7 | jobs: 8 | demo: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: azu/rss-to-twitter@v1 12 | with: 13 | # RSS feed URL 14 | RSS_URL: "https://hnrss.org/newest" 15 | TWEET_TEMPLATE: 'New Post: "%title%" %url%' 16 | UPDATE_WITHIN_MINUTES: 15 17 | # Twitter API Key 18 | TWITTER_APIKEY: ${{ secrets.APIKEY }} 19 | TWITTER_APIKEY_SECRET: ${{ secrets.APIKEY_SECRET }} 20 | TWITTER_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 21 | TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.ACCESS_TOKEN_SECRET }} 22 | -------------------------------------------------------------------------------- /.github/workflows/issue-opened.yml: -------------------------------------------------------------------------------- 1 | on: 2 | issues: 3 | types: [opened] 4 | 5 | name: Issue opened 6 | 7 | jobs: 8 | assign: 9 | name: Assign issues to project 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 3 12 | steps: 13 | - uses: technote-space/load-config-action@v1 14 | with: 15 | CONFIG_FILENAME: workflow-settings.json, workflow-details.json 16 | IGNORE_WARNING: 'true' 17 | - uses: technote-space/create-project-card-action@v1 18 | with: 19 | PROJECT: ${{ env.PROJECT }} 20 | COLUMN: ${{ env.ISSUE_COLUMN }} 21 | 22 | assignAuthor: 23 | name: Assign author to issue 24 | runs-on: ubuntu-latest 25 | timeout-minutes: 3 26 | steps: 27 | - uses: technote-space/assign-author@v1 28 | -------------------------------------------------------------------------------- /.github/workflows/pr-opened.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request_target: 3 | types: [opened] 4 | 5 | name: Pull Request opened 6 | 7 | jobs: 8 | assignToProject: 9 | name: Assign PullRequest to Project 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 3 12 | steps: 13 | - uses: technote-space/load-config-action@v1 14 | with: 15 | CONFIG_FILENAME: workflow-settings.json, workflow-details.json 16 | IGNORE_WARNING: 'true' 17 | - uses: technote-space/create-project-card-action@v1 18 | with: 19 | PROJECT: ${{ env.PROJECT }} 20 | COLUMN: ${{ env.PR_COLUMN }} 21 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 22 | 23 | assignAuthor: 24 | name: Assign author to PR 25 | runs-on: ubuntu-latest 26 | timeout-minutes: 3 27 | steps: 28 | - uses: technote-space/assign-author@v1 29 | 30 | addLabelsByBranch: 31 | name: PR Labeler 32 | runs-on: ubuntu-latest 33 | timeout-minutes: 3 34 | steps: 35 | - uses: technote-space/pr-labeler-action@v4 36 | -------------------------------------------------------------------------------- /.github/workflows/pr-updated.yml: -------------------------------------------------------------------------------- 1 | on: pull_request_target 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | 6 | name: Pull Request updated 7 | 8 | jobs: 9 | triage: 10 | name: Pull Request Labeler 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 3 13 | if: "! startsWith(github.head_ref, 'release/')" 14 | steps: 15 | - uses: actions/labeler@v2 16 | with: 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | history: 20 | name: Pull Request Body 21 | runs-on: ubuntu-latest 22 | timeout-minutes: 3 23 | if: github.event.pull_request.head.user.id == github.event.pull_request.base.user.id 24 | steps: 25 | - uses: technote-space/load-config-action@v1 26 | with: 27 | CONFIG_FILENAME: workflow-settings.json, workflow-details.json 28 | IGNORE_WARNING: 'true' 29 | - uses: technote-space/pr-commit-body-action@v1 30 | with: 31 | EXCLUDE_MESSAGES: ${{ env.EXCLUDE_MESSAGES }} 32 | TITLE: ${{ env.PR_BODY_TITLE }} 33 | LINK_ISSUE_KEYWORD: ${{ (startsWith(github.head_ref, 'release/') && 'closes') || '' }} 34 | FILTER_PR: true 35 | CHANGE_TEMPLATE: ${{ env.CHANGE_TEMPLATE }} 36 | 37 | manageRelease: 38 | name: Manage release 39 | runs-on: ubuntu-latest 40 | timeout-minutes: 3 41 | if: "github.event.pull_request.head.user.id == github.event.pull_request.base.user.id && startsWith(github.head_ref, 'release/') && ! startsWith(github.head_ref, 'release/v')" 42 | steps: 43 | - uses: technote-space/load-config-action@v1 44 | with: 45 | CONFIG_FILENAME: workflow-settings.json, workflow-details.json 46 | IGNORE_WARNING: 'true' 47 | - uses: technote-space/release-type-action@v1 48 | with: 49 | EXCLUDE_MESSAGES: ${{ env.EXCLUDE_MESSAGES }} 50 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 51 | 52 | checkVersion: 53 | name: Check package version 54 | runs-on: ubuntu-latest 55 | timeout-minutes: 3 56 | if: "github.event.action == 'synchronize' && github.event.pull_request.head.user.id == github.event.pull_request.base.user.id && startsWith(github.head_ref, 'release/')" 57 | steps: 58 | - uses: technote-space/load-config-action@v1 59 | with: 60 | CONFIG_FILENAME: workflow-settings.json, workflow-details.json 61 | IGNORE_WARNING: 'true' 62 | - name: Set running flag 63 | run: echo "RUNNING=1" >> $GITHUB_ENV 64 | - uses: actions/checkout@v3 65 | with: 66 | ref: ${{ github.head_ref }} 67 | - name: Set running flag 68 | run: | 69 | if [[ ! -f package.json ]] || [[ $(< package.json jq -r '.version == null') == 'true' ]]; then 70 | echo "RUNNING=" >> $GITHUB_ENV 71 | fi 72 | 73 | - name: Sort 74 | run: npx sort-package-json 75 | if: env.RUNNING 76 | - name: Get version 77 | uses: technote-space/get-next-version-action@v1 78 | with: 79 | EXCLUDE_MESSAGES: ${{ env.EXCLUDE_MESSAGES }} 80 | if: "env.RUNNING && ! startsWith(github.head_ref, 'release/v')" 81 | - name: Get version 82 | run: echo "NEXT_VERSION=${HEAD_REF#release/}" >> $GITHUB_ENV 83 | env: 84 | HEAD_REF: ${{ github.head_ref }} 85 | if: env.RUNNING && startsWith(github.head_ref, 'release/v') 86 | - name: Check package version 87 | uses: technote-space/package-version-check-action@v1 88 | with: 89 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 90 | BRANCH_PREFIX: release/ 91 | NEXT_VERSION: ${{ env.NEXT_VERSION }} 92 | if: env.NEXT_VERSION 93 | 94 | checkPublish: 95 | name: Check publish 96 | runs-on: ubuntu-latest 97 | timeout-minutes: 3 98 | if: "github.event.pull_request.head.user.id == github.event.pull_request.base.user.id && startsWith(github.head_ref, 'release/')" 99 | steps: 100 | - name: Set running flag 101 | run: echo "RUNNING=1" >> $GITHUB_ENV 102 | - name: Set running flag 103 | run: | 104 | if [ -z "$NPM_AUTH_TOKEN" ]; then 105 | echo "RUNNING=" >> $GITHUB_ENV 106 | fi 107 | env: 108 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 109 | 110 | - uses: actions/checkout@v3 111 | with: 112 | ref: ${{ github.head_ref }} 113 | if: env.RUNNING 114 | - uses: technote-space/can-npm-publish-action@v1 115 | if: env.RUNNING 116 | -------------------------------------------------------------------------------- /.github/workflows/project-card-moved.yml: -------------------------------------------------------------------------------- 1 | on: 2 | project_card: 3 | types: [created, moved] 4 | 5 | name: Project Card Event 6 | 7 | jobs: 8 | triage: 9 | name: Auto card labeler 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 3 12 | steps: 13 | - uses: technote-space/auto-card-labeler@v1 14 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: create 2 | 3 | name: Release 4 | permissions: write-all 5 | jobs: 6 | release: 7 | name: Release GitHub Actions 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: technote-space/release-github-actions@v8 11 | with: 12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 13 | CREATE_MAJOR_VERSION_TAG: true 14 | CREATE_MINOR_VERSION_TAG: true 15 | -------------------------------------------------------------------------------- /.github/workflows/toc.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | types: [opened, synchronize, reopened, closed] 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | 8 | name: TOC Generator 9 | 10 | jobs: 11 | toc: 12 | if: github.event.pull_request.head.user.id == github.event.pull_request.base.user.id 13 | name: TOC Generator 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 3 16 | steps: 17 | - uses: technote-space/load-config-action@v1 18 | with: 19 | CONFIG_FILENAME: workflow-settings.json, workflow-details.json 20 | IGNORE_WARNING: 'true' 21 | - uses: technote-space/toc-generator@v4 22 | with: 23 | GITHUB_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} 24 | TARGET_BRANCH_PREFIX: ${{ env.BRANCH_PREFIX }} 25 | FOLDING: ${{ env.TOC_FOLDING }} 26 | MAX_HEADER_LEVEL: ${{ env.TOC_MAX_HEADER_LEVEL }} 27 | TOC_TITLE: ${{ env.TOC_TITLE }} 28 | CREATE_PR: ${{ env.TOC_CREATE_PR }} 29 | TARGET_PATHS: ${{ env.TOC_TARGET_PATHS }} 30 | FOOTER: ${{ env.TOC_FOOTER }} 31 | -------------------------------------------------------------------------------- /.github/workflows/update-dependencies.yml: -------------------------------------------------------------------------------- 1 | on: 2 | schedule: 3 | - cron: 14 2 * * 5 4 | pull_request: 5 | types: [opened, reopened, closed] 6 | repository_dispatch: 7 | types: [update-deps] 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | 13 | name: Update dependencies 14 | jobs: 15 | update: 16 | name: Update npm dependencies 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 10 19 | if: "! startsWith(github.head_ref, 'release/v')" 20 | steps: 21 | - name: Set running flag 22 | run: echo "RUNNING1=" >> $GITHUB_ENV 23 | - name: Set running flag 24 | run: echo "RUNNING1=1" >> $GITHUB_ENV 25 | if: github.event.pull_request.head.user.id == github.event.pull_request.base.user.id 26 | - uses: technote-space/load-config-action@v1 27 | if: env.RUNNING1 28 | with: 29 | CONFIG_FILENAME: workflow-settings.json, workflow-details.json 30 | IGNORE_WARNING: 'true' 31 | - name: Update dependencies 32 | if: env.RUNNING1 33 | id: update_deps 34 | uses: technote-space/create-pr-action@v2 35 | with: 36 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 37 | EXECUTE_COMMANDS: | 38 | npx npm-check-updates -u --packageFile package.json 39 | yarn install 40 | yarn upgrade 41 | yarn audit 42 | COMMIT_MESSAGE: 'chore: update npm dependencies' 43 | PR_DEFAULT_BRANCH_PREFIX: release/ 44 | PR_DEFAULT_BRANCH_NAME: next-${CURRENT_VERSION} 45 | PR_DEFAULT_BRANCH_TITLE: 'feat: release' 46 | TARGET_BRANCH_PREFIX: release/ 47 | AUTO_MERGE_THRESHOLD_DAYS: 14 48 | 49 | - name: Set running flag 50 | run: echo "RUNNING2=" >> $GITHUB_ENV 51 | - name: Set running flag 52 | run: echo "RUNNING2=1" >> $GITHUB_ENV 53 | if: env.RUNNING1 && steps.update_deps.outputs.result != 'succeeded' && github.event_name == 'pull_request' && github.event.action != 'closed' && startsWith(github.head_ref, 'release/') 54 | - uses: actions/checkout@v3 55 | if: env.RUNNING2 56 | - name: Set running flag 57 | run: | 58 | if [[ ! -f package.json ]] || [[ $(< package.json jq -r '.version == null') == 'true' ]]; then 59 | echo "RUNNING2=" >> $GITHUB_ENV 60 | fi 61 | - name: Sort 62 | run: npx sort-package-json 63 | if: env.RUNNING2 64 | - name: Get version 65 | uses: technote-space/get-next-version-action@v1 66 | with: 67 | EXCLUDE_MESSAGES: ${{ env.EXCLUDE_MESSAGES }} 68 | if: "env.RUNNING2 && ! startsWith(github.head_ref, 'release/v')" 69 | - name: Get version 70 | run: echo "NEXT_VERSION=${HEAD_REF#release/}" >> $GITHUB_ENV 71 | env: 72 | HEAD_REF: ${{ github.head_ref }} 73 | if: env.RUNNING2 && startsWith(github.head_ref, 'release/v') 74 | - name: Check package version 75 | uses: technote-space/package-version-check-action@v1 76 | with: 77 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 78 | BRANCH_PREFIX: release/ 79 | NEXT_VERSION: ${{ env.NEXT_VERSION }} 80 | if: env.NEXT_VERSION 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.vscode 3 | /node_modules 4 | /coverage 5 | /lib 6 | /.work 7 | .eslintcache 8 | .env 9 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint -- --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "(src|__tests__)/**/*.{js,jsx,ts,tsx}": "yarn lint:fix" 3 | } 4 | -------------------------------------------------------------------------------- /.releasegarc: -------------------------------------------------------------------------------- 1 | { 2 | "inputs": { 3 | "OUTPUT_BUILD_INFO_FILENAME": "build.json", 4 | "TEST_TAG_PREFIX": "test/", 5 | "CLEAN_TEST_TAG": "true" 6 | } 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 azu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RSS To Twitter 2 | 3 | [![CI Status](https://github.com/azu/rss-to-twitter/workflows/CI/badge.svg)](https://github.com/azu/rss-to-twitter/actions) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/azu/rss-to-twitter/blob/master/LICENSE) 5 | 6 | GitHub Actions post twitter from RSS Feeds. 7 | 8 | ## Post Steps 9 | 10 | 1. Fetch RSS Feeds 11 | 2. Filter feed items by publish time 12 | 3. Post to twitter. 13 | 14 | If your action uses `on.schedule.cron`, filter feed items by publish time compare to previous cron execution time. 15 | If your action uses other events like `on.push`, you need to set `UPDATE_WITHIN_MINUTES` option. 16 | 17 | ## Usage 18 | 19 | ### Prepare Twitter API Keys 20 | 21 | 1. Create Twitter App - 22 | 2. Change your app permission to `Read and Write` 23 | - ![ss 1](docs/img.png) 24 | - ![ss 2](docs/img_1.png) 25 | 3. Get API Key/API Key Secret and Access Token/Access Token Secret 26 | - ![Twitter APIKEY](docs/apikey.png) 27 | - `TWITTER_APIKEY` and `TWITTER_APIKEY_SECRET` 28 | - ![Twitter ACCESS TOKEN](docs/accesstoken.png) 29 | - `TWITTER_ACCESS_TOKEN` and `TWITTER_ACCESS_TOKEN_SECRET` 30 | - :warning: Check "Created with **Read and Write** permissions" on your app page. 31 | 4. Add these keys to GitHub Secrets 32 | - `TWITTER_APIKEY` 33 | - `TWITTER_APIKEY_SECRET` 34 | - `TWITTER_ACCESS_TOKEN` 35 | - `TWITTER_ACCESS_TOKEN_SECRET` 36 | 37 | :memo: Bearer Token is not needed. 38 | 39 | ### On schedule 40 | 41 | Post new feed item via schedule cron every 15 minutes. 42 | 43 | ```yaml 44 | name: rss-to-twitter 45 | on: 46 | schedule: 47 | # every 15 minutes 48 | - cron: "*/15 * * * *" 49 | workflow_dispatch: 50 | jobs: 51 | twitter: 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: azu/rss-to-twitter@v2 55 | with: 56 | # RSS feed URL 57 | RSS_URL: "https://hnrss.org/newest" 58 | TWEET_TEMPLATE: 'New Post: "%title%" %url%' 59 | UPDATE_WITHIN_MINUTES: 15 # for workflow_dispatch 60 | TWITTER_APIKEY: ${{ secrets.TWITTER_APIKEY }} 61 | TWITTER_APIKEY_SECRET: ${{ secrets.TWITTER_APIKEY_SECRET }} 62 | TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} 63 | TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} 64 | ``` 65 | 66 | > **Note**: filter feed items by publish time compare to previous cron execution time. 67 | 68 | ### On Page build 69 | 70 | Post new feed item via GitHub Pages Build event. 71 | 72 | ```yaml 73 | name: rss-to-twitter 74 | on: 75 | page_build 76 | jobs: 77 | twitter: 78 | # if github.event.build.error.message is not null, it means that the build failed. Skip it 79 | if: ${{ github.event.build.error.message == null }} 80 | runs-on: ubuntu-latest 81 | steps: 82 | - uses: azu/rss-to-twitter@v2 83 | with: 84 | RSS_URL: "https://you.github.io/feed.xml" 85 | TWEET_TEMPLATE: 'New Post: "%title%" %url%' 86 | UPDATE_WITHIN_MINUTES: 15 # post items that are published within 15 minutes 87 | TWITTER_APIKEY: ${{ secrets.TWITTER_APIKEY }} 88 | TWITTER_APIKEY_SECRET: ${{ secrets.TWITTER_APIKEY_SECRET }} 89 | TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} 90 | TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} 91 | ``` 92 | 93 | - Example: 94 | - Workflow: https://github.com/ecmascript-daily/ecmascript-daily.github.com/blob/master/.github/workflows/rss-to-twitter.yml 95 | - Twitter: https://twitter.com/EcmascriptDaily/ 96 | 97 | > **Note**: filter feed items by publish time within 15 minutes. 98 | 99 | > **Warning**: If you deploy your site by GitHub Actions, you need to use Personal Access Token instead of `${{ secrets. GITHUB_TOKEN }}`. `${{ secrets. GITHUB_TOKEN }}` can not trigger `page_build` event. It is limitation of GitHub Actions's `${{ secrets. GITHUB_TOKEN }}`. 100 | 101 | - [Automatic token authentication - GitHub Docs](https://docs.github.com/en/enterprise-server@2.22/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow) 102 | - [github actions - Push event doesn't trigger workflow on push paths - Stack Overflow](https://stackoverflow.com/questions/67550727/push-event-doesnt-trigger-workflow-on-push-paths) 103 | 104 | Instead of It, you can use Personal Access Token for deploy, and it triggers `page_build` event. 105 | 106 | - Example: 107 | - Deploy Workflow: https://github.com/jser/jser.github.io/blob/a0fcfc6ef3829055ee10807009d04fb6431a4daf/.github/workflows/deploy.yml#L26-L35 108 | - RSS to Twitter Workflow:https://github.com/jser/jser.github.io/blob/a0fcfc6ef3829055ee10807009d04fb6431a4daf/.github/workflows/rss-to-twitter.yml 109 | - Twitter:https://twitter.com/jser_info 110 | 111 | ## TWEET_TEMPLATE 112 | 113 | - `%title%`: Item title 114 | - `%url`: Item url 115 | - `%desc%`: Item content snip(max 280 charaters) 116 | 117 | ## Release Flow 118 | 119 | 1. Tag to `v*` on Release Pages 120 | 2. CI build action and push it 121 | 122 | ## License 123 | 124 | MIT 125 | 126 | ---- 127 | 128 | ## Table of Contents 129 | 130 | 131 | 132 |
133 | Details 134 | 135 | - [Setup](#setup) 136 | - [yarn](#yarn) 137 | - [npm](#npm) 138 | - [Workflows](#workflows) 139 | - [ci.yml](#ciyml) 140 | - [add-version-tag.yml](#add-version-tagyml) 141 | - [toc.yml](#tocyml) 142 | - [issue-opened.yml](#issue-openedyml) 143 | - [pr-opened.yml](#pr-openedyml) 144 | - [pr-updated.yml](#pr-updatedyml) 145 | - [project-card-moved.yml](#project-card-movedyml) 146 | - [broken-link-check.yml](#broken-link-checkyml) 147 | - [update-dependencies.yml](#update-dependenciesyml) 148 | - [add-test-tag.yml](#add-test-tagyml) 149 | - [Secrets](#secrets) 150 | - [Test release](#test-release) 151 | - [Helpers](#helpers) 152 | - [Author](#author) 153 | 154 |
155 | 156 | 157 | ## Setup 158 | ### yarn 159 | - `yarn setup` 160 | ### npm 161 | - `npm run setup` 162 | 163 | ## Workflows 164 | 165 | Some `workflows` are included by default. 166 | 167 | ### ci.yml 168 | CI Workflow 169 | 170 | 1. ESLint 171 | 1. Jest 172 | - Send coverage report to codecov if `CODECOV_TOKEN` is set. 173 | 1. Release GitHub Actions 174 | - if tag is added. 175 | 1. Publish package 176 | - if tag is added and `NPM_AUTH_TOKEN` is set. 177 | 1. Publish release 178 | - if 3 and 4 jobs are succeeded. 179 | 1. Notify by slack 180 | - if workflow is failure 181 | 182 | [ACCESS_TOKEN](#access_token) is required. 183 | [SLACK_WEBHOOK_URL](#slack_webhook_url) is required. 184 | 185 | ### add-version-tag.yml 186 | Add the release tag when pull request is merged. 187 | 188 | 1. Get next version from commits histories. 189 | see [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) 190 | 1. Add tag. 191 | 1. Create branch for next version. 192 | 193 | [ACCESS_TOKEN](#access_token) is required. 194 | 195 | ### toc.yml 196 | Create TOC (Table of contents) 197 | 198 | [ACCESS_TOKEN](#access_token) is required. 199 | 200 | ### issue-opened.yml 201 | - Assign the issue to project 202 | default setting: 203 | ``` 204 | Project: Backlog 205 | Column: To do 206 | ``` 207 | - Assign author to issue 208 | 209 | ### pr-opened.yml 210 | - Assign the PR to project 211 | default setting: 212 | ``` 213 | Project: Backlog 214 | Column: In progress 215 | ``` 216 | [ACCESS_TOKEN](#access_token) is required. 217 | - Assign author to PR 218 | - Add labels by branch 219 | [setting](.github/pr-labeler.yml) 220 | 221 | ### pr-updated.yml 222 | - Add labels by changed files 223 | [setting](.github/labeler.yml) 224 | - Create PR histories 225 | - Manage PR by release type 226 | [ACCESS_TOKEN](#access_token) is required. 227 | - Check version in package.json 228 | [ACCESS_TOKEN](#access_token) is required. 229 | - Check if it can be published to npm 230 | if `NPM_AUTH_TOKEN` is set 231 | 232 | ### project-card-moved.yml 233 | Manage labels by moving project cards 234 | 235 | ### broken-link-check.yml 236 | Check broken link in README 237 | 238 | ### update-dependencies.yml 239 | Update package dependencies 240 | 241 | - schedule 242 | - PR opened, closed 243 | - repository dispatch 244 | 245 | ### add-test-tag.yml 246 | Add tag for test release 247 | 248 | ### Secrets 249 | #### ACCESS_TOKEN 250 | [Personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) with the public_repo or repo scope 251 | (repo is required for private repositories) 252 | 253 | #### SLACK_WEBHOOK_URL 254 | https://api.slack.com/messaging/webhooks 255 | 256 | ## Test release 257 | [![azu/release-github-actions-cli - GitHub](https://gh-card.dev/repos/azu/release-github-actions-cli.svg)](https://github.com/azu/release-github-actions-cli) 258 | 259 | 1. Create `.env` 260 | Set [Personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) 261 | ```dotenv 262 | token=1234567890abcdef1234567890abcdef12345678 263 | ``` 264 | 1. Run `yarn release` 265 | - Dry run: `yarn release -n` 266 | - Help: `yarn release -h` 267 | 268 | ![cli](https://github.com/azu/rss-to-twitter/raw/images/cli.gif) 269 | 270 | Then, you can use your `GitHub Actions` like follows: 271 | 272 | ```yaml 273 | on: push 274 | name: Test 275 | jobs: 276 | toc: 277 | name: Test 278 | runs-on: ubuntu-latest 279 | steps: 280 | - uses: owner/repo@gh-actions 281 | ``` 282 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/free-pro-team@latest/actions/creating-actions/metadata-syntax-for-github-actions 2 | name: RSS To Twitter 3 | 4 | description: GitHub Actions posts twitter from RSS Feeds. 5 | 6 | author: azu 7 | 8 | inputs: 9 | RSS_URL: 10 | description: RSS URL to fetch. 11 | required: true 12 | TWEET_TEMPLATE: 13 | description: "Template for tweet text. You can use %title%, %url%, %desc%" 14 | default: '"%title%" %url%' 15 | required: false 16 | UPDATE_WITHIN_MINUTES: 17 | description: "Post only if the feed is updated within the specified minutes." 18 | default: "60" 19 | required: false 20 | # Twitter API token 21 | # Create your own Twitter App token from https://developer.twitter.com/en/portal/dashboard 22 | TWITTER_APIKEY: 23 | description: Twitter API Key(App Key) 24 | required: true 25 | TWITTER_APIKEY_SECRET: 26 | description: Twitter API Secret(App key secret) 27 | required: true 28 | TWITTER_ACCESS_TOKEN: 29 | description: Twitter Access Token 30 | required: true 31 | TWITTER_ACCESS_TOKEN_SECRET: 32 | description: Twitter Access Token Secret 33 | required: true 34 | 35 | #outputs: 36 | # result: 37 | # description: action result 38 | 39 | branding: 40 | # https://feathericons.com/ 41 | # e.g. https://haya14busa.github.io/github-action-brandings/ 42 | icon: 'package' 43 | color: 'orange' 44 | 45 | runs: 46 | using: node20 47 | main: lib/main.js 48 | # pre: lib/pre.js 49 | # pre-if: 'runner.os == linux' 50 | # post: lib/post.js 51 | # post-if: 'success()' 52 | -------------------------------------------------------------------------------- /docs/accesstoken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azu/rss-to-twitter/c51e9dd521cc51e25912e0e22971dbb81484cccf/docs/accesstoken.png -------------------------------------------------------------------------------- /docs/apikey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azu/rss-to-twitter/c51e9dd521cc51e25912e0e22971dbb81484cccf/docs/apikey.png -------------------------------------------------------------------------------- /docs/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azu/rss-to-twitter/c51e9dd521cc51e25912e0e22971dbb81484cccf/docs/img.png -------------------------------------------------------------------------------- /docs/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azu/rss-to-twitter/c51e9dd521cc51e25912e0e22971dbb81484cccf/docs/img_1.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@azu/rss-to-twitter", 3 | "version": "0.0.1", 4 | "description": "GitHub Actions post twitter from RSS Feeds.", 5 | "keywords": [ 6 | "github", 7 | "github actions" 8 | ], 9 | "homepage": "https://github.com/azu/rss-to-twitter", 10 | "bugs": { 11 | "url": "https://github.com/azu/rss-to-twitter/issues" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/azu/rss-to-twitter.git" 16 | }, 17 | "license": "MIT", 18 | "author": { 19 | "name": "azu" 20 | }, 21 | "files": [ 22 | "lib", 23 | "action.yml" 24 | ], 25 | "scripts": { 26 | "build": "rm -rdf lib && rollup -c", 27 | "cover": "vitest run", 28 | "lint": "eslint 'src/**/*.ts' --cache", 29 | "lint:fix": "eslint --fix 'src/**/*.ts'", 30 | "release": "yarn release-ga --test", 31 | "test": "yarn lint && yarn typecheck && yarn cover", 32 | "typecheck": "tsc --noEmit", 33 | "update": "npm_config_yes=true npx npm-check-updates -u --timeout 100000 && yarn install && yarn upgrade && yarn audit" 34 | }, 35 | "devDependencies": { 36 | "@actions/core": "^1.10.0", 37 | "@actions/github": "^5.1.1", 38 | "@commitlint/cli": "^17.3.0", 39 | "@commitlint/config-conventional": "^17.3.0", 40 | "@octokit/openapi-types": "^14.0.0", 41 | "@octokit/plugin-paginate-rest": "^5.0.1", 42 | "@octokit/types": "^8.0.0", 43 | "@rollup/plugin-commonjs": "^23.0.3", 44 | "@rollup/plugin-json": "^5.0.2", 45 | "@rollup/plugin-node-resolve": "^15.0.1", 46 | "@rollup/plugin-typescript": "^10.0.1", 47 | "@sindresorhus/tsconfig": "^3.0.1", 48 | "@technote-space/filter-github-action": "^0.6.6", 49 | "@technote-space/github-action-helper": "^5.3.10", 50 | "@technote-space/github-action-log-helper": "^0.2.9", 51 | "@technote-space/github-action-test-helper": "^0.11.1", 52 | "@technote-space/release-github-actions-cli": "^1.9.3", 53 | "@types/node": "^18.11.10", 54 | "@typescript-eslint/eslint-plugin": "^5.45.0", 55 | "@typescript-eslint/parser": "^5.45.0", 56 | "@vitest/coverage-c8": "^0.25.3", 57 | "@vitest/ui": "^0.31.1", 58 | "eslint": "^8.28.0", 59 | "eslint-plugin-import": "^2.26.0", 60 | "husky": "^8.0.2", 61 | "lint-staged": "^13.0.4", 62 | "nock": "^13.2.9", 63 | "rollup": "^3.5.1", 64 | "typescript": "^4.9.3", 65 | "vitest": "0.31.1" 66 | }, 67 | "publishConfig": { 68 | "access": "public" 69 | }, 70 | "dependencies": { 71 | "cron-parser": "^4.8.1", 72 | "rss-parser": "^3.13.0", 73 | "tweet-truncator": "^3.0.1", 74 | "twitter-api-v2": "^1.14.3" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import pluginCommonjs from '@rollup/plugin-commonjs'; 2 | import pluginJson from '@rollup/plugin-json'; 3 | import pluginNodeResolve from '@rollup/plugin-node-resolve'; 4 | import pluginTypescript from '@rollup/plugin-typescript'; 5 | 6 | export default { 7 | input: 'src/main.ts', 8 | output: { 9 | file: 'lib/main.js', 10 | format: 'cjs', 11 | }, 12 | plugins: [ 13 | pluginTypescript(), 14 | pluginNodeResolve(), 15 | pluginCommonjs(), 16 | pluginJson(), 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { setFailed } from '@actions/core'; 3 | import { Context } from '@actions/github/lib/context'; 4 | import { ContextHelper } from '@technote-space/github-action-helper'; 5 | import { Logger } from '@technote-space/github-action-log-helper'; 6 | import { execute } from './process'; 7 | 8 | (async(): Promise => { 9 | const logger = new Logger(); 10 | const context = new Context(); 11 | ContextHelper.showActionInfo(resolve(__dirname, '..'), logger, context); 12 | await execute(logger, context); 13 | })().catch(error => { 14 | console.log(error); 15 | setFailed(error.message); 16 | }); 17 | -------------------------------------------------------------------------------- /src/process.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-magic-numbers */ 2 | import { Logger } from '@technote-space/github-action-log-helper'; 3 | import { generateContext } from '@technote-space/github-action-test-helper'; 4 | import { describe, it } from 'vitest'; 5 | import { execute } from './process'; 6 | 7 | describe('execute', () => { 8 | // disableNetConnect(nock); 9 | // testEnv(rootDir); 10 | // testChildProcess(); 11 | 12 | it.skip('should execute', async() => { 13 | // const mockStdout = spyOnStdout(); 14 | process.env.INPUT_RSS_URL = 'https://jser.info/rss/'; 15 | // This cron means every day at 00:00 16 | process.env.INPUT_SCHEDULE = '0 0 * * *'; 17 | process.env.INPUT_TWEET_TEMPLATE = '"%title%" %url%\n%desc%'; 18 | // require env 19 | // process.env.INPUT_TWITTER_APIKEY = 'x'; 20 | // process.env.INPUT_TWITTER_APIKEY_SECRET = 'x'; 21 | // process.env.INPUT_TWITTER_ACCESS_TOKEN = 'x-7U/x'; 22 | // process.env.INPUT_TWITTER_ACCESS_TOKEN_SECRET = 'x'; 23 | await execute(new Logger(), generateContext({ owner: 'hello', repo: 'world' })); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/process.ts: -------------------------------------------------------------------------------- 1 | import type { Logger } from '@technote-space/github-action-log-helper'; 2 | import * as core from '@actions/core'; 3 | import { Context } from '@actions/github/lib/context'; 4 | import cronParser from 'cron-parser'; 5 | import Parser from 'rss-parser'; 6 | import { truncate } from 'tweet-truncator'; 7 | import { TwitterApi } from 'twitter-api-v2'; 8 | 9 | async function fetchRSSFeed(rssUrl: string, retryCount = 3) { 10 | if (retryCount <= 0) { 11 | throw new Error('Failed to fetch RSS feed'); 12 | } 13 | const parser = new Parser(); 14 | try { 15 | return await parser.parseURL(rssUrl); 16 | } catch (error) { 17 | console.error(error); 18 | return await fetchRSSFeed(rssUrl, retryCount - 1); 19 | } 20 | } 21 | 22 | async function getPrevExecuteTime(cronSyntax: string, currentDate: Date) { 23 | const result = cronParser.parseExpression(cronSyntax, { 24 | currentDate: currentDate, 25 | }); 26 | return result.prev(); 27 | } 28 | 29 | async function postToTwitter(statusText: string, twitterConfig: { 30 | TWITTER_APIKEY: string; 31 | TWITTER_APIKEY_SECRET: string; 32 | TWITTER_ACCESS_TOKEN: string; 33 | TWITTER_ACCESS_TOKEN_SECRET: string; 34 | }) { 35 | const twitterClient = new TwitterApi({ 36 | appKey: twitterConfig.TWITTER_APIKEY, 37 | appSecret: twitterConfig.TWITTER_APIKEY_SECRET, 38 | accessToken: twitterConfig.TWITTER_ACCESS_TOKEN, 39 | accessSecret: twitterConfig.TWITTER_ACCESS_TOKEN_SECRET, 40 | }); 41 | return twitterClient.v2.tweet(statusText); 42 | } 43 | 44 | const computePrevTime = async(currentDate: Date, logger: Logger, context: Context) => { 45 | const isScheduleEvent = context.eventName === 'schedule'; 46 | if (isScheduleEvent) { 47 | const diffMinutes = 5; // cover 5 minutes delay 48 | const scheduleCron = context.payload.schedule; 49 | if (typeof scheduleCron !== 'string') { 50 | throw new Error('schedule.cron is not string'); 51 | } 52 | logger.info('schedule.cron: %s', scheduleCron); 53 | const adjustedDate = new Date(currentDate.getTime() - diffMinutes * 60 * 1000); 54 | const prevExecutionTime = await getPrevExecuteTime(scheduleCron, adjustedDate); 55 | return prevExecutionTime.toDate(); 56 | } else { 57 | // Update within hours 58 | // Default: 60 minutes 59 | const updateWithinMinutesStr = core.getInput('UPDATE_WITHIN_MINUTES', { 60 | required: false 61 | }); 62 | const updateWithinMinutes = updateWithinMinutesStr ? parseInt(updateWithinMinutesStr, 10) : 60; 63 | logger.info('update within minutes: %d', updateWithinMinutes); 64 | return new Date(currentDate.getTime() - updateWithinMinutes * 60 * 1000); 65 | } 66 | }; 67 | export const execute = async(logger: Logger, context: Context): Promise => { 68 | const rssUrl = core.getInput('RSS_URL', { 69 | required: true, 70 | trimWhitespace: true 71 | }); 72 | logger.startProcess('fetch rss feed: ' + rssUrl); 73 | const rssFeed = await fetchRSSFeed(rssUrl); 74 | logger.info('title: %s, items: %d', rssFeed.title, rssFeed.items.length); 75 | 76 | const currentDate = new Date(); 77 | const prevExecutionTime = await computePrevTime(currentDate, logger, context); 78 | logger.info(`current time: ${currentDate.toISOString()}, prev execute time: ${prevExecutionTime.toISOString()}`); 79 | const updatedItems = rssFeed.items.filter((item) => { 80 | if (!item.pubDate) { 81 | return false;// skip if no publish date 82 | } 83 | const pubDate = new Date(item.pubDate); 84 | const isNewItem = pubDate.getTime() >= prevExecutionTime.getTime(); 85 | logger.debug(`title: ${item.title}, pubDate: ${pubDate.toISOString()}, isNewItem: ${isNewItem}`); 86 | return isNewItem; 87 | }); 88 | logger.info('updated items: %d', updatedItems.length); 89 | if (updatedItems.length === 0) { 90 | return logger.endProcess(); 91 | } 92 | logger.startProcess('Tweet updated %d items', updatedItems.length); 93 | const TWITTER_ACCESS_TOKEN_SECRET = core.getInput('TWITTER_ACCESS_TOKEN_SECRET', { 94 | required: true, 95 | }); 96 | const TWITTER_ACCESS_TOKEN = core.getInput('TWITTER_ACCESS_TOKEN', { 97 | required: true, 98 | }); 99 | const TWITTER_APIKEY = core.getInput('TWITTER_APIKEY', { 100 | required: true, 101 | }); 102 | const TWITTER_APIKEY_SECRET = core.getInput('TWITTER_APIKEY_SECRET', { 103 | required: true, 104 | }); 105 | 106 | const tweetTemplate = core.getInput('TWEET_TEMPLATE') || '"%title%" %url%'; 107 | const reverseUpdatedItems = updatedItems.reverse(); 108 | for (const updatedItem of reverseUpdatedItems) { 109 | const statusText = truncate({ 110 | title: updatedItem.title, 111 | url: updatedItem.link, 112 | // force to slice 140 characters 113 | desc: updatedItem.contentSnippet?.slice(0, 140), 114 | tags: [], 115 | quote: '' 116 | }, { 117 | // "%title%" %url% 118 | template: tweetTemplate 119 | }); 120 | try { 121 | await postToTwitter(statusText, { 122 | TWITTER_APIKEY, 123 | TWITTER_APIKEY_SECRET, 124 | TWITTER_ACCESS_TOKEN, 125 | TWITTER_ACCESS_TOKEN_SECRET, 126 | }); 127 | } catch (error: any) { 128 | logger.debug(JSON.stringify(error)); 129 | logger.error(error.message); 130 | } 131 | } 132 | logger.endProcess(); 133 | }; 134 | -------------------------------------------------------------------------------- /src/setup.ts: -------------------------------------------------------------------------------- 1 | // import { setupGlobal } from '@technote-space/github-action-test-helper'; 2 | // 3 | // setupGlobal(); 4 | // 5 | // // jest.mock('./src/constant', () => ({ 6 | // // ...jest.requireActual('./src/constant'), 7 | // // INTERVAL_MS: 0, 8 | // // })); 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sindresorhus/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "target": "ES2021", 6 | "module": "ES2020", 7 | "lib": [ 8 | "ES2021" 9 | ], 10 | "moduleResolution": "node", 11 | "noPropertyAccessFromIndexSignature": false, 12 | "noImplicitAny": false 13 | }, 14 | "include": [ 15 | "src" 16 | ] 17 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vite'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | test: { 7 | setupFiles: './src/setup.ts', 8 | clearMocks: true, 9 | mockReset: true, 10 | restoreMocks: true, 11 | coverage: { 12 | reporter: ['html', 'lcov', 'text'], 13 | }, 14 | deps: { 15 | inline: [/github-action-test-helper/, /github-action-helper/] 16 | }, 17 | }, 18 | }); 19 | --------------------------------------------------------------------------------