├── .commitlintrc ├── .editorconfig ├── .eslintrc ├── .github ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── 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-details.json ├── workflow-settings.json └── workflows │ ├── add-release-tag.yml │ ├── add-test-tag.yml │ ├── check-warnings.yml │ ├── ci.yml │ ├── issue-opened.yml │ ├── pr-opened.yml │ ├── pr-updated.yml │ ├── project-card-moved.yml │ ├── sync-workflows.yml │ ├── toc.yml │ └── update-dependencies.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .lintstagedrc ├── .releasegarc ├── LICENSE ├── README.ja.md ├── README.md ├── _config.yml ├── action.yml ├── package.json ├── rollup.config.mjs ├── src ├── constant.ts ├── fixtures │ ├── build1.json │ ├── build2.json │ ├── repos.listReleases.json │ ├── repos.updateRelease.json │ ├── test2 │ │ └── package.json │ ├── test3 │ │ └── package.json │ ├── test4 │ │ └── package.json │ ├── test5 │ │ └── package.json │ ├── test6 │ │ └── package.json │ ├── test7 │ │ └── package.json │ └── test8 │ │ └── package.json ├── index.test.ts ├── index.ts ├── main.ts ├── setup.ts ├── types.ts └── utils │ ├── command.ts │ ├── command1.test.ts │ ├── command2.test.ts │ ├── misc.test.ts │ └── misc.ts ├── tsconfig.json ├── vite.config.ts └── 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 | * @technote-space 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/technote-space/release-github-actions/issues 3 | [fork]: https://github.com/technote-space/release-github-actions/fork 4 | [pr]: https://github.com/technote-space/release-github-actions/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/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: https://paypal.me/technote0space -------------------------------------------------------------------------------- /.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: 'technote-space' 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: 'technote-space' 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-details.json: -------------------------------------------------------------------------------- 1 | { 2 | "TOC_FOOTER": "*generated with [TOC Generator](https://github.com/technote-space/toc-generator)*" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | 12 | name: Add release tag 13 | 14 | jobs: 15 | tag: 16 | name: Add release tag 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 3 19 | 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/') 20 | steps: 21 | - uses: technote-space/load-config-action@v1 22 | with: 23 | CONFIG_FILENAME: workflow-settings.json, workflow-details.json 24 | IGNORE_WARNING: 'true' 25 | - name: Get version 26 | uses: technote-space/get-next-version-action@v1 27 | with: 28 | EXCLUDE_MESSAGES: ${{ env.EXCLUDE_MESSAGES }} 29 | if: "! startsWith(github.head_ref, 'release/v')" 30 | - name: Get version 31 | run: echo "NEXT_VERSION=${HEAD_REF#release/}" >> $GITHUB_ENV 32 | env: 33 | HEAD_REF: ${{ github.head_ref }} 34 | if: startsWith(github.head_ref, 'release/v') 35 | - uses: actions/github-script@v3 36 | with: 37 | github-token: ${{ secrets.ACCESS_TOKEN }} 38 | script: | 39 | github.git.createRef({ 40 | owner: context.repo.owner, 41 | repo: context.repo.repo, 42 | ref: `refs/tags/${process.env.NEXT_VERSION}`, 43 | sha: context.sha 44 | }) 45 | if: env.NEXT_VERSION 46 | - uses: actions/github-script@v3 47 | with: 48 | github-token: ${{ secrets.ACCESS_TOKEN }} 49 | script: | 50 | github.git.createRef({ 51 | owner: context.repo.owner, 52 | repo: context.repo.repo, 53 | ref: `refs/heads/release/next-${process.env.NEXT_VERSION}`, 54 | sha: context.sha 55 | }) 56 | if: env.NEXT_VERSION 57 | -------------------------------------------------------------------------------- /.github/workflows/add-test-tag.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | types: [synchronize] 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | 8 | name: Add test tag 9 | 10 | jobs: 11 | tag: 12 | name: Add test tag 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 3 15 | if: github.event.pull_request.head.user.id == github.event.pull_request.base.user.id && startsWith(github.head_ref, 'release/') 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: actions/checkout@v3 22 | - uses: technote-space/get-git-comment-action@v1 23 | - name: Get version 24 | uses: technote-space/get-next-version-action@v1 25 | with: 26 | EXCLUDE_MESSAGES: ${{ env.EXCLUDE_MESSAGES }} 27 | if: "! startsWith(github.head_ref, 'release/v') && (contains(env.COMMIT_MESSAGE, 'chore: update dependencies') || contains(env.COMMIT_MESSAGE, 'chore: update npm dependencies'))" 28 | - name: Get version 29 | run: echo "NEXT_VERSION=${HEAD_REF#release/}" >> $GITHUB_ENV 30 | env: 31 | HEAD_REF: ${{ github.head_ref }} 32 | if: "startsWith(github.head_ref, 'release/v') && (contains(env.COMMIT_MESSAGE, 'chore: update dependencies') || contains(env.COMMIT_MESSAGE, 'chore: update npm dependencies'))" 33 | - name: Get tag name 34 | run: echo "TAG_NAME=${NEXT_VERSION}.${RUN_ID}" >> $GITHUB_ENV 35 | env: 36 | HEAD_REF: ${{ github.head_ref }} 37 | RUN_ID: ${{ github.run_id }} 38 | if: env.NEXT_VERSION 39 | - uses: actions/github-script@v3 40 | with: 41 | github-token: ${{ secrets.ACCESS_TOKEN }} 42 | script: | 43 | github.git.createRef({ 44 | owner: context.repo.owner, 45 | repo: context.repo.repo, 46 | ref: `refs/tags/test/${process.env.TAG_NAME}`, 47 | sha: context.payload.pull_request.head.sha 48 | }) 49 | if: env.TAG_NAME 50 | -------------------------------------------------------------------------------- /.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 | 8 | jobs: 9 | eslint: 10 | name: ESLint 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 5 13 | env: 14 | LINT: 1 15 | steps: 16 | - name: Set running flag 17 | run: echo "RUNNING=1" >> $GITHUB_ENV 18 | - uses: actions/checkout@v3 19 | - uses: technote-space/get-git-comment-action@v1 20 | - uses: technote-space/get-diff-action@v6 21 | with: 22 | PATTERNS: +(src|__tests__)/**/*.+(js|ts) 23 | FILES: | 24 | yarn.lock 25 | .eslintrc 26 | if: "! contains(env.COMMIT_MESSAGE, '[skip ci]') && ! contains(env.COMMIT_MESSAGE, '[ci skip]')" 27 | - name: Set running flag 28 | run: echo "RUNNING=" >> $GITHUB_ENV 29 | if: "! env.GIT_DIFF" 30 | 31 | - uses: actions/setup-node@v3 32 | with: 33 | node-version: 16 34 | cache: yarn 35 | if: env.RUNNING 36 | - name: Install Package dependencies 37 | run: yarn install 38 | if: env.RUNNING 39 | - name: Check code style 40 | run: yarn eslint ${{ env.GIT_DIFF_FILTERED }} 41 | if: env.RUNNING && !env.MATCHED_FILES 42 | - name: Check code style 43 | run: yarn lint 44 | if: env.RUNNING && env.MATCHED_FILES 45 | 46 | cover: 47 | name: Coverage 48 | needs: eslint 49 | runs-on: ${{matrix.os}} 50 | timeout-minutes: 10 51 | strategy: 52 | matrix: 53 | os: [ubuntu-20.04, ubuntu-22.04, ubuntu-latest, macos-latest] 54 | steps: 55 | - name: Set running flag 56 | run: echo "RUNNING=1" >> $GITHUB_ENV 57 | - uses: actions/checkout@v3 58 | - uses: technote-space/get-git-comment-action@v1 59 | - uses: technote-space/get-diff-action@v6 60 | with: 61 | PATTERNS: +(src|__tests__)/**/*.+(js|ts|snap) 62 | FILES: | 63 | yarn.lock 64 | jest.config.js 65 | vite.config.ts 66 | if: "! contains(env.COMMIT_MESSAGE, '[skip ci]') && ! contains(env.COMMIT_MESSAGE, '[ci skip]')" 67 | - name: Set running flag 68 | run: echo "RUNNING=" >> $GITHUB_ENV 69 | if: "! env.GIT_DIFF" 70 | - name: Set running flag 71 | if: "matrix.os == 'ubuntu-latest' && ! startsWith(github.ref, 'refs/tags/') && github.event.base_ref == format('refs/heads/{0}', github.event.repository.default_branch)" 72 | run: echo "RUNNING=1" >> $GITHUB_ENV 73 | - name: Set running flag 74 | if: "matrix.os == 'ubuntu-latest' && ! startsWith(github.ref, 'refs/tags/') && startsWith(github.base_ref, 'refs/heads/develop/v')" 75 | run: echo "RUNNING=1" >> $GITHUB_ENV 76 | - name: Set running flag 77 | if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/v') 78 | run: echo "RUNNING=1" >> $GITHUB_ENV 79 | - name: Set running flag 80 | run: | 81 | if [[ ! -f package.json ]] || ! < package.json jq -r '.scripts | keys[]' | grep -qe '^cover$'; then 82 | echo "RUNNING=" >> $GITHUB_ENV 83 | fi 84 | 85 | - uses: actions/setup-node@v3 86 | with: 87 | node-version: 16 88 | cache: yarn 89 | if: env.RUNNING 90 | - name: Install Package dependencies 91 | run: yarn install 92 | if: env.RUNNING 93 | - name: Run tests 94 | run: yarn cover 95 | if: env.RUNNING 96 | - name: Codecov 97 | run: | 98 | if [ -n "$CODECOV_TOKEN" ]; then 99 | curl -s https://codecov.io/bash | bash -s -- -t $CODECOV_TOKEN -f $COVERAGE_FILE 100 | fi 101 | env: 102 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 103 | COVERAGE_FILE: ./coverage/lcov.info 104 | if: env.RUNNING && matrix.os == 'ubuntu-latest' 105 | 106 | release: 107 | name: Release GitHub Actions 108 | needs: cover 109 | runs-on: ubuntu-latest 110 | timeout-minutes: 5 111 | if: startsWith(github.ref, 'refs/tags/') 112 | steps: 113 | - uses: actions/checkout@v3 114 | - uses: actions/setup-node@v3 115 | with: 116 | node-version: 16 117 | cache: yarn 118 | 119 | - uses: technote-space/load-config-action@v1 120 | with: 121 | CONFIG_FILENAME: workflow-settings.json, workflow-details.json 122 | IGNORE_WARNING: 'true' 123 | - name: Release GitHub Actions 124 | uses: technote-space/release-github-actions@gh-actions 125 | with: 126 | OUTPUT_BUILD_INFO_FILENAME: build.json 127 | TEST_TAG_PREFIX: test/ 128 | ORIGINAL_TAG_PREFIX: original/ 129 | CLEAN_TEST_TAG: true 130 | DELETE_NODE_MODULES: ${{ env.RELEASE_GA_DELETE_NODE_MODULES }} 131 | 132 | package: 133 | name: Publish Package 134 | needs: cover 135 | runs-on: ubuntu-latest 136 | timeout-minutes: 5 137 | if: startsWith(github.ref, 'refs/tags/v') 138 | strategy: 139 | matrix: 140 | target: ['npm', 'gpr'] 141 | steps: 142 | - name: Set running flag 143 | run: echo "RUNNING=1" >> $GITHUB_ENV 144 | - name: Set running flag 145 | run: | 146 | if [ -z "$NPM_AUTH_TOKEN" ]; then 147 | echo "RUNNING=" >> $GITHUB_ENV 148 | fi 149 | env: 150 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 151 | - uses: actions/checkout@v3 152 | if: env.RUNNING 153 | - name: Check package version 154 | uses: technote-space/package-version-check-action@v1 155 | with: 156 | COMMIT_DISABLED: 1 157 | if: env.RUNNING 158 | - name: Set running flag 159 | run: npx can-npm-publish || echo "RUNNING=" >> $GITHUB_ENV 160 | if: env.RUNNING && matrix.target == 'npm' 161 | - name: Set running flag 162 | run: | 163 | LATEST=`npm view . version` 2> /dev/null || : 164 | CURRENT=`cat package.json | jq -r .version` 165 | if [ "$LATEST" = "$CURRENT" ]; then 166 | echo "RUNNING=" >> $GITHUB_ENV 167 | fi 168 | env: 169 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 170 | if: env.RUNNING && matrix.target == 'gpr' 171 | 172 | - name: Setup Node.js 173 | uses: actions/setup-node@v3 174 | with: 175 | node-version: 16 176 | registry-url: https://registry.npmjs.org/ 177 | cache: yarn 178 | if: env.RUNNING && matrix.target == 'npm' 179 | - name: Setup Node.js 180 | uses: actions/setup-node@v3 181 | with: 182 | node-version: 16 183 | registry-url: https://npm.pkg.github.com 184 | cache: yarn 185 | if: env.RUNNING && matrix.target == 'gpr' 186 | - name: Install Package dependencies 187 | run: yarn install 188 | if: env.RUNNING 189 | - name: Build 190 | run: yarn build 191 | if: env.RUNNING 192 | - name: Publish 193 | run: | 194 | npm config set //registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN 195 | npm publish 196 | env: 197 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 198 | if: env.RUNNING && matrix.target == 'npm' 199 | - name: Publish 200 | run: | 201 | npm publish 202 | env: 203 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 204 | if: env.RUNNING && matrix.target == 'gpr' 205 | 206 | publishRelease: 207 | name: Create Release 208 | needs: [release, package] 209 | runs-on: ubuntu-latest 210 | timeout-minutes: 5 211 | steps: 212 | - name: Get version 213 | run: echo "TAG_NAME=${HEAD_REF#refs/tags/}" >> $GITHUB_ENV 214 | env: 215 | HEAD_REF: ${{ github.ref }} 216 | - name: Create Release 217 | id: drafter 218 | uses: technote-space/release-drafter@v6 219 | with: 220 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 221 | DRAFT: false 222 | NAME: ${{ env.TAG_NAME }} 223 | TAG: ${{ env.TAG_NAME }} 224 | - uses: 8398a7/action-slack@v3 225 | with: 226 | status: ${{ job.status }} 227 | text: ${{ format('<{0}>', steps.drafter.outputs.html_url) }} 228 | env: 229 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 230 | if: success() && env.SLACK_WEBHOOK_URL 231 | 232 | slack: 233 | name: Slack 234 | needs: publishRelease 235 | runs-on: ubuntu-latest 236 | timeout-minutes: 3 237 | if: always() 238 | steps: 239 | - uses: technote-space/workflow-conclusion-action@v3 240 | - uses: 8398a7/action-slack@v3 241 | with: 242 | status: failure 243 | env: 244 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 245 | if: env.WORKFLOW_CONCLUSION == 'failure' && env.SLACK_WEBHOOK_URL 246 | -------------------------------------------------------------------------------- /.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/sync-workflows.yml: -------------------------------------------------------------------------------- 1 | on: 2 | schedule: 3 | - cron: 0 15 * * 4 4 | repository_dispatch: 5 | types: [sync-workflows] 6 | workflow_dispatch: 7 | 8 | name: Sync workflows 9 | jobs: 10 | release: 11 | name: Sync workflows 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 5 14 | steps: 15 | - name: Sync workflows 16 | uses: technote-space/create-pr-action@v2 17 | with: 18 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 19 | EXECUTE_COMMANDS: | 20 | rm -rdf .github/workflows/.tmp 21 | mkdir -p .github/workflows/.tmp 22 | git clone --depth=1 https://github.com/technote-space/github-actions-workflows.git .github/workflows/.tmp/workflows 23 | 24 | bash .github/workflows/.tmp/workflows/gh-actions/copy.sh 25 | sed -i 's/cron:.\+$/cron: 0 3 * * 2,6/' .github/workflows/update-dependencies.yml 26 | 27 | rm -rdf .github/workflows/.tmp 28 | COMMIT_MESSAGE: 'chore: sync workflows' 29 | PR_BRANCH_PREFIX: chore/ 30 | PR_BRANCH_NAME: 'chore-sync-workflows' 31 | PR_TITLE: 'chore: sync workflows' 32 | ONLY_DEFAULT_BRANCH: true 33 | -------------------------------------------------------------------------------- /.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.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: 0 3 * * 2,6 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/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.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 | "*.{js,jsx,ts,tsx}": "yarn lint:fix" 3 | } -------------------------------------------------------------------------------- /.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 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Technote 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.ja.md: -------------------------------------------------------------------------------- 1 | # Release GitHub Actions 2 | 3 | [![CI Status](https://github.com/technote-space/release-github-actions/workflows/CI/badge.svg)](https://github.com/technote-space/release-github-actions/actions) 4 | [![codecov](https://codecov.io/gh/technote-space/release-github-actions/branch/main/graph/badge.svg)](https://codecov.io/gh/technote-space/release-github-actions) 5 | [![CodeFactor](https://www.codefactor.io/repository/github/technote-space/release-github-actions/badge)](https://www.codefactor.io/repository/github/technote-space/release-github-actions) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/technote-space/release-github-actions/blob/main/LICENSE) 7 | 8 | *Read this in other languages: [English](README.md), [日本語](README.ja.md).* 9 | 10 | これは `GitHub Actions` のリリースを自動化するための `GitHub Actions` です。 11 | タグを作成するとこのアクションは自動で以下を行います。 12 | 1. ビルド実行 13 | 1. リリース用ブランチ作成 14 | 1. リリース用ブランチに[タグ](#tags)を張り替え 15 | 1. 同じタグ名 かつ 公開済みのリリースが存在する場合、再度公開 (タグを張り替えた場合、リリースが下書き状態になるため) 16 | 17 | ## Table of Contents 18 | 19 | 20 | 21 |
22 | Details 23 | 24 | - [使用方法](#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95) 25 | - [CLI ツール](#cli-%E3%83%84%E3%83%BC%E3%83%AB) 26 | - [スクリーンショット](#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88) 27 | - [オプション](#%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3) 28 | - [Execute commands](#execute-commands) 29 | - [ビルド](#%E3%83%93%E3%83%AB%E3%83%89) 30 | - [ファイル削除](#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%89%8A%E9%99%A4) 31 | - [Action イベント詳細](#action-%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E8%A9%B3%E7%B4%B0) 32 | - [対象イベント](#%E5%AF%BE%E8%B1%A1%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88) 33 | - [condition](#condition) 34 | - [動機](#%E5%8B%95%E6%A9%9F) 35 | - [補足](#%E8%A3%9C%E8%B6%B3) 36 | - [Tags](#tags) 37 | - [Author](#author) 38 | 39 | *generated with [TOC Generator](https://github.com/technote-space/toc-generator)* 40 | 41 |
42 | 43 | 44 | ## 使用方法 45 | 例:`.github/workflows/release.yml` 46 | ```yaml 47 | #on: 48 | # push: 49 | # tags: 50 | # - "v*" 51 | 52 | on: create 53 | 54 | name: Release 55 | jobs: 56 | release: 57 | name: Release GitHub Actions 58 | runs-on: ubuntu-latest 59 | steps: 60 | - uses: technote-space/release-github-actions@v6 61 | ``` 62 | 63 | [対象イベントの詳細](#action-%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E8%A9%B3%E7%B4%B0) 64 | 65 | ## CLI ツール 66 | [![technote-space/release-github-actions-cli - GitHub](https://gh-card.dev/repos/technote-space/release-github-actions-cli.svg)](https://github.com/technote-space/release-github-actions-cli) 67 | 68 | ## スクリーンショット 69 | ![Release](https://raw.githubusercontent.com/technote-space/release-github-actions/images/screenshot-7.gif) 70 | 71 | ## オプション 72 | | name | description | default | required | e.g. | 73 | |:---:|:---|:---:|:---:|:---:| 74 | | BUILD_COMMAND | ビルド用コマンド
[コマンドの詳細](#execute-commands) | | | `yarn build:all` | 75 | | CLEAN_TARGETS | リリース前に掃除するファイルやディレクトリ (カンマ区切り)
絶対パスや `..` は使用できません
[コマンドの詳細](#execute-commands) |`.[!.]*,__tests__,docs,src,*.[jt]s,*.[mc][jt]s,*.json,*.lock,*.yml,*.yaml` | true | `.[!.]*,*.txt` | 76 | | PACKAGE_MANAGER | 依存関係のインストールに使用するパッケージマネージャー
`yarn.lock` や `package-lock.json` がある場合は自動で使用するパッケージマネージャーを決定しますが、このオプションで強制することができます
(`npm` または `yarn`) | | | `yarn` | 77 | | COMMIT_MESSAGE | コミット時に設定するメッセージ | `feat: build for release` | true | `feat: release` | 78 | | COMMIT_NAME | コミット時に設定する名前 | `github-actions[bot]` | true | | 79 | | COMMIT_EMAIL | コミット時に設定する名前 | `41898282+github-actions[bot]@users.noreply.github.com` | true | | 80 | | BRANCH_NAME | GitHub Actions 用のブランチ名 | `gh-actions` | true | `gh-actions/${MAJOR}/${MINOR}/${PATCH}` | 81 | | BUILD_COMMAND_TARGET | ビルド用コマンド検索ターゲット | `prepare, build, production, prod, package, pack` | | `compile` | 82 | | ALLOW_MULTIPLE_BUILD_COMMANDS | 複数のビルドコマンド実行を許可するかどうか | `true` | | `false` | 83 | | CREATE_MAJOR_VERSION_TAG | メジャーバージョンタグ(例:v1)を作成するかどうか
[タグの詳細](#tags) | `true` | | `false` | 84 | | CREATE_MINOR_VERSION_TAG | マイナーバージョンタグ(例:v1.2)を作成するかどうか
[タグの詳細](#tags) | `true` | | `false` | 85 | | CREATE_PATCH_VERSION_TAG | パッチバージョンタグ(例:v1.2.3)を作成するかどうか
[タグの詳細](#tags) | `true` | | `false` | 86 | | FETCH_DEPTH | 取得するコミット履歴の制限数 | `3` | | `5` | 87 | | TEST_TAG_PREFIX | テスト用タグのプリフィックス | | | `test/` | 88 | | CLEAN_TEST_TAG | テストタグを掃除するかどうか | `false` | | `true` | 89 | | ORIGINAL_TAG_PREFIX | 元のタグを残す際に付与するプリフィックス | | | `original/` | 90 | | DELETE_NODE_MODULES | node_modules を削除するかどうか | `false` | | `true` | 91 | | GITHUB_TOKEN | アクセストークン | `${{github.token}}` | true | `${{secrets.ACCESS_TOKEN}}` | 92 | 93 | ## Execute commands 94 | ### ビルド 95 | - `prepare`、 `build`、 `production`、 `prod`、 `package` または `pack` が package.json の scripts に含まれる場合、ビルド用のコマンドとしてそれらを使用します。([BUILD_COMMAND_TARGET](#%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3) で変更可能です) 96 | - `npm run install` や `yarn install` のようなインストール用コマンドが存在しない場合、インストール用コマンドが追加されます。 97 | 98 | したがって、`BUILD_COMMAND` が設定されていない かつ package.json に `build` が存在する場合、以下のコマンドが実行されます。 99 | 100 | ```shell 101 | yarn install 102 | yarn build 103 | yarn install --production 104 | ``` 105 | 106 | `build` と `pack` が含まれる場合は、以下のコマンドになります。 107 | 108 | ```shell 109 | yarn install 110 | yarn build 111 | yarn pack 112 | yarn install --production 113 | ``` 114 | 115 | ### ファイル削除 116 | `GitHub Actions` の実行には「ビルドに使用するソース」や「テストファイル」、「テストの設定」などを必要としません。 117 | そして `GitHub Actions` は使用されるたびにダウンロードされるため、ファイルは少ないほうが良いです。 118 | 119 | `CLEAN_TARGETS` オプションはこの目的のために使用されます。 120 | default: `.[!.]*,__tests__,docs,src,*.[jt]s,*.[mc][jt]s,*.json,*.lock,*.yml,*.yaml` 121 | 122 | ```shell 123 | rm -rdf .[!.]* 124 | rm -rdf *.js 125 | rm -rdf *.mjs 126 | rm -rdf *.ts 127 | rm -rdf *.cts 128 | rm -rdf *.json 129 | rm -rdf *.lock 130 | rm -rdf *.yml 131 | rm -rdf *.yaml 132 | rm -rdf __tests__ docs src 133 | ``` 134 | 135 | (action.yml は削除の対象ではありません) 136 | 137 | ## Action イベント詳細 138 | ### 対象イベント 139 | | eventName: action | condition | 140 | |:---:|:---:| 141 | |push: *|[condition](#condition)| 142 | |release: published|[condition](#condition)| 143 | |create: *|[condition](#condition)| 144 | 145 | ### condition 146 | - tags 147 | - semantic versioning tag (例:`v1.2.3`) 148 | - [テストタグ](#%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3) (例:`test/v1.2.3`) 149 | 150 | ## 動機 151 | `GitHub Actions`をリリースするには、すべてのビルドファイルと `node_modules` のような依存関係が必要ですが、通常はそれらをコミットしません。 152 | したがって`GitHub Actions`リリースする際には以下のような手順が必要です。 153 | 1. ローカルの開発用ブランチで開発 154 | 1. リリース用にビルド 155 | 1. `node_modules` のような依存モジュールを含めて必要なソースをリリース用ブランチにコミット 156 | 1. タグを付与 (メジャー、マイナー、パッチバージョンの考慮が必要) 157 | 1. GitHub にプッシュ 158 | 1. リリースを作成 159 | 160 | リリースの度にこれらの手順を実行するのはとても面倒です。 161 | 162 | この `GitHub Actions` を使用することで手順は単純になります。 163 | 1. ローカルの開発用ブランチで開発 164 | 1. リリースを作成 (タグを作成) 165 | 1. 自動化された手順が完了するのを待つ 166 | 1. リリース用にビルド 167 | 1. `node_modules` のような依存モジュールを含めて必要なソースをリリース用ブランチにコミット 168 | 1. タグを付与 (メジャー、マイナー、パッチバージョンの考慮が必要) 169 | 1. GitHub にプッシュ 170 | 171 | ## 補足 172 | ### Tags 173 | タグ名は [Semantic Versioning](https://semver.org/) に従っている必要があります。 174 | 以下のタグが作成されます。 175 | - 指定されたタグ名 176 | - メジャーバージョンのタグ名 (指定されたタグ名から生成) 177 | - 例:`v1` 178 | - マイナーバージョンのタグ名 (指定されたタグ名から生成) 179 | - 例:`v1.2` 180 | - パッチバージョンのタグ名 (指定されたタグ名から生成) 181 | - 例:`v1.2.3` 182 | 183 | ## Author 184 | [GitHub (Technote)](https://github.com/technote-space) 185 | [Blog](https://technote.space) 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Release GitHub Actions 2 | 3 | [![CI Status](https://github.com/technote-space/release-github-actions/workflows/CI/badge.svg)](https://github.com/technote-space/release-github-actions/actions) 4 | [![codecov](https://codecov.io/gh/technote-space/release-github-actions/branch/main/graph/badge.svg)](https://codecov.io/gh/technote-space/release-github-actions) 5 | [![CodeFactor](https://www.codefactor.io/repository/github/technote-space/release-github-actions/badge)](https://www.codefactor.io/repository/github/technote-space/release-github-actions) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/technote-space/release-github-actions/blob/main/LICENSE) 7 | 8 | *Read this in other languages: [English](README.md), [日本語](README.ja.md).* 9 | 10 | This is a `GitHub Actions` that automates the release of `GitHub Actions`. 11 | Once you create a new tag, this action will automatically 12 | 1. Run build 13 | 1. Create branch for release 14 | 1. Change [tags](#tags) to release branch 15 | 1. If there is release which has same tag name and has been published, re-publish it (Because if the tag is changed, the release will be in a draft state). 16 | 17 | ## Table of Contents 18 | 19 | 20 | 21 |
22 | Details 23 | 24 | - [Usage](#usage) 25 | - [CLI Tool](#cli-tool) 26 | - [Screenshot](#screenshot) 27 | - [Options](#options) 28 | - [Execute commands](#execute-commands) 29 | - [Build](#build) 30 | - [Delete files](#delete-files) 31 | - [Action event details](#action-event-details) 32 | - [Target events](#target-events) 33 | - [condition](#condition) 34 | - [Motivation](#motivation) 35 | - [Addition](#addition) 36 | - [Tags](#tags) 37 | - [Author](#author) 38 | 39 | *generated with [TOC Generator](https://github.com/technote-space/toc-generator)* 40 | 41 |
42 | 43 | 44 | ## Usage 45 | e.g. `.github/workflows/release.yml` 46 | ```yaml 47 | #on: 48 | # push: 49 | # tags: 50 | # - "v*" 51 | 52 | on: create 53 | 54 | name: Release 55 | jobs: 56 | release: 57 | name: Release GitHub Actions 58 | runs-on: ubuntu-latest 59 | steps: 60 | - uses: technote-space/release-github-actions@v6 61 | ``` 62 | 63 | [More details of target event](#action-event-details) 64 | 65 | ## CLI Tool 66 | [![technote-space/release-github-actions-cli - GitHub](https://gh-card.dev/repos/technote-space/release-github-actions-cli.svg)](https://github.com/technote-space/release-github-actions-cli) 67 | 68 | ## Screenshot 69 | ![Release](https://raw.githubusercontent.com/technote-space/release-github-actions/images/screenshot-7.gif) 70 | 71 | ## Options 72 | | name | description | default | required | e.g. | 73 | |:---:|:---|:---:|:---:|:---:| 74 | | BUILD_COMMAND | Build command
[More details of execute command](#execute-commands) | | | `yarn build:all` | 75 | | CLEAN_TARGETS | Files or directories to clean before release (Comma separated)
Absolute path and `..` are not permitted to use.
[More details of execute command](#execute-commands) | `.[!.]*,__tests__,docs,src,*.[jt]s,*.[mc][jt]s,*.json,*.lock,*.yml,*.yaml` | true | `.[!.]*,*.txt` | 76 | | PACKAGE_MANAGER | Package manager to use to install dependencies
If there is `yarn.lock` or` package-lock.json`, the action automatically determines the package manager to use, but this option can be used to specify it explicitly.
(`npm` or `yarn`) | | | `yarn` | 77 | | COMMIT_MESSAGE | Commit message | `feat: build for release` | true | `feat: release` | 78 | | COMMIT_NAME | Commit name | `github-actions[bot]` | true | | 79 | | COMMIT_EMAIL | Commit email | `41898282+github-actions[bot]@users.noreply.github.com` | true | | 80 | | BRANCH_NAME | Branch name for `GitHub Actions` release | `gh-actions` | true | `gh-actions/${MAJOR}/${MINOR}/${PATCH}` | 81 | | BUILD_COMMAND_TARGET | Command for search build command | `prepare, build, production, prod, package, pack` | | `compile` | 82 | | ALLOW_MULTIPLE_BUILD_COMMANDS | Whether to allow run multiple build commands. | `true` | | `false` | 83 | | CREATE_MAJOR_VERSION_TAG | Whether to create major version tag (e.g. v1)
[Detail of tags](#tags) | `true` | | `false` | 84 | | CREATE_MINOR_VERSION_TAG | Whether to create minor version tag (e.g. v1.2)
[Detail of tags](#tags) | `true` | | `false` | 85 | | CREATE_PATCH_VERSION_TAG | Whether to create patch version tag (e.g. v1.2.3)
[Detail of tags](#tags) | `true` | | `false` | 86 | | FETCH_DEPTH | Limit fetching to the specified number of commits from the tip of each remote branch history | `3` | | `5` | 87 | | TEST_TAG_PREFIX | Prefix for test tag | | | `test/` | 88 | | CLEAN_TEST_TAG | Whether to clean test tag | `false` | | `true` | 89 | | ORIGINAL_TAG_PREFIX | Prefix to add when leaving the original tag | | | `original/` | 90 | | DELETE_NODE_MODULES | Whether to delete node_modules | `false` | | `true` | 91 | | GITHUB_TOKEN | Access token | `${{github.token}}` | true | `${{secrets.ACCESS_TOKEN}}` | 92 | 93 | ## Execute commands 94 | ### Build 95 | - If package.json includes `prepare`, `build`, `production`, `prod`, `package` or `pack` in scripts, the commands are used for build. (You can change this with [BUILD_COMMAND_TARGET](#options)) 96 | - If command does not have install command like `npm run install` or `yarn install`, install commands are added. 97 | 98 | so if `BUILD_COMMAND` is not provided and package.json has `build` script, 99 | the following commands are executed for build. 100 | 101 | ```shell 102 | yarn install 103 | yarn build 104 | yarn install --production 105 | ``` 106 | 107 | If `build` and `pack` are included, the commands are: 108 | 109 | ```shell 110 | yarn install 111 | yarn build 112 | yarn pack 113 | yarn install --production 114 | ``` 115 | 116 | ### Delete files 117 | To execute `GitHub Actions`, `src files used for build`, `test files`, `test settings`, etc. are not required. 118 | And `GitHub Actions` is downloaded every time when it is used, so fewer files are better. 119 | 120 | `CLEAN_TARGETS` option is used for this purpose. 121 | default: `.[!.]*,__tests__,docs,src,*.[jt]s,*.[mc][jt]s,*.json,*.lock,*.yml,*.yaml` 122 | 123 | ```shell 124 | rm -rdf .[!.]* 125 | rm -rdf *.js 126 | rm -rdf *.mjs 127 | rm -rdf *.ts 128 | rm -rdf *.cts 129 | rm -rdf *.json 130 | rm -rdf *.lock 131 | rm -rdf *.yml 132 | rm -rdf *.yaml 133 | rm -rdf __tests__ docs src 134 | ``` 135 | 136 | (action.yml is not subject to deletion.) 137 | 138 | ## Action event details 139 | ### Target events 140 | | eventName: action | condition | 141 | |:---:|:---:| 142 | |push: *|[condition](#condition)| 143 | |release: published|[condition](#condition)| 144 | |create: *|[condition](#condition)| 145 | 146 | ### condition 147 | - tags 148 | - semantic versioning tag (e.g. `v1.2.3`) 149 | - [test tag](#options) (e.g. `test/v1.2.3`) 150 | 151 | ## Motivation 152 | Releasing `GitHub Actions` needs all build files and dependencies like `node_modules`, but are not usually committed. 153 | So if you want to release `GitHub Actions`, you have to do following steps. 154 | 1. Develop locally on the branch for develop 155 | 1. Build for release 156 | 1. Commit all source code including dependencies like `node_modules` to branch for release 157 | 1. Add tags (consider major, minor and patch versions) 158 | 1. Push to GitHub 159 | 1. Publish release 160 | 161 | It is very troublesome to do this steps for every release. 162 | 163 | If you use this `GitHub Actions`, the steps to do are simpler. 164 | 1. Develop locally on the branch for develop 165 | 1. Publish release (Create tag) 166 | 1. Wait for the automated steps to finish 167 | 1. Build for release 168 | 1. Commit all source code including dependencies like `node_modules` to branch for release 169 | 1. Add tags (consider major, minor and patch versions) 170 | 1. Push to GitHub 171 | 172 | ## Addition 173 | ### Tags 174 | Tag name format must be [Semantic Versioning](https://semver.org/). 175 | The following tags will be created. 176 | - tag name 177 | - major tag name (generated by tag name) 178 | - e.g. `v1` 179 | - minor tag name (generated by tag name) 180 | - e.g. `v1.2` 181 | - patch tag name (generated by tag name) 182 | - e.g. `v1.2.3` 183 | 184 | ## Author 185 | [GitHub (Technote)](https://github.com/technote-space) 186 | [Blog](https://technote.space) 187 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect 2 | 3 | # Google Analytics 4 | google_analytics: UA-78163306-3 -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Release GitHub Actions 2 | 3 | description: This is a GitHub Actions that automates the release of GitHub Actions. 4 | 5 | author: technote-space 6 | 7 | inputs: 8 | GITHUB_TOKEN: 9 | description: Secret GitHub API token to use for making API requests. 10 | default: ${{ github.token }} 11 | required: true 12 | PACKAGE_MANAGER: 13 | description: Package manager to use to install dependencies. 14 | default: '' 15 | required: false 16 | BUILD_COMMAND: 17 | description: Commands to build. 18 | default: '' 19 | required: false 20 | COMMIT_MESSAGE: 21 | description: Commit message of build commit. 22 | default: 'feat: build for release' 23 | required: true 24 | COMMIT_NAME: 25 | description: Git commit name. 26 | default: 'github-actions[bot]' 27 | required: true 28 | COMMIT_EMAIL: 29 | description: Git commit email. 30 | default: '41898282+github-actions[bot]@users.noreply.github.com' 31 | required: true 32 | BRANCH_NAME: 33 | description: Branch name. 34 | default: 'gh-actions' 35 | required: true 36 | CLEAN_TARGETS: 37 | description: Files or directories to clean before release. (Comma separated) 38 | default: '.[!.]*,__tests__,docs,src,*.[jt]s,*.[mc][jt]s,*.json,*.lock,*.yml,*.yaml' 39 | required: true 40 | BUILD_COMMAND_TARGET: 41 | description: Command for search build command. 42 | default: 'prepare,build,production,prod,package,pack' 43 | required: true 44 | ALLOW_MULTIPLE_BUILD_COMMANDS: 45 | description: Whether to allow run multiple build commands. 46 | default: 'true' 47 | required: false 48 | CREATE_MAJOR_VERSION_TAG: 49 | description: Whether to create major version tag. 50 | default: 'true' 51 | required: false 52 | CREATE_MINOR_VERSION_TAG: 53 | description: Whether to create minor version tag. 54 | default: 'true' 55 | required: false 56 | CREATE_PATCH_VERSION_TAG: 57 | description: Whether to create patch version tag. 58 | default: 'true' 59 | required: false 60 | OUTPUT_BUILD_INFO_FILENAME: 61 | description: Filename of build information. 62 | default: '' 63 | required: false 64 | FETCH_DEPTH: 65 | description: Limit fetching to the specified number of commits from the tip of each remote branch history. 66 | default: '3' 67 | required: false 68 | TEST_TAG_PREFIX: 69 | description: Tag name prefix for test. 70 | default: '' 71 | required: false 72 | CLEAN_TEST_TAG: 73 | description: Whether to clean test tag. 74 | default: 'false' 75 | required: false 76 | ORIGINAL_TAG_PREFIX: 77 | description: Tag name prefix for original tag. 78 | default: '' 79 | required: false 80 | DELETE_NODE_MODULES: 81 | description: Whether to delete node_modules. 82 | default: 'false' 83 | required: false 84 | 85 | branding: 86 | icon: 'tag' 87 | color: 'green' 88 | 89 | runs: 90 | using: node16 91 | main: lib/main.js 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@technote-space/release-github-actions", 3 | "version": "8.0.3", 4 | "description": "GitHub actions to auto release.", 5 | "keywords": [ 6 | "github", 7 | "github actions", 8 | "release" 9 | ], 10 | "homepage": "https://github.com/technote-space/release-github-actions", 11 | "bugs": { 12 | "url": "https://github.com/technote-space/release-github-actions/issues" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/technote-space/release-github-actions.git" 17 | }, 18 | "license": "MIT", 19 | "author": { 20 | "name": "Technote", 21 | "email": "technote.space@gmail.com", 22 | "url": "https://technote.space" 23 | }, 24 | "type": "module", 25 | "exports": { 26 | "import": "./lib/index.mjs", 27 | "require": "./lib/index.cjs" 28 | }, 29 | "main": "lib/index.mjs", 30 | "types": "lib/index.d.ts", 31 | "files": [ 32 | "lib", 33 | "action.yml" 34 | ], 35 | "scripts": { 36 | "build": "tsc --emitDeclarationOnly && rollup -c", 37 | "cover": "vitest run --coverage", 38 | "postinstall": "[ -n \"$CI\" ] || [ ! -f node_modules/.bin/husky ] || husky install", 39 | "lint": "eslint 'src/**/*.ts' --cache", 40 | "lint:fix": "eslint --fix 'src/**/*.ts'", 41 | "prepublishOnly": "[ -n \"$CI\" ] || [ ! -f node_modules/.bin/pinst ] || pinst --disable", 42 | "postpublish": "[ -n \"$CI\" ] || [ ! -f node_modules/.bin/pinst ] || pinst --enable", 43 | "release": "npm_config_yes=true npx @technote-space/release-github-actions-cli --test", 44 | "test": "yarn lint && yarn typecheck && yarn cover", 45 | "typecheck": "tsc --noEmit", 46 | "update": "npm_config_yes=true npx npm-check-updates -u --timeout 100000 && yarn install && yarn upgrade && yarn audit" 47 | }, 48 | "devDependencies": { 49 | "@actions/core": "^1.10.0", 50 | "@actions/github": "^5.1.1", 51 | "@commitlint/cli": "^17.4.2", 52 | "@commitlint/config-conventional": "^17.4.2", 53 | "@rollup/plugin-commonjs": "^24.0.1", 54 | "@rollup/plugin-json": "^6.0.0", 55 | "@rollup/plugin-node-resolve": "^15.0.1", 56 | "@rollup/plugin-typescript": "^11.0.0", 57 | "@sindresorhus/tsconfig": "^3.0.1", 58 | "@technote-space/filter-github-action": "^0.6.7", 59 | "@technote-space/github-action-helper": "^5.3.10", 60 | "@technote-space/github-action-log-helper": "^0.2.10", 61 | "@technote-space/github-action-test-helper": "^0.11.2", 62 | "@types/node": "^18.13.0", 63 | "@typescript-eslint/eslint-plugin": "^5.51.0", 64 | "@typescript-eslint/parser": "^5.51.0", 65 | "@vitest/coverage-c8": "^0.28.4", 66 | "eslint": "^8.34.0", 67 | "eslint-plugin-import": "^2.27.5", 68 | "husky": "^8.0.3", 69 | "lint-staged": "^13.1.1", 70 | "memize": "^1.1.0", 71 | "nock": "^13.3.0", 72 | "pinst": "^3.0.0", 73 | "rollup": "^3.15.0", 74 | "typescript": "^4.9.5", 75 | "vitest": "^0.28.4" 76 | }, 77 | "publishConfig": { 78 | "access": "public" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /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 | const common = { 7 | plugins: [ 8 | pluginTypescript(), 9 | pluginNodeResolve(), 10 | pluginCommonjs(), 11 | pluginJson(), 12 | ], 13 | }; 14 | 15 | export default [ 16 | { 17 | ...common, 18 | input: 'src/main.ts', 19 | output: { 20 | file: 'lib/main.js', 21 | format: 'cjs', 22 | }, 23 | }, 24 | { 25 | ...common, 26 | input: 'src/index.ts', 27 | output: { 28 | file: 'lib/index.cjs', 29 | format: 'cjs', 30 | }, 31 | }, 32 | { 33 | ...common, 34 | input: 'src/index.ts', 35 | output: { 36 | file: 'lib/index.mjs', 37 | format: 'es', 38 | }, 39 | }, 40 | ]; 41 | -------------------------------------------------------------------------------- /src/constant.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from '@actions/github/lib/context'; 2 | import { isValidContext } from './utils/misc'; 3 | 4 | export const DEFAULT_FETCH_DEPTH = 3; 5 | export const TARGET_EVENTS = { 6 | 'create': [ 7 | (context: Context): boolean => isValidContext(context), 8 | ], 9 | 'release': [ 10 | [ 11 | 'published', 12 | (context: Context): boolean => isValidContext(context), 13 | ], 14 | ], 15 | 'push': [ 16 | (context: Context): boolean => isValidContext(context), 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /src/fixtures/build1.json: -------------------------------------------------------------------------------- 1 | { 2 | "tagName": "v1.2.3", 3 | "branch": "gh-actions", 4 | "tags": [ 5 | "v1.2.3", 6 | "v1", 7 | "v1.2" 8 | ], 9 | "updated_at": "2020-01-01T01:23:45.000Z" 10 | } -------------------------------------------------------------------------------- /src/fixtures/build2.json: -------------------------------------------------------------------------------- 1 | { 2 | "branch": "gh-actions", 3 | "tags": [ 4 | "v1.2.3", 5 | "v1", 6 | "v1.2" 7 | ], 8 | "updated_at": "2020-01-01T01:23:45.000Z" 9 | } -------------------------------------------------------------------------------- /src/fixtures/repos.listReleases.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "url": "https://api.github.com/repos/octocat/Hello-World/releases/1", 4 | "html_url": "https://github.com/octocat/Hello-World/releases/v1.0.0", 5 | "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/1/assets", 6 | "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}", 7 | "tarball_url": "https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.0", 8 | "zipball_url": "https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.0", 9 | "id": 1, 10 | "node_id": "MDc6UmVsZWFzZTE=", 11 | "tag_name": "v1.2.3", 12 | "target_commitish": "master", 13 | "name": "v1.0.0", 14 | "body": "Description of the release", 15 | "draft": false, 16 | "prerelease": false, 17 | "created_at": "2013-02-27T19:35:32Z", 18 | "published_at": "2013-02-27T19:35:32Z", 19 | "author": { 20 | "login": "octocat", 21 | "id": 1, 22 | "node_id": "MDQ6VXNlcjE=", 23 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 24 | "gravatar_id": "", 25 | "url": "https://api.github.com/users/octocat", 26 | "html_url": "https://github.com/octocat", 27 | "followers_url": "https://api.github.com/users/octocat/followers", 28 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 29 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 30 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 31 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 32 | "organizations_url": "https://api.github.com/users/octocat/orgs", 33 | "repos_url": "https://api.github.com/users/octocat/repos", 34 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 35 | "received_events_url": "https://api.github.com/users/octocat/received_events", 36 | "type": "User", 37 | "site_admin": false 38 | }, 39 | "assets": [ 40 | { 41 | "url": "https://api.github.com/repos/octocat/Hello-World/releases/assets/1", 42 | "browser_download_url": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/example.zip", 43 | "id": 1, 44 | "node_id": "MDEyOlJlbGVhc2VBc3NldDE=", 45 | "name": "example.zip", 46 | "label": "short description", 47 | "state": "uploaded", 48 | "content_type": "application/zip", 49 | "size": 1024, 50 | "download_count": 42, 51 | "created_at": "2013-02-27T19:35:32Z", 52 | "updated_at": "2013-02-27T19:35:32Z", 53 | "uploader": { 54 | "login": "octocat", 55 | "id": 1, 56 | "node_id": "MDQ6VXNlcjE=", 57 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 58 | "gravatar_id": "", 59 | "url": "https://api.github.com/users/octocat", 60 | "html_url": "https://github.com/octocat", 61 | "followers_url": "https://api.github.com/users/octocat/followers", 62 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 63 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 64 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 65 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 66 | "organizations_url": "https://api.github.com/users/octocat/orgs", 67 | "repos_url": "https://api.github.com/users/octocat/repos", 68 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 69 | "received_events_url": "https://api.github.com/users/octocat/received_events", 70 | "type": "User", 71 | "site_admin": false 72 | } 73 | } 74 | ] 75 | } 76 | ] 77 | -------------------------------------------------------------------------------- /src/fixtures/repos.updateRelease.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://api.github.com/repos/octocat/Hello-World/releases/1", 3 | "html_url": "https://github.com/octocat/Hello-World/releases/v1.0.0", 4 | "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/1/assets", 5 | "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}", 6 | "tarball_url": "https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.0", 7 | "zipball_url": "https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.0", 8 | "id": 1, 9 | "node_id": "MDc6UmVsZWFzZTE=", 10 | "tag_name": "v1.0.0", 11 | "target_commitish": "master", 12 | "name": "v1.0.0", 13 | "body": "Description of the release", 14 | "draft": false, 15 | "prerelease": false, 16 | "created_at": "2013-02-27T19:35:32Z", 17 | "published_at": "2013-02-27T19:35:32Z", 18 | "author": { 19 | "login": "octocat", 20 | "id": 1, 21 | "node_id": "MDQ6VXNlcjE=", 22 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 23 | "gravatar_id": "", 24 | "url": "https://api.github.com/users/octocat", 25 | "html_url": "https://github.com/octocat", 26 | "followers_url": "https://api.github.com/users/octocat/followers", 27 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 28 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 29 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 30 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 31 | "organizations_url": "https://api.github.com/users/octocat/orgs", 32 | "repos_url": "https://api.github.com/users/octocat/repos", 33 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 34 | "received_events_url": "https://api.github.com/users/octocat/received_events", 35 | "type": "User", 36 | "site_admin": false 37 | }, 38 | "assets": [ 39 | { 40 | "url": "https://api.github.com/repos/octocat/Hello-World/releases/assets/1", 41 | "browser_download_url": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/example.zip", 42 | "id": 1, 43 | "node_id": "MDEyOlJlbGVhc2VBc3NldDE=", 44 | "name": "example.zip", 45 | "label": "short description", 46 | "state": "uploaded", 47 | "content_type": "application/zip", 48 | "size": 1024, 49 | "download_count": 42, 50 | "created_at": "2013-02-27T19:35:32Z", 51 | "updated_at": "2013-02-27T19:35:32Z", 52 | "uploader": { 53 | "login": "octocat", 54 | "id": 1, 55 | "node_id": "MDQ6VXNlcjE=", 56 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 57 | "gravatar_id": "", 58 | "url": "https://api.github.com/users/octocat", 59 | "html_url": "https://github.com/octocat", 60 | "followers_url": "https://api.github.com/users/octocat/followers", 61 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 62 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 63 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 64 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 65 | "organizations_url": "https://api.github.com/users/octocat/orgs", 66 | "repos_url": "https://api.github.com/users/octocat/repos", 67 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 68 | "received_events_url": "https://api.github.com/users/octocat/received_events", 69 | "type": "User", 70 | "site_admin": false 71 | } 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /src/fixtures/test2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.1" 4 | } 5 | -------------------------------------------------------------------------------- /src/fixtures/test3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "test1": "test1", 6 | "test2": "test2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/fixtures/test4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "prepare": "test5", 6 | "production": "test2", 7 | "prod": "test3", 8 | "package": "test4", 9 | "build": "test1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/fixtures/test5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "production": "test2", 6 | "prod": "test3", 7 | "package": "test4" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/fixtures/test6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "prod": "test3", 6 | "package": "test4" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/fixtures/test7/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "package": "test4" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/fixtures/test8/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "build": "tsc && yarn ncc build lib/main.js && rm -rf lib" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest'; 2 | import { Command, Misc, Types } from '.'; 3 | 4 | it('helpers can be imported', () => { 5 | expect(Misc).not.toBeFalsy(); 6 | expect(Command).not.toBeFalsy(); 7 | expect(Types).not.toBeFalsy(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * as Command from './utils/command'; 2 | export * as Misc from './utils/misc'; 3 | export * as Types from './types'; 4 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { setFailed } from '@actions/core'; 3 | import { Context } from '@actions/github/lib/context'; 4 | import { isTargetEvent } from '@technote-space/filter-github-action'; 5 | import { ContextHelper, Utils } from '@technote-space/github-action-helper'; 6 | import { Logger } from '@technote-space/github-action-log-helper'; 7 | import { TARGET_EVENTS } from './constant'; 8 | import { deploy } from './utils/command'; 9 | 10 | const run = async(): Promise => { 11 | const logger = new Logger(); 12 | const context = new Context(); 13 | ContextHelper.showActionInfo(resolve(__dirname, '..'), logger, context); 14 | 15 | if (!isTargetEvent(TARGET_EVENTS, context)) { 16 | logger.info('This is not target event.'); 17 | return; 18 | } 19 | 20 | await deploy(Utils.getOctokit(), context); 21 | }; 22 | 23 | run().catch(error => { 24 | console.log(error); 25 | setFailed(error.message); 26 | }); 27 | -------------------------------------------------------------------------------- /src/setup.ts: -------------------------------------------------------------------------------- 1 | import { setupGlobal } from '@technote-space/github-action-test-helper'; 2 | 3 | setupGlobal(); 4 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type ReposListReleasesResponseItem = { 2 | draft: boolean; 3 | id: number; 4 | 'tag_name': string; 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/command.ts: -------------------------------------------------------------------------------- 1 | import type { ReposListReleasesResponseItem } from '../types'; 2 | import type { Context } from '@actions/github/lib/context'; 3 | import type { Octokit } from '@technote-space/github-action-helper/dist/types'; 4 | import fs from 'fs'; 5 | import { resolve, dirname } from 'path'; 6 | import { Command, ContextHelper, GitHelper, Utils } from '@technote-space/github-action-helper'; 7 | import { Logger } from '@technote-space/github-action-log-helper'; 8 | import { 9 | getBuildCommands, 10 | getCommitMessage, 11 | getCommitName, 12 | getCommitEmail, 13 | getCreateTags, 14 | getOriginalTagPrefix, 15 | isTestTag, 16 | isEnabledCleanTestTag, 17 | getTestTagPrefix, 18 | getOutputBuildInfoFilename, 19 | getFetchDepth, 20 | getParams, 21 | getReplaceDirectory, 22 | } from './misc'; 23 | 24 | export const replaceDirectory = (context: Context) => (message: string): string => { 25 | const directories = getReplaceDirectory(context); 26 | return Object.keys(directories).reduce((value, directory) => Utils.replaceAll(Utils.replaceAll(value, ` -C ${directory}`, ''), directory, directories[directory]!), message); 27 | }; 28 | 29 | export const prepareFiles = async(logger: Logger, helper: GitHelper, context: Context): Promise => { 30 | const { buildDir, pushDir } = getParams(context); 31 | fs.mkdirSync(buildDir, { recursive: true }); 32 | 33 | logger.startProcess('Cloning the remote repo for build...'); 34 | await helper.checkout(buildDir, context); 35 | 36 | logger.startProcess('Running build for release...'); 37 | await helper.runCommand(buildDir, getBuildCommands(buildDir, pushDir)); 38 | }; 39 | 40 | export const createBuildInfoFile = async(logger: Logger, context: Context): Promise => { 41 | const filename = getOutputBuildInfoFilename(); 42 | if (!filename) { 43 | return; 44 | } 45 | 46 | const { buildDir, branchName, tagName } = getParams(context); 47 | 48 | logger.startProcess('Creating build info file...'); 49 | const filepath = resolve(buildDir, filename); 50 | const dir = dirname(filepath); 51 | if (!fs.existsSync(dir)) { 52 | fs.mkdirSync(dir, { recursive: true }); 53 | } 54 | 55 | fs.writeFileSync(filepath, JSON.stringify({ 56 | owner: context.repo.owner, 57 | repo: context.repo.repo, 58 | sha: context.sha, 59 | ref: context.ref, 60 | tagName: tagName, 61 | branch: branchName, 62 | tags: getCreateTags(tagName), 63 | 'updated_at': (new Date).toISOString(), 64 | })); 65 | }; 66 | 67 | export const clone = async(logger: Logger, helper: GitHelper, context: Context): Promise => { 68 | const { pushDir, branchName } = getParams(context); 69 | logger.startProcess('Fetching...'); 70 | await helper.fetchOrigin(pushDir, context, ['--no-tags'], [Utils.getRefspec(branchName)]); 71 | 72 | logger.startProcess('Switching branch to [%s]...', branchName); 73 | await helper.switchBranch(pushDir, branchName); 74 | }; 75 | 76 | export const checkBranch = async(clonedBranch: string, logger: Logger, helper: GitHelper, context: Context): Promise => { 77 | const { pushDir, branchName } = getParams(context); 78 | if (branchName !== clonedBranch) { 79 | logger.info('remote branch %s not found.', branchName); 80 | logger.info('now branch: %s', clonedBranch); 81 | 82 | logger.startProcess('Initializing local git repo [%s]...', branchName); 83 | await helper.gitInit(pushDir, branchName); 84 | } 85 | }; 86 | 87 | export const config = async(logger: Logger, helper: GitHelper, context: Context): Promise => { 88 | const { pushDir } = getParams(context); 89 | const name = getCommitName(); 90 | const email = getCommitEmail(); 91 | logger.startProcess('Configuring git committer to be %s <%s>...', name, email); 92 | 93 | await helper.config(pushDir, { name, email }); 94 | }; 95 | 96 | export const commit = async(helper: GitHelper, context: Context): Promise => helper.commit(getParams(context).pushDir, getCommitMessage(), { allowEmpty: true }); 97 | 98 | export const getDeleteTestTag = async(tagName: string, prefix: string, helper: GitHelper, context: Context): Promise> => (await helper.getTags(getParams(context).pushDir, { quiet: true })) 99 | .filter(tag => Utils.getPrefixRegExp(prefix).test(tag)) 100 | .map(tag => tag.replace(Utils.getPrefixRegExp(prefix), '')) 101 | .filter(tag => Utils.versionCompare(tag, tagName, false) < 0) // eslint-disable-line no-magic-numbers 102 | .map(tag => `${prefix}${tag}`); 103 | 104 | export const deleteTestTags = async(helper: GitHelper, context: Context): Promise => { 105 | const { pushDir, tagName } = getParams(context); 106 | if (!isTestTag(tagName) && isEnabledCleanTestTag()) { 107 | const prefixForTestTag = getTestTagPrefix(); 108 | if (prefixForTestTag) { 109 | await helper.deleteTag(pushDir, await getDeleteTestTag(tagName, prefixForTestTag, helper, context), context); 110 | 111 | const prefixForOriginalTag = getOriginalTagPrefix(); 112 | if (prefixForOriginalTag) { 113 | await helper.deleteTag(pushDir, await getDeleteTestTag(tagName, prefixForOriginalTag + prefixForTestTag, helper, context), context); 114 | } 115 | } 116 | } 117 | }; 118 | 119 | export const push = async(logger: Logger, helper: GitHelper, context: Context): Promise => { 120 | const { pushDir, branchName, tagName, branchNames } = getParams(context); 121 | logger.startProcess('Pushing to %s@%s (tag: %s)...', ContextHelper.getRepository(context), branchName, tagName); 122 | 123 | const prefixForOriginalTag = getOriginalTagPrefix(); 124 | if (prefixForOriginalTag) { 125 | const originalTag = prefixForOriginalTag + tagName; 126 | await helper.fetchTags(pushDir, context); 127 | await helper.copyTag(pushDir, originalTag, tagName, context); 128 | } 129 | 130 | const tagNames = getCreateTags(tagName); 131 | await helper.fetchTags(pushDir, context); 132 | await deleteTestTags(helper, context); 133 | await helper.deleteLocalTag(pushDir, tagNames); 134 | await helper.addLocalTag(pushDir, tagNames); 135 | await helper.push(pushDir, branchName, context, { withTag: true, force: true }); 136 | await branchNames.reduce(async(prev, branch) => { 137 | await prev; 138 | await helper.createBranch(pushDir, branch); 139 | await helper.forcePush(pushDir, branch, context); 140 | }, Promise.resolve()); 141 | }; 142 | 143 | const findRelease = async(octokit: Octokit, context: Context): Promise => { 144 | const { tagName } = getParams(context); 145 | const releases = await octokit.rest.repos.listReleases({ 146 | owner: context.repo.owner, 147 | repo: context.repo.repo, 148 | }); 149 | return releases.data.find(release => release.tag_name === tagName); 150 | }; 151 | 152 | export const updateRelease = async(release: ReposListReleasesResponseItem | undefined, logger: Logger, octokit: Octokit, context: Context): Promise => { 153 | if (!release || release.draft) { 154 | return; 155 | } 156 | 157 | logger.startProcess('Re-publishing release...'); 158 | await octokit.rest.repos.updateRelease({ 159 | owner: context.repo.owner, 160 | repo: context.repo.repo, 161 | 'release_id': release.id, 162 | draft: false, 163 | }); 164 | }; 165 | 166 | export const copyFiles = async(logger: Logger, command: Command, context: Context): Promise => { 167 | const { buildDir, pushDir } = getParams(context); 168 | logger.startProcess('Copying %s contents to %s...', buildDir, pushDir); 169 | 170 | await command.execAsync({ 171 | command: 'rsync', 172 | args: ['-rl', '--exclude', '.git', '--delete', `${buildDir}/`, pushDir], 173 | }); 174 | }; 175 | 176 | export const prepareCommit = async(logger: Logger, command: Command, helper: GitHelper, context: Context): Promise => { 177 | await clone(logger, helper, context); 178 | await checkBranch(await helper.getCurrentBranchName(getParams(context).pushDir), logger, helper, context); 179 | await prepareFiles(logger, helper, context); 180 | await createBuildInfoFile(logger, context); 181 | await copyFiles(logger, command, context); 182 | }; 183 | 184 | const executeCommit = async(release: ReposListReleasesResponseItem | undefined, logger: Logger, helper: GitHelper, octokit: Octokit, context: Context): Promise => { 185 | await config(logger, helper, context); 186 | await commit(helper, context); 187 | await push(logger, helper, context); 188 | await updateRelease(release, logger, octokit, context); 189 | return true; 190 | }; 191 | 192 | export const deploy = async(octokit: Octokit, context: Context): Promise => { 193 | const logger = new Logger(replaceDirectory(context)); 194 | const command = new Command(logger); 195 | const { branchName } = getParams(context); 196 | 197 | logger.startProcess('Deploying branch %s to %s...', branchName, ContextHelper.getRepository(context)); 198 | 199 | const helper = new GitHelper(logger, { depth: getFetchDepth() }); 200 | const release = await findRelease(octokit, context); 201 | await prepareCommit(logger, command, helper, context); 202 | await executeCommit(release, logger, helper, octokit, context); 203 | }; 204 | -------------------------------------------------------------------------------- /src/utils/command1.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-magic-numbers */ 2 | import { resolve } from 'path'; 3 | import { GitHelper, Command } from '@technote-space/github-action-helper'; 4 | import { Logger } from '@technote-space/github-action-log-helper'; 5 | import { 6 | getContext, 7 | testEnv, 8 | testFs, 9 | spyOnSpawn, 10 | execCalledWith, 11 | testChildProcess, 12 | setChildProcessParams, 13 | spyOnStdout, 14 | stdoutCalledWith, 15 | } from '@technote-space/github-action-test-helper'; 16 | import { beforeEach, describe, expect, it, vi } from 'vitest'; 17 | import { 18 | replaceDirectory, 19 | clone, 20 | checkBranch, 21 | prepareFiles, 22 | createBuildInfoFile, 23 | copyFiles, 24 | config, 25 | getDeleteTestTag, 26 | deleteTestTags, 27 | push, 28 | } from './command'; 29 | import { getParams } from './misc'; 30 | 31 | const setExists = testFs(); 32 | const rootDir = resolve(__dirname, '..', '..'); 33 | const logger = new Logger(); 34 | const helper = new GitHelper(logger, { token: 'test-token' }); 35 | 36 | beforeEach(() => { 37 | getParams.clear(); 38 | Logger.resetForTesting(); 39 | }); 40 | 41 | describe('replaceDirectory', () => { 42 | testEnv(rootDir); 43 | 44 | const workDir = resolve('test-dir/.work'); 45 | const buildDir = resolve('test-dir/.work/build'); 46 | const pushDir = resolve('test-dir/.work/push'); 47 | 48 | it('should replace build directory', () => { 49 | process.env.GITHUB_WORKSPACE = 'test-dir'; 50 | 51 | expect(replaceDirectory(getContext({}))(`git -C ${buildDir} fetch`)).toBe('git fetch'); 52 | }); 53 | 54 | it('should replace build directory', () => { 55 | process.env.GITHUB_WORKSPACE = 'test-dir'; 56 | 57 | expect(replaceDirectory(getContext({}))(`git -C ${pushDir} fetch`)).toBe('git fetch'); 58 | }); 59 | 60 | it('should replace working directory', () => { 61 | process.env.GITHUB_WORKSPACE = 'test-dir'; 62 | 63 | expect(replaceDirectory(getContext({}))(`git -C ${workDir} fetch`)).toBe('git fetch'); 64 | }); 65 | 66 | it('should replace directories', () => { 67 | process.env.GITHUB_WORKSPACE = 'test-dir'; 68 | 69 | expect(replaceDirectory(getContext({}))(`abc ${buildDir} && pqr ${workDir}/xyz ${pushDir}/123`)).toBe('abc && pqr /xyz /123'); 70 | }); 71 | }); 72 | 73 | describe('clone', () => { 74 | testEnv(rootDir); 75 | 76 | it('should run clone command', async() => { 77 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 78 | process.env.INPUT_BRANCH_NAME = 'test-branch'; 79 | process.env.GITHUB_WORKSPACE = 'test-dir'; 80 | const mockExec = spyOnSpawn(); 81 | 82 | await clone(logger, helper, getContext({ 83 | repo: { 84 | owner: 'Hello', 85 | repo: 'World', 86 | }, 87 | })); 88 | 89 | execCalledWith(mockExec, [ 90 | 'git init \'.\'', 91 | 'git remote add origin \'https://octocat:test-token@github.com/Hello/World.git\' || :', 92 | 'git fetch --no-tags origin \'refs/heads/test-branch:refs/remotes/origin/test-branch\' || :', 93 | 'git checkout -b test-branch origin/test-branch || :', 94 | 'git checkout test-branch || :', 95 | ]); 96 | }); 97 | }); 98 | 99 | describe('checkBranch', () => { 100 | testEnv(rootDir); 101 | 102 | it('should do nothing', async() => { 103 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 104 | process.env.INPUT_BRANCH_NAME = 'test-branch'; 105 | process.env.GITHUB_WORKSPACE = 'test-dir'; 106 | const mockExec = spyOnSpawn(); 107 | 108 | await checkBranch('test-branch', logger, helper, getContext({})); 109 | 110 | expect(mockExec).not.toBeCalled(); 111 | }); 112 | 113 | it('should run git init command', async() => { 114 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 115 | process.env.INPUT_BRANCH_NAME = 'test-branch'; 116 | process.env.GITHUB_WORKSPACE = 'test-dir'; 117 | const mockExec = spyOnSpawn(); 118 | 119 | await checkBranch('test-branch2', logger, helper, getContext({})); 120 | 121 | execCalledWith(mockExec, [ 122 | 'git init \'.\'', 123 | 'git checkout --orphan test-branch', 124 | ]); 125 | }); 126 | }); 127 | 128 | describe('prepareFiles', () => { 129 | testEnv(rootDir); 130 | 131 | const buildDir = resolve('test-dir/.work/build'); 132 | const pushDir = resolve('test-dir/.work/push'); 133 | 134 | const commonCheck: string[] = [ 135 | 'yarn install --production', 136 | `mv -f '${resolve(buildDir, 'action.yaml')}' '${resolve(pushDir, 'action.yml')}' > /dev/null 2>&1 || :`, 137 | `mv -f '${resolve(buildDir, 'action.yml')}' '${resolve(pushDir, 'action.yml')}' > /dev/null 2>&1 || :`, 138 | 'rm -rdf .[!.]*', 139 | 'rm -rdf *.[jt]s', 140 | 'rm -rdf *.[mc][jt]s', 141 | 'rm -rdf *.json', 142 | 'rm -rdf *.lock', 143 | 'rm -rdf *.yml', 144 | 'rm -rdf *.yaml', 145 | 'rm -rdf __tests__ docs src', 146 | `mv -f '${resolve(pushDir, 'action.yml')}' '${resolve(buildDir, 'action.yml')}' > /dev/null 2>&1 || :`, 147 | ]; 148 | 149 | it('should checkout branch', async() => { 150 | process.env.INPUT_PACKAGE_MANAGER = 'yarn'; 151 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 152 | process.env.GITHUB_WORKSPACE = 'test-dir'; 153 | const mockExec = spyOnSpawn(); 154 | 155 | await prepareFiles(logger, helper, getContext({ 156 | repo: { 157 | owner: 'Hello', 158 | repo: 'World', 159 | }, 160 | ref: 'refs/heads/test', 161 | sha: 'test-sha', 162 | })); 163 | 164 | execCalledWith(mockExec, ([ 165 | 'git init \'.\'', 166 | 'git remote add origin \'https://octocat:test-token@github.com/Hello/World.git\' || :', 167 | 'git fetch --no-tags origin \'refs/heads/test:refs/remotes/origin/test\' || :', 168 | 'git checkout -qf test-sha', 169 | ] as any[]).concat(commonCheck)); // eslint-disable-line @typescript-eslint/no-explicit-any 170 | }); 171 | 172 | it('should checkout tag', async() => { 173 | process.env.INPUT_PACKAGE_MANAGER = 'yarn'; 174 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 175 | process.env.GITHUB_WORKSPACE = 'test-dir'; 176 | const mockExec = spyOnSpawn(); 177 | 178 | await prepareFiles(logger, helper, getContext({ 179 | repo: { 180 | owner: 'Hello', 181 | repo: 'World', 182 | }, 183 | ref: 'refs/tags/v1.0-beta+exp.sha.5114f85', 184 | sha: 'test-sha', 185 | })); 186 | 187 | execCalledWith(mockExec, ([ 188 | 'git init \'.\'', 189 | 'git remote add origin \'https://octocat:test-token@github.com/Hello/World.git\' || :', 190 | 'git fetch --no-tags origin \'refs/tags/v1.0-beta+exp.sha.5114f85:refs/tags/v1.0-beta+exp.sha.5114f85\' || :', 191 | 'git checkout -qf test-sha', 192 | ] as any[]).concat(commonCheck)); // eslint-disable-line @typescript-eslint/no-explicit-any 193 | }); 194 | 195 | it('should clean specified targets 1', async() => { 196 | process.env.INPUT_PACKAGE_MANAGER = 'yarn'; 197 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 198 | process.env.GITHUB_WORKSPACE = 'test-dir'; 199 | process.env.INPUT_CLEAN_TARGETS = 'test1,-test2,test3 test4,-test5 , test6;test7, test8/*.txt, *.test9'; 200 | const mockExec = spyOnSpawn(); 201 | 202 | await prepareFiles(logger, helper, getContext({ 203 | repo: { 204 | owner: 'Hello', 205 | repo: 'World', 206 | }, 207 | ref: 'refs/tags/test', 208 | sha: 'test-sha', 209 | })); 210 | 211 | execCalledWith(mockExec, ([ 212 | 'git init \'.\'', 213 | 'git remote add origin \'https://octocat:test-token@github.com/Hello/World.git\' || :', 214 | 'git fetch --no-tags origin \'refs/tags/test:refs/tags/test\' || :', 215 | 'git checkout -qf test-sha', 216 | 'yarn install --production', 217 | `mv -f '${resolve(buildDir, 'action.yaml')}' '${resolve(pushDir, 'action.yml')}' > /dev/null 2>&1 || :`, 218 | `mv -f '${resolve(buildDir, 'action.yml')}' '${resolve(pushDir, 'action.yml')}' > /dev/null 2>&1 || :`, 219 | 'rm -rdf -- -test2', 220 | 'rm -rdf -- -test5', 221 | 'rm -rdf test8/*.txt', 222 | 'rm -rdf *.test9', 223 | 'rm -rdf test1 \'test3 test4\' \'test6;test7\'', 224 | `mv -f '${resolve(pushDir, 'action.yml')}' '${resolve(buildDir, 'action.yml')}' > /dev/null 2>&1 || :`, 225 | ])); 226 | }); 227 | 228 | it('should clean specified targets 2', async() => { 229 | process.env.INPUT_PACKAGE_MANAGER = 'yarn'; 230 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 231 | process.env.GITHUB_WORKSPACE = 'test-dir'; 232 | process.env.INPUT_CLEAN_TARGETS = '-test1, -test2/?<>:|"\'@#$%^& ;.*.test3 , ?<>:|"\'@#$%^& ;/test4 test5/*.txt,;?<>:|"\'@#$%^& ;.txt,rm -rf /'; 233 | const mockExec = spyOnSpawn(); 234 | 235 | await prepareFiles(logger, helper, getContext({ 236 | repo: { 237 | owner: 'Hello', 238 | repo: 'World', 239 | }, 240 | ref: 'refs/tags/test', 241 | sha: 'test-sha', 242 | })); 243 | 244 | execCalledWith(mockExec, ([ 245 | 'git init \'.\'', 246 | 'git remote add origin \'https://octocat:test-token@github.com/Hello/World.git\' || :', 247 | 'git fetch --no-tags origin \'refs/tags/test:refs/tags/test\' || :', 248 | 'git checkout -qf test-sha', 249 | 'yarn install --production', 250 | `mv -f '${resolve(buildDir, 'action.yaml')}' '${resolve(pushDir, 'action.yml')}' > /dev/null 2>&1 || :`, 251 | `mv -f '${resolve(buildDir, 'action.yml')}' '${resolve(pushDir, 'action.yml')}' > /dev/null 2>&1 || :`, 252 | 'rm -rdf -- -test1', 253 | 'rm -rdf -- -test2/\\?\\<\\>\\:\\|\\"\\\'\\@\\#\\$\\%\\^\\&\\ \\;.*.test3', 254 | 'rm -rdf ?\\<\\>\\:\\|\\"\\\'\\@\\#\\$\\%\\^\\&\\ \\;/test4 test5/*.txt', 255 | 'rm -rdf \';?<>:|"\'\\\'\'@#$%^& ;.txt\' \'rm -rf /\'', 256 | `mv -f '${resolve(pushDir, 'action.yml')}' '${resolve(buildDir, 'action.yml')}' > /dev/null 2>&1 || :`, 257 | ])); 258 | }); 259 | }); 260 | 261 | describe('createBuildInfoFile', () => { 262 | testEnv(rootDir); 263 | 264 | it('should do nothing', async() => { 265 | process.env.INPUT_OUTPUT_BUILD_INFO_FILENAME = '/'; 266 | process.env.INPUT_BRANCH_NAME = 'test-branch'; 267 | process.env.GITHUB_WORKSPACE = 'test-dir'; 268 | // eslint-disable-next-line @typescript-eslint/no-var-requires 269 | const writeMock = vi.spyOn(require('fs'), 'writeFileSync'); 270 | 271 | await createBuildInfoFile(logger, getContext({ 272 | eventName: 'push', 273 | ref: 'refs/tags/v1.2.3', 274 | })); 275 | 276 | expect(writeMock).not.toBeCalled(); 277 | }); 278 | 279 | it('should write file', async() => { 280 | process.env.INPUT_OUTPUT_BUILD_INFO_FILENAME = 'info.json'; 281 | process.env.INPUT_BRANCH_NAME = 'test-branch'; 282 | process.env.GITHUB_WORKSPACE = 'test-dir'; 283 | // eslint-disable-next-line @typescript-eslint/no-var-requires 284 | const mkdirMock = vi.spyOn(require('fs'), 'mkdirSync'); 285 | // eslint-disable-next-line @typescript-eslint/no-var-requires 286 | const writeMock = vi.spyOn(require('fs'), 'writeFileSync'); 287 | 288 | await createBuildInfoFile(logger, getContext({ 289 | eventName: 'push', 290 | ref: 'refs/tags/v1.2.3', 291 | })); 292 | 293 | expect(mkdirMock).toBeCalledTimes(1); 294 | expect(writeMock).toBeCalledTimes(1); 295 | }); 296 | 297 | it('should not create dir', async() => { 298 | process.env.INPUT_OUTPUT_BUILD_INFO_FILENAME = 'info.json'; 299 | process.env.INPUT_BRANCH_NAME = 'test-branch'; 300 | process.env.GITHUB_WORKSPACE = 'test-dir'; 301 | // eslint-disable-next-line @typescript-eslint/no-var-requires 302 | const mkdirMock = vi.spyOn(require('fs'), 'mkdirSync'); 303 | // eslint-disable-next-line @typescript-eslint/no-var-requires 304 | const writeMock = vi.spyOn(require('fs'), 'writeFileSync'); 305 | setExists(true); 306 | 307 | await createBuildInfoFile(logger, getContext({ 308 | eventName: 'push', 309 | ref: 'refs/tags/v1.2.3', 310 | })); 311 | 312 | expect(mkdirMock).toBeCalledTimes(0); 313 | expect(writeMock).toBeCalledTimes(1); 314 | }); 315 | }); 316 | 317 | describe('copyFiles', () => { 318 | testEnv(rootDir); 319 | 320 | it('should run rsync command', async() => { 321 | process.env.GITHUB_WORKSPACE = 'test-dir'; 322 | const mockExec = spyOnSpawn(); 323 | 324 | await copyFiles(logger, new Command(logger), getContext({})); 325 | 326 | const buildDir = resolve('test-dir/.work/build'); 327 | const pushDir = resolve('test-dir/.work/push'); 328 | execCalledWith(mockExec, [ 329 | `rsync -rl --exclude '.git' --delete '${buildDir}/' '${pushDir}'`, 330 | ]); 331 | }); 332 | }); 333 | 334 | describe('config', () => { 335 | testEnv(rootDir); 336 | 337 | it('should run git config command', async() => { 338 | process.env.GITHUB_WORKSPACE = 'test-dir'; 339 | const mockExec = spyOnSpawn(); 340 | 341 | await config(logger, helper, getContext({})); 342 | 343 | execCalledWith(mockExec, [ 344 | 'git config \'user.name\' \'github-actions[bot]\'', 345 | 'git config \'user.email\' \'41898282+github-actions[bot]@users.noreply.github.com\'', 346 | ]); 347 | }); 348 | }); 349 | 350 | describe('getDeleteTestTag', () => { 351 | testEnv(rootDir); 352 | testChildProcess(); 353 | 354 | it('should return empty', async() => { 355 | setChildProcessParams({ 356 | stdout: (command: string): string => { 357 | if (command.endsWith('git tag')) { 358 | return ''; 359 | } 360 | return ''; 361 | }, 362 | }); 363 | 364 | expect(await getDeleteTestTag('v1.2.3', 'test/', helper, getContext({}))).toEqual([]); 365 | }); 366 | 367 | it('should get delete test tag', async() => { 368 | setChildProcessParams({ 369 | stdout: (command: string): string => { 370 | if (command.endsWith('git tag')) { 371 | return 'v1\nv1.2\nv1.2.2\ntest/v0\ntest/v1\ntest/v1.1\ntest/v1.2\ntest/v1.2.2\ntest/v1.2.3\ntest/v1.2.3.1'; 372 | } 373 | return ''; 374 | }, 375 | }); 376 | 377 | expect(await getDeleteTestTag('v1.2.3', 'test/', helper, getContext({}))).toEqual([ 378 | 'test/v0', 379 | 'test/v1.1', 380 | 'test/v1.2.2', 381 | ]); 382 | }); 383 | 384 | it('should get delete original test tag', async() => { 385 | setChildProcessParams({ 386 | stdout: (command: string): string => { 387 | if (command.endsWith('git tag')) { 388 | return 'v1\noriginal/v1.2\nv1.2.2\ntest/v0\noriginal/test/v1\ntest/v1.1\ntest/v1.2\noriginal/test/v1.2.2\noriginal/test/v1.2.3\ntest/v1.2.3.1'; 389 | } 390 | return ''; 391 | }, 392 | }); 393 | 394 | expect(await getDeleteTestTag('v1.2.3', 'original/test/', helper, getContext({}))).toEqual([ 395 | 'original/test/v1.2.2', 396 | ]); 397 | }); 398 | }); 399 | 400 | describe('deleteTestTags', () => { 401 | testEnv(rootDir); 402 | testChildProcess(); 403 | const context = getContext({ 404 | eventName: 'push', 405 | ref: 'refs/tags/v1.2.3', 406 | repo: { 407 | owner: 'Hello', 408 | repo: 'World', 409 | }, 410 | }); 411 | const tags = 'v1\nv1.2\nv1.2.2\ntest/v0\noriginal/test/v0\ntest/v1\noriginal/test/v1\ntest/v1.1\noriginal/test/v1.1\ntest/v1.2\noriginal/test/v1.2\ntest/v1.2.2\noriginal/test/v1.2.2\ntest/v1.2.3\noriginal/test/v1.2.3'; 412 | 413 | it('should do nothing 1', async() => { 414 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 415 | process.env.INPUT_ORIGINAL_TAG_PREFIX = 'original/'; 416 | process.env.INPUT_CLEAN_TEST_TAG = '1'; 417 | const mockExec = spyOnSpawn(); 418 | 419 | await deleteTestTags(helper, context); 420 | 421 | execCalledWith(mockExec, []); 422 | }); 423 | 424 | it('should do nothing 2', async() => { 425 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 426 | process.env.INPUT_TEST_TAG_PREFIX = 'test/'; 427 | process.env.INPUT_CLEAN_TEST_TAG = ''; 428 | const mockExec = spyOnSpawn(); 429 | 430 | await deleteTestTags(helper, context); 431 | 432 | execCalledWith(mockExec, []); 433 | }); 434 | 435 | it('should delete test tags', async() => { 436 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 437 | process.env.INPUT_TEST_TAG_PREFIX = 'test/'; 438 | process.env.INPUT_CLEAN_TEST_TAG = '1'; 439 | const mockExec = spyOnSpawn(); 440 | setChildProcessParams({ 441 | stdout: (command: string): string => { 442 | if (command.endsWith('git tag')) { 443 | return tags; 444 | } 445 | return ''; 446 | }, 447 | }); 448 | 449 | await deleteTestTags(helper, context); 450 | 451 | execCalledWith(mockExec, [ 452 | 'git tag', 453 | 'git push \'https://octocat:test-token@github.com/Hello/World.git\' --delete tags/test/v0 \'tags/test/v1.1\' \'tags/test/v1.2.2\' || :', 454 | 'git tag -d test/v0 \'test/v1.1\' \'test/v1.2.2\' || :', 455 | ]); 456 | }); 457 | 458 | it('should delete original test tags', async() => { 459 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 460 | process.env.INPUT_TEST_TAG_PREFIX = 'test/'; 461 | process.env.INPUT_ORIGINAL_TAG_PREFIX = 'original/'; 462 | process.env.INPUT_CLEAN_TEST_TAG = '1'; 463 | const mockExec = spyOnSpawn(); 464 | setChildProcessParams({ 465 | stdout: (command: string): string => { 466 | if (command.endsWith('git tag')) { 467 | return tags; 468 | } 469 | return ''; 470 | }, 471 | }); 472 | 473 | await deleteTestTags(helper, context); 474 | 475 | execCalledWith(mockExec, [ 476 | 'git tag', 477 | 'git push \'https://octocat:test-token@github.com/Hello/World.git\' --delete tags/test/v0 \'tags/test/v1.1\' \'tags/test/v1.2.2\' || :', 478 | 'git tag -d test/v0 \'test/v1.1\' \'test/v1.2.2\' || :', 479 | 'git tag', 480 | 'git push \'https://octocat:test-token@github.com/Hello/World.git\' --delete tags/original/test/v0 \'tags/original/test/v1.1\' \'tags/original/test/v1.2.2\' || :', 481 | 'git tag -d original/test/v0 \'original/test/v1.1\' \'original/test/v1.2.2\' || :', 482 | ]); 483 | }); 484 | }); 485 | 486 | describe('push', () => { 487 | testEnv(rootDir); 488 | testChildProcess(); 489 | 490 | it('should run git push command', async() => { 491 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 492 | process.env.GITHUB_WORKSPACE = 'test-dir'; 493 | process.env.INPUT_BRANCH_NAME = 'test-branch'; 494 | process.env.INPUT_CLEAN_TEST_TAG = '1'; 495 | const mockExec = spyOnSpawn(); 496 | const mockStdout = spyOnStdout(); 497 | 498 | await push(logger, helper, getContext({ 499 | eventName: 'push', 500 | ref: 'refs/tags/v1.0-beta+exp.sha.5114f85', 501 | repo: { 502 | owner: 'Hello', 503 | repo: 'World', 504 | }, 505 | })); 506 | 507 | execCalledWith(mockExec, [ 508 | 'git tag', 509 | 'git tag -d stdout > /dev/null 2>&1', 510 | 'git fetch \'https://octocat:test-token@github.com/Hello/World.git\' --tags > /dev/null 2>&1', 511 | 'git tag -d \'v1.0.0-beta+exp.sha.5114f85\' \'v1.0-beta+exp.sha.5114f85\' \'v1-beta+exp.sha.5114f85\' || :', 512 | 'git tag \'v1.0.0-beta+exp.sha.5114f85\'', 513 | 'git tag \'v1.0-beta+exp.sha.5114f85\'', 514 | 'git tag \'v1-beta+exp.sha.5114f85\'', 515 | 'git push --tags --force \'https://octocat:test-token@github.com/Hello/World.git\' \'test-branch:refs/heads/test-branch\'', 516 | ]); 517 | stdoutCalledWith(mockStdout, [ 518 | '::group::Pushing to Hello/World@test-branch (tag: v1.0-beta+exp.sha.5114f85)...', 519 | '[command]git fetch origin --tags', 520 | '[command]git tag -d \'v1.0.0-beta+exp.sha.5114f85\' \'v1.0-beta+exp.sha.5114f85\' \'v1-beta+exp.sha.5114f85\'', 521 | ' >> stdout', 522 | '[command]git tag \'v1.0.0-beta+exp.sha.5114f85\'', 523 | ' >> stdout', 524 | '[command]git tag \'v1.0-beta+exp.sha.5114f85\'', 525 | ' >> stdout', 526 | '[command]git tag \'v1-beta+exp.sha.5114f85\'', 527 | ' >> stdout', 528 | '[command]git push --tags --force origin test-branch:refs/heads/test-branch', 529 | ' >> stdout', 530 | ]); 531 | }); 532 | 533 | it('should run git push command with pushing original tag', async() => { 534 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 535 | process.env.GITHUB_WORKSPACE = 'test-dir'; 536 | process.env.INPUT_BRANCH_NAME = 'releases/${MAJOR},test-branch1,test-branch2'; 537 | process.env.INPUT_TEST_TAG_PREFIX = 'test/'; 538 | process.env.INPUT_ORIGINAL_TAG_PREFIX = 'original/'; 539 | process.env.INPUT_CLEAN_TEST_TAG = '1'; 540 | const mockExec = spyOnSpawn(); 541 | const mockStdout = spyOnStdout(); 542 | 543 | await push(logger, helper, getContext({ 544 | eventName: 'push', 545 | ref: 'refs/tags/test/v1.2.3', 546 | repo: { 547 | owner: 'Hello', 548 | repo: 'World', 549 | }, 550 | })); 551 | 552 | execCalledWith(mockExec, [ 553 | 'git tag', 554 | 'git tag -d stdout > /dev/null 2>&1', 555 | 'git fetch \'https://octocat:test-token@github.com/Hello/World.git\' --tags > /dev/null 2>&1', 556 | 'git push \'https://octocat:test-token@github.com/Hello/World.git\' --delete \'tags/original/test/v1.2.3\' || :', 557 | 'git tag -d \'original/test/v1.2.3\' || :', 558 | 'git tag \'original/test/v1.2.3\' \'test/v1.2.3\'', 559 | 'git push \'https://octocat:test-token@github.com/Hello/World.git\' \'refs/tags/original/test/v1.2.3\'', 560 | 'git tag', 561 | 'git tag -d stdout > /dev/null 2>&1', 562 | 'git fetch \'https://octocat:test-token@github.com/Hello/World.git\' --tags > /dev/null 2>&1', 563 | 'git tag -d \'test/v1.2.3\' \'test/v1.2\' test/v1 || :', 564 | 'git tag \'test/v1.2.3\'', 565 | 'git tag \'test/v1.2\'', 566 | 'git tag test/v1', 567 | 'git push --tags --force \'https://octocat:test-token@github.com/Hello/World.git\' \'releases/v1:refs/heads/releases/v1\'', 568 | 'git checkout -b test-branch1', 569 | 'git push --force \'https://octocat:test-token@github.com/Hello/World.git\' \'test-branch1:refs/heads/test-branch1\'', 570 | 'git checkout -b test-branch2', 571 | 'git push --force \'https://octocat:test-token@github.com/Hello/World.git\' \'test-branch2:refs/heads/test-branch2\'', 572 | ]); 573 | stdoutCalledWith(mockStdout, [ 574 | '::group::Pushing to Hello/World@releases/v1 (tag: test/v1.2.3)...', 575 | '[command]git fetch origin --tags', 576 | '[command]git push origin --delete tags/original/test/v1.2.3', 577 | ' >> stdout', 578 | '[command]git tag -d \'original/test/v1.2.3\'', 579 | ' >> stdout', 580 | '[command]git tag \'original/test/v1.2.3\' \'test/v1.2.3\'', 581 | ' >> stdout', 582 | '[command]git push origin refs/tags/original/test/v1.2.3', 583 | ' >> stdout', 584 | '[command]git fetch origin --tags', 585 | '[command]git tag -d \'test/v1.2.3\' \'test/v1.2\' test/v1', 586 | ' >> stdout', 587 | '[command]git tag \'test/v1.2.3\'', 588 | ' >> stdout', 589 | '[command]git tag \'test/v1.2\'', 590 | ' >> stdout', 591 | '[command]git tag test/v1', 592 | ' >> stdout', 593 | '[command]git push --tags --force origin releases/v1:refs/heads/releases/v1', 594 | ' >> stdout', 595 | '[command]git checkout -b test-branch1', 596 | ' >> stdout', 597 | '[command]git push --force origin test-branch1:refs/heads/test-branch1', 598 | ' >> stdout', 599 | '[command]git checkout -b test-branch2', 600 | ' >> stdout', 601 | '[command]git push --force origin test-branch2:refs/heads/test-branch2', 602 | ' >> stdout', 603 | ]); 604 | }); 605 | }); 606 | -------------------------------------------------------------------------------- /src/utils/command2.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-magic-numbers */ 2 | import { resolve } from 'path'; 3 | import { Context } from '@actions/github/lib/context'; 4 | import { Octokit } from '@technote-space/github-action-helper/dist/types'; 5 | import { Logger } from '@technote-space/github-action-log-helper'; 6 | import { 7 | getContext, 8 | testEnv, 9 | disableNetConnect, 10 | getApiFixture, 11 | spyOnSpawn, 12 | testChildProcess, 13 | testFs, 14 | setChildProcessParams, 15 | getOctokit, 16 | } from '@technote-space/github-action-test-helper'; 17 | import nock from 'nock'; 18 | import { beforeEach, describe, expect, it, vi } from 'vitest'; 19 | import { ReposListReleasesResponseItem } from '../types'; 20 | import { 21 | updateRelease, 22 | deploy, 23 | } from './command'; 24 | import { getParams } from './misc'; 25 | 26 | const rootDir = resolve(__dirname, '../..'); 27 | const common = async(callback: (fn1, fn2, mockExec) => void, method: (GitHub, Context) => Promise, tagName = 'v1.2.3'): Promise => { 28 | const mockExec = spyOnSpawn(); 29 | const fn1 = vi.fn(); 30 | const fn2 = vi.fn(); 31 | nock('https://api.github.com') 32 | .get('/repos/Hello/World/releases') 33 | .reply(200, () => { 34 | fn1(); 35 | return getApiFixture(resolve(__dirname, '..', 'fixtures'), 'repos.listReleases'); 36 | }) 37 | .patch('/repos/Hello/World/releases/1', body => { 38 | expect(body).toEqual({ draft: false }); 39 | return body; 40 | }) 41 | .reply(200, () => { 42 | fn2(); 43 | return getApiFixture(resolve(__dirname, '..', 'fixtures'), 'repos.updateRelease'); 44 | }); 45 | 46 | await method(getOctokit(), getContext({ 47 | eventName: 'push', 48 | repo: { 49 | owner: 'Hello', 50 | repo: 'World', 51 | }, 52 | ref: `refs/tags/${tagName}`, 53 | sha: 'test-sha', 54 | })); 55 | 56 | callback(fn1, fn2, mockExec); 57 | }; 58 | const logger = new Logger(); 59 | 60 | beforeEach(() => { 61 | getParams.clear(); 62 | Logger.resetForTesting(); 63 | }); 64 | 65 | describe('updateRelease', () => { 66 | testEnv(rootDir); 67 | disableNetConnect(nock); 68 | 69 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 70 | const getReleaseItem = (override: { [key: string]: any }): ReposListReleasesResponseItem => Object.assign({ 71 | url: '', 72 | 'html_url': '', 73 | 'assets_url': '', 74 | 'upload_url': '', 75 | 'tarball_url': '', 76 | 'zipball_url': '', 77 | id: 1, 78 | 'node_id': '', 79 | 'tag_name': '', 80 | 'target_commitish': '', 81 | name: '', 82 | body: '', 83 | draft: false, 84 | prerelease: false, 85 | 'created_at': '', 86 | 'published_at': '', 87 | author: { 88 | login: '', 89 | id: 1, 90 | 'node_id': '', 91 | 'avatar_url': '', 92 | 'gravatar_id': '', 93 | url: '', 94 | 'html_url': '', 95 | 'followers_url': '', 96 | 'following_url': '', 97 | 'gists_url': '', 98 | 'starred_url': '', 99 | 'subscriptions_url': '', 100 | 'organizations_url': '', 101 | 'repos_url': '', 102 | 'events_url': '', 103 | 'received_events_url': '', 104 | type: '', 105 | 'site_admin': false, 106 | }, 107 | assets: [], 108 | }, override); 109 | 110 | it('should do nothing 1', async() => { 111 | await common((fn1, fn2) => { 112 | expect(fn1).not.toBeCalled(); 113 | expect(fn2).not.toBeCalled(); 114 | }, async(octokit: Octokit, context: Context) => { 115 | await updateRelease(undefined, logger, octokit, context); 116 | }); 117 | }); 118 | 119 | it('should do nothing 2', async() => { 120 | await common((fn1, fn2) => { 121 | expect(fn1).not.toBeCalled(); 122 | expect(fn2).not.toBeCalled(); 123 | }, async(octokit: Octokit, context: Context) => { 124 | await updateRelease(getReleaseItem({ draft: true }), logger, octokit, context); 125 | }); 126 | }); 127 | 128 | it('should update release', async() => { 129 | await common((fn1, fn2) => { 130 | expect(fn1).not.toBeCalled(); 131 | expect(fn2).toBeCalledTimes(1); 132 | }, async(octokit: Octokit, context: Context) => { 133 | await updateRelease(getReleaseItem({}), logger, octokit, context); 134 | }); 135 | }); 136 | }); 137 | 138 | describe('deploy', () => { 139 | disableNetConnect(nock); 140 | testEnv(rootDir); 141 | testChildProcess(); 142 | testFs(); 143 | 144 | it('should commit', async() => { 145 | process.env.INPUT_GITHUB_TOKEN = 'test-token'; 146 | setChildProcessParams({ stdout: 'A test.txt' }); 147 | 148 | await common((fn1, fn2, mockExec) => { 149 | expect(mockExec).toBeCalled(); 150 | expect(fn1).toBeCalledTimes(1); 151 | expect(fn2).toBeCalledTimes(1); 152 | }, deploy); 153 | }); 154 | }); 155 | -------------------------------------------------------------------------------- /src/utils/misc.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-magic-numbers */ 2 | import { resolve } from 'path'; 3 | import { isTargetEvent } from '@technote-space/filter-github-action'; 4 | import { testEnv, generateContext } from '@technote-space/github-action-test-helper'; 5 | import { beforeEach, describe, expect, it } from 'vitest'; 6 | import { DEFAULT_FETCH_DEPTH, TARGET_EVENTS } from '../constant'; 7 | import { 8 | getParams, 9 | getSearchBuildCommandTargets, 10 | getCommitMessage, 11 | getCommitName, 12 | getCommitEmail, 13 | getBranchNames, 14 | getFetchDepth, 15 | isTestTag, 16 | getTestTag, 17 | getClearFilesCommands, 18 | getBuildCommands, 19 | detectBuildCommands, 20 | isValidTagName, 21 | getMajorTag, 22 | getMinorTag, 23 | getPatchTag, 24 | isCreateMajorVersionTag, 25 | isCreateMinorVersionTag, 26 | isCreatePatchVersionTag, 27 | isEnabledCleanTestTag, 28 | getOutputBuildInfoFilename, 29 | getCreateTags, 30 | } from './misc'; 31 | 32 | const rootDir = resolve(__dirname, '../..'); 33 | 34 | beforeEach(() => { 35 | getParams.clear(); 36 | }); 37 | 38 | describe('isTargetEvent', () => { 39 | testEnv(rootDir); 40 | 41 | it('should return true 1', () => { 42 | expect(isTargetEvent(TARGET_EVENTS, generateContext({ 43 | event: 'push', 44 | ref: 'refs/tags/v1.2.3', 45 | }))).toBe(true); 46 | }); 47 | 48 | it('should return true 2', () => { 49 | expect(isTargetEvent(TARGET_EVENTS, generateContext({ 50 | event: 'release', 51 | action: 'published', 52 | }, { 53 | payload: { 54 | release: { 55 | 'tag_name': 'v1.2.3', 56 | }, 57 | }, 58 | }))).toBe(true); 59 | }); 60 | 61 | it('should return true 3', () => { 62 | expect(isTargetEvent(TARGET_EVENTS, generateContext({ 63 | event: 'create', 64 | ref: 'refs/tags/v1.2.3', 65 | }))).toBe(true); 66 | }); 67 | 68 | it('should return false 1', () => { 69 | expect(isTargetEvent(TARGET_EVENTS, generateContext({ 70 | event: 'pull_request', 71 | ref: 'refs/tags/test', 72 | }))).toBe(false); 73 | }); 74 | 75 | it('should return false 2', () => { 76 | expect(isTargetEvent(TARGET_EVENTS, generateContext({ 77 | event: 'push', 78 | ref: 'refs/tags/test', 79 | }))).toBe(false); 80 | }); 81 | 82 | it('should return false 3', () => { 83 | process.env.INPUT_BRANCH_PREFIX = 'release'; 84 | expect(isTargetEvent(TARGET_EVENTS, generateContext({ 85 | event: 'push', 86 | ref: 'refs/heads/release/v1.2.3', 87 | }))).toBe(false); 88 | }); 89 | 90 | it('should return false 4', () => { 91 | process.env.INPUT_BRANCH_PREFIX = 'release'; 92 | expect(isTargetEvent(TARGET_EVENTS, generateContext({ 93 | event: 'push', 94 | ref: 'refs/heads/release/v1.2.3', 95 | }))).toBe(false); 96 | }); 97 | 98 | it('should return false 5', () => { 99 | expect(isTargetEvent(TARGET_EVENTS, generateContext({ 100 | event: 'release', 101 | action: 'published', 102 | }, { 103 | payload: { 104 | release: { 105 | 'tag_name': 'abc', 106 | }, 107 | }, 108 | }))).toBe(false); 109 | }); 110 | 111 | it('should return false 6', () => { 112 | expect(isTargetEvent(TARGET_EVENTS, generateContext({ 113 | event: 'release', 114 | action: 'created', 115 | ref: 'refs/tags/v1.2.3', 116 | }, { 117 | payload: { 118 | release: { 119 | 'tag_name': 'v1.2.3', 120 | }, 121 | }, 122 | }))).toBe(false); 123 | }); 124 | 125 | it('should return false 7', () => { 126 | expect(isTargetEvent(TARGET_EVENTS, generateContext({ 127 | event: 'create', 128 | ref: 'refs/heads/v1.2.3', 129 | }))).toBe(false); 130 | }); 131 | }); 132 | 133 | describe('getParams', () => { 134 | testEnv(rootDir); 135 | 136 | it('should get params 1', () => { 137 | const params = getParams(generateContext({ ref: 'refs/tags/v1.2.3' })); 138 | expect(params).toHaveProperty('workDir'); 139 | expect(params).toHaveProperty('buildDir'); 140 | expect(params).toHaveProperty('pushDir'); 141 | expect(params).toHaveProperty('tagName'); 142 | expect(params).toHaveProperty('branchName'); 143 | expect(params.tagName).toBe('v1.2.3'); 144 | expect(params.branchName).toBe('gh-actions'); 145 | }); 146 | 147 | it('should get params 2', () => { 148 | process.env.INPUT_TEST_TAG_PREFIX = 'test/'; 149 | 150 | const params = getParams(generateContext({ ref: 'refs/tags/test/v2.3.4' })); 151 | expect(params.tagName).toBe('test/v2.3.4'); 152 | expect(params.branchName).toBe('gh-actions'); 153 | }); 154 | 155 | it('should get params 3', () => { 156 | process.env.INPUT_BRANCH_NAME = 'gh-actions'; 157 | 158 | const params = getParams(generateContext({ ref: 'refs/tags/v1.2.3' })); 159 | expect(params.tagName).toBe('v1.2.3'); 160 | expect(params.branchName).toBe('gh-actions'); 161 | }); 162 | 163 | it('should get params 4', () => { 164 | process.env.INPUT_BRANCH_NAME = 'releases/${MAJOR}/${MINOR}/${PATCH}'; 165 | 166 | const params = getParams(generateContext({ ref: 'refs/tags/v2.3.4' })); 167 | expect(params.tagName).toBe('v2.3.4'); 168 | expect(params.branchName).toBe('releases/v2/v2.3/v2.3.4'); 169 | }); 170 | }); 171 | 172 | describe('getSearchBuildCommandTargets', () => { 173 | testEnv(rootDir); 174 | 175 | it('should get targets', () => { 176 | process.env.INPUT_BUILD_COMMAND_TARGET = 'test1,test2\ntest3'; 177 | expect(getSearchBuildCommandTargets()).toEqual(['test1', 'test2', 'test3']); 178 | }); 179 | 180 | it('should throw error', () => { 181 | process.env.INPUT_BUILD_COMMAND_TARGET = ''; 182 | expect(() => getSearchBuildCommandTargets()).toThrow(); 183 | }); 184 | }); 185 | 186 | describe('getCommitMessage', () => { 187 | testEnv(rootDir); 188 | 189 | it('should get commit message', () => { 190 | process.env.INPUT_COMMIT_MESSAGE = 'test'; 191 | expect(getCommitMessage()).toBe('test'); 192 | }); 193 | 194 | it('should throw error', () => { 195 | process.env.INPUT_COMMIT_MESSAGE = ''; 196 | expect(() => getCommitMessage()).toThrow(); 197 | }); 198 | }); 199 | 200 | describe('getCommitName', () => { 201 | testEnv(rootDir); 202 | 203 | it('should get commit name', () => { 204 | process.env.INPUT_COMMIT_NAME = 'test'; 205 | expect(getCommitName()).toBe('test'); 206 | }); 207 | 208 | it('should throw error', () => { 209 | process.env.INPUT_COMMIT_NAME = ''; 210 | expect(() => getCommitName()).toThrow(); 211 | }); 212 | }); 213 | 214 | describe('getCommitEmail', () => { 215 | testEnv(rootDir); 216 | 217 | it('should get commit email', () => { 218 | process.env.INPUT_COMMIT_EMAIL = 'test'; 219 | expect(getCommitEmail()).toBe('test'); 220 | }); 221 | 222 | it('should throw error', () => { 223 | process.env.INPUT_COMMIT_EMAIL = ''; 224 | expect(() => getCommitEmail()).toThrow(); 225 | }); 226 | }); 227 | 228 | describe('getBranchNames', () => { 229 | testEnv(rootDir); 230 | 231 | it('should get branch name', () => { 232 | process.env.INPUT_BRANCH_NAME = 'test'; 233 | expect(getBranchNames()).toEqual(['test']); 234 | }); 235 | 236 | it('should throw error', () => { 237 | process.env.INPUT_BRANCH_NAME = ''; 238 | expect(() => getBranchNames()).toThrow(); 239 | }); 240 | }); 241 | 242 | describe('getFetchDepth', () => { 243 | testEnv(rootDir); 244 | 245 | it('should get fetch depth', () => { 246 | process.env.INPUT_FETCH_DEPTH = '10'; 247 | expect(getFetchDepth()).toBe(10); 248 | }); 249 | 250 | it('should get default fetch depth 1', () => { 251 | process.env.INPUT_FETCH_DEPTH = ''; 252 | expect(getFetchDepth()).toBe(DEFAULT_FETCH_DEPTH); 253 | }); 254 | 255 | it('should get default fetch depth 2', () => { 256 | process.env.INPUT_FETCH_DEPTH = 'test'; 257 | expect(getFetchDepth()).toBe(DEFAULT_FETCH_DEPTH); 258 | }); 259 | }); 260 | 261 | describe('isTestTag', () => { 262 | testEnv(rootDir); 263 | 264 | it('should return true', () => { 265 | process.env.INPUT_TEST_TAG_PREFIX = 'test/'; 266 | expect(isTestTag('test/v1.2.3')).toBe(true); 267 | }); 268 | 269 | it('should return false', () => { 270 | process.env.INPUT_TEST_TAG_PREFIX = 'test/'; 271 | expect(isTestTag('v1.2.3')).toBe(false); 272 | }); 273 | }); 274 | 275 | describe('getTestTag', () => { 276 | testEnv(rootDir); 277 | 278 | it('should get test tag', () => { 279 | process.env.INPUT_TEST_TAG_PREFIX = 'test/'; 280 | expect(getTestTag('test/v1.2.3')).toBe('v1.2.3'); 281 | }); 282 | }); 283 | 284 | describe('getClearFilesCommands', () => { 285 | testEnv(rootDir); 286 | 287 | it('should get clear files commands', () => { 288 | expect(getClearFilesCommands([])).toEqual([]); 289 | expect(getClearFilesCommands(['.[!.]*', '__tests__', 'src', '*.js', '*.ts', '*.json', '*.lock', '*.yml', '*.yaml'])).toEqual([ 290 | 'rm -rdf .[!.]*', 291 | 'rm -rdf *.js', 292 | 'rm -rdf *.ts', 293 | 'rm -rdf *.json', 294 | 'rm -rdf *.lock', 295 | 'rm -rdf *.yml', 296 | 'rm -rdf *.yaml', 297 | { command: 'rm', args: ['-rdf', '__tests__', 'src'] }, 298 | ]); 299 | expect(getClearFilesCommands(['?<>:|"\'@#$%^& ;/?<>:|"\'@#$%^& ;.*', '-?<>:|"\'@#$%^& ;', '*.?<>:|"\'@#$%^& ;'])).toEqual([ 300 | 'rm -rdf -- -\\?\\<\\>\\:\\|\\"\\\'\\@\\#\\$\\%\\^\\&\\ \\;', 301 | 'rm -rdf ?\\<\\>\\:\\|\\"\\\'\\@\\#\\$\\%\\^\\&\\ \\;/\\?<>:|"\'@#$%^& ;.*', 302 | 'rm -rdf *.\\?\\<\\>\\:\\|\\"\\\'\\@\\#\\$\\%\\^\\&\\ \\;', 303 | ]); 304 | expect(getClearFilesCommands(['test/?>; abc.txt', '-test1 test2.txt', ';rm -rf /', '-test1 test2/*.txt'])).toEqual([ 305 | 'rm -rdf -- -test1\\ test2.txt', 306 | 'rm -rdf -- -test1\\ test2/*.txt', 307 | { command: 'rm', args: ['-rdf', 'test/?>; abc.txt', ';rm -rf /'] }, 308 | ]); 309 | }); 310 | }); 311 | 312 | describe('getBuildCommands', () => { 313 | testEnv(rootDir); 314 | const pushDir = resolve(__dirname, '../fixtures/.push'); 315 | const buildDir1 = resolve(__dirname, '../fixtures/test1'); 316 | const buildDir7 = resolve(__dirname, '../fixtures/test7'); 317 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 318 | const mv1 = (buildDir: string): Array<{ [key: string]: any }> => [ 319 | { 320 | command: 'mv', 321 | args: ['-f', resolve(buildDir, 'action.yaml'), resolve(pushDir, 'action.yml')], 322 | suppressError: true, 323 | quiet: true, 324 | }, 325 | { 326 | command: 'mv', 327 | args: ['-f', resolve(buildDir, 'action.yml'), resolve(pushDir, 'action.yml')], 328 | suppressError: true, 329 | quiet: true, 330 | }, 331 | ]; 332 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 333 | const mv2 = (buildDir: string): Array<{ [key: string]: any }> => [ 334 | { 335 | command: 'mv', 336 | args: ['-f', resolve(pushDir, 'action.yml'), resolve(buildDir, 'action.yml')], 337 | suppressError: true, 338 | quiet: true, 339 | }, 340 | ]; 341 | const rm = [ 342 | 'rm -rdf .[!.]*', 343 | 'rm -rdf *.[jt]s', 344 | 'rm -rdf *.[mc][jt]s', 345 | 'rm -rdf *.json', 346 | 'rm -rdf *.lock', 347 | 'rm -rdf *.yml', 348 | 'rm -rdf *.yaml', 349 | { command: 'rm', args: ['-rdf', '__tests__', 'docs', 'src'] }, 350 | ]; 351 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 352 | const clean = (buildDir: string): Array => [ 353 | ...mv1(buildDir), 354 | ...rm, 355 | ...mv2(buildDir), 356 | ]; 357 | 358 | it('should get build commands 1', () => { 359 | process.env.INPUT_PACKAGE_MANAGER = 'yarn'; 360 | process.env.INPUT_BUILD_COMMAND = 'test'; 361 | expect(getBuildCommands(buildDir7, pushDir)).toEqual([ 362 | 'yarn install', 363 | 'test', 364 | 'yarn package', 365 | 'yarn install --production', 366 | ...clean(buildDir7), 367 | ]); 368 | }); 369 | 370 | it('should get build commands 2', () => { 371 | process.env.INPUT_PACKAGE_MANAGER = 'yarn'; 372 | expect(getBuildCommands(buildDir7, pushDir)).toEqual([ 373 | 'yarn install', 374 | 'yarn package', 375 | 'yarn install --production', 376 | ...clean(buildDir7), 377 | ]); 378 | }); 379 | 380 | it('should get build commands 3', () => { 381 | process.env.INPUT_PACKAGE_MANAGER = 'yarn'; 382 | process.env.INPUT_BUILD_COMMAND = 'yarn package'; 383 | expect(getBuildCommands(buildDir7, pushDir)).toEqual([ 384 | 'yarn install', 385 | 'yarn package', 386 | 'yarn install --production', 387 | ...clean(buildDir7), 388 | ]); 389 | }); 390 | 391 | it('should get build commands 4', () => { 392 | process.env.INPUT_PACKAGE_MANAGER = 'yarn'; 393 | process.env.INPUT_BUILD_COMMAND = 'yarn install && yarn package'; 394 | expect(getBuildCommands(buildDir7, pushDir)).toEqual([ 395 | 'yarn install', 396 | 'yarn package', 397 | ...clean(buildDir7), 398 | ]); 399 | }); 400 | 401 | it('should get build commands 5', () => { 402 | process.env.INPUT_PACKAGE_MANAGER = 'yarn'; 403 | process.env.INPUT_BUILD_COMMAND = 'test'; 404 | expect(getBuildCommands(buildDir1, pushDir)).toEqual([ 405 | 'yarn install', 406 | 'test', 407 | 'yarn install --production', 408 | ...clean(buildDir1), 409 | ]); 410 | }); 411 | 412 | it('should get build commands 6', () => { 413 | process.env.INPUT_PACKAGE_MANAGER = 'yarn'; 414 | expect(getBuildCommands(buildDir1, pushDir)).toEqual([ 415 | 'yarn install --production', 416 | ...clean(buildDir1), 417 | ]); 418 | }); 419 | 420 | it('should get build commands 7', () => { 421 | process.env.INPUT_PACKAGE_MANAGER = 'yarn'; 422 | process.env.INPUT_CLEAN_TARGETS = 'test1,-test2,test3 test4,-test5 , test6;test7'; 423 | expect(getBuildCommands(buildDir1, pushDir)).toEqual([ 424 | 'yarn install --production', 425 | ...mv1(buildDir1), 426 | 'rm -rdf -- -test2', 427 | 'rm -rdf -- -test5', 428 | { command: 'rm', args: ['-rdf', 'test1', 'test3 test4', 'test6;test7'] }, 429 | ...mv2(buildDir1), 430 | ]); 431 | }); 432 | 433 | it('should get build commands 8', () => { 434 | process.env.INPUT_PACKAGE_MANAGER = 'invalid-pkg-mgr'; 435 | process.env.INPUT_BUILD_COMMAND = 'test'; 436 | expect(getBuildCommands(buildDir7, pushDir)).toEqual([ 437 | 'npm install', 438 | 'test', 439 | 'npm run package', 440 | 'rm -rdf node_modules', 441 | 'npm install --production', 442 | ...clean(buildDir7), 443 | ]); 444 | }); 445 | 446 | it('should get build commands 9', () => { 447 | process.env.INPUT_BUILD_COMMAND = 'test'; 448 | expect(getBuildCommands(buildDir7, pushDir)).toEqual([ 449 | 'npm install', 450 | 'test', 451 | 'npm run package', 452 | 'rm -rdf node_modules', 453 | 'npm install --production', 454 | ...clean(buildDir7), 455 | ]); 456 | }); 457 | 458 | it('should get build commands 10', () => { 459 | process.env.INPUT_PACKAGE_MANAGER = 'npm'; 460 | expect(getBuildCommands(buildDir7, pushDir)).toEqual([ 461 | 'npm install', 462 | 'npm run package', 463 | 'rm -rdf node_modules', 464 | 'npm install --production', 465 | ...clean(buildDir7), 466 | ]); 467 | }); 468 | 469 | it('should get build commands 11', () => { 470 | process.env.INPUT_PACKAGE_MANAGER = 'yarn'; 471 | process.env.INPUT_CLEAN_TARGETS = ''; 472 | expect(getBuildCommands(buildDir7, pushDir)).toEqual([ 473 | 'yarn install', 474 | 'yarn package', 475 | 'yarn install --production', 476 | ...mv1(buildDir7), 477 | ...mv2(buildDir7), 478 | ]); 479 | }); 480 | 481 | it('should get build commands 10', () => { 482 | process.env.INPUT_PACKAGE_MANAGER = 'yarn'; 483 | process.env.INPUT_DELETE_NODE_MODULES = 'true'; 484 | process.env.INPUT_BUILD_COMMAND = 'tsc && yarn ncc build lib/main.js && rm -rf lib'; 485 | expect(getBuildCommands(buildDir1, pushDir)).toEqual([ 486 | 'yarn install', 487 | 'tsc', 488 | 'yarn ncc build lib/main.js', 489 | 'rm -rf lib', 490 | 'rm -rdf node_modules', 491 | ...clean(buildDir1), 492 | ]); 493 | }); 494 | }); 495 | 496 | describe('detectBuildCommands', () => { 497 | testEnv(rootDir); 498 | 499 | it('should return false 1', () => { 500 | expect(detectBuildCommands(resolve(__dirname, '../fixtures/test1'), 'yarn ', [])).toEqual([]); 501 | }); 502 | 503 | it('should return false 2', () => { 504 | expect(detectBuildCommands(resolve(__dirname, '../fixtures/test2'), 'yarn ', [])).toEqual([]); 505 | }); 506 | 507 | it('should return false 3', () => { 508 | expect(detectBuildCommands(resolve(__dirname, '../fixtures/test3'), 'yarn ', [])).toEqual([]); 509 | }); 510 | 511 | it('should detect build command 1', () => { 512 | expect(detectBuildCommands(resolve(__dirname, '../fixtures/test4'), 'yarn ', [])).toEqual(['prepare', 'build', 'production', 'prod', 'package']); 513 | }); 514 | 515 | it('should detect build command 2', () => { 516 | expect(detectBuildCommands(resolve(__dirname, '../fixtures/test5'), 'yarn ', ['yarn prod'])).toEqual(['production', 'package']); 517 | }); 518 | 519 | it('should detect build command 3', () => { 520 | expect(detectBuildCommands(resolve(__dirname, '../fixtures/test6'), 'npm run ', ['npm run prod', 'yarn package'])).toEqual(['package']); 521 | }); 522 | 523 | it('should detect build command 4', () => { 524 | expect(detectBuildCommands(resolve(__dirname, '../fixtures/test7'), 'yarn ', ['yarn prod'])).toEqual(['package']); 525 | }); 526 | 527 | it('should detect build command 5', () => { 528 | process.env.INPUT_ALLOW_MULTIPLE_BUILD_COMMANDS = 'false'; 529 | expect(detectBuildCommands(resolve(__dirname, '../fixtures/test4'), 'yarn ', [])).toEqual(['prepare']); 530 | }); 531 | }); 532 | 533 | describe('isValidTagName', () => { 534 | testEnv(rootDir); 535 | 536 | it('should return true 1', () => { 537 | expect(isValidTagName('0')).toBe(true); 538 | expect(isValidTagName('v12')).toBe(true); 539 | expect(isValidTagName('1.2')).toBe(true); 540 | expect(isValidTagName('V1.2.3')).toBe(true); 541 | expect(isValidTagName('v12.23.34.45')).toBe(true); 542 | expect(isValidTagName('v1.0-beta+exp.sha.5114f85')).toBe(true); 543 | }); 544 | 545 | it('should return true 2', () => { 546 | process.env.INPUT_TEST_TAG_PREFIX = 'test/'; 547 | expect(isValidTagName('test/v12')).toBe(true); 548 | expect(isValidTagName('test/1.2')).toBe(true); 549 | }); 550 | 551 | it('should return false 1', () => { 552 | expect(isValidTagName('')).toBe(false); 553 | expect(isValidTagName('abc')).toBe(false); 554 | expect(isValidTagName('v1.')).toBe(false); 555 | expect(isValidTagName('v.9')).toBe(false); 556 | }); 557 | 558 | it('should return false 2', () => { 559 | process.env.INPUT_TEST_TAG_PREFIX = 'test/'; 560 | expect(isValidTagName('test/')).toBe(false); 561 | expect(isValidTagName('test/abc')).toBe(false); 562 | }); 563 | }); 564 | 565 | describe('getMajorTag', () => { 566 | it('should get major tag', () => { 567 | expect(getMajorTag('0')).toBe('v0'); 568 | expect(getMajorTag('v12')).toBe('v12'); 569 | expect(getMajorTag('1.2')).toBe('v1'); 570 | expect(getMajorTag('V1.2.3')).toBe('v1'); 571 | expect(getMajorTag('v12.23.34.45')).toBe('v12'); 572 | }); 573 | }); 574 | 575 | describe('getMinorTag', () => { 576 | it('should get minor tag', () => { 577 | expect(getMinorTag('0')).toBe('v0.0'); 578 | expect(getMinorTag('v12')).toBe('v12.0'); 579 | expect(getMinorTag('1.2')).toBe('v1.2'); 580 | expect(getMinorTag('V1.2.3')).toBe('v1.2'); 581 | expect(getMinorTag('v12.23.34.45')).toBe('v12.23'); 582 | }); 583 | }); 584 | 585 | describe('getPatchTag', () => { 586 | it('should get patch tag', () => { 587 | expect(getPatchTag('0')).toBe('v0.0.0'); 588 | expect(getPatchTag('v12')).toBe('v12.0.0'); 589 | expect(getPatchTag('1.2')).toBe('v1.2.0'); 590 | expect(getPatchTag('V1.2.3')).toBe('v1.2.3'); 591 | expect(getPatchTag('v12.23.34.45')).toBe('v12.23.34'); 592 | }); 593 | }); 594 | 595 | describe('isCreateMajorVersionTag', () => { 596 | testEnv(rootDir); 597 | 598 | it('should return true 1', () => { 599 | expect(isCreateMajorVersionTag()).toBe(true); 600 | }); 601 | it('should return true 2', () => { 602 | process.env.INPUT_CREATE_MAJOR_VERSION_TAG = '1'; 603 | expect(isCreateMajorVersionTag()).toBe(true); 604 | }); 605 | it('should return true 3', () => { 606 | process.env.INPUT_CREATE_MAJOR_VERSION_TAG = 'abc'; 607 | expect(isCreateMajorVersionTag()).toBe(true); 608 | }); 609 | 610 | it('should return false 1', () => { 611 | process.env.INPUT_CREATE_MAJOR_VERSION_TAG = 'false'; 612 | expect(isCreateMajorVersionTag()).toBe(false); 613 | }); 614 | 615 | it('should return false 2', () => { 616 | process.env.INPUT_CREATE_MAJOR_VERSION_TAG = '0'; 617 | expect(isCreateMajorVersionTag()).toBe(false); 618 | }); 619 | }); 620 | 621 | describe('isCreateMinorVersionTag', () => { 622 | testEnv(rootDir); 623 | 624 | it('should return true 1', () => { 625 | expect(isCreateMinorVersionTag()).toBe(true); 626 | }); 627 | it('should return true 2', () => { 628 | process.env.INPUT_CREATE_MINOR_VERSION_TAG = '1'; 629 | expect(isCreateMinorVersionTag()).toBe(true); 630 | }); 631 | it('should return true 3', () => { 632 | process.env.INPUT_CREATE_MINOR_VERSION_TAG = 'abc'; 633 | expect(isCreateMinorVersionTag()).toBe(true); 634 | }); 635 | 636 | it('should return false 1', () => { 637 | process.env.INPUT_CREATE_MINOR_VERSION_TAG = 'false'; 638 | expect(isCreateMinorVersionTag()).toBe(false); 639 | }); 640 | 641 | it('should return false 2', () => { 642 | process.env.INPUT_CREATE_MINOR_VERSION_TAG = '0'; 643 | expect(isCreateMinorVersionTag()).toBe(false); 644 | }); 645 | }); 646 | 647 | describe('isCreatePatchVersionTag', () => { 648 | testEnv(rootDir); 649 | 650 | it('should return true 1', () => { 651 | expect(isCreatePatchVersionTag()).toBe(true); 652 | }); 653 | it('should return true 2', () => { 654 | process.env.INPUT_CREATE_PATCH_VERSION_TAG = '1'; 655 | expect(isCreatePatchVersionTag()).toBe(true); 656 | }); 657 | it('should return true 3', () => { 658 | process.env.INPUT_CREATE_PATCH_VERSION_TAG = 'abc'; 659 | expect(isCreatePatchVersionTag()).toBe(true); 660 | }); 661 | 662 | it('should return false 1', () => { 663 | process.env.INPUT_CREATE_PATCH_VERSION_TAG = 'false'; 664 | expect(isCreatePatchVersionTag()).toBe(false); 665 | }); 666 | 667 | it('should return false 2', () => { 668 | process.env.INPUT_CREATE_PATCH_VERSION_TAG = '0'; 669 | expect(isCreatePatchVersionTag()).toBe(false); 670 | }); 671 | }); 672 | 673 | describe('isEnabledCleanTestTag', () => { 674 | testEnv(rootDir); 675 | 676 | it('should return true 1', () => { 677 | process.env.INPUT_CLEAN_TEST_TAG = '1'; 678 | expect(isEnabledCleanTestTag()).toBe(true); 679 | }); 680 | 681 | it('should return true 2', () => { 682 | process.env.INPUT_CLEAN_TEST_TAG = 'true'; 683 | expect(isEnabledCleanTestTag()).toBe(true); 684 | }); 685 | 686 | it('should return false 1', () => { 687 | process.env.INPUT_CLEAN_TEST_TAG = ''; 688 | expect(isEnabledCleanTestTag()).toBe(false); 689 | }); 690 | 691 | it('should return false 2', () => { 692 | process.env.INPUT_CLEAN_TEST_TAG = 'false'; 693 | expect(isEnabledCleanTestTag()).toBe(false); 694 | }); 695 | }); 696 | 697 | describe('getOutputBuildInfoFilename', () => { 698 | testEnv(rootDir); 699 | 700 | it('should get filename', () => { 701 | process.env.INPUT_OUTPUT_BUILD_INFO_FILENAME = 'test'; 702 | expect(getOutputBuildInfoFilename()).toBe('test'); 703 | }); 704 | 705 | it('should get empty 1', () => { 706 | process.env.INPUT_OUTPUT_BUILD_INFO_FILENAME = ''; 707 | expect(getOutputBuildInfoFilename()).toBe(''); 708 | }); 709 | 710 | it('should get empty 2', () => { 711 | expect(getOutputBuildInfoFilename()).toBe(''); 712 | }); 713 | 714 | it('should get empty 3', () => { 715 | process.env.INPUT_OUTPUT_BUILD_INFO_FILENAME = '/tmp/test.json'; 716 | expect(getOutputBuildInfoFilename()).toBe(''); 717 | }); 718 | 719 | it('should get empty 4', () => { 720 | process.env.INPUT_OUTPUT_BUILD_INFO_FILENAME = '../test.json'; 721 | expect(getOutputBuildInfoFilename()).toBe(''); 722 | }); 723 | }); 724 | 725 | describe('getCreateTags', () => { 726 | testEnv(rootDir); 727 | 728 | it('should get create tags 1', () => { 729 | expect(getCreateTags('v1.2.3')).toEqual(['v1.2.3', 'v1.2', 'v1']); 730 | }); 731 | 732 | it('should get create tags 2', () => { 733 | expect(getCreateTags('v1')).toEqual(['v1.0.0', 'v1.0', 'v1']); 734 | }); 735 | 736 | it('should get create tags 3', () => { 737 | expect(getCreateTags('v1.2')).toEqual(['v1.2.0', 'v1.2', 'v1']); 738 | }); 739 | 740 | it('should get create tags 4', () => { 741 | process.env.INPUT_CREATE_MAJOR_VERSION_TAG = 'false'; 742 | expect(getCreateTags('v1.2.3')).toEqual(['v1.2.3', 'v1.2']); 743 | }); 744 | 745 | it('should get create tags 5', () => { 746 | process.env.INPUT_CREATE_MINOR_VERSION_TAG = 'false'; 747 | expect(getCreateTags('v1.2.3')).toEqual(['v1.2.3', 'v1']); 748 | }); 749 | 750 | it('should get create tags 6', () => { 751 | expect(getCreateTags('v1.2.3.4')).toEqual(['v1.2.3.4', 'v1.2.3', 'v1.2', 'v1']); 752 | }); 753 | 754 | it('should get create tags 7', () => { 755 | process.env.INPUT_CREATE_PATCH_VERSION_TAG = 'false'; 756 | expect(getCreateTags('v1.2.3')).toEqual(['v1.2.3', 'v1.2', 'v1']); 757 | }); 758 | 759 | it('should get create tags 8', () => { 760 | process.env.INPUT_CREATE_PATCH_VERSION_TAG = 'false'; 761 | expect(getCreateTags('v1.2.3.4')).toEqual(['v1.2.3.4', 'v1.2', 'v1']); 762 | }); 763 | 764 | it('should get create tags 9', () => { 765 | process.env.INPUT_CREATE_MAJOR_VERSION_TAG = 'false'; 766 | process.env.INPUT_CREATE_MINOR_VERSION_TAG = 'false'; 767 | process.env.INPUT_CREATE_PATCH_VERSION_TAG = 'false'; 768 | expect(getCreateTags('v1.2.3')).toEqual(['v1.2.3']); 769 | }); 770 | 771 | it('should get create tags 10', () => { 772 | process.env.INPUT_TEST_TAG_PREFIX = 'test/'; 773 | expect(getCreateTags('test/v1.2.3')).toEqual(['test/v1.2.3', 'test/v1.2', 'test/v1']); 774 | }); 775 | 776 | it('should get create tags 11', () => { 777 | process.env.INPUT_CREATE_MAJOR_VERSION_TAG = 'false'; 778 | process.env.INPUT_CREATE_MINOR_VERSION_TAG = 'false'; 779 | process.env.INPUT_CREATE_PATCH_VERSION_TAG = 'false'; 780 | process.env.INPUT_TEST_TAG_PREFIX = 'test/'; 781 | expect(getCreateTags('test/v1.2.3')).toEqual(['test/v1.2.3']); 782 | }); 783 | }); 784 | -------------------------------------------------------------------------------- /src/utils/misc.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from '@actions/github/lib/context'; 2 | import fs from 'fs'; 3 | import { resolve } from 'path'; 4 | import { getInput } from '@actions/core' ; 5 | import { Utils, ContextHelper } from '@technote-space/github-action-helper'; 6 | import memize from 'memize'; 7 | import { DEFAULT_FETCH_DEPTH } from '../constant'; 8 | 9 | type CommandType = string | { 10 | command: string; 11 | args?: Array | undefined; 12 | quiet?: boolean | undefined; 13 | altCommand?: string | undefined; 14 | suppressError?: boolean | undefined; 15 | suppressOutput?: boolean | undefined; 16 | stderrToStdout?: boolean | undefined; 17 | }; 18 | 19 | const getCleanTargets = (): Array => Utils.getArrayInput('CLEAN_TARGETS') 20 | .map(target => target.replace(/[\x00-\x1f\x80-\x9f]/, '').trim()) // eslint-disable-line no-control-regex 21 | .filter(target => target && !target.startsWith('/') && !target.includes('..')); 22 | 23 | export const getSearchBuildCommandTargets = (): Array => Utils.getArrayInput('BUILD_COMMAND_TARGET', true); 24 | 25 | export const detectBuildCommands = (dir: string, runCommand: string, commands: Array): Array => { 26 | const packageFile = resolve(dir, 'package.json'); 27 | if (!fs.existsSync(packageFile)) { 28 | return []; 29 | } 30 | 31 | const parsed = JSON.parse(fs.readFileSync(packageFile, 'utf8')); 32 | if (!('scripts' in parsed)) { 33 | return []; 34 | } 35 | 36 | const scripts = parsed['scripts']; 37 | const targets = Array(); 38 | for (const target of getSearchBuildCommandTargets()) { 39 | if (target in scripts && !commands.includes(`${runCommand}${target}`)) { 40 | targets.push(target); 41 | } 42 | } 43 | 44 | // eslint-disable-next-line no-magic-numbers 45 | return Utils.getBoolValue(getInput('ALLOW_MULTIPLE_BUILD_COMMANDS')) ? targets : targets.slice(0, 1); 46 | }; 47 | 48 | export const getBackupCommands = (buildDir: string, pushDir: string): Array => [ 49 | { 50 | command: 'mv', 51 | args: ['-f', resolve(buildDir, 'action.yaml'), resolve(pushDir, 'action.yml')], 52 | suppressError: true, 53 | quiet: true, 54 | }, 55 | { 56 | command: 'mv', 57 | args: ['-f', resolve(buildDir, 'action.yml'), resolve(pushDir, 'action.yml')], 58 | suppressError: true, 59 | quiet: true, 60 | }, 61 | ]; 62 | 63 | export const getRestoreBackupCommands = (buildDir: string, pushDir: string): Array => [ 64 | { 65 | command: 'mv', 66 | args: ['-f', resolve(pushDir, 'action.yml'), resolve(buildDir, 'action.yml')], 67 | suppressError: true, 68 | quiet: true, 69 | }, 70 | ]; 71 | 72 | export const getClearFilesCommands = (targets: Array): Array => { 73 | const commands: Array = []; 74 | const searchValues = '?<>:|"\'@#$%^& ;'; 75 | const replaceValue = '$1\\$2'; 76 | const escapeFunc = (item: string): string => searchValues.split('').reduce((acc, val) => acc.replace(new RegExp('([^\\\\])(' + Utils.escapeRegExp(val) + ')'), replaceValue), item); 77 | const beginWithDash = targets.filter(item => item.startsWith('-')).map(escapeFunc); 78 | const withWildcard = targets.filter(item => !item.startsWith('-') && item.includes('*')).map(escapeFunc); 79 | const withoutWildcard = targets.filter(item => !item.startsWith('-') && !item.includes('*')); 80 | 81 | if (beginWithDash.length) { 82 | commands.push(...beginWithDash.map(target => `rm -rdf -- ${target}`)); 83 | } 84 | 85 | if (withWildcard.length) { 86 | commands.push(...withWildcard.map(target => `rm -rdf ${target}`)); 87 | } 88 | 89 | if (withoutWildcard.length) { 90 | commands.push({ command: 'rm', args: ['-rdf', ...withoutWildcard] }); 91 | } 92 | 93 | return commands; 94 | }; 95 | 96 | export const getBuildCommands = (buildDir: string, pushDir: string): Array => { 97 | const commands: Array = Utils.getArrayInput('BUILD_COMMAND', false, '&&').map(command => command.replace(/\s{2,}/g, ' ')); 98 | const pkgManager = Utils.useNpm(buildDir, getInput('PACKAGE_MANAGER')) ? 'npm' : 'yarn'; 99 | const runSubCommand = pkgManager === 'npm' ? ' run ' : ' '; 100 | const runCommand = [pkgManager, runSubCommand].join(''); 101 | const hasInstallCommand = !!commands.filter(command => command.includes(`${runCommand}install`)).length; 102 | const buildCommands = detectBuildCommands(buildDir, runCommand, commands); 103 | const deleteNodeModules = Utils.getBoolValue(getInput('DELETE_NODE_MODULES')); 104 | 105 | if (buildCommands.length) { 106 | commands.push(...buildCommands.map(command => `${runCommand}${command}`)); 107 | } 108 | 109 | if (!hasInstallCommand && commands.length) { 110 | commands.unshift(`${pkgManager} install`); 111 | } 112 | 113 | if (deleteNodeModules) { 114 | commands.push('rm -rdf node_modules'); 115 | } else if (!hasInstallCommand) { 116 | if ('npm' === pkgManager) { 117 | commands.push('rm -rdf node_modules'); 118 | } 119 | commands.push(`${pkgManager} install --production`); 120 | } 121 | 122 | return [ 123 | ...commands, 124 | ...getBackupCommands(buildDir, pushDir), 125 | ...getClearFilesCommands(getCleanTargets()), 126 | ...getRestoreBackupCommands(buildDir, pushDir), 127 | ]; 128 | }; 129 | 130 | export const getCommitMessage = (): string => getInput('COMMIT_MESSAGE', { required: true }); 131 | 132 | export const getCommitName = (): string => getInput('COMMIT_NAME', { required: true }); 133 | 134 | export const getCommitEmail = (): string => getInput('COMMIT_EMAIL', { required: true }); 135 | 136 | export const getBranchNames = (): Array => Utils.getArrayInput('BRANCH_NAME', true); 137 | 138 | export const getFetchDepth = (): number => { 139 | const depth = getInput('FETCH_DEPTH'); 140 | if (depth && /^\d+$/.test(depth)) { 141 | return parseInt(depth, 10); 142 | } 143 | 144 | return DEFAULT_FETCH_DEPTH; 145 | }; 146 | 147 | export const getTestTagPrefix = (): string => getInput('TEST_TAG_PREFIX'); 148 | 149 | const getTestTagPrefixRegExp = (): RegExp => Utils.getPrefixRegExp(getTestTagPrefix()); 150 | 151 | export const isTestTag = (tagName: string): boolean => !!getTestTagPrefix() && getTestTagPrefixRegExp().test(tagName); 152 | 153 | export const getTestTag = (tagName: string): string => tagName.replace(getTestTagPrefixRegExp(), ''); 154 | 155 | export const getOriginalTagPrefix = (): string => getInput('ORIGINAL_TAG_PREFIX'); 156 | 157 | export const isCreateMajorVersionTag = (): boolean => Utils.getBoolValue(getInput('CREATE_MAJOR_VERSION_TAG')); 158 | 159 | export const isCreateMinorVersionTag = (): boolean => Utils.getBoolValue(getInput('CREATE_MINOR_VERSION_TAG')); 160 | 161 | export const isCreatePatchVersionTag = (): boolean => Utils.getBoolValue(getInput('CREATE_PATCH_VERSION_TAG')); 162 | 163 | export const isEnabledCleanTestTag = (): boolean => Utils.getBoolValue(getInput('CLEAN_TEST_TAG')); 164 | 165 | export const getOutputBuildInfoFilename = (): string => { 166 | const filename = getInput('OUTPUT_BUILD_INFO_FILENAME'); 167 | if (filename.startsWith('/') || filename.includes('..')) { 168 | return ''; 169 | } 170 | 171 | return filename; 172 | }; 173 | 174 | type createTagType = (tagName: string) => string; 175 | 176 | // eslint-disable-next-line no-magic-numbers 177 | export const getMajorTag = (tagName: string): string => 'v' + Utils.normalizeVersion(tagName, { slice: 1 }); 178 | 179 | // eslint-disable-next-line no-magic-numbers 180 | export const getMinorTag = (tagName: string): string => 'v' + Utils.normalizeVersion(tagName, { slice: 2 }); 181 | 182 | // eslint-disable-next-line no-magic-numbers 183 | export const getPatchTag = (tagName: string): string => 'v' + Utils.normalizeVersion(tagName, { slice: 3 }); 184 | 185 | export const isValidTagName = (tagName: string): boolean => Utils.isValidSemanticVersioning(tagName) || (isTestTag(tagName) && Utils.isValidSemanticVersioning(getTestTag(tagName))); 186 | 187 | export const getCreateTags = (tagName: string): Array => { 188 | const settings = [ 189 | { condition: isCreateMajorVersionTag, createTag: getMajorTag }, 190 | { condition: isCreateMinorVersionTag, createTag: getMinorTag }, 191 | { condition: isCreatePatchVersionTag, createTag: getPatchTag }, 192 | ]; 193 | const createTag = isTestTag(tagName) ? (create: createTagType): string => getTestTagPrefix() + create(getTestTag(tagName)) : (create: createTagType): string => create(tagName); 194 | 195 | return Utils.uniqueArray(settings.filter(setting => setting.condition()).map(setting => createTag(setting.createTag)).concat(tagName)).sort().reverse(); 196 | }; 197 | 198 | const params = (context: Context): { workDir: string; buildDir: string; pushDir: string; branchName: string; branchNames: Array; tagName: string } => { 199 | const workDir = resolve(Utils.getWorkspace(), '.work'); 200 | const buildDir = resolve(workDir, 'build'); 201 | const pushDir = resolve(workDir, 'push'); 202 | const tagName = ContextHelper.getTagName(context); 203 | const normalized = isTestTag(tagName) ? getTestTag(tagName) : tagName; 204 | const rawBranchNames = getBranchNames(); 205 | const getBranch = (branch: string): string => [ 206 | { key: 'MAJOR', func: getMajorTag }, 207 | { key: 'MINOR', func: getMinorTag }, 208 | { key: 'PATCH', func: getPatchTag }, 209 | ].reduce((acc, item) => Utils.replaceAll(acc, `\${${item.key}}`, item.func(normalized)), branch); 210 | const branchNames = rawBranchNames.map(getBranch); 211 | const branchName = branchNames[0]!; 212 | // eslint-disable-next-line no-magic-numbers 213 | return { workDir, buildDir, pushDir, branchName, branchNames: branchNames.slice(1), tagName }; 214 | }; 215 | 216 | export const getParams = memize(params); 217 | 218 | export const getReplaceDirectory = (context: Context): { [key: string]: string } => { 219 | const { workDir, buildDir, pushDir } = getParams(context); 220 | return { 221 | [buildDir]: '', 222 | [pushDir]: '', 223 | [workDir]: '', 224 | }; 225 | }; 226 | 227 | export const isValidContext = (context: Context): boolean => isValidTagName(getParams(context).tagName); 228 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sindresorhus/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "lib", 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 | } 18 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------