├── ._travis.yml
├── .github
└── workflows
│ ├── ci.yaml
│ └── codeql-analysis.yml
├── .gitignore
├── .releaserc.js
├── .yarn
├── plugins
│ └── @yarnpkg
│ │ └── plugin-workspace-tools.cjs
└── releases
│ └── yarn-4.0.0-rc.45.cjs
├── .yarnrc.yml
├── LICENSE
├── README.md
├── jest.config.js
├── package.json
├── packages
├── common
│ ├── .eslintrc.cjs
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.cjs
│ ├── package.json
│ ├── src
│ │ ├── main
│ │ │ └── ts
│ │ │ │ ├── flags.ts
│ │ │ │ ├── ifaces.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── tpl.ts
│ │ └── test
│ │ │ └── ts
│ │ │ └── unit
│ │ │ ├── flags.ts
│ │ │ ├── index.ts
│ │ │ └── tpl.ts
│ ├── tsconfig.es6.json
│ ├── tsconfig.esnext.json
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── typedoc.json
├── config-monorepo
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ └── src
│ │ ├── main
│ │ └── js
│ │ │ └── index.js
│ │ └── test
│ │ └── js
│ │ └── index.js
├── config
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ └── src
│ │ ├── main
│ │ └── js
│ │ │ └── index.js
│ │ └── test
│ │ └── js
│ │ └── index.js
├── git-sync
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.cjs
│ ├── package.json
│ └── src
│ │ ├── main
│ │ └── js
│ │ │ ├── get-git-auth-url.js
│ │ │ └── index.js
│ │ └── test
│ │ └── js
│ │ └── index.js
├── git-utils
│ ├── .eslintrc.cjs
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.cjs
│ ├── package.json
│ ├── src
│ │ ├── main
│ │ │ └── ts
│ │ │ │ ├── git
│ │ │ │ ├── add.ts
│ │ │ │ ├── branch.ts
│ │ │ │ ├── checkout.ts
│ │ │ │ ├── commit.ts
│ │ │ │ ├── config.ts
│ │ │ │ ├── etc.ts
│ │ │ │ ├── exec.ts
│ │ │ │ ├── fetch.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── init.ts
│ │ │ │ ├── push.ts
│ │ │ │ ├── rebase.ts
│ │ │ │ ├── remote.ts
│ │ │ │ ├── tag.ts
│ │ │ │ └── user.ts
│ │ │ │ ├── ifaces.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── misc.ts
│ │ └── test
│ │ │ ├── fixtures
│ │ │ ├── basicPackage
│ │ │ │ ├── inner
│ │ │ │ │ └── file.txt
│ │ │ │ └── package.json
│ │ │ └── foo
│ │ │ │ ├── bar
│ │ │ │ └── foobar.txt
│ │ │ │ ├── baz
│ │ │ │ └── foobaz.txt
│ │ │ │ └── foo
│ │ │ │ └── foofoo.txt
│ │ │ └── ts
│ │ │ ├── it
│ │ │ └── git.ts
│ │ │ └── unit
│ │ │ ├── git.ts
│ │ │ └── index.ts
│ ├── tsconfig.es6.json
│ ├── tsconfig.esnext.json
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── typedoc.json
├── infra
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── index.js
│ ├── jest.config.json
│ ├── package.json
│ └── tsconfig.compiler.json
├── metabranch
│ ├── .eslintrc.cjs
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.cjs
│ ├── package.json
│ ├── src
│ │ ├── main
│ │ │ ├── exports
│ │ │ │ ├── es5.cjs
│ │ │ │ └── es6.mjs
│ │ │ └── ts
│ │ │ │ ├── actions.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── interface.ts
│ │ │ │ ├── options.ts
│ │ │ │ └── plugin.ts
│ │ └── test
│ │ │ ├── fixtures
│ │ │ ├── basicPackage
│ │ │ │ ├── inner
│ │ │ │ │ └── file.txt
│ │ │ │ └── package.json
│ │ │ └── foo
│ │ │ │ ├── bar
│ │ │ │ └── foobar.txt
│ │ │ │ ├── baz
│ │ │ │ └── foobaz.txt
│ │ │ │ └── foo
│ │ │ │ └── foofoo.txt
│ │ │ └── ts
│ │ │ ├── actions.ts
│ │ │ ├── index.ts
│ │ │ └── plugin.ts
│ ├── tsconfig.es5.json
│ ├── tsconfig.es6.json
│ ├── tsconfig.esnext.json
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── typedoc.json
├── plugin-actions
│ ├── .eslintrc.js
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ │ ├── main
│ │ │ └── ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── interface.ts
│ │ │ │ └── providers
│ │ │ │ └── metabranch.ts
│ │ └── test
│ │ │ ├── js
│ │ │ └── index.js
│ │ │ └── ts
│ │ │ └── index.ts
│ ├── tsconfig.es5.json
│ ├── tsconfig.es6.json
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── typedoc.json
├── plugin-creator
│ ├── .eslintrc.cjs
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.mjs
│ ├── package.json
│ ├── src
│ │ ├── main
│ │ │ ├── exports
│ │ │ │ ├── es5.cjs
│ │ │ │ └── es6.mjs
│ │ │ └── ts
│ │ │ │ ├── index.ts
│ │ │ │ └── interface.ts
│ │ └── test
│ │ │ ├── cjs
│ │ │ └── index.cjs
│ │ │ ├── fixtures
│ │ │ └── yarnWorkspaces
│ │ │ │ ├── package.json
│ │ │ │ └── packages
│ │ │ │ ├── a
│ │ │ │ └── package.json
│ │ │ │ ├── b
│ │ │ │ └── package.json
│ │ │ │ ├── c
│ │ │ │ ├── .releaserc.json
│ │ │ │ └── package.json
│ │ │ │ └── d
│ │ │ │ └── package.json
│ │ │ ├── mjs
│ │ │ └── index.mjs
│ │ │ └── ts
│ │ │ ├── index.ts
│ │ │ └── integration.ts
│ ├── tsconfig.es5.json
│ ├── tsconfig.es6.json
│ ├── tsconfig.esnext.json
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── typedoc.json
├── preset
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ └── src
│ │ └── test
│ │ └── js
│ │ └── index.js
├── testing-suite
│ ├── .eslintrc.cjs
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.cjs
│ ├── package.json
│ ├── src
│ │ ├── main
│ │ │ └── ts
│ │ │ │ ├── file.ts
│ │ │ │ ├── git.ts
│ │ │ │ └── index.ts
│ │ └── test
│ │ │ ├── fixtures
│ │ │ ├── basicPackage
│ │ │ │ └── package.json
│ │ │ └── foo
│ │ │ │ ├── bar
│ │ │ │ └── foobar.txt
│ │ │ │ ├── baz
│ │ │ │ └── foobaz.txt
│ │ │ │ └── foo
│ │ │ │ └── foofoo.txt
│ │ │ └── ts
│ │ │ ├── it
│ │ │ └── git.ts
│ │ │ └── unit
│ │ │ ├── file.ts
│ │ │ ├── git.ts
│ │ │ └── index.ts
│ ├── tsconfig.es5.json
│ ├── tsconfig.es6.json
│ ├── tsconfig.esnext.json
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── typedoc.json
└── toolkit
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── msr.js
│ ├── package.json
│ └── semrel.js
├── renovate.json
├── scripts
├── js
│ └── coverage-merge.js
└── sh
│ ├── patch-pkg-main.sh
│ └── update.sh
├── tsconfig.esnext.json
├── tsconfig.json
└── yarn.lock
/._travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js: 14
3 | install: skip
4 | cache: yarn
5 |
6 | os: linux
7 | dist: focal
8 |
9 | jobs:
10 | fast_finish: true
11 | include:
12 | - stage: verify
13 | if: branch != master AND type != pull_request
14 | install:
15 | - yarn
16 | - yarn bootstrap
17 | script:
18 | - yarn build
19 | - if [ "$CI_TEST" != "false" ]; then
20 | yarn test;
21 | fi
22 | - &build
23 | if: branch = master
24 | stage: build
25 | install:
26 | - yarn
27 | - yarn bootstrap
28 | script:
29 | - yarn build
30 | - yarn uglify
31 | # https://docs.travis-ci.com/user/using-workspaces/
32 | workspaces:
33 | create:
34 | name: linux-shared
35 | paths:
36 | - node_modules
37 | - packages
38 | - <<: *build
39 | if: branch = master AND env(CI_WIN_BUILD) = true AND type = pull_request
40 | os: windows
41 | script: yarn build
42 | # https://travis-ci.community/t/timeout-after-build-finished-and-succeeded/1336/2
43 | env: YARN_GPG=no
44 | workspaces:
45 | create:
46 | name: win-shared
47 | paths:
48 | - node_modules
49 | - packages/**/target
50 |
51 | - &test
52 | if: branch = master AND type = pull_request AND env(CI_TEST) != false
53 | stage: test
54 | script: yarn test
55 | workspaces:
56 | use: linux-shared
57 | - <<: *test
58 | node_js: 12
59 | - <<: *test
60 | if: branch = master AND type != pull_request AND env(CI_TEST) != false
61 | before_script:
62 | - if [ "$CC_TEST_REPORTER_ID" != "" ]; then
63 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter;
64 | chmod +x ./cc-test-reporter;
65 | ./cc-test-reporter before-build;
66 | fi
67 | script:
68 | - if [ "$CC_TEST_REPORTER_ID" != "" ]; then
69 | yarn test:report;
70 | else
71 | yarn test;
72 | fi
73 | after_script:
74 | - if [ "$CC_TEST_REPORTER_ID" != "" ]; then
75 | ./cc-test-reporter format-coverage -t lcov ./coverage/lcov.info;
76 | ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT;
77 | fi
78 | - <<: *test
79 | if: branch = master AND type = pull_request AND env(CI_WIN_BUILD) = true AND env(CI_TEST) != false
80 | os: windows
81 | env: YARN_GPG=no
82 | install: yarn bootstrap
83 | workspaces:
84 | use: win-shared
85 | - <<: *test
86 | if: branch = master AND type = pull_request AND env(CI_WIN_BUILD) = true AND env(CI_TEST) != false
87 | os: windows
88 | node_js: 12
89 | install: yarn bootstrap
90 | env: YARN_GPG=no
91 | workspaces:
92 | use: win-shared
93 |
94 | - stage: release
95 | if: branch = master AND type != pull_request AND env(CI_RELEASE) = true
96 | workspaces:
97 | use: linux-shared
98 | script: yarn release
99 | # script: echo 'Deploy step is disabled' && exit 0
100 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | # This is a Github Workflow that runs tests on any push or pull request.
2 | # If the tests pass and this is a push to the master branch it also runs Semantic Release.
3 | name: CI
4 | on: [push, pull_request]
5 | jobs:
6 | init:
7 | name: init
8 | runs-on: ubuntu-22.04
9 | outputs:
10 | skip: ${{ steps.ci-skip-step.outputs.ci-skip }}
11 | skip-not: ${{ steps.ci-skip-step.outputs.ci-skip-not }}
12 | steps:
13 | - uses: actions/checkout@v3
14 |
15 | - id: ci-skip-step
16 | uses: mstachniuk/ci-skip@v1
17 |
18 | lint:
19 | name: lint
20 | needs: init
21 | runs-on: ubuntu-22.04
22 | steps:
23 | - uses: actions/checkout@v3
24 | - uses: actions/setup-node@v3
25 | with:
26 | node-version: 16
27 | - name: Install deps
28 | run: yarn
29 | - name: Lint
30 | run: yarn lint
31 |
32 | build:
33 | name: build
34 | needs: lint
35 | if: ${{ needs.init.outputs.skip == 'false' }}
36 | runs-on: ubuntu-22.04
37 | steps:
38 | - name: Checkout
39 | uses: actions/checkout@v3
40 |
41 | - uses: actions/setup-node@v3
42 | with:
43 | node-version: 16
44 | - name: Install deps
45 | run: yarn
46 |
47 | - name: Build
48 | run: yarn build
49 |
50 | - name: Save target (artifact)
51 | uses: actions/upload-artifact@v3
52 | with:
53 | name: target
54 | retention-days: 1
55 | # If a wildcard pattern is used, the path hierarchy will be preserved after the first wildcard pattern.
56 | # https://github.com/actions/upload-artifact#upload-using-multiple-paths-and-exclusions
57 | path: |
58 | !packages/*/node_modules
59 | !packages/*/src
60 | packages
61 | renovate.json
62 |
63 | test_push:
64 | needs: build
65 | if: github.event_name == 'push'
66 | runs-on: ubuntu-22.04
67 | steps:
68 | - name: Checkout
69 | uses: actions/checkout@v3
70 |
71 | - name: Restore target
72 | uses: actions/download-artifact@v3
73 | with:
74 | name: target
75 | - uses: actions/setup-node@v3
76 | with:
77 | node-version: 16
78 | - name: Install deps
79 | run: |
80 | sudo apt-get install moreutils
81 | yarn
82 |
83 | - name: Unit test only
84 | run: yarn test:unit
85 |
86 | - name: Push coverage
87 | if: github.ref == 'refs/heads/master'
88 | uses: actions/upload-artifact@v3
89 | with:
90 | name: target
91 | retention-days: 1
92 | path: coverage
93 |
94 | test_pr:
95 | if: github.event_name == 'pull_request'
96 | needs: build
97 | strategy:
98 | matrix:
99 | os: [ ubuntu-22.04 ]
100 | node-version: [ 14, 16 ]
101 | name: Test (Node v${{ matrix.node-version }}, OS ${{ matrix.os }})
102 | runs-on: ${{ matrix.os }}
103 | steps:
104 | - name: Checkout
105 | uses: actions/checkout@v3
106 |
107 | - name: Restore target
108 | uses: actions/download-artifact@v3
109 | with:
110 | name: target
111 | - uses: actions/setup-node@v3
112 | with:
113 | node-version: ${{ matrix.node-version }}
114 | - name: Install deps
115 | run: |
116 | sudo apt-get install moreutils
117 | yarn
118 | - name: Unit test only
119 | if: matrix.node-version != '16' || matrix.os != 'ubuntu-22.04'
120 | run: yarn test:unit
121 | - name: Full test
122 | if: matrix.node-version == '16' && matrix.os == 'ubuntu-22.04'
123 | run: yarn test
124 |
125 | release:
126 | name: Release
127 | # https://github.community/t/trigger-job-on-tag-push-only/18076
128 | if: github.event_name == 'push' && github.ref == 'refs/heads/master'
129 | needs: test_push
130 | runs-on: ubuntu-22.04
131 | steps:
132 | - name: Checkout
133 | uses: actions/checkout@v3
134 | with:
135 | fetch-depth: 0
136 |
137 | - name: Restore target
138 | uses: actions/download-artifact@v3
139 | with:
140 | name: target
141 | - uses: actions/setup-node@v3
142 | with:
143 | node-version: 16
144 |
145 | - name: Codeclimate
146 | uses: paambaati/codeclimate-action@v3.2.0
147 | env:
148 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
149 | with:
150 | coverageLocations: |
151 | ${{github.workspace}}/coverage/*.lcov:lcov
152 |
153 | - name: Install deps
154 | # https://github.com/npm/cli/issues/4600
155 | run: |
156 | npm -v
157 | yarn -v
158 | yarn
159 |
160 | - name: Release
161 | env:
162 | GH_TOKEN: ${{ secrets.GH_TOKEN }}
163 | GH_USER: 'qiwibot'
164 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
165 | GIT_AUTHOR_EMAIL: 'opensource@qiwi.com'
166 | GIT_COMMITTER_EMAIL: 'opensource@qiwi.com'
167 | GIT_AUTHOR_NAME: '@qiwibot'
168 | GIT_COMMITTER_NAME: '@qiwibot'
169 | YARN_ENABLE_IMMUTABLE_INSTALLS: false
170 | run: yarn release
171 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | # ******** NOTE ********
12 |
13 | name: "CodeQL"
14 |
15 | on:
16 | push:
17 | branches: [ master ]
18 | pull_request:
19 | # The branches below must be a subset of the branches above
20 | branches: [ master ]
21 | schedule:
22 | - cron: '23 19 * * 5'
23 |
24 | jobs:
25 | analyze:
26 | name: Analyze
27 | runs-on: ubuntu-latest
28 |
29 | strategy:
30 | fail-fast: false
31 | matrix:
32 | language: [ 'javascript' ]
33 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
34 | # Learn more...
35 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
36 |
37 | steps:
38 | - name: Checkout repository
39 | uses: actions/checkout@v3
40 |
41 | # Initializes the CodeQL tools for scanning.
42 | - name: Initialize CodeQL
43 | uses: github/codeql-action/init@v2
44 | with:
45 | languages: ${{ matrix.language }}
46 | # If you wish to specify custom queries, you can do so here or in a config file.
47 | # By default, queries listed here will override any specified in a config file.
48 | # Prefix the list here with "+" to use these queries and those in the config file.
49 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
50 |
51 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
52 | # If this step fails, then you should remove it and run the build manually (see below)
53 | - name: Autobuild
54 | uses: github/codeql-action/autobuild@v2
55 |
56 | # ℹ️ Command-line programs to run using the OS shell.
57 | # 📚 https://git.io/JvXDl
58 |
59 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
60 | # and modify them (or add more) to build your code if your project
61 | # uses a compiled language
62 |
63 | #- run: |
64 | # make bootstrap
65 | # make release
66 |
67 | - name: Perform CodeQL Analysis
68 | uses: github/codeql-action/analyze@v2
69 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Coverage
19 | flow-coverage
20 | lib-cov
21 | coverage
22 | .nyc_output
23 |
24 | # Bundle
25 | build
26 | dist
27 | lib
28 | target
29 |
30 | # Docs
31 | docs
32 | doc
33 |
34 | # Deps
35 | node_modules/
36 | jspm_packages/
37 | flow-typed/npm/
38 |
39 | .npm
40 | .eslintcache
41 | .node_repl_history
42 | .env
43 |
44 | # IDE
45 | .idea
46 | *.iml
47 |
48 | # Libdefs
49 | flow-typed
50 | typings
51 |
52 | # Temp
53 | temp
54 | *.tmp
55 |
56 | # Typescript
57 | *.tsbuildinfo
58 | .tsbuildinfo
59 | buildcache
60 | .buildcache
61 | .rts2_cache_cjs
62 | .rts2_cache_es
63 | .rts2_cache_umd
64 |
65 | # Yarn berry
66 | .pnp.*
67 | .yarn/*
68 | !.yarn/patches
69 | !.yarn/plugins
70 | !.yarn/releases
71 | !.yarn/sdks
72 | !.yarn/versions
73 |
--------------------------------------------------------------------------------
/.releaserc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | cmd: 'yarn',
3 | changelog: 'changelog'
4 | }
5 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | enableTelemetry: false
2 |
3 | networkConcurrency: 256
4 |
5 | nmSelfReferences: false
6 |
7 | nodeLinker: node-modules
8 |
9 | plugins:
10 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
11 | spec: "@yarnpkg/plugin-workspace-tools"
12 |
13 | yarnPath: .yarn/releases/yarn-4.0.0-rc.45.cjs
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @qiwi/semrel
2 | [](https://github.com/qiwi/semantic-release-toolkit/actions/workflows/ci.yaml)
3 | [](https://codeclimate.com/github/qiwi/semantic-release-toolkit/maintainability)
4 | [](https://codeclimate.com/github/qiwi/semantic-release-toolkit/test_coverage)
5 |
6 | [Semantic-release](https://github.com/semantic-release/semantic-release) tools, plugins and configs for QIWI OSS projects
7 |
8 | ## Contents
9 | | Package | Description | Latest |
10 | |----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
11 | | [@qiwi/git-utils](./packages/git-utils) | Utils and helpers to interact with git | [](https://www.npmjs.com/package/@qiwi/git-utils) |
12 | | [@qiwi/semrel-toolkit](./packages/toolkit) | All-in-one utility to run [semantic-release](https://github.com/semantic-release/semantic-release) and [multi-semantic-release](https://github.com/qiwi/multi-semantic-release) tasks | [](https://www.npmjs.com/package/@qiwi/semrel-toolkit) |
13 | | [@qiwi/semrel-config](./packages/config) | Basic config to deploy a single github-hosted npm-package | [](https://www.npmjs.com/package/@qiwi/semrel-config) |
14 | | [@qiwi/semrel-config-monorepo](./packages/config) | Config to release github-hosted monorepos | [](https://www.npmjs.com/package/@qiwi/semrel-config-monorepo) |
15 | | [@qiwi/semrel-preset](./packages/preset) | Semrel plugin preset | [](https://www.npmjs.com/package/@qiwi/semrel-preset) |
16 | | [@qiwi/semrel-plugin-creator](./packages/plugin-creator) | Semrel plugin factory | [](https://www.npmjs.com/package/@qiwi/semrel-plugin-creator) |
17 | | [@qiwi/semrel-infra](./packages/infra) | Infra package: common assets, deps, etc | [](https://www.npmjs.com/package/@qiwi/semrel-infra) |
18 | | [@qiwi/semrel-testing-suite](./packages/testing-suite) | Testing helpers to verify release flow
**experimental** | [](https://www.npmjs.com/package/@qiwi/semrel-testing-suite) |
19 | | [@qiwi/semrel-metabranch](./packages/metabranch) | Plugin for two-way data sync with remote branch
**experimental** | [](https://www.npmjs.com/package/@qiwi/semrel-metabranch) |
20 |
21 | ### Coming ~~soon~~ someday
22 | | Package | Description |
23 | |-----------------------|-----------------------------------------------------------------------------------------------|
24 | | @qiwi/semrel-actions | Configurable custom actions/side-effects provider |
25 | | @qiwi/msr | **[multi-semantic-release](https://github.com/qiwi/multi-semantic-release)** reforged with TS |
26 | | @qiwi/semrel-monorepo | Represents **msr** as a regular plugin |
27 |
28 | ## License
29 | [MIT](./LICENSE)
30 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | const PROJECT = process.env.JEST_PROJECT
2 | const projects = [
3 | 'common',
4 | 'config',
5 | 'config-monorepo',
6 | 'git-utils',
7 | 'metabranch',
8 | 'preset',
9 | 'plugin-creator',
10 | 'plugin-actions',
11 | 'testing-suite'
12 | ]
13 |
14 | module.exports = {
15 | collectCoverage: true,
16 | collectCoverageFrom: [
17 | '/**/src/main/**/*.(j|t)s'
18 | ],
19 | testFailureExitCode: 1,
20 | projects: (PROJECT ? [PROJECT] : projects).map(name => `/packages/${name}/`),
21 | }
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-toolkit-monorepo",
3 | "version": "0.0.0",
4 | "description": "Semantic release tools, plugins and configs for QIWI OSS projects",
5 | "workspaces": [
6 | "packages/*"
7 | ],
8 | "private": true,
9 | "scripts": {
10 | "clean": "yarn workspaces foreach -p run clean",
11 | "lint": "yarn workspaces foreach -tp run lint",
12 | "lint:fix": "yarn workspaces foreach -tp run lint:fix",
13 | "test": "./node_modules/.bin/concurrently yarn:lint yarn:test:unit yarn:test:depcheck yarn:test:depaudit",
14 | "jest:pre": "sh ./scripts/sh/patch-pkg-main.sh true",
15 | "jest:post": "sh ./scripts/sh/patch-pkg-main.sh",
16 | "test:unit": "yarn jest:pre && NODE_OPTIONS=--experimental-vm-modules ./node_modules/.bin/jest --detectOpenHandles --forceExit --runInBand && yarn jest:post",
17 | "test:report": "yarn test && yarn coveralls:push",
18 | "test:concurrent": "yarn workspaces foreach -tp run && yarn coverage:merge",
19 | "test:depcheck": "yarn workspaces foreach -tp run test:depcheck",
20 | "test:depaudit": "yarn npm audit -R -A --severity moderate --environment production || echo 'yarn audit failed :(' && exit 0",
21 | "test:depauditfix": "npm_config_yes=true npx yarn-audit-fix --audit-level=moderate",
22 | "prebuild": "tsc -b",
23 | "build": "yarn workspaces foreach -tp run build",
24 | "coverage:merge": "node scripts/js/coverage-merge.js",
25 | "coveralls:push": "cat ./coverage/lcov.info | npm_config_yes=true npx coveralls || echo 'coveralls push failed :(' && exit 0",
26 | "release": "npm_config_yes=true npx zx-bulk-release",
27 | "updeps": "npm_config_yes=true npx npm-upgrade-monorepo",
28 | "postupdate": "yarn && yarn build && yarn test"
29 | },
30 | "devDependencies": {
31 | "find-git-root": "^1.0.4"
32 | },
33 | "resolutions": {
34 | "npm/chalk": "^4.1.2"
35 | },
36 | "repository": {
37 | "type": "git",
38 | "url": "git+https://github.com/qiwi/semantic-release-toolkit.git"
39 | },
40 | "packageManager": "yarn@4.0.0-rc.45"
41 | }
42 |
--------------------------------------------------------------------------------
/packages/common/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'eslint-config-qiwi',
4 | 'prettier',
5 | ],
6 | overrides: [
7 | {
8 | files: ['./src/test/**/*.ts'],
9 | rules: {
10 | 'unicorn/consistent-function-scoping': 'off',
11 | 'sonarjs/no-duplicate-string': 'off'
12 | }
13 | }
14 | ]
15 | };
16 |
--------------------------------------------------------------------------------
/packages/common/README.md:
--------------------------------------------------------------------------------
1 | # @qiwi/semrel-common
2 | Common semrel utils: config reader, git-client, etc.
3 |
4 | ## Install
5 | ```shell script
6 | yarn add @qiwi/semrel-common
7 | ```
8 |
9 | ## Usage
10 | ```typescript
11 | import { gitTags } from '@qiwi/semrel-common'
12 |
13 | const tags = await gitTags({ cwd: '/foo/bar/baz', branch: 'master' })
14 | ```
15 |
--------------------------------------------------------------------------------
/packages/common/jest.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {...require('../infra/jest.config.json')}
2 |
--------------------------------------------------------------------------------
/packages/common/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-common",
3 | "version": "3.4.2",
4 | "publishConfig": {
5 | "access": "public"
6 | },
7 | "description": "Semrel common utils",
8 | "keywords": [],
9 | "type": "module",
10 | "module": "./target/es6/index.js",
11 | "main": "./target/es6/index.js",
12 | "exports": "./target/es6/index.js",
13 | "source": "target/ts/index.ts",
14 | "types": "./target/es6/index.d.ts",
15 | "files": [
16 | "README.md",
17 | "CHANGELOG.md",
18 | "target",
19 | "typings",
20 | "flow-typed"
21 | ],
22 | "scripts": {
23 | "clean": "rimraf target typings flow-typed buildcache coverage docs",
24 | "lint": "eslint 'src/**/*.ts'",
25 | "lint:fix": "yarn lint --fix",
26 | "format": "prettier --write 'src/**/*.ts'",
27 | "test": "concurrently yarn:lint yarn:test:unit yarn:test:depcheck",
28 | "test:unit": "NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles --forceExit --runInBand",
29 | "test:depcheck": "npm_config_yes=true npx -p depcheck -p @babel/parser@7.16.4 depcheck --ignores='@jest/globals,@types/*,tslib,eslint-*,prettier-*,@qiwi/semrel-infra,@qiwi/semrel-common,semantic-release' --ignore-patterns='typings,flow-typed/*'",
30 | "build": "concurrently yarn:build:esnext yarn:build:es6 yarn:build:ts yarn:build:libdef yarn:docs && yarn build:esmfix",
31 | "build:esnext": "mkdirp target/esnext && tsc -p tsconfig.esnext.json",
32 | "build:es6": "mkdirp target/es6 && tsc -p tsconfig.es6.json",
33 | "build:ts": "cpy ./ ../../../target/ts/ --dot --cwd=./src/main/ts/",
34 | "build:libdef": "libdefkit --tsconfig=tsconfig.esnext.json --tsconfig=tsconfig.es6.json --entry=@qiwi/semrel-common/target/es6",
35 | "build:esmfix": "yarn tsc-esm-fix --target=target/es6 --target=target/esnext --ext=.js",
36 | "docs": "typedoc --options ./typedoc.json ./src/main/ts",
37 | "uglify": "for f in $(find target -name '*.js'); do short=${f%.js}; terser -c -m -o $short.js -- $f; done",
38 | "postupdate": "yarn && yarn build && yarn test"
39 | },
40 | "dependencies": {
41 | "@qiwi/substrate": "^1.20.15",
42 | "@types/node": "^18.11.7",
43 | "@types/semantic-release": "^17.2.4",
44 | "lodash-es": "^4.17.21",
45 | "minimist": "^1.2.7",
46 | "tslib": "^2.4.0"
47 | },
48 | "devDependencies": {
49 | "@qiwi/semrel-infra": "workspace:*"
50 | },
51 | "repository": {
52 | "type": "git",
53 | "url": "git+https://github.com/qiwi/semantic-release-toolkit.git"
54 | },
55 | "author": "Anton Golub ",
56 | "license": "MIT",
57 | "bugs": {
58 | "url": "https://github.com/qiwi/semantic-release-toolkit/issues"
59 | },
60 | "homepage": "https://github.com/qiwi/semantic-release-toolkit/#readme",
61 | "prettier": "prettier-config-qiwi"
62 | }
63 |
--------------------------------------------------------------------------------
/packages/common/src/main/ts/flags.ts:
--------------------------------------------------------------------------------
1 | import minimist from 'minimist'
2 |
3 | export const parseFlags = (argv: string[]): ReturnType =>
4 | minimist(argv, { '--': true })
5 |
6 | const checkValue = (
7 | key: string,
8 | value: any,
9 | omitlist: any[],
10 | picklist: any[],
11 | ): boolean =>
12 | value !== null &&
13 | value !== undefined &&
14 | value !== false &&
15 | value !== 'false' &&
16 | !omitlist.includes(key) &&
17 | (picklist.length === 0 || picklist.includes(key))
18 |
19 | const formatFlag = (key: string): string =>
20 | (key.length === 1 ? '-' : '--') + key
21 |
22 | export const formatFlags = (
23 | flags: Record,
24 | ...picklist: string[]
25 | ): string[] =>
26 | Object.keys(flags).reduce((memo, key: string) => {
27 | const omitlist = ['_', '--']
28 | const value = flags[key]
29 | const flag = formatFlag(key)
30 |
31 | if (checkValue(key, value, omitlist, picklist)) {
32 | memo.push(flag)
33 |
34 | if (value !== true) {
35 | memo.push(value)
36 | }
37 | }
38 |
39 | return memo
40 | }, [])
41 |
--------------------------------------------------------------------------------
/packages/common/src/main/ts/ifaces.ts:
--------------------------------------------------------------------------------
1 | import { Extends } from '@qiwi/substrate'
2 |
3 | export interface ISyncSensitive {
4 | sync?: boolean
5 | }
6 |
7 | export type TSyncDirective = boolean | undefined | ISyncSensitive
8 |
9 | export type ParseSync = Extends<
10 | T,
11 | boolean | undefined,
12 | T,
13 | T extends ISyncSensitive ? T['sync'] : never
14 | >
15 |
16 | export type SyncGuard = Extends<
17 | S,
18 | { sync: true } | true,
19 | Extends, never, V>,
20 | Extends, V, Promise>
21 | >
22 |
--------------------------------------------------------------------------------
/packages/common/src/main/ts/index.ts:
--------------------------------------------------------------------------------
1 | export * from './tpl'
2 | export * from './flags'
3 |
--------------------------------------------------------------------------------
/packages/common/src/main/ts/tpl.ts:
--------------------------------------------------------------------------------
1 | import { IAnyMap } from '@qiwi/substrate'
2 | import { template as compile } from 'lodash-es'
3 | import { Context } from 'semantic-release'
4 |
5 | export const tpl = (template: string, context: IAnyMap, logger: Context['logger']): string => {
6 | try {
7 | return compile(template)(context)
8 | } catch (err) {
9 | logger.error('lodash.template render failure', err)
10 |
11 | return template
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/common/src/test/ts/unit/flags.ts:
--------------------------------------------------------------------------------
1 | import { formatFlags, parseFlags } from '../../../main/ts/flags'
2 |
3 | describe('formatFlags()', () => {
4 | it('return proper values', () => {
5 | const cases: [Record, string[], string[]][] = [
6 | [{ _: [], '--': [] }, [], []],
7 | [{ foo: 'bar' }, [], ['--foo', 'bar']],
8 | [{ a: true, b: 'true', c: false, d: 'false' }, [], ['-a', '-b', 'true']],
9 | [{ verbose: true }, [], ['--verbose']],
10 | [
11 | { f: true, foo: 'bar', b: true, baz: 'qux' },
12 | ['f', 'baz'],
13 | ['-f', '--baz', 'qux'],
14 | ],
15 | [
16 | parseFlags([
17 | '-w',
18 | '1',
19 | '--force',
20 | '--audit-level=moderate',
21 | '--only=dev',
22 | '-c',
23 | 'ccc',
24 | '--',
25 | '--bar',
26 | '-b',
27 | '2',
28 | ]),
29 | ['force', 'audit-level', 'only', 'bar', 'b', 'c'],
30 | ['--force', '--audit-level', 'moderate', '--only', 'dev', '-c', 'ccc'],
31 | ],
32 | ]
33 |
34 | cases.forEach(([input, picklist, output]) => {
35 | expect(formatFlags(input, ...picklist)).toEqual(output)
36 | })
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/packages/common/src/test/ts/unit/index.ts:
--------------------------------------------------------------------------------
1 | import {formatFlags, parseFlags, tpl} from '../../../main/ts'
2 |
3 | describe('index', () => {
4 | it('properly exports its inners', () => {
5 | const utils = [formatFlags, parseFlags, tpl]
6 |
7 | utils.forEach((method) => expect(method).toEqual(expect.any(Function)))
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/packages/common/src/test/ts/unit/tpl.ts:
--------------------------------------------------------------------------------
1 | import { jest } from '@jest/globals'
2 |
3 | import { tpl } from '../../../main/ts'
4 |
5 | describe('tpl', () => {
6 | const error = jest.fn((...vars: any[]) => { console.log(vars) })
7 | const logger: any = {
8 | log (msg: string, ...vars: any[]) { console.log(vars || msg) },
9 | error
10 | }
11 |
12 | it('inject data to string', () => {
13 | expect(tpl('foo <%= bar %>', { bar: 'baz' }, logger)).toBe('foo baz')
14 | })
15 |
16 | it('returns template as is on failure', () => {
17 | const res = tpl('foo <%= bar.baz %>', { a: { b: 'c' } }, logger)
18 |
19 | expect(error).toHaveBeenCalledWith('lodash.template render failure', expect.any(Object))
20 | expect(res).toBe('foo <%= bar.baz %>')
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/packages/common/tsconfig.es6.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es6",
5 | "module": "es6",
6 | "outDir": "target/es6"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/common/tsconfig.esnext.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "esnext",
6 | "outDir": "target/esnext"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/common/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../packages/infra/tsconfig.compiler.json",
3 | "compilerOptions": {
4 | "rootDir": "./src/main/ts/",
5 | "baseUrl": "./src/main/ts/",
6 | "tsBuildInfoFile": "./buildcache/.tsbuildinfo",
7 | },
8 | "references": [],
9 | "include": [
10 | "src/main/**/*"
11 | ],
12 | "exclude": [
13 | "node_modules"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/common/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "esnext",
6 | "importHelpers": true,
7 | "noEmitHelpers": true,
8 | "esModuleInterop": true
9 | },
10 | "include": [
11 | "src/**/*"
12 | ],
13 | "exclude": [
14 | "node_modules"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/packages/common/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-common",
3 | "out": "./docs",
4 | "exclude": ["src/test", "**/node_modules/**", "paralleljs"],
5 | "externalPattern": ["**/node_modules/**"],
6 | "excludePrivate": false,
7 | "hideGenerator": true,
8 | "readme": "README.md",
9 | "theme": "default",
10 | "tsconfig": "./tsconfig.es6.json"
11 | }
12 |
--------------------------------------------------------------------------------
/packages/config-monorepo/README.md:
--------------------------------------------------------------------------------
1 | # @qiwi/semrel-config-monorepo
2 | Shared QIWI OSS config for [multi-semantic-release](https://github.com/qiwi/multi-semantic-release)
3 |
4 | ## Install
5 | ```shell script
6 | yarn add @qiwi/semrel-config-monorepo -D
7 | ```
8 |
9 | ## Usage
10 | Inject this shared config in any way supported by [**msr** configuration flow](https://github.com/dhoulb/multi-semantic-release#configuration).
11 | ```json
12 | {
13 | "extends": "@qiwi/semrel-config--monorepo"
14 | }
15 | ```
16 |
--------------------------------------------------------------------------------
/packages/config-monorepo/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {...require('../infra/jest.config.json')}
2 |
--------------------------------------------------------------------------------
/packages/config-monorepo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-config-monorepo",
3 | "version": "1.6.4",
4 | "publishConfig": {
5 | "access": "public"
6 | },
7 | "description": "QIWI shared semrel config for monorepos",
8 | "keywords": [],
9 | "main": "target/es5/index.js",
10 | "source": "target/es5/index.js",
11 | "files": [
12 | "README.md",
13 | "CHANGELOG.md",
14 | "target"
15 | ],
16 | "scripts": {
17 | "test": " yarn test:unit",
18 | "test:unit": "jest",
19 | "clean": "rimraf target",
20 | "build": "yarn build:es5",
21 | "build:es5": "mkdirp target/es5 && cpy src/main/js/ target/es5/ --flat",
22 | "postupdate": "yarn && yarn build && yarn test",
23 | "format": "prettier --write 'src/**/*.ts'"
24 | },
25 | "dependencies": {
26 | "@qiwi/semrel-preset": "workspace:*"
27 | },
28 | "devDependencies": {
29 | "@qiwi/semrel-infra": "workspace:*"
30 | },
31 | "peerDependencies": {
32 | "semantic-release": "*"
33 | },
34 | "repository": {
35 | "type": "git",
36 | "url": "git+https://github.com/qiwi/semantic-release-toolkit.git"
37 | },
38 | "author": "Anton Golub ",
39 | "license": "MIT",
40 | "bugs": {
41 | "url": "https://github.com/qiwi/semantic-release-toolkit/issues"
42 | },
43 | "homepage": "https://github.com/qiwi/semantic-release-toolkit/#readme"
44 | }
45 |
--------------------------------------------------------------------------------
/packages/config-monorepo/src/main/js/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | branch: 'master',
3 | plugins: [
4 | [
5 | '@semantic-release/commit-analyzer',
6 | {
7 | preset: 'angular',
8 | releaseRules: [
9 | {type: 'docs', release: 'patch'},
10 | {type: 'refactor', release: 'patch'},
11 | ],
12 | parserOpts: {
13 | noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES']
14 | }
15 | }
16 | ],
17 | '@semantic-release/release-notes-generator',
18 | '@semantic-release/changelog',
19 | [
20 | '@semantic-release/exec',
21 | {
22 | prepareCmd: 'CI=true YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install && git add ../../yarn.lock',
23 | }
24 | ],
25 | '@semrel-extra/npm',
26 | [
27 | '@semantic-release/github',
28 | {
29 | successComment: false,
30 | failComment: false
31 | }
32 | ],
33 | [
34 | '@semantic-release/git',
35 | {
36 | message: 'chore(release): ${nextRelease.gitTag} [skip ci]\n\n${nextRelease.notes}'
37 | }
38 | ]
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/packages/config-monorepo/src/test/js/index.js:
--------------------------------------------------------------------------------
1 | const config = require('../../main/js')
2 |
3 | describe('@qiwi/semrel-config-monorepo', () => {
4 | it('is not empty', () => {
5 | expect(config).not.toBeUndefined()
6 | expect(config).toEqual(require('../../../target/es5'))
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/packages/config/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## @qiwi/semrel-config [1.4.1](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.4.0...@qiwi/semrel-config@1.4.1) (2022-05-09)
2 |
3 |
4 | ### Performance Improvements
5 |
6 | * deps revision ([7974aab](https://github.com/qiwi/semantic-release-toolkit/commit/7974aab6ad056e840061ba03218e14cf81a2fd07))
7 |
8 |
9 |
10 |
11 |
12 | ### Dependencies
13 |
14 | * **@qiwi/semrel-preset:** upgraded to 3.1.10
15 |
16 | # @qiwi/semrel-config [1.4.0](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.23...@qiwi/semrel-config@1.4.0) (2022-04-12)
17 |
18 |
19 | ### Features
20 |
21 | * replace @qiwi/semantic-release-gh-pages-plugin with @qiwi/semrel-metabranch ([ca7bd1c](https://github.com/qiwi/semantic-release-toolkit/commit/ca7bd1c22b2483e5d0d6902c017bbef0b726d354))
22 |
23 | ## @qiwi/semrel-config [1.3.23](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.22...@qiwi/semrel-config@1.3.23) (2022-04-09)
24 |
25 |
26 |
27 |
28 |
29 | ### Dependencies
30 |
31 | * **@qiwi/semrel-preset:** upgraded to 3.1.9
32 | * **@qiwi/semrel-infra:** upgraded to 3.2.1
33 |
34 | ## @qiwi/semrel-config [1.3.22](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.21...@qiwi/semrel-config@1.3.22) (2022-03-12)
35 |
36 |
37 |
38 |
39 |
40 | ### Dependencies
41 |
42 | * **@qiwi/semrel-preset:** upgraded to 3.1.8
43 | * **@qiwi/semrel-infra:** upgraded to 3.2.0
44 |
45 | ## @qiwi/semrel-config [1.3.21](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.20...@qiwi/semrel-config@1.3.21) (2022-03-06)
46 |
47 |
48 |
49 |
50 |
51 | ### Dependencies
52 |
53 | * **@qiwi/semrel-preset:** upgraded to 3.1.7
54 | * **@qiwi/semrel-infra:** upgraded to 3.1.0
55 |
56 | ## @qiwi/semrel-config [1.3.20](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.19...@qiwi/semrel-config@1.3.20) (2022-03-06)
57 |
58 |
59 |
60 |
61 |
62 | ### Dependencies
63 |
64 | * **@qiwi/semrel-preset:** upgraded to 3.1.6
65 | * **@qiwi/semrel-infra:** upgraded to 3.0.8
66 |
67 | ## @qiwi/semrel-config [1.3.19](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.18...@qiwi/semrel-config@1.3.19) (2022-02-22)
68 |
69 |
70 |
71 |
72 |
73 | ### Dependencies
74 |
75 | * **@qiwi/semrel-preset:** upgraded to 3.1.5
76 |
77 | ## @qiwi/semrel-config [1.3.18](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.17...@qiwi/semrel-config@1.3.18) (2021-12-29)
78 |
79 |
80 |
81 |
82 |
83 | ### Dependencies
84 |
85 | * **@qiwi/semrel-preset:** upgraded to 3.1.4
86 |
87 | ## @qiwi/semrel-config [1.3.17](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.16...@qiwi/semrel-config@1.3.17) (2021-12-23)
88 |
89 |
90 |
91 |
92 |
93 | ### Dependencies
94 |
95 | * **@qiwi/semrel-preset:** upgraded to 3.1.3
96 |
97 | ## @qiwi/semrel-config [1.3.16](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.15...@qiwi/semrel-config@1.3.16) (2021-12-22)
98 |
99 |
100 |
101 |
102 |
103 | ### Dependencies
104 |
105 | * **@qiwi/semrel-preset:** upgraded to 3.1.2
106 |
107 | ## @qiwi/semrel-config [1.3.15](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.14...@qiwi/semrel-config@1.3.15) (2021-11-18)
108 |
109 |
110 |
111 |
112 |
113 | ### Dependencies
114 |
115 | * **@qiwi/semrel-preset:** upgraded to 3.1.1
116 |
117 | ## @qiwi/semrel-config [1.3.14](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.13...@qiwi/semrel-config@1.3.14) (2021-11-18)
118 |
119 |
120 |
121 |
122 |
123 | ### Dependencies
124 |
125 | * **@qiwi/semrel-preset:** upgraded to 3.1.0
126 |
127 | ## @qiwi/semrel-config [1.3.13](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.12...@qiwi/semrel-config@1.3.13) (2021-11-17)
128 |
129 |
130 |
131 |
132 |
133 | ### Dependencies
134 |
135 | * **@qiwi/semrel-preset:** upgraded to 3.0.0
136 |
137 | ## @qiwi/semrel-config [1.3.12](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.11...@qiwi/semrel-config@1.3.12) (2021-10-28)
138 |
139 |
140 |
141 |
142 |
143 | ### Dependencies
144 |
145 | * **@qiwi/semrel-preset:** upgraded to 2.1.0
146 |
147 | ## @qiwi/semrel-config [1.3.11](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.10...@qiwi/semrel-config@1.3.11) (2021-09-27)
148 |
149 |
150 |
151 |
152 |
153 | ### Dependencies
154 |
155 | * **@qiwi/semrel-preset:** upgraded to 2.0.0
156 |
157 | ## @qiwi/semrel-config [1.3.10](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.9...@qiwi/semrel-config@1.3.10) (2021-08-25)
158 |
159 |
160 |
161 |
162 |
163 | ### Dependencies
164 |
165 | * **@qiwi/semrel-preset:** upgraded to 1.3.2
166 |
167 | ## @qiwi/semrel-config [1.3.9](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.8...@qiwi/semrel-config@1.3.9) (2021-07-07)
168 |
169 |
170 |
171 |
172 |
173 | ### Dependencies
174 |
175 | * **@qiwi/semrel-preset:** upgraded to 1.3.1
176 |
177 | ## @qiwi/semrel-config [1.3.8](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.7...@qiwi/semrel-config@1.3.8) (2021-06-18)
178 |
179 |
180 |
181 |
182 |
183 | ### Dependencies
184 |
185 | * **@qiwi/semrel-preset:** upgraded to 1.3.0
186 |
187 | ## @qiwi/semrel-config [1.3.7](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.6...@qiwi/semrel-config@1.3.7) (2021-06-17)
188 |
189 |
190 |
191 |
192 |
193 | ### Dependencies
194 |
195 | * **@qiwi/semrel-preset:** upgraded to 1.2.6
196 |
197 | ## @qiwi/semrel-config [1.3.6](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.5...@qiwi/semrel-config@1.3.6) (2021-05-17)
198 |
199 |
200 |
201 |
202 |
203 | ### Dependencies
204 |
205 | * **@qiwi/semrel-preset:** upgraded to 1.2.5
206 |
207 | ## @qiwi/semrel-config [1.3.5](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.4...@qiwi/semrel-config@1.3.5) (2021-04-04)
208 |
209 |
210 |
211 |
212 |
213 | ### Dependencies
214 |
215 | * **@qiwi/semrel-preset:** upgraded to 1.2.4
216 |
217 | ## @qiwi/semrel-config [1.3.4](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.3...@qiwi/semrel-config@1.3.4) (2021-04-02)
218 |
219 |
220 |
221 |
222 |
223 | ### Dependencies
224 |
225 | * **@qiwi/semrel-preset:** upgraded to 1.2.3
226 |
227 | ## @qiwi/semrel-config [1.3.3](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.2...@qiwi/semrel-config@1.3.3) (2021-04-02)
228 |
229 |
230 |
231 |
232 |
233 | ### Dependencies
234 |
235 | * **@qiwi/semrel-preset:** upgraded to 1.2.2
236 |
237 | ## @qiwi/semrel-config [1.3.2](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.1...@qiwi/semrel-config@1.3.2) (2021-04-02)
238 |
239 |
240 |
241 |
242 |
243 | ### Dependencies
244 |
245 | * **@qiwi/semrel-preset:** upgraded to 1.2.1
246 |
247 | ## @qiwi/semrel-config [1.3.1](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.3.0...@qiwi/semrel-config@1.3.1) (2021-04-02)
248 |
249 |
250 |
251 |
252 |
253 | ### Dependencies
254 |
255 | * **@qiwi/semrel-preset:** upgraded to 1.2.0
256 |
257 | # @qiwi/semrel-config [1.3.0](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.2.2...@qiwi/semrel-config@1.3.0) (2021-03-22)
258 |
259 |
260 | ### Features
261 |
262 | * **config:** release refactoring as patche ([c2bbc97](https://github.com/qiwi/semantic-release-toolkit/commit/c2bbc97e4e265e839e034671bf629210ae99db45))
263 |
264 |
265 |
266 |
267 |
268 | ### Dependencies
269 |
270 | * **@qiwi/semrel-preset:** upgraded to 1.1.2
271 |
272 | ## @qiwi/semrel-config [1.2.2](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.2.1...@qiwi/semrel-config@1.2.2) (2021-03-12)
273 |
274 |
275 |
276 |
277 |
278 | ### Dependencies
279 |
280 | * **@qiwi/semrel-preset:** upgraded to 1.1.1
281 |
282 | ## @qiwi/semrel-config [1.2.1](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.2.0...@qiwi/semrel-config@1.2.1) (2021-03-11)
283 |
284 |
285 |
286 |
287 |
288 | ### Dependencies
289 |
290 | * **@qiwi/semrel-preset:** upgraded to 1.1.0
291 |
292 | # @qiwi/semrel-config [1.2.0](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.1.0...@qiwi/semrel-config@1.2.0) (2021-01-31)
293 |
294 |
295 | ### Features
296 |
297 | * **config:** include semrel-preset to pkg deps ([6998b42](https://github.com/qiwi/semantic-release-toolkit/commit/6998b4212df4665274b43978ca7ab2fad58b37ec))
298 |
299 | # @qiwi/semrel-config [1.1.0](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/semrel-config@1.0.0...@qiwi/semrel-config@1.1.0) (2020-10-29)
300 |
301 |
302 | ### Features
303 |
304 | * introduce plugin preset package ([dff254f](https://github.com/qiwi/semantic-release-toolkit/commit/dff254ff4b4d5088e165acb97e28f9e40f84bd20))
305 |
306 | # @qiwi/semrel-config 1.0.0 (2020-10-28)
307 |
308 |
309 | ### Features
310 |
311 | * add semrel-config and semrel-config-monorepo ([ad7ba33](https://github.com/qiwi/semantic-release-toolkit/commit/ad7ba33cf6f6705c1f1f1919c197d5ad7345de4b))
312 |
--------------------------------------------------------------------------------
/packages/config/README.md:
--------------------------------------------------------------------------------
1 | # @qiwi/semrel-config
2 | Shared QIWI OSS config for [semantic-release](https://github.com/semantic-release/semantic-release)
3 |
4 | ## Install
5 | ```shell script
6 | yarn add @qiwi/semrel-config -D
7 | ```
8 |
9 | ## Usage
10 | Inject this shared config in any way supported by [semrel configuration flow](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#configuration). For example, **.releaserc**:
11 | ```json
12 | {
13 | "extends": "@qiwi/semrel-config"
14 | }
15 | ```
16 |
--------------------------------------------------------------------------------
/packages/config/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {...require('../infra/jest.config.json')}
2 |
--------------------------------------------------------------------------------
/packages/config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-config",
3 | "version": "1.4.1",
4 | "publishConfig": {
5 | "access": "public"
6 | },
7 | "description": "QIWI shared semrel config",
8 | "keywords": [],
9 | "main": "target/es5/index.js",
10 | "source": "target/es5/index.js",
11 | "files": [
12 | "README.md",
13 | "CHANGELOG.md",
14 | "target"
15 | ],
16 | "scripts": {
17 | "test": "yarn test:unit",
18 | "test:unit": "jest --runInBand",
19 | "clean": "rimraf target coverage",
20 | "build": "yarn build:es5",
21 | "build:es5": "mkdirp target/es5 && cpy src/main/js/ target/es5/ --flat",
22 | "postupdate": "yarn && yarn build && yarn test",
23 | "format": "prettier --write 'src/**/*.ts'"
24 | },
25 | "dependencies": {
26 | "@qiwi/semrel-preset": "workspace:*"
27 | },
28 | "devDependencies": {
29 | "@qiwi/semrel-infra": "workspace:*"
30 | },
31 | "peerDependencies": {
32 | "semantic-release": "*"
33 | },
34 | "repository": {
35 | "type": "git",
36 | "url": "git+https://github.com/qiwi/semantic-release-toolkit.git"
37 | },
38 | "author": "Anton Golub ",
39 | "license": "MIT",
40 | "bugs": {
41 | "url": "https://github.com/qiwi/semantic-release-toolkit/issues"
42 | },
43 | "homepage": "https://github.com/qiwi/semantic-release-toolkit/#readme"
44 | }
45 |
--------------------------------------------------------------------------------
/packages/config/src/main/js/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | branch: 'master',
3 | plugins: [
4 | [
5 | '@semantic-release/commit-analyzer',
6 | {
7 | preset: 'angular',
8 | releaseRules: [
9 | {type: 'docs', release: 'patch'},
10 | {type: 'refactor', release: 'patch'},
11 | ],
12 | parserOpts: {
13 | noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES']
14 | }
15 | }
16 | ],
17 | '@semantic-release/release-notes-generator',
18 | '@semantic-release/changelog',
19 | [
20 | '@qiwi/semrel-metabranch',
21 | {
22 | publish: {
23 | action: 'push',
24 | branch: 'gh-pages',
25 | from: './docs',
26 | to: './',
27 | message: 'update docs ${nextRelease.gitTag}',
28 | }
29 | }
30 | ],
31 | '@semantic-release/npm',
32 | '@semantic-release/github',
33 | '@semantic-release/git'
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/packages/config/src/test/js/index.js:
--------------------------------------------------------------------------------
1 | const config = require('../../main/js')
2 |
3 | describe('@qiwi/semrel-config', () => {
4 | it('is not empty', () => {
5 | expect(config).not.toBeUndefined()
6 | expect(config).toEqual(require('../../../target/es5'))
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/packages/git-sync/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## @qiwi/git-sync [1.2.3](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/git-sync@1.2.2...@qiwi/git-sync@1.2.3) (2022-05-09)
2 |
3 |
4 | ### Performance Improvements
5 |
6 | * update msr to v6.2.0 ([04700e6](https://github.com/qiwi/semantic-release-toolkit/commit/04700e68b547ae82910dcf51d39813a3b84c6b0d))
7 |
8 |
9 |
10 |
11 |
12 | ### Dependencies
13 |
14 | * **@qiwi/semrel-metabranch:** upgraded to 3.1.2
15 | * **@qiwi/semrel-testing-suite:** upgraded to 3.1.2
16 |
17 | ## @qiwi/git-sync [1.2.2](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/git-sync@1.2.1...@qiwi/git-sync@1.2.2) (2022-04-09)
18 |
19 |
20 | ### Bug Fixes
21 |
22 | * update msr ([ee7c860](https://github.com/qiwi/semantic-release-toolkit/commit/ee7c860edbcb8f423e82bd13a4e5df27d2cb6edc))
23 |
24 |
25 |
26 |
27 |
28 | ### Dependencies
29 |
30 | * **@qiwi/semrel-metabranch:** upgraded to 3.1.1
31 | * **@qiwi/semrel-infra:** upgraded to 3.2.1
32 | * **@qiwi/semrel-testing-suite:** upgraded to 3.1.1
33 |
34 | ## @qiwi/git-sync [1.2.1](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/git-sync@1.2.0...@qiwi/git-sync@1.2.1) (2022-04-04)
35 |
36 |
37 | ### Bug Fixes
38 |
39 | * **deps:** update dependency hosted-git-info to v5 ([#122](https://github.com/qiwi/semantic-release-toolkit/issues/122)) ([82afc3d](https://github.com/qiwi/semantic-release-toolkit/commit/82afc3d46ca5cccfe4c98a21af9182593c6afad8))
40 |
41 | # @qiwi/git-sync [1.2.0](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/git-sync@1.1.0...@qiwi/git-sync@1.2.0) (2022-03-12)
42 |
43 |
44 | ### Features
45 |
46 | * exclude git-utils from common assets ([7cd7eab](https://github.com/qiwi/semantic-release-toolkit/commit/7cd7eabe167dae403eafb6c2d27b5829f2a3181b))
47 |
48 |
49 |
50 |
51 |
52 | ### Dependencies
53 |
54 | * **@qiwi/semrel-metabranch:** upgraded to 3.1.0
55 | * **@qiwi/semrel-infra:** upgraded to 3.2.0
56 | * **@qiwi/semrel-testing-suite:** upgraded to 3.1.0
57 |
58 | # @qiwi/git-sync [1.1.0](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/git-sync@1.0.0...@qiwi/git-sync@1.1.0) (2022-03-06)
59 |
60 |
61 | ### Features
62 |
63 | * introduce @qiwi/git-utils package ([e5d68f8](https://github.com/qiwi/semantic-release-toolkit/commit/e5d68f864fecd8f7be5ce97a533bda1ce6568096)), closes [#120](https://github.com/qiwi/semantic-release-toolkit/issues/120)
64 |
65 |
66 |
67 |
68 |
69 | ### Dependencies
70 |
71 | * **@qiwi/semrel-metabranch:** upgraded to 3.0.6
72 | * **@qiwi/semrel-infra:** upgraded to 3.1.0
73 | * **@qiwi/semrel-testing-suite:** upgraded to 3.0.6
74 |
--------------------------------------------------------------------------------
/packages/git-sync/README.md:
--------------------------------------------------------------------------------
1 | # @qiwi/git-sync
2 | Git helper to fetch & push dirs. For everything else there is [nodegit](https://github.com/nodegit/nodegit).
3 |
4 | ## Usage
5 | ### JS API
6 | ```javascript
7 | import { gitSync } from '@qiwi/git-sync'
8 |
9 | // Upload
10 | await gitSync({
11 | action: 'push',
12 | from: './docs',
13 | to: './',
14 | brach: 'gh-pages',
15 | url: 'https://https://github.com/some/repo.git',
16 | env: {
17 | GIT_TOKEN: 'token'
18 | }
19 | })
20 |
21 | // Download
22 | await gitSync({
23 | action: 'fetch',
24 | url: 'https://https://github.com/some/repo.git',
25 | brach: 'gh-pages',
26 | from: './',
27 | to: './docs',
28 | env: {
29 | GIT_TOKEN: 'token'
30 | }
31 | })
32 | ```
33 |
34 | ### CLI
35 | **Fetch** from remote to local:
36 | ```shell
37 | GIT_TOKEN='token' gitsync --url='https://https://github.com/some/repo.git' --action='fetch' --from='./' --to='./docs' --branch='gh-pages'
38 | ```
39 | the same via ssh:
40 | ```shell
41 | GIT_SSH_COMMAND='ssh -i private_key_file -o IdentitiesOnly=yes' gitsync --url='git@github.com:some/repo.git' --action='fetch' --from='./' --to='./docs'
42 | ```
43 |
44 | **Push** local dir to remote:
45 | ```shell
46 | GIT_TOKEN='token' gitsync --action='push' --from='./docs' --to='./' --branch='gh-pages'
47 | ```
48 |
49 | ## License
50 | [MIT](./../../LICENSE)
51 |
--------------------------------------------------------------------------------
/packages/git-sync/jest.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {...require('../infra/jest.config.json')}
2 |
--------------------------------------------------------------------------------
/packages/git-sync/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/git-sync",
3 | "version": "1.2.3",
4 | "publishConfig": {
5 | "access": "public"
6 | },
7 | "description": "Git fetch/push helper",
8 | "keywords": [],
9 | "type": "module",
10 | "exports": "./target/es6/index.js",
11 | "module": "./target/es6/index.js",
12 | "source": "./target/es6/index.js",
13 | "files": [
14 | "README.md",
15 | "CHANGELOG.md",
16 | "target"
17 | ],
18 | "scripts": {
19 | "clean": "rimraf target typings flow-typed buildcache coverage docs",
20 | "_lint": "eslint 'src/**/*.js'",
21 | "lint": "echo 'linter disabled'",
22 | "lint:fix": "yarn lint --fix",
23 | "format": "prettier --write 'src/**/*.js'",
24 | "test": "concurrently yarn:lint yarn:test:unit yarn:test:depcheck",
25 | "test:unit": "NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles --forceExit --runInBand",
26 | "_test:depcheck": "npm_config_yes=true npx -p depcheck -p @babel/parser@7.16.4 depcheck --ignores '@jest/globals,@types/*,tslib,eslint-*,prettier-*,@qiwi/semrel-infra,@qiwi/semrel-common' --ignore-patterns 'typings,flow-typed/*'",
27 | "test:depcheck": "echo 'depcheck disabled' && exit 0",
28 | "build": "concurrently yarn:build:es6",
29 | "build:es6": "cpy ./src/main/js/ ./target/es6/ --flat",
30 | "postupdate": "yarn && yarn build && yarn test"
31 | },
32 | "dependencies": {
33 | "@qiwi/semrel-metabranch": "workspace:*",
34 | "debug": "^4.3.4",
35 | "execa": "^6.1.0",
36 | "hosted-git-info": "^5.2.0"
37 | },
38 | "devDependencies": {
39 | "@qiwi/semrel-infra": "workspace:*",
40 | "@qiwi/semrel-testing-suite": "workspace:*",
41 | "@types/node": "^18.11.7",
42 | "resolve-from": "^5.0.0"
43 | },
44 | "repository": {
45 | "type": "git",
46 | "url": "git+https://github.com/qiwi/semantic-release-toolkit.git"
47 | },
48 | "author": "Anton Golub ",
49 | "license": "MIT",
50 | "bugs": {
51 | "url": "https://github.com/qiwi/semantic-release-toolkit/issues"
52 | },
53 | "homepage": "https://github.com/qiwi/semantic-release-toolkit/#readme",
54 | "prettier": "prettier-config-qiwi"
55 | }
56 |
--------------------------------------------------------------------------------
/packages/git-sync/src/main/js/get-git-auth-url.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copy-pasted as is from semantic-release sources
3 | * https://github.com/semantic-release/semantic-release/blob/master/lib/get-git-auth-url.js
4 | */
5 |
6 | const {parse, format} = require('url'); // eslint-disable-line node/no-deprecated-api
7 | const hostedGitInfo = require('hosted-git-info');
8 | const debug = require('debug')('get-git-auth-url');
9 |
10 | function isNil(value) {
11 | return value == null;
12 | }
13 |
14 | /**
15 | * Verify the write access authorization to remote repository with push dry-run.
16 | *
17 | * @param {String} repositoryUrl The remote repository URL.
18 | * @param {String} branch The repository branch for which to verify write access.
19 | * @param {Object} [execaOpts] Options to pass to `execa`.
20 | *
21 | * @throws {Error} if not authorized to push.
22 | */
23 | async function verifyAuth(repositoryUrl, branch, execaOptions) {
24 | try {
25 | await execa('git', ['push', '--dry-run', '--no-verify', repositoryUrl, `HEAD:${branch}`], execaOptions);
26 | } catch (error) {
27 | debug(error);
28 | throw error;
29 | }
30 | }
31 |
32 | /**
33 | * Machinery to format a repository URL with the given credentials
34 | *
35 | * @param {String} protocol URL protocol (which should not be present in repositoryUrl)
36 | * @param {String} repositoryUrl User-given repository URL
37 | * @param {String} gitCredentials The basic auth part of the URL
38 | *
39 | * @return {String} The formatted Git repository URL.
40 | */
41 | function formatAuthUrl(protocol, repositoryUrl, gitCredentials) {
42 | const [match, auth, host, basePort, path] =
43 | /^(?!.+:\/\/)(?:(?.*)@)?(?.*?):(?\d+)?:?\/?(?.*)$/.exec(repositoryUrl) || [];
44 | const {port, hostname, ...parsed} = parse(
45 | match ? `ssh://${auth ? `${auth}@` : ''}${host}${basePort ? `:${basePort}` : ''}/${path}` : repositoryUrl
46 | );
47 |
48 | return format({
49 | ...parsed,
50 | auth: gitCredentials,
51 | host: `${hostname}${protocol === 'ssh:' ? '' : port ? `:${port}` : ''}`,
52 | protocol: protocol && /http[^s]/.test(protocol) ? 'http' : 'https',
53 | });
54 | }
55 |
56 | /**
57 | * Verify authUrl by calling git.verifyAuth, but don't throw on failure
58 | *
59 | * @param {Object} context semantic-release context.
60 | * @param {String} authUrl Repository URL to verify
61 | *
62 | * @return {String} The authUrl as is if the connection was successfull, null otherwise
63 | */
64 | async function ensureValidAuthUrl({cwd, env, branch}, authUrl) {
65 | try {
66 | await verifyAuth(authUrl, branch.name, {cwd, env});
67 | return authUrl;
68 | } catch (error) {
69 | debug(error);
70 | return null;
71 | }
72 | }
73 |
74 | /**
75 | * Determine the the git repository URL to use to push, either:
76 | * - The `repositoryUrl` as is if allowed to push
77 | * - The `repositoryUrl` converted to `https` or `http` with Basic Authentication
78 | *
79 | * In addition, expand shortcut URLs (`owner/repo` => `https://github.com/owner/repo.git`) and transform `git+https` / `git+http` URLs to `https` / `http`.
80 | *
81 | * @param {Object} context semantic-release context.
82 | *
83 | * @return {String} The formatted Git repository URL.
84 | */
85 | module.exports = async (context) => {
86 | const {cwd, env, branch} = context;
87 | const GIT_TOKENS = {
88 | GIT_CREDENTIALS: undefined,
89 | GH_TOKEN: undefined,
90 | // GitHub Actions require the "x-access-token:" prefix for git access
91 | // https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#http-based-git-access-by-an-installation
92 | GITHUB_TOKEN: isNil(env.GITHUB_ACTION) ? undefined : 'x-access-token:',
93 | GL_TOKEN: 'gitlab-ci-token:',
94 | GITLAB_TOKEN: 'gitlab-ci-token:',
95 | BB_TOKEN: 'x-token-auth:',
96 | BITBUCKET_TOKEN: 'x-token-auth:',
97 | BB_TOKEN_BASIC_AUTH: '',
98 | BITBUCKET_TOKEN_BASIC_AUTH: '',
99 | };
100 |
101 | let {repositoryUrl} = context.options;
102 | const info = hostedGitInfo.fromUrl(repositoryUrl, {noGitPlus: true});
103 | const {protocol, ...parsed} = parse(repositoryUrl);
104 |
105 | if (info && info.getDefaultRepresentation() === 'shortcut') {
106 | // Expand shorthand URLs (such as `owner/repo` or `gitlab:owner/repo`)
107 | repositoryUrl = info.https();
108 | } else if (protocol && protocol.includes('http')) {
109 | // Replace `git+https` and `git+http` with `https` or `http`
110 | repositoryUrl = format({...parsed, protocol: protocol.includes('https') ? 'https' : 'http', href: null});
111 | }
112 |
113 | // Test if push is allowed without transforming the URL (e.g. is ssh keys are set up)
114 | try {
115 | debug('Verifying ssh auth by attempting to push to %s', repositoryUrl);
116 | await verifyAuth(repositoryUrl, branch.name, {cwd, env});
117 | } catch (_) {
118 | debug('SSH key auth failed, falling back to https.');
119 | const envVars = Object.keys(GIT_TOKENS).filter((envVar) => !isNil(env[envVar]));
120 |
121 | // Skip verification if there is no ambiguity on which env var to use for authentication
122 | if (envVars.length === 1) {
123 | const gitCredentials = `${GIT_TOKENS[envVars[0]] || ''}${env[envVars[0]]}`;
124 | return formatAuthUrl(protocol, repositoryUrl, gitCredentials);
125 | }
126 |
127 | if (envVars.length > 1) {
128 | debug(`Found ${envVars.length} credentials in environment, trying all of them`);
129 |
130 | const candidateRepositoryUrls = [];
131 | for (const envVar of envVars) {
132 | const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar]}`;
133 | const authUrl = formatAuthUrl(protocol, repositoryUrl, gitCredentials);
134 | candidateRepositoryUrls.push(ensureValidAuthUrl(context, authUrl));
135 | }
136 |
137 | const validRepositoryUrls = await Promise.all(candidateRepositoryUrls);
138 | const chosenAuthUrlIndex = validRepositoryUrls.findIndex((url) => url !== null);
139 | if (chosenAuthUrlIndex > -1) {
140 | debug(`Using "${envVars[chosenAuthUrlIndex]}" to authenticate`);
141 | return validRepositoryUrls[chosenAuthUrlIndex];
142 | }
143 | }
144 | }
145 |
146 | return repositoryUrl;
147 | };
148 |
--------------------------------------------------------------------------------
/packages/git-sync/src/main/js/index.js:
--------------------------------------------------------------------------------
1 | export const foo = 'bar'
2 |
--------------------------------------------------------------------------------
/packages/git-sync/src/test/js/index.js:
--------------------------------------------------------------------------------
1 | import {foo} from '../../main/js/index.js'
2 |
3 | describe('foo', () => {
4 | it('bar', () => {
5 | expect(foo).toBe('bar')
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/packages/git-utils/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'eslint-config-qiwi',
4 | 'prettier',
5 | ],
6 | overrides: [
7 | {
8 | files: ['./src/test/**/*.ts'],
9 | rules: {
10 | 'unicorn/consistent-function-scoping': 'off',
11 | 'sonarjs/no-duplicate-string': 'off'
12 | }
13 | }
14 | ]
15 | };
16 |
--------------------------------------------------------------------------------
/packages/git-utils/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## @qiwi/git-utils [1.1.2](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/git-utils@1.1.1...@qiwi/git-utils@1.1.2) (2022-05-09)
2 |
3 |
4 | ### Performance Improvements
5 |
6 | * deps revision ([7974aab](https://github.com/qiwi/semantic-release-toolkit/commit/7974aab6ad056e840061ba03218e14cf81a2fd07))
7 | * update msr to v6.2.0 ([04700e6](https://github.com/qiwi/semantic-release-toolkit/commit/04700e68b547ae82910dcf51d39813a3b84c6b0d))
8 |
9 |
10 |
11 |
12 |
13 | ### Dependencies
14 |
15 | * **@qiwi/semrel-common:** upgraded to 3.4.2
16 |
17 | ## @qiwi/git-utils [1.1.1](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/git-utils@1.1.0...@qiwi/git-utils@1.1.1) (2022-04-09)
18 |
19 |
20 |
21 |
22 |
23 | ### Dependencies
24 |
25 | * **@qiwi/semrel-common:** upgraded to 3.4.1
26 | * **@qiwi/semrel-infra:** upgraded to 3.2.1
27 |
28 | # @qiwi/git-utils [1.1.0](https://github.com/qiwi/semantic-release-toolkit/compare/@qiwi/git-utils@1.0.0...@qiwi/git-utils@1.1.0) (2022-03-12)
29 |
30 |
31 | ### Features
32 |
33 | * exclude git-utils from common assets ([7cd7eab](https://github.com/qiwi/semantic-release-toolkit/commit/7cd7eabe167dae403eafb6c2d27b5829f2a3181b))
34 | * **git-utils:** allow empty commits, add gitInitOrigin stage to gitInitTestingRepo flow ([7d0b972](https://github.com/qiwi/semantic-release-toolkit/commit/7d0b97273d3d23278160edc293c969b06fcf5284))
35 |
36 |
37 |
38 |
39 |
40 | ### Dependencies
41 |
42 | * **@qiwi/semrel-common:** upgraded to 3.4.0
43 | * **@qiwi/semrel-infra:** upgraded to 3.2.0
44 |
45 | # @qiwi/git-utils 1.0.0 (2022-03-06)
46 |
47 |
48 | ### Features
49 |
50 | * introduce @qiwi/git-utils package ([e5d68f8](https://github.com/qiwi/semantic-release-toolkit/commit/e5d68f864fecd8f7be5ce97a533bda1ce6568096)), closes [#120](https://github.com/qiwi/semantic-release-toolkit/issues/120)
51 | * introduce semrel-git-utils package ([b35457b](https://github.com/qiwi/semantic-release-toolkit/commit/b35457bf84b5fb4fc600c12defe48416cf3cd92a)), closes [#36](https://github.com/qiwi/semantic-release-toolkit/issues/36)
52 |
53 |
54 |
55 |
56 |
57 | ### Dependencies
58 |
59 | * **@qiwi/semrel-infra:** upgraded to 3.1.0
60 |
--------------------------------------------------------------------------------
/packages/git-utils/README.md:
--------------------------------------------------------------------------------
1 | # @qiwi/git-utils
2 | Git utils for CI/CD tools.
3 |
4 | ## Install
5 | ```shell script
6 | yarn add @qiwi/git-utils
7 | ```
8 |
9 | ## Usage
10 | ```typescript
11 | import { gitTags } from '@qiwi/git-utils'
12 |
13 | const tags = await gitTags({ cwd: '/foo/bar/baz', branch: 'master' })
14 | ```
15 |
--------------------------------------------------------------------------------
/packages/git-utils/jest.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {...require('../infra/jest.config.json')}
2 |
--------------------------------------------------------------------------------
/packages/git-utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/git-utils",
3 | "version": "1.1.2",
4 | "publishConfig": {
5 | "access": "public"
6 | },
7 | "description": "Common git utils",
8 | "keywords": [],
9 | "type": "module",
10 | "module": "./target/es6/index.js",
11 | "main": "./target/es6/index.js",
12 | "exports": "./target/es6/index.js",
13 | "source": "target/ts/index.ts",
14 | "types": "./target/es6/index.d.ts",
15 | "files": [
16 | "README.md",
17 | "CHANGELOG.md",
18 | "target",
19 | "typings",
20 | "flow-typed"
21 | ],
22 | "scripts": {
23 | "clean": "rimraf target typings flow-typed buildcache coverage docs",
24 | "lint": "eslint 'src/**/*.ts'",
25 | "lint:fix": "yarn lint --fix",
26 | "format": "prettier --write 'src/**/*.ts'",
27 | "test": "concurrently yarn:lint yarn:test:unit yarn:test:depcheck",
28 | "test:unit": "NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles --forceExit --runInBand",
29 | "test:depcheck": "npm_config_yes=true npx -p depcheck -p @babel/parser@7.16.4 depcheck --ignores '@jest/globals,@types/*,tslib,eslint-*,prettier-*,@qiwi/semrel-infra,@qiwi/semrel-common,semantic-release' --ignore-patterns 'typings,flow-typed/*'",
30 | "build": "concurrently yarn:build:esnext yarn:build:es6 yarn:build:ts yarn:build:libdef yarn:docs && yarn build:esmfix",
31 | "build:esnext": "mkdirp target/esnext && tsc -p tsconfig.esnext.json",
32 | "build:es6": "mkdirp target/es6 && tsc -p tsconfig.es6.json",
33 | "build:ts": "cpy ./ ../../../target/ts/ --dot --cwd=./src/main/ts/",
34 | "build:libdef": "libdefkit --tsconfig=tsconfig.esnext.json --tsconfig=tsconfig.es6.json --entry=@qiwi/semrel-common/target/es6",
35 | "build:esmfix": "yarn tsc-esm-fix --target=target/es6 --target=target/esnext --ext=.js",
36 | "docs": "typedoc --options ./typedoc.json ./src/main/ts",
37 | "uglify": "for f in $(find target -name '*.js'); do short=${f%.js}; terser -c -m -o $short.js -- $f; done",
38 | "postupdate": "yarn && yarn build && yarn test"
39 | },
40 | "dependencies": {
41 | "@antongolub/git-root": "^1.5.7",
42 | "@qiwi/semrel-common": "workspace:*",
43 | "@qiwi/substrate": "^1.20.15",
44 | "@types/node": "^18.11.7",
45 | "debug": "^4.3.4",
46 | "execa": "^6.1.0",
47 | "file-url": "^4.0.0",
48 | "nanoid": "^4.0.0",
49 | "tempy": "^3.0.0",
50 | "tslib": "^2.4.0"
51 | },
52 | "devDependencies": {
53 | "@qiwi/semrel-infra": "workspace:*",
54 | "fs-extra": "^10.1.0"
55 | },
56 | "repository": {
57 | "type": "git",
58 | "url": "git+https://github.com/qiwi/semantic-release-toolkit.git"
59 | },
60 | "author": "Anton Golub ",
61 | "license": "MIT",
62 | "bugs": {
63 | "url": "https://github.com/qiwi/semantic-release-toolkit/issues"
64 | },
65 | "homepage": "https://github.com/qiwi/semantic-release-toolkit/#readme",
66 | "prettier": "prettier-config-qiwi"
67 | }
68 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/add.ts:
--------------------------------------------------------------------------------
1 | import { gitExec, IGitCommon, TGitResult } from './exec'
2 |
3 | export interface IGitAdd extends IGitCommon {
4 | file?: string
5 | }
6 |
7 | export const gitAdd = ({
8 | cwd,
9 | sync,
10 | file = '.',
11 | }: T): TGitResult =>
12 | gitExec({
13 | cwd,
14 | sync: sync as T['sync'],
15 | args: ['add', file],
16 | })
17 |
18 | export const gitAddAll = ({
19 | cwd,
20 | sync,
21 | }: T): TGitResult =>
22 | gitExec({
23 | cwd,
24 | sync: sync as T['sync'],
25 | args: ['add', '--all'],
26 | })
27 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/branch.ts:
--------------------------------------------------------------------------------
1 | import { gitExec, IGitCommon, TGitResult } from './exec'
2 |
3 | export interface IGitBranch extends IGitCommon {
4 | branch: string
5 | }
6 |
7 | /**
8 | * Create a branch in a local Git repository.
9 | *
10 | * @param {string} cwd The CWD of the Git repository.
11 | * @param {string} branch Branch name to create.
12 | * @returns {Promise} Promise that resolves when done.
13 | */
14 | export const gitBranch = ({
15 | cwd,
16 | sync,
17 | branch,
18 | }: T): TGitResult => {
19 | // Check params.
20 | // check(cwd, 'cwd: absolute')
21 | // check(branch, 'branch: lower')
22 |
23 | return gitExec({
24 | cwd,
25 | sync: sync as T['sync'],
26 | args: ['branch', branch],
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/checkout.ts:
--------------------------------------------------------------------------------
1 | import { formatFlags } from '@qiwi/semrel-common'
2 |
3 | import { gitExec, IGitCommon, TGitResult } from './exec'
4 |
5 | export interface IGitCheckout extends IGitCommon {
6 | branch: string
7 | b?: boolean
8 | f?: boolean
9 | }
10 |
11 | export const gitCheckout = ({
12 | cwd,
13 | sync,
14 | branch,
15 | b,
16 | f = !b,
17 | }: T): TGitResult => {
18 | // check(branch, 'branch: kebab')
19 |
20 | const flags = formatFlags({ b, f })
21 |
22 | return gitExec({
23 | cwd,
24 | sync: sync as T['sync'],
25 | args: ['checkout', ...flags, branch],
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/commit.ts:
--------------------------------------------------------------------------------
1 | import { formatFlags } from '@qiwi/semrel-common'
2 |
3 | import { exec } from '../misc'
4 | import { gitGetHead } from './etc'
5 | import { gitExec, IGitCommon, TGitResult } from './exec'
6 |
7 | export interface IGitCommit extends IGitCommon {
8 | message: string
9 | all?: boolean
10 | }
11 |
12 | /**
13 | * Get the current HEAD SHA in a local Git repository.
14 | *
15 | * @param {string} cwd The CWD of the Git repository.
16 | * @return {Promise} Promise that resolves to the SHA of the head commit.
17 | */
18 | export const gitCommit = ({
19 | cwd,
20 | sync,
21 | message,
22 | all,
23 | }: T): TGitResult => {
24 | // check(cwd, 'cwd: absolute')
25 | // check(message, 'message: string+')
26 |
27 | const flags = formatFlags({ all, message })
28 |
29 | return exec(
30 | () =>
31 | gitExec({
32 | cwd,
33 | sync,
34 | args: ['commit', ...flags, '--no-gpg-sign', '--allow-empty'],
35 | }),
36 | () => gitGetHead({ cwd, sync: sync as T['sync'] }),
37 | )
38 | }
39 |
40 | // export const a: string = gitCommit({sync: false, cwd: 'a', message: 'ff'})
41 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/config.ts:
--------------------------------------------------------------------------------
1 | import { gitExec, IGitCommon, TGitResult } from './exec'
2 |
3 | export interface IGitConfigAdd extends IGitCommon {
4 | key: string
5 | value: any
6 | }
7 |
8 | export interface IGitConfigGet extends IGitCommon {
9 | key: string
10 | }
11 |
12 | /**
13 | * Add a Git config setting.
14 | *
15 | * @param {string} cwd The CWD of the Git repository.
16 | * @param {string} name Config name.
17 | * @param {any} value Config value.
18 | * @returns {Promise} Promise that resolves when done.
19 | */
20 | export const gitConfigAdd = ({
21 | cwd,
22 | key,
23 | value,
24 | sync,
25 | }: T): TGitResult => {
26 | // check(cwd, 'cwd: absolute')
27 | // check(name, 'name: string+')
28 |
29 | return gitExec({
30 | cwd,
31 | sync: sync as T['sync'],
32 | args: ['config', '--add', key, value],
33 | })
34 | }
35 |
36 | export const gitConfig = gitConfigAdd
37 |
38 | /**
39 | * Get a Git config setting.
40 | *
41 | * @param {string} cwd The CWD of the Git repository.
42 | * @param {string} name Config name.
43 | * @returns {Promise} Promise that resolves when done.
44 | */
45 | export const gitConfigGet = ({
46 | cwd,
47 | key,
48 | sync,
49 | }: T): TGitResult => {
50 | // check(cwd, 'cwd: absolute')
51 | // check(name, 'name: string+')
52 |
53 | return gitExec({
54 | cwd,
55 | sync: sync as T['sync'],
56 | args: ['config', key],
57 | })
58 | }
59 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/etc.ts:
--------------------------------------------------------------------------------
1 | import { exec, format } from '../misc'
2 | import { gitExec, IGitCommon, TGitResult } from './exec'
3 |
4 | /**
5 | * Get the current HEAD SHA in a local Git repository.
6 | *
7 | * @param {string} cwd The CWD of the Git repository.
8 | * @return {Promise} Promise that resolves to the SHA of the head commit.
9 | */
10 |
11 | export const gitGetHead = ({
12 | cwd,
13 | sync,
14 | }: T): TGitResult =>
15 | gitExec({
16 | cwd,
17 | args: ['rev-parse', 'HEAD'],
18 | sync: sync as T['sync'],
19 | })
20 |
21 | export interface IGitShowCommitted extends IGitCommon {
22 | hash?: string
23 | }
24 |
25 | export const gitShowCommitted = ({
26 | cwd,
27 | sync,
28 | hash = 'HEAD',
29 | }: T): TGitResult =>
30 | exec(
31 | () =>
32 | gitExec({
33 | cwd,
34 | sync: sync as T['sync'],
35 | args: ['diff-tree', '--no-commit-id', '--name-only', '-r', hash],
36 | }),
37 | (stdout) => format(sync as T['sync'], (stdout as string).split('\n')),
38 | )
39 |
40 | export const gitStatus = ({
41 | cwd,
42 | sync,
43 | }: T): TGitResult =>
44 | gitExec({
45 | cwd,
46 | sync: sync as T['sync'],
47 | args: ['status', '--short'],
48 | })
49 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/exec.ts:
--------------------------------------------------------------------------------
1 | import debug, { Debugger } from 'debug'
2 | import { execa, execaSync, SyncOptions } from 'execa'
3 | import { nanoid } from 'nanoid'
4 |
5 | import { ISyncSensitive, SyncGuard } from '../ifaces'
6 |
7 | export interface IGitCommon extends ISyncSensitive {
8 | cwd: string
9 | debug?: Debugger
10 | }
11 |
12 | export type TGitResult<
13 | S extends boolean | undefined,
14 | V = string
15 | > = SyncGuard
16 |
17 | export interface TGitExecContext extends IGitCommon {
18 | args?: any[]
19 | }
20 |
21 | const defaultDebug = debug('git-exec')
22 |
23 | export const gitExec = (
24 | opts: T,
25 | ): TGitResult => {
26 | const debug = opts.debug || defaultDebug
27 | const { cwd, args = [], sync } = opts
28 |
29 | const execaArgs: [string, readonly string[], SyncOptions] = [
30 | 'git',
31 | args as string[],
32 | { cwd },
33 | ]
34 | const gitExecId = nanoid()
35 | const log = (output: T): T => {
36 | debug(`[${gitExecId}]`, output)
37 | return output
38 | }
39 |
40 | log(execaArgs)
41 |
42 | if (sync === true) {
43 | const res = execaSync(...execaArgs)
44 | return (log(res.stdout || res.stderr) as unknown) as TGitResult<
45 | T['sync']
46 | >
47 | }
48 |
49 | return (execa(...execaArgs).then(({ stdout , stderr}) =>
50 | log(stdout.toString() || stderr.toString()),
51 | ) as unknown) as TGitResult
52 | }
53 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/fetch.ts:
--------------------------------------------------------------------------------
1 | import { formatFlags } from '@qiwi/semrel-common'
2 |
3 | import { gitExec, IGitCommon, TGitResult } from './exec'
4 |
5 | export interface IGitFetch extends IGitCommon {
6 | remote?: string
7 | branch?: string
8 | depth?: number
9 | }
10 |
11 | export const gitFetchAll = ({
12 | cwd,
13 | sync,
14 | }: T): TGitResult =>
15 | gitExec({
16 | cwd,
17 | sync: sync as T['sync'],
18 | args: ['fetch', '--all'],
19 | })
20 |
21 | export const gitFetch = ({
22 | cwd,
23 | remote = 'origin',
24 | branch,
25 | sync,
26 | depth,
27 | }: T): TGitResult => {
28 | const flags = formatFlags({ depth })
29 |
30 | return branch
31 | ? gitExec({
32 | cwd,
33 | sync: sync as T['sync'],
34 | args: ['fetch', ...flags, remote, branch],
35 | })
36 | : gitFetchAll({cwd, sync: sync as T['sync']})
37 | }
38 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Lifted and tweaked from semantic-release because we follow how they test their internals.
3 | * https://github.com/semantic-release/semantic-release/blob/master/test/helpers/git-utils.js
4 | */
5 |
6 | export * from './add'
7 | export * from './branch'
8 | export * from './checkout'
9 | export * from './config'
10 | export * from './exec'
11 | export * from './etc'
12 | export * from './fetch'
13 | export * from './init'
14 | export * from './commit'
15 | export * from './push'
16 | export * from './rebase'
17 | export * from './remote'
18 | export * from './tag'
19 | export * from './user'
20 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/init.ts:
--------------------------------------------------------------------------------
1 | import { gitRoot } from '@antongolub/git-root'
2 | import { formatFlags } from '@qiwi/semrel-common'
3 | import fileUrl from 'file-url'
4 | import { temporaryDirectory } from 'tempy'
5 |
6 | import { exec, format } from '../misc'
7 | import { gitCheckout } from './checkout'
8 | import { gitExec, TGitResult } from './exec'
9 | import { gitPush } from './push'
10 | import { gitRemoteAdd } from './remote'
11 |
12 | export interface IGitInit {
13 | cwd?: string
14 | sync?: boolean
15 | bare?: boolean
16 | }
17 |
18 | export interface IGitInitOrigin extends IGitInit {
19 | cwd: string
20 | branch?: string
21 | }
22 |
23 | export { gitRoot }
24 |
25 | export const gitInit = ({
26 | cwd = temporaryDirectory(),
27 | sync,
28 | bare,
29 | }: T): TGitResult =>
30 | exec(
31 | () => gitRoot(cwd, sync),
32 | (parentGitDir) => {
33 | if (parentGitDir) {
34 | throw new Error(
35 | `${cwd} belongs to repo ${parentGitDir as string} already`,
36 | )
37 | }
38 | },
39 | () => {
40 | const flags = formatFlags({ bare })
41 |
42 | return gitExec({
43 | cwd: cwd as string,
44 | sync,
45 | args: ['init', ...flags],
46 | })
47 | },
48 | () => format(sync as T['sync'], cwd),
49 | )
50 |
51 | /**
52 | * Init bare Git repository in a temp directory.
53 | *
54 | * @return {Promise} Promise that resolves to string URL of the of the remote origin.
55 | */
56 | export const gitInitRemote = ({
57 | cwd = temporaryDirectory(),
58 | sync,
59 | }: T): TGitResult =>
60 | exec(
61 | () => gitInit({ cwd, sync, bare: true }),
62 | // Turn remote path into a file URL.
63 | (cwd) => format(sync as T['sync'], fileUrl(cwd)),
64 | )
65 |
66 | /**
67 | * Create a remote Git repository and set it as the origin for a Git repository.
68 | * _Created in a temp folder._
69 | *
70 | * @param {string} cwd The cwd to create and set the origin for.
71 | * @param {string} [releaseBranch] Optional branch to be added in case of prerelease is activated for a branch.
72 | * @return {Promise} Promise that resolves to string URL of the of the remote origin.
73 | */
74 | export const gitInitOrigin = ({
75 | cwd,
76 | sync,
77 | branch,
78 | }: T): TGitResult => {
79 | // Check params.
80 | // check(cwd, 'cwd: absolute')
81 |
82 | let url: string
83 |
84 | return exec(
85 | // Turn remote path into a file URL.
86 | () => gitInitRemote({ sync }),
87 | (_url) => {
88 | url = _url
89 | },
90 | () => gitRemoteAdd({ sync, cwd, url }),
91 | () =>
92 | // Set up a release branch. Return to master afterwards.
93 | branch &&
94 | exec(
95 | // sync as T['sync'],
96 | () => gitCheckout({ cwd, sync, branch, b: true }),
97 | () => gitCheckout({ cwd, sync, branch: 'master' }),
98 | ),
99 | () => gitPush({ cwd, sync, branch }),
100 | () => format(sync as T['sync'], url),
101 | )
102 | }
103 |
104 | /* (cwd: string, releaseBranch?: string): string => {
105 | // Check params.
106 | check(cwd, 'cwd: absolute')
107 |
108 | // Turn remote path into a file URL.
109 | const url = gitInitRemote()
110 |
111 | // Set origin on local repo.
112 | execaSync('git', ['remote', 'add', 'origin', url], { cwd })
113 |
114 | // Set up a release branch. Return to master afterwards.
115 | if (releaseBranch) {
116 | execaSync('git', ['checkout', '-b', releaseBranch], { cwd })
117 | execaSync('git', ['checkout', 'master'], { cwd })
118 | }
119 |
120 | execaSync('git', ['push', '--all', 'origin'], { cwd })
121 |
122 | // Return URL for remote.
123 | return url
124 | } */
125 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/push.ts:
--------------------------------------------------------------------------------
1 | import { exec } from '../misc'
2 | import { gitGetHead } from './etc'
3 | import { gitExec, IGitCommon, TGitResult } from './exec'
4 | import { gitFetch } from './fetch'
5 | import { gitRebaseToRemote } from './rebase'
6 |
7 | export interface IGitPush extends IGitCommon {
8 | branch?: string
9 | refspec?: string
10 | remote?: string
11 | }
12 |
13 | export interface IGitPushRebase extends IGitPush {
14 | depth?: number
15 | }
16 |
17 | export const gitPush = ({
18 | cwd,
19 | sync,
20 | branch,
21 | remote = 'origin',
22 | refspec = `HEAD:refs/heads/${branch || 'master'}`,
23 | }: T): TGitResult => {
24 | // check(cwd, 'cwd: absolute')
25 | // check(remote, 'remote: string')
26 | // check(branch, 'branch: lower')
27 |
28 | const args = branch
29 | ? ['push', '--tags', remote, refspec]
30 | : ['push', '--all', '--follow-tags', remote]
31 |
32 | return exec(
33 | () =>
34 | gitExec({
35 | cwd,
36 | sync,
37 | args,
38 | }),
39 | () => gitGetHead({ cwd, sync: sync as T['sync'] }),
40 | )
41 | }
42 |
43 | export const gitPushRebase = ({
44 | cwd,
45 | sync,
46 | branch,
47 | remote = 'origin',
48 | refspec,
49 | }: T): TGitResult =>
50 | (sync
51 | ? gitPushRebaseSync({ cwd, sync, branch, remote, refspec})
52 | : gitPushRebaseAsync({ cwd, sync, branch, remote, refspec })) as TGitResult<
53 | T['sync']
54 | >
55 |
56 | export const gitPushRebaseAsync = async ({
57 | cwd,
58 | sync,
59 | branch,
60 | remote = 'origin',
61 | refspec,
62 | depth,
63 | }: T): Promise => {
64 | let retries = 5
65 |
66 | while (retries > 0) {
67 | try {
68 | try {
69 | await gitFetch({ cwd, sync, branch, remote, depth })
70 | await gitRebaseToRemote({ cwd, sync, branch, remote })
71 | } catch (e) {
72 | console.warn('rebase failed', e)
73 | }
74 |
75 | return await gitPush({ cwd, sync, branch, remote, refspec })
76 | } catch (e) {
77 | retries -= 1
78 | console.warn('push failed', 'retries left', retries, e)
79 | }
80 | }
81 |
82 | throw new Error('`gitPushRebase` failed')
83 | }
84 |
85 | export const gitPushRebaseSync = ({
86 | cwd,
87 | sync,
88 | branch,
89 | remote = 'origin',
90 | refspec,
91 | depth,
92 | }: T): string => {
93 | let retries = 5
94 |
95 | while (retries > 0) {
96 | try {
97 | try {
98 | gitFetch({ cwd, sync, branch, remote, depth })
99 | gitRebaseToRemote({ cwd, sync, branch, remote })
100 | } catch (e) {
101 | console.warn('rebase failed', e)
102 | }
103 |
104 | return (gitPush({ cwd, sync, branch, remote, refspec }) as unknown) as string
105 | } catch (e) {
106 | retries -= 1
107 | console.warn('push failed', 'retries left', retries, e)
108 | }
109 | }
110 |
111 | throw new Error('`gitPushRebase` failed')
112 | }
113 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/rebase.ts:
--------------------------------------------------------------------------------
1 | import { gitExec, IGitCommon, TGitResult } from './exec'
2 |
3 | export interface IGitRebase extends IGitCommon {
4 | remote?: string
5 | branch?: string
6 | }
7 |
8 | export const gitRebaseToRemote = ({
9 | cwd,
10 | sync,
11 | remote,
12 | branch,
13 | }: T): TGitResult =>
14 | gitExec({
15 | cwd,
16 | sync: sync as T['sync'],
17 | args: ['rebase', `${remote}/${branch}`],
18 | })
19 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/remote.ts:
--------------------------------------------------------------------------------
1 | import { gitExec, IGitCommon, TGitResult } from './exec'
2 |
3 | export interface IGitRemoteAdd extends IGitCommon {
4 | url: string
5 | remote?: string
6 | }
7 |
8 | export const gitRemoteAdd = ({
9 | cwd,
10 | sync,
11 | url,
12 | remote = 'origin',
13 | }: T): TGitResult =>
14 | gitExec({
15 | cwd,
16 | sync: sync as T['sync'],
17 | args: ['remote', 'add', remote, url],
18 | })
19 |
20 | export interface IGitSetRemoteHead extends IGitCommon {
21 | remote?: string
22 | }
23 |
24 | export const gitRemoteSetHead = ({
25 | cwd,
26 | sync,
27 | remote = 'origin',
28 | }: T): TGitResult =>
29 | gitExec({
30 | cwd,
31 | sync: sync as T['sync'],
32 | args: ['remote', 'set-head', remote, '--auto'],
33 | })
34 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/tag.ts:
--------------------------------------------------------------------------------
1 | import { effect } from '../misc'
2 | import { gitExec, IGitCommon, TGitResult } from './exec'
3 |
4 | export interface IGitTag extends IGitCommon {
5 | tag?: string
6 | hash?: string
7 | }
8 |
9 | /**
10 | * Create a tag on the HEAD commit in a local Git repository.
11 | *
12 | * @param {string} cwd The CWD of the Git repository.
13 | * @param {string} tag The tag name to create.
14 | * @param {string} hash=false SHA for the commit on which to create the tag. If falsy the tag is created on the latest commit.
15 | * @returns {Promise} Promise that resolves when done.
16 | */
17 | export const gitTag = ({
18 | cwd,
19 | sync,
20 | tag,
21 | hash,
22 | }: T): TGitResult => {
23 | // Check params.
24 | // check(cwd, 'cwd: absolute')
25 | // check(tag, 'tagName: string+')
26 | // check(hash, 'hash: alphanumeric{40}?')
27 |
28 | const flags = hash ? ['-f', tag, hash] : [tag]
29 |
30 | return gitExec({
31 | cwd,
32 | sync: sync as T['sync'],
33 | args: ['tag', ...flags],
34 | })
35 | }
36 |
37 | /**
38 | * Get tag list associated with a commit SHA.
39 | *
40 | * @param {string} cwd The CWD of the Git repository.
41 | * @param {string} hash The commit SHA for which to retrieve the associated tag.
42 | * @return {Promise} The tag associated with the SHA in parameter or `null`.
43 | */
44 | export const gitGetTags = ({
45 | cwd,
46 | sync,
47 | hash,
48 | }: T): TGitResult => {
49 | // Check params.
50 | // check(cwd, 'cwd: absolute')
51 | // check(hash, 'hash: alphanumeric{40}')
52 |
53 | return effect(
54 | gitExec({
55 | cwd,
56 | sync,
57 | args: ['tag', '--merged', hash],
58 | }),
59 | (tags) => (tags ? tags.split('\n') : []),
60 | )
61 | }
62 |
63 | /**
64 | * Get the first commit SHA tagged `tagName` in a local Git repository.
65 | *
66 | * @param {string} cwd The CWD of the Git repository.
67 | * @param {string} tag Tag name for which to retrieve the commit sha.
68 | * @return {Promise} Promise that resolves to the SHA of the first commit associated with `tagName`.
69 | */
70 | export const gitGetTagHash = ({
71 | cwd,
72 | sync,
73 | tag,
74 | }: T): TGitResult => {
75 | // Check params.
76 | // Check params.
77 | // check(cwd, 'cwd: absolute')
78 | // check(tag, 'tag: string+')
79 |
80 | return gitExec({
81 | cwd,
82 | sync: sync as T['sync'],
83 | args: ['rev-list', '-1', tag],
84 | })
85 | }
86 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/git/user.ts:
--------------------------------------------------------------------------------
1 | import { exec } from '../misc'
2 | import { gitConfigAdd } from './config'
3 | import { IGitCommon, TGitResult } from './exec'
4 |
5 | export interface IGitSetUser extends IGitCommon {
6 | name: string
7 | email: string
8 | }
9 |
10 | /**
11 | * Set user for git repo.
12 | *
13 | * @param {string} cwd The CWD of the Git repository.
14 | * @param {string} name User name.
15 | * @param {string} email User email.
16 | * @returns {Promise} Promise that resolves when done.
17 | */
18 | export const gitSetUser = ({
19 | cwd,
20 | name,
21 | email,
22 | sync,
23 | }: T): TGitResult => {
24 | // check(email, 'email: string+')
25 | // check(name, 'name: string+')
26 |
27 | return exec(
28 | () =>
29 | gitConfigAdd({
30 | cwd,
31 | sync: sync as T['sync'],
32 | key: 'user.name',
33 | value: name,
34 | }),
35 | () =>
36 | gitConfigAdd({
37 | cwd,
38 | sync: sync as T['sync'],
39 | key: 'user.email',
40 | value: email,
41 | }),
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/ifaces.ts:
--------------------------------------------------------------------------------
1 | import { Extends } from '@qiwi/substrate'
2 |
3 | export interface ISyncSensitive {
4 | sync?: boolean
5 | }
6 |
7 | export type TSyncDirective = boolean | undefined | ISyncSensitive
8 |
9 | export type ParseSync = Extends<
10 | T,
11 | boolean | undefined,
12 | T,
13 | T extends ISyncSensitive ? T['sync'] : never
14 | >
15 |
16 | export type SyncGuard = Extends<
17 | S,
18 | { sync: true } | true,
19 | Extends, never, V>,
20 | Extends, V, Promise>
21 | >
22 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/index.ts:
--------------------------------------------------------------------------------
1 | export * from './git'
2 | export * from './misc'
3 |
--------------------------------------------------------------------------------
/packages/git-utils/src/main/ts/misc.ts:
--------------------------------------------------------------------------------
1 | import { GetLength, ICallable, Prev } from '@qiwi/substrate'
2 | import util from 'util'
3 |
4 | import { SyncGuard, TSyncDirective } from './ifaces'
5 |
6 | export type BoolOrEmpty = boolean | undefined
7 |
8 | export const isPromiseLike = (value: any): boolean =>
9 | typeof (value as any)?.then === 'function'
10 |
11 | export const execute = (
12 | ...callbacks: C
13 | ): ReturnType>]> =>
14 | callbacks.reduce((prev, cb) => effect(prev, cb)) as ReturnType<
15 | C[Prev>]
16 | >
17 |
18 | export const exec = (
19 | // sync: S,
20 | ...callbacks: C
21 | ): ReturnType>]> =>
22 | callbacks.reduce((prev, cb) => effect(prev, cb), {} as any) // as SyncGuard>]>, S>
23 |
24 | export const effect = (
25 | value: V,
26 | cb: C,
27 | ): ReturnType =>
28 | isPromiseLike(value) ? (value as any)?.then(cb) : cb(value)
29 |
30 | export const format = (
31 | sync: S,
32 | value: V,
33 | ): SyncGuard =>
34 | (sync === true
35 | ? value
36 | : isPromiseLike(value)
37 | ? value
38 | : Promise.resolve(value)) as SyncGuard
39 |
40 | export const extractValue = (value: any): any =>
41 | isPromiseLike(value)
42 | ? // For example: "Promise { 'foo' }"
43 | util.inspect(value).slice(11, -3)
44 | : value
45 |
46 | // export const a: SyncGuard = Promise.resolve('a')
47 |
--------------------------------------------------------------------------------
/packages/git-utils/src/test/fixtures/basicPackage/inner/file.txt:
--------------------------------------------------------------------------------
1 | contents
2 |
--------------------------------------------------------------------------------
/packages/git-utils/src/test/fixtures/basicPackage/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "msr-test-basic",
3 | "author": "Dave Houlbrooke =8.3"
9 | },
10 | "release": {
11 | "plugins": [
12 | "@semantic-release/commit-analyzer",
13 | "@semantic-release/release-notes-generator"
14 | ],
15 | "noCi": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/git-utils/src/test/fixtures/foo/bar/foobar.txt:
--------------------------------------------------------------------------------
1 | foobar
2 |
--------------------------------------------------------------------------------
/packages/git-utils/src/test/fixtures/foo/baz/foobaz.txt:
--------------------------------------------------------------------------------
1 | foobaz
2 |
--------------------------------------------------------------------------------
/packages/git-utils/src/test/fixtures/foo/foo/foofoo.txt:
--------------------------------------------------------------------------------
1 | foofoo
2 |
--------------------------------------------------------------------------------
/packages/git-utils/src/test/ts/it/git.ts:
--------------------------------------------------------------------------------
1 | import { dirname } from 'node:path'
2 |
3 | import fs from 'fs-extra'
4 | import path from 'path'
5 | import { rootTemporaryDirectory,temporaryDirectory } from 'tempy'
6 | import { fileURLToPath } from 'url'
7 |
8 | import {
9 | gitCheckout,
10 | gitConfigAdd,
11 | gitConfigGet,
12 | gitExec,
13 | gitInit,
14 | gitRoot,
15 | gitSetUser,
16 | } from '../../../main/ts'
17 |
18 | const __filename = fileURLToPath(import.meta.url)
19 | const __dirname = dirname(__filename)
20 | const root = path.resolve(__dirname, '../../../../../../')
21 |
22 | describe('git-utils', () => {
23 | describe('gitRoot()', () => {
24 | it('returns the closest .git containing path', async () => {
25 | expect(await gitRoot(__filename)).toBe(root)
26 | })
27 |
28 | // https://git-scm.com/docs/gitrepository-layout
29 | describe('gitdir ref', () => {
30 | it('handles `gitdir: ref` and returns target path if exists', async () => {
31 | const temp0 = temporaryDirectory()
32 | const temp1 = temporaryDirectory()
33 | const data = `gitdir: ${temp1}.git `
34 |
35 | await fs.outputFile(path.join(temp0, '.git'), data, {
36 | encoding: 'utf8',
37 | })
38 |
39 | expect(await gitRoot(temp0)).toBe(temp1)
40 | })
41 |
42 | it('returns undefined if `gitdir: ref` is unreachable', async () => {
43 | const temp = temporaryDirectory()
44 | const data = `gitdir: /foo/bar/baz.git `
45 |
46 | await fs.outputFile(path.join(temp, '.git'), data, { encoding: 'utf8' })
47 |
48 | expect(await gitRoot(temp)).toBeUndefined()
49 | })
50 |
51 | it('returns undefined if `gitdir: ref` is invalid', async () => {
52 | const temp = temporaryDirectory()
53 | const data = `gitdir: broken-ref-format`
54 |
55 | await fs.outputFile(path.join(temp, '.git'), data, { encoding: 'utf8' })
56 |
57 | expect(await gitRoot(temp)).toBeUndefined()
58 | })
59 | })
60 |
61 | it('returns undefined if `.git` is not found', async () => {
62 | expect(await gitRoot(rootTemporaryDirectory)).toBeUndefined()
63 | })
64 | })
65 |
66 | describe('gitConfigAdd() / gitConfigGet()', () => {
67 | it('sets git config value', async () => {
68 | const key = 'user.name'
69 | const value = 'Foo Bar'
70 | const cwd = await gitInit({})
71 |
72 | await gitConfigAdd({ cwd, key, value })
73 |
74 | expect(await gitConfigGet({ cwd, key })).toBe(value)
75 | })
76 | })
77 |
78 | describe('gitInit()', () => {
79 | const isGitDir = async (cwd: string): Promise =>
80 | (await gitRoot(cwd)) === cwd
81 |
82 | it('inits a new git project in temp dir', async () => {
83 | const cwd = await gitInit({})
84 |
85 | expect(cwd).toEqual(expect.any(String))
86 | expect(cwd).not.toBe(root)
87 | expect(await isGitDir(cwd)).toBe(true)
88 | })
89 |
90 | it('inits repo in specified dir', async () => {
91 | const cwd = temporaryDirectory()
92 | const _cwd = await gitInit({ cwd })
93 |
94 | expect(cwd).toBe(_cwd)
95 | expect(await isGitDir(cwd)).toBe(true)
96 | })
97 |
98 | it('asserts that cwd does not belong to git repo', async () => {
99 | expect(gitInit({ cwd: __dirname })).rejects.toThrowError(
100 | `${__dirname} belongs to repo ${root} already`,
101 | )
102 | })
103 | })
104 |
105 | describe('gitCheckout()', () => {
106 | it('checkout -b creates a branch', async () => {
107 | const cwd = await gitInit({ cwd: temporaryDirectory() })
108 | await gitSetUser({ cwd, name: 'Foo Bar', email: 'foo@bar.com' })
109 |
110 | await fs.writeFile(path.resolve(cwd, 'test.txt'), 'test', {
111 | encoding: 'utf-8',
112 | })
113 |
114 | await gitExec({ cwd, args: ['add', '.'] })
115 | await gitExec({ cwd, args: ['commit', '-a', '-m', 'initial'] })
116 |
117 | await gitCheckout({ cwd, b: true, branch: 'foobar' })
118 |
119 | const branches = await gitExec({ cwd, args: ['branch', '-a'] })
120 |
121 | expect(branches.includes('foobar')).toBeTruthy()
122 | })
123 | })
124 | })
125 |
--------------------------------------------------------------------------------
/packages/git-utils/src/test/ts/unit/git.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/facebook/jest/pull/11818/files#diff-7a98537bdfc98f8a0321f1e556bc226e1eb013e30c266962a788c77df4289a61R181
2 | import { jest } from '@jest/globals'
3 | import { ICallable } from '@qiwi/substrate'
4 | import { temporaryDirectory } from 'tempy'
5 |
6 | const fakeExec = (..._args: any[]) => ({ stdout: 'output' }) // eslint-disable-line
7 | const execa = jest.fn(() => Promise.resolve(fakeExec()))
8 | const execaSync = jest.fn(fakeExec)
9 |
10 | jest.unstable_mockModule('execa', () => ({
11 | __esModule: true,
12 | execa,
13 | execaSync
14 | }))
15 |
16 | const {
17 | gitAdd,
18 | gitAddAll,
19 | gitBranch,
20 | gitCheckout,
21 | gitCommit,
22 | gitConfigAdd,
23 | gitConfigGet,
24 | gitFetch,
25 | gitFetchAll,
26 | gitGetHead,
27 | gitGetTagHash,
28 | gitGetTags,
29 | gitInit,
30 | gitInitOrigin,
31 | gitInitRemote,
32 | gitPush,
33 | gitPushRebase,
34 | gitRebaseToRemote,
35 | gitRemoteAdd,
36 | gitRemoteSetHead,
37 | gitSetUser,
38 | gitShowCommitted,
39 | gitStatus,
40 | gitTag,
41 | } = await import('../../../main/ts')
42 |
43 | describe('git-utils', () => {
44 | afterAll(jest.restoreAllMocks)
45 | afterEach(jest.clearAllMocks)
46 |
47 | const cwd = temporaryDirectory()
48 | const cases: [ICallable, Record, any[][], any?][] = [
49 | [gitInit, { cwd }, [['git', ['init'], { cwd }]], cwd],
50 | [
51 | gitInitRemote,
52 | { cwd },
53 | [['git', ['init', '--bare'], { cwd }]],
54 | expect.any(String),
55 | ],
56 | [
57 | gitInitOrigin,
58 | { cwd, branch: 'foo' },
59 | [['git', ['push', '--tags', 'origin', 'HEAD:refs/heads/foo'], { cwd }]],
60 | expect.any(String),
61 | ],
62 | [
63 | gitCheckout,
64 | { cwd, b: true, branch: 'foobar' },
65 | [['git', ['checkout', '-b', 'foobar'], { cwd }]],
66 | 'output',
67 | ],
68 | [
69 | gitConfigAdd,
70 | { cwd, key: 'user.name', value: 'Foo Bar' },
71 | [['git', ['config', '--add', 'user.name', 'Foo Bar'], { cwd }]],
72 | 'output',
73 | ],
74 | [
75 | gitConfigGet,
76 | { cwd, key: 'user.name' },
77 | [['git', ['config', 'user.name'], { cwd }]],
78 | 'output',
79 | ],
80 | [
81 | gitRemoteAdd,
82 | { cwd, remote: 'qiwi', url: 'git@gh.com:qiwi/foo.git' },
83 | [['git', ['remote', 'add', 'qiwi', 'git@gh.com:qiwi/foo.git'], { cwd }]],
84 | 'output',
85 | ],
86 | [gitFetchAll, { cwd }, [['git', ['fetch', '--all'], { cwd }]], 'output'],
87 | [
88 | gitFetch,
89 | { cwd, origin: 'qiwi', branch: 'master' },
90 | [['git', ['fetch', 'origin', 'master'], { cwd }]],
91 | 'output',
92 | ],
93 | [gitFetch, { cwd }, [['git', ['fetch', '--all'], { cwd }]], 'output'],
94 | [
95 | gitRemoteSetHead,
96 | { cwd, remote: 'qiwi' },
97 | [['git', ['remote', 'set-head', 'qiwi', '--auto'], { cwd }]],
98 | 'output',
99 | ],
100 | [
101 | gitAdd,
102 | { cwd, file: 'qiwi*' },
103 | [['git', ['add', 'qiwi*'], { cwd }]],
104 | 'output',
105 | ],
106 | [gitAddAll, { cwd }, [['git', ['add', '--all'], { cwd }]], 'output'],
107 | [gitTag, { cwd, tag: 'foo' }, [['git', ['tag', 'foo'], { cwd }]], 'output'],
108 | [
109 | gitTag,
110 | { cwd, tag: 'foo', hash: 'bar' },
111 | [['git', ['tag', '-f', 'foo', 'bar'], { cwd }]],
112 | 'output',
113 | ],
114 | [
115 | gitGetTags,
116 | { cwd, hash: 'bar' },
117 | [['git', ['tag', '--merged', 'bar'], { cwd }]],
118 | ['output'],
119 | ],
120 | [
121 | gitGetTagHash,
122 | { cwd, tag: 'foo' },
123 | [['git', ['rev-list', '-1', 'foo'], { cwd }]],
124 | 'output',
125 | ],
126 | [
127 | gitBranch,
128 | { cwd, branch: 'foo' },
129 | [['git', ['branch', 'foo'], { cwd }]],
130 | 'output',
131 | ],
132 | [gitGetHead, { cwd }, [['git', ['rev-parse', 'HEAD'], { cwd }]], 'output'],
133 | [
134 | gitCommit,
135 | { cwd, message: 'foo' },
136 | [['git', ['commit', '--message', 'foo', '--no-gpg-sign', '--allow-empty'], { cwd }]],
137 | 'output',
138 | ],
139 | [
140 | gitCommit,
141 | { cwd, message: 'foo', all: true },
142 | [
143 | [
144 | 'git',
145 | ['commit', '--all', '--message', 'foo', '--no-gpg-sign', '--allow-empty'],
146 | { cwd },
147 | ],
148 | ],
149 | 'output',
150 | ],
151 | [
152 | gitPush,
153 | { cwd, branch: 'metabranch', remote: 'qiwi' },
154 | [
155 | [
156 | 'git',
157 | ['push', '--tags', 'qiwi', 'HEAD:refs/heads/metabranch'],
158 | { cwd },
159 | ],
160 | ],
161 | 'output',
162 | ],
163 | [
164 | gitRebaseToRemote,
165 | { cwd, branch: 'metabranch', remote: 'qiwi' },
166 | [['git', ['rebase', 'qiwi/metabranch'], { cwd }]],
167 | 'output',
168 | ],
169 | [
170 | gitPushRebase,
171 | { cwd, branch: 'metabranch', remote: 'qiwi' },
172 | [
173 | ['git', ['fetch', 'qiwi', 'metabranch'], { cwd }],
174 | ['git', ['rebase', 'qiwi/metabranch'], { cwd }],
175 | [
176 | 'git',
177 | ['push', '--tags', 'qiwi', 'HEAD:refs/heads/metabranch'],
178 | { cwd },
179 | ],
180 | ['git', ['rev-parse', 'HEAD'], { cwd }],
181 | ],
182 | 'output',
183 | ],
184 | [gitStatus, { cwd }, [['git', ['status', '--short'], { cwd }]], 'output'],
185 | [
186 | gitShowCommitted,
187 | { cwd, hash: 'foo' },
188 | [
189 | [
190 | 'git',
191 | ['diff-tree', '--no-commit-id', '--name-only', '-r', 'foo'],
192 | { cwd },
193 | ],
194 | ],
195 | ['output'],
196 | ],
197 | [
198 | gitSetUser,
199 | { cwd, name: 'Foo Bar', email: 'foo@bar.com' },
200 | [
201 | ['git', ['config', '--add', 'user.name', 'Foo Bar'], { cwd }],
202 | ['git', ['config', '--add', 'user.email', 'foo@bar.com'], { cwd }],
203 | ],
204 | 'output',
205 | ],
206 | ]
207 |
208 | cases.forEach(([fn, ctx, argsOfArgs, result]) => {
209 | it(`${fn.name}`, async () => {
210 | await fn(ctx)
211 | expect(fn({ ...ctx, sync: true })).toEqual(result)
212 |
213 | argsOfArgs.forEach((args) => {
214 | expect(execa).toHaveBeenCalledWith(...args)
215 | expect(execaSync).toHaveBeenCalledWith(...args)
216 | })
217 | })
218 | })
219 | })
220 |
--------------------------------------------------------------------------------
/packages/git-utils/src/test/ts/unit/index.ts:
--------------------------------------------------------------------------------
1 | import { gitStatus } from '../../../main/ts'
2 |
3 | describe('index', () => {
4 | it('properly exports its inners', () => {
5 | const gitMethods = [gitStatus]
6 |
7 | gitMethods.forEach((method) => expect(method).toEqual(expect.any(Function)))
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/packages/git-utils/tsconfig.es6.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es6",
5 | "module": "es6",
6 | "outDir": "target/es6"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/git-utils/tsconfig.esnext.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "esnext",
6 | "outDir": "target/esnext"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/git-utils/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../packages/infra/tsconfig.compiler.json",
3 | "compilerOptions": {
4 | "rootDir": "./src/main/ts/",
5 | "baseUrl": "./src/main/ts/",
6 | "tsBuildInfoFile": "./buildcache/.tsbuildinfo",
7 | },
8 | "references": [],
9 | "include": [
10 | "src/main/**/*"
11 | ],
12 | "exclude": [
13 | "node_modules"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/git-utils/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "esnext",
6 | "importHelpers": true,
7 | "noEmitHelpers": true,
8 | "esModuleInterop": true
9 | },
10 | "include": [
11 | "src/**/*"
12 | ],
13 | "exclude": [
14 | "node_modules"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/packages/git-utils/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/git-utils",
3 | "out": "./docs",
4 | "exclude": ["src/test", "**/node_modules/**", "paralleljs"],
5 | "externalPattern": ["**/node_modules/**"],
6 | "excludePrivate": false,
7 | "hideGenerator": true,
8 | "readme": "README.md",
9 | "theme": "default",
10 | "tsconfig": "./tsconfig.es6.json"
11 | }
12 |
--------------------------------------------------------------------------------
/packages/infra/README.md:
--------------------------------------------------------------------------------
1 | # @qiwi/semrel-infra
2 |
3 | Infra package: build tools, configs and other shared assets
4 |
--------------------------------------------------------------------------------
/packages/infra/index.js:
--------------------------------------------------------------------------------
1 | export const infra = '@qiwi/semrel-infra'
2 |
--------------------------------------------------------------------------------
/packages/infra/jest.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "testEnvironment": "node",
3 | "transform": {
4 | "^.+\\.tsx?$": ["ts-jest", {
5 | "useESM": true,
6 | "tsconfig": "/tsconfig.test.json"
7 | }]
8 | },
9 | "extensionsToTreatAsEsm": [".ts", ".esm", ".esm.js"],
10 | "transformIgnorePatterns": [],
11 | "collectCoverage": true,
12 | "collectCoverageFrom": [
13 | "/src/main/**/*.(j|t)s"
14 | ],
15 | "testMatch": [
16 | "/src/test/js/**/*.js",
17 | "/src/test/cjs/**/*.(c)?js",
18 | "/src/test/mjs/**/*.(m)?js",
19 | "/src/test/ts/**/*.ts"
20 | ],
21 | "testPathIgnorePatterns": [
22 | "/node_modules/"
23 | ],
24 | "moduleFileExtensions": [
25 | "ts",
26 | "tsx",
27 | "js",
28 | "jsx",
29 | "json",
30 | "node",
31 | "mjs",
32 | "cjs"
33 | ],
34 | "preset": "ts-jest"
35 | }
36 |
--------------------------------------------------------------------------------
/packages/infra/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-infra",
3 | "version": "3.2.4",
4 | "publishConfig": {
5 | "access": "public"
6 | },
7 | "description": "QIWI semrel infra",
8 | "files": [],
9 | "scripts": {
10 | "postupdate": "yarn"
11 | },
12 | "type": "module",
13 | "bin": {
14 | "concurrently": "./../../node_modules/.bin/concurrently",
15 | "cpy": "./../../node_modules/.bin/cpy",
16 | "depcheck": "./../../node_modules/.bin/depcheck",
17 | "eslint": "./../../node_modules/.bin/eslint",
18 | "jest": "./../../node_modules/.bin/jest",
19 | "libdefkit": "./../../node_modules/.bin/libdefkit",
20 | "mkdirp": "./../../node_modules/.bin/mkdirp",
21 | "prettier": "./../../node_modules/.bin/prettier",
22 | "rimraf": "./../../node_modules/.bin/rimraf",
23 | "snazzy": "./../../node_modules/.bin/snazzy",
24 | "terser": "./../../node_modules/.bin/terser",
25 | "tsc-esm-fix": "./../../node_modules/.bin/tsc-esm-fix",
26 | "typedoc": "./../../node_modules/.bin/typedoc"
27 | },
28 | "devDependencies": {
29 | "@jest/globals": "^29.2.2",
30 | "@qiwi/libdefkit": "^5.0.0",
31 | "@swissquote/crafty-preset-jest": "^1.20.0",
32 | "@types/jest": "^29.2.0",
33 | "concurrently": "^8.0.0",
34 | "cpy-cli": "^4.2.0",
35 | "depcheck": "^1.4.3",
36 | "eslint": "^8.26.0",
37 | "eslint-config-prettier": "^8.5.0",
38 | "eslint-config-qiwi": "^1.17.8",
39 | "jest": "^29.2.2",
40 | "mkdirp": "^1.0.4",
41 | "prettier": "^2.7.1",
42 | "prettier-config-qiwi": "^2.0.0",
43 | "rimraf": "^3.0.2",
44 | "snazzy": "^9.0.0",
45 | "terser": "^5.15.1",
46 | "ts-jest": "^29.0.3",
47 | "tsc-esm-fix": "^2.20.5",
48 | "typedoc": "^0.24.0",
49 | "typescript": "^4.8.4"
50 | },
51 | "repository": {
52 | "type": "git",
53 | "url": "git+https://github.com/qiwi/semantic-release-toolkit.git"
54 | },
55 | "author": "Anton Golub ",
56 | "license": "MIT",
57 | "bugs": {
58 | "url": "https://github.com/qiwi/semantic-release-toolkit/issues"
59 | },
60 | "homepage": "https://github.com/qiwi/semantic-release-toolkit/#readme"
61 | }
62 |
--------------------------------------------------------------------------------
/packages/infra/tsconfig.compiler.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["esnext"],
4 | "noImplicitAny": true,
5 | "noEmitHelpers": true,
6 | "noUnusedLocals": true,
7 | "noUnusedParameters": true,
8 | "skipLibCheck": false,
9 | "resolveJsonModule": true,
10 | "strictNullChecks": true,
11 | "experimentalDecorators": true,
12 | "emitDecoratorMetadata": true,
13 | "downlevelIteration": true,
14 | "forceConsistentCasingInFileNames": true,
15 |
16 | "target": "esnext",
17 | "module": "esnext",
18 | "moduleResolution": "node",
19 | "declaration": true,
20 | "declarationMap": true,
21 | "sourceMap": true,
22 | "strict": true,
23 |
24 | "esModuleInterop": true,
25 | "allowSyntheticDefaultImports": true,
26 | "importHelpers": true,
27 | "incremental": true,
28 | "composite": true,
29 | },
30 | "include": [
31 | "src/main/**/*"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/packages/metabranch/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'eslint-config-qiwi',
4 | 'prettier',
5 | ],
6 | overrides: [
7 | {
8 | files: ['./src/test/**/*.ts'],
9 | rules: {
10 | 'unicorn/consistent-function-scoping': 'off',
11 | 'sonarjs/no-duplicate-string': 'off',
12 | '@typescript-eslint/no-var-requires': 'off',
13 | }
14 | }
15 | ]
16 | };
17 |
--------------------------------------------------------------------------------
/packages/metabranch/README.md:
--------------------------------------------------------------------------------
1 | # @qiwi/semrel-metabranch
2 | [Semrel](https://github.com/semantic-release/semantic-release) plugin for two-way data sync with any remote branch on any release step.
3 |
4 |
5 | | Step | Description |
6 | |--------------------|-------------|
7 | | `verifyConditions` | Performs actions as declared in step options. |
8 | | `analyzeCommits` | As prev. |
9 | | `verifyRelease` | ... |
10 | | `generateNotes` | ... |
11 | | `prepare` | ... |
12 | | `publish` | ... |
13 | | `addChannel` | ... |
14 | | `success` | ... |
15 | | `fail` | ... |
16 |
17 | ## Install
18 | ```shell script
19 | yarn add @qiwi/semrel-metabranch -D
20 | ```
21 |
22 | ## Usage
23 | As a part of plugin declaration:
24 | ```json
25 | // .release.rc
26 | {
27 | "plugins": [[
28 | "@qiwi/semrel-metabranch",
29 | {
30 | "verify": {
31 | "action": "fetch",
32 | "branch": "metabranch",
33 | "from": "foo",
34 | "to": "bar"
35 | }
36 | }
37 | ]]
38 | }
39 | ```
40 | Action declared in release step:
41 | ```json
42 | {
43 | "publish": [[
44 | "@qiwi/semrel-metabranch",
45 | {
46 | "action": "push",
47 | "branch": "metabranch",
48 | "from": "foo/**/*.txt",
49 | "to": "bar",
50 | "message": "commit message"
51 | }
52 | ]]
53 | }
54 | ```
55 |
56 | ### GitHub Pages docs pushing example
57 | ```js
58 | module.exports = {
59 | debug: true,
60 | branch: 'master',
61 | plugins: [
62 | [
63 | '@qiwi/semrel-metabranch',
64 | {
65 | publish: {
66 | action: 'push',
67 | branch: 'gh-pages',
68 | from: './docs',
69 | to: '.',
70 | message: 'update docs ${nextRelease.gitTag}'
71 | }
72 | }
73 | ],
74 | ...
75 | ]
76 | }
77 | ```
78 |
79 | ### Configuration
80 | ##### Environment variables
81 |
82 | | Variable | Description |
83 | |------------------------------| --------------------------------------------------------- |
84 | | `GH_TOKEN` or `GITHUB_TOKEN` | **Required.** The token used to authenticate with GitHub. |
85 |
86 | ##### Options
87 |
88 | | Option | Description | Default |
89 | |-----------------|------------------------| --------|
90 | | `action` | Action to perform: `fetch`/`push` |
91 | | `branch` | Branch to push | `metabranch` |
92 | | `message` | Commit message powered by lodash.template: `docs <%= nextRelease.gitTag %>` | `update meta` |
93 | | `from` | Source glob pattern | `.` (root) |
94 | | `to` | Destination directory | `.` (root) |
95 |
96 |
97 | ## API
98 | ### TActionOptions
99 | ```typescript
100 | export type TBaseActionOptions = {
101 | branch: string
102 | from: string | string[]
103 | to: string
104 | message: string
105 | }
106 |
107 | export type TActionOptionsNormalized = TBaseActionOptions & {
108 | repo: string
109 | cwd: string
110 | temp: string
111 | }
112 |
113 | export type TActionType = 'fetch' | 'push'
114 |
115 | export type TActionOptions = Partial & {
116 | repo: string
117 | }
118 |
119 | export type TPluginOptions = Partial & {
120 | action: TActionType
121 | }
122 | ```
123 |
124 | ### Defaults
125 | ```typescript
126 | export const branch = 'metabranch'
127 | export const from = '.'
128 | export const to = '.'
129 | export const message = 'update meta'
130 |
131 | export const defaults = {
132 | branch,
133 | from,
134 | to,
135 | message,
136 | }
137 | ```
138 |
139 | # License
140 | MIT
141 |
--------------------------------------------------------------------------------
/packages/metabranch/jest.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {...require('../infra/jest.config.json')}
2 |
--------------------------------------------------------------------------------
/packages/metabranch/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-metabranch",
3 | "version": "3.1.2",
4 | "publishConfig": {
5 | "access": "public"
6 | },
7 | "description": "Semrel plugin for two-way data sync with remote branch",
8 | "keywords": [],
9 | "exports": {
10 | ".": {
11 | "module": "./target/exports/es6.mjs",
12 | "import": "./target/exports/es6.mjs",
13 | "require": "./target/exports/es5.cjs"
14 | }
15 | },
16 | "module": "./target/exports/es6.mjs",
17 | "source": "./target/ts/index.ts",
18 | "types": "./target/es6/index.d.ts",
19 | "files": [
20 | "README.md",
21 | "CHANGELOG.md",
22 | "target",
23 | "typings",
24 | "flow-typed"
25 | ],
26 | "scripts": {
27 | "clean": "rimraf target typings flow-typed buildcache coverage docs",
28 | "lint": "eslint 'src/**/*.ts'",
29 | "lint:fix": "yarn lint --fix",
30 | "format": "prettier --write 'src/**/*.ts'",
31 | "test": "concurrently yarn:lint yarn:test:unit yarn:test:depcheck",
32 | "test:unit": "NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles --forceExit --runInBand",
33 | "test:depcheck": "npm_config_yes=true npx -p depcheck -p @babel/parser@7.16.4 depcheck --ignores '@qiwi/esm,@jest/globals,@types/*,tslib,eslint-*,prettier-*,@qiwi/semrel-infra,@qiwi/semrel-common' --ignore-patterns 'typings,flow-typed/*'",
34 | "build": "concurrently yarn:build:es5 yarn:build:es6 yarn:build:exports yarn:build:ts yarn:build:esnext yarn:build:libdef yarn:docs && yarn build:esmfix",
35 | "build:esnext": "mkdirp target/esnext && tsc -p tsconfig.esnext.json",
36 | "build:es5": "mkdirp target/es5 && tsc -p tsconfig.es5.json",
37 | "build:es6": "mkdirp target/es6 && tsc -p tsconfig.es6.json",
38 | "build:ts": "cpy ./ ../../../target/ts/ --dot --cwd=./src/main/ts/",
39 | "build:esmfix": "yarn tsc-esm-fix --target=target/es6 --target=target/esnext --dirnameVar=false --ext=.mjs",
40 | "build:exports": "cpy src/main/exports/ target/exports/ --flat",
41 | "build:libdef": "libdefkit --tsconfig=tsconfig.es5.json --tsconfig=tsconfig.es6.json --tsconfig=tsconfig.esnext.json",
42 | "docs": "typedoc --options ./typedoc.json ./src/main/ts",
43 | "uglify": "for f in $(find target -name '*.js'); do short=${f%.js}; terser -c -m -o $short.js -- $f; done",
44 | "postupdate": "yarn && yarn build && yarn test"
45 | },
46 | "dependencies": {
47 | "@qiwi/esm": "^1.1.8",
48 | "@qiwi/git-utils": "workspace:*",
49 | "@qiwi/semrel-common": "workspace:*",
50 | "@qiwi/semrel-plugin-creator": "workspace:*",
51 | "@types/node": "^18.11.7",
52 | "@types/semantic-release": "^17.2.4",
53 | "execa": "^6.1.0",
54 | "fs-extra": "^10.1.0",
55 | "globby": "^13.1.2",
56 | "tempy": "^3.0.0",
57 | "tslib": "^2.4.0"
58 | },
59 | "devDependencies": {
60 | "@qiwi/semrel-infra": "workspace:*",
61 | "@qiwi/semrel-testing-suite": "workspace:*",
62 | "resolve-from": "^5.0.0",
63 | "semantic-release": "^19.0.5"
64 | },
65 | "repository": {
66 | "type": "git",
67 | "url": "git+https://github.com/qiwi/semantic-release-toolkit.git"
68 | },
69 | "author": "Anton Golub ",
70 | "license": "MIT",
71 | "bugs": {
72 | "url": "https://github.com/qiwi/semantic-release-toolkit/issues"
73 | },
74 | "homepage": "https://github.com/qiwi/semantic-release-toolkit/#readme",
75 | "prettier": "prettier-config-qiwi",
76 | "main": "./target/exports/es5.cjs"
77 | }
78 |
--------------------------------------------------------------------------------
/packages/metabranch/src/main/exports/es5.cjs:
--------------------------------------------------------------------------------
1 | require = require('@qiwi/esm')(module, {
2 | mode: 'all',
3 | cjs: {
4 | cache:false,
5 | esModule:true,
6 | extensions:true,
7 | namedExports:true
8 | },
9 | force: false,
10 | cache: false,
11 | await: false,
12 | sourceMap: false,
13 | })
14 |
15 | module.exports = require('../es5/index.js')
16 |
--------------------------------------------------------------------------------
/packages/metabranch/src/main/exports/es6.mjs:
--------------------------------------------------------------------------------
1 | export * from '../es6/index.mjs'
2 |
--------------------------------------------------------------------------------
/packages/metabranch/src/main/ts/actions.ts:
--------------------------------------------------------------------------------
1 | import {
2 | gitAddAll,
3 | gitCheckout,
4 | gitCommit,
5 | gitFetch,
6 | gitInit,
7 | gitPushRebase,
8 | gitRemoteAdd,
9 | gitRemoteSetHead,
10 | gitSetUser,
11 | gitShowCommitted,
12 | gitStatus,
13 | } from '@qiwi/git-utils'
14 | import { Debugger } from '@qiwi/semrel-plugin-creator'
15 | import fs from 'fs-extra'
16 | import { globby } from 'globby'
17 | import path from 'path'
18 |
19 | import { TActionOptionsNormalized, TActionType, TUserInfo } from './interface'
20 |
21 | export const prepareTempRepo = async (
22 | cwd: string,
23 | repo: string,
24 | branch: string,
25 | { email, name }: TUserInfo,
26 | ): Promise => {
27 | await gitInit({ cwd })
28 | await gitSetUser({ cwd, name, email })
29 | await gitRemoteAdd({ cwd, url: repo, remote: 'origin' })
30 |
31 | try {
32 | await gitFetch({ cwd, remote: 'origin', branch })
33 | await gitCheckout({ cwd, branch: `origin/${branch}` })
34 | } catch {
35 | await gitFetch({ cwd, remote: 'origin' })
36 | await gitRemoteSetHead({ cwd, remote: 'origin' })
37 | }
38 |
39 | return cwd
40 | }
41 |
42 | type TSyncOptions = {
43 | from: string | string[]
44 | to: string
45 | baseFrom: string
46 | baseTo: string
47 | debug: Debugger
48 | }
49 |
50 | const synchronize = async ({
51 | from,
52 | to,
53 | baseFrom,
54 | baseTo,
55 | debug,
56 | }: TSyncOptions): Promise => {
57 | const copy = (src: string, dest: string) => {
58 | debug('copy', 'from=', src, 'to=', dest)
59 | return fs.copy(src, dest)
60 | }
61 | const entries: string[] = Array.isArray(from) ? from : [from]
62 | const patterns: string[] = []
63 | const dirs: string[] = []
64 |
65 | await Promise.all(
66 | entries.map(async (entry: string) => {
67 | const entryAbs = path.resolve(baseFrom, entry)
68 |
69 | try {
70 | if ((await fs.lstat(entryAbs))?.isDirectory()) {
71 | dirs.push(entryAbs)
72 |
73 | return
74 | }
75 | } catch {}
76 |
77 | patterns.push(entry)
78 | }),
79 | )
80 |
81 | await globby(patterns, { cwd: baseFrom, absolute: true }).then((files) =>
82 | Promise.all([
83 | ...files.map((file) =>
84 | copy(file, path.resolve(baseTo, to, path.relative(baseFrom, file))),
85 | ),
86 | ...dirs.map((dir) => copy(dir, path.resolve(baseTo, to))),
87 | ]),
88 | )
89 | }
90 |
91 | export const fetch = async (opts: TActionOptionsNormalized): Promise => {
92 | const { branch, from, to, cwd, temp, repo, debug, user } = opts
93 |
94 | await prepareTempRepo(temp, repo, branch, user)
95 |
96 | await synchronize({ from, to, baseFrom: temp, baseTo: cwd, debug })
97 | }
98 |
99 | export const push = async (opts: TActionOptionsNormalized): Promise => {
100 | const { branch, from, to, cwd, temp, repo, message, debug, user } = opts
101 |
102 | await prepareTempRepo(temp, repo, branch, user)
103 |
104 | await synchronize({ from, to, baseFrom: cwd, baseTo: temp, debug })
105 |
106 | await gitAddAll({ cwd: temp })
107 |
108 | const status = await gitStatus({ cwd: temp })
109 | debug('status=', status)
110 |
111 | if (!status) {
112 | // debug('contents=', fs.readdirSync(temp))
113 | return ''
114 | }
115 |
116 | await gitCommit({ cwd: temp, message })
117 |
118 | const commitId = await gitPushRebase({ cwd: temp, remote: 'origin', branch })
119 | const committedFiles = await gitShowCommitted({ cwd: temp, hash: commitId })
120 |
121 | debug('commitId=', commitId, 'committedFiles=', committedFiles.join(', '))
122 |
123 | return commitId
124 | }
125 |
126 | export const perform = async (
127 | action: TActionType,
128 | options: TActionOptionsNormalized,
129 | ): Promise => {
130 | if (action === 'push') {
131 | return push(options)
132 | }
133 |
134 | if (action === 'fetch') {
135 | return fetch(options)
136 | }
137 |
138 | throw new Error(
139 | `[metabranch] unsupported action '${action}'. Allowed values: 'fetch' and 'push'`,
140 | )
141 | }
142 |
--------------------------------------------------------------------------------
/packages/metabranch/src/main/ts/index.ts:
--------------------------------------------------------------------------------
1 | import { plugin } from './plugin'
2 |
3 | const {
4 | verifyConditions,
5 | analyzeCommits,
6 | verifyRelease,
7 | generateNotes,
8 | prepare,
9 | publish,
10 | addChannel,
11 | success,
12 | fail,
13 | } = plugin
14 |
15 | export {
16 | verifyConditions,
17 | analyzeCommits,
18 | verifyRelease,
19 | generateNotes,
20 | prepare,
21 | publish,
22 | addChannel,
23 | success,
24 | fail,
25 | }
26 |
27 | export * from './actions'
28 | export * from './interface'
29 | export * from './plugin'
30 | export * from './options'
31 |
32 | export default plugin
33 |
--------------------------------------------------------------------------------
/packages/metabranch/src/main/ts/interface.ts:
--------------------------------------------------------------------------------
1 | import { Debugger } from '@qiwi/semrel-plugin-creator'
2 |
3 | export type TBaseActionOptions = {
4 | branch: string
5 | from: string | string[]
6 | to: string
7 | message: string
8 | }
9 |
10 | export type TUserInfo = {
11 | name: string
12 | email: string
13 | }
14 |
15 | export type TActionOptionsNormalized = TBaseActionOptions & {
16 | debug: Debugger
17 | repo: string
18 | cwd: string
19 | temp: string
20 | user: TUserInfo
21 | }
22 |
23 | export type TActionType = 'fetch' | 'push'
24 |
25 | export type TActionOptions = Partial & {
26 | debug: Debugger
27 | repo: string
28 | user: TUserInfo
29 | }
30 |
31 | export type TPluginOptions = Partial & {
32 | action: TActionType
33 | }
34 |
--------------------------------------------------------------------------------
/packages/metabranch/src/main/ts/options.ts:
--------------------------------------------------------------------------------
1 | import { temporaryDirectory } from 'tempy'
2 |
3 | import { TActionOptions, TActionOptionsNormalized } from './interface'
4 |
5 | export const branch = 'metabranch'
6 | export const from = '.'
7 | export const to = '.'
8 | export const message = 'update meta'
9 |
10 | export const defaults = {
11 | branch,
12 | from,
13 | to,
14 | message,
15 | }
16 |
17 | export const normalizeOptions = ({
18 | branch = defaults.branch,
19 | from = defaults.from,
20 | to = defaults.to,
21 | message = defaults.message,
22 | cwd = process.cwd(),
23 | temp = temporaryDirectory(),
24 | repo,
25 | debug,
26 | user,
27 | }: TActionOptions): TActionOptionsNormalized => ({
28 | branch,
29 | from,
30 | to,
31 | message,
32 | cwd,
33 | temp,
34 | repo,
35 | debug,
36 | user,
37 | })
38 |
--------------------------------------------------------------------------------
/packages/metabranch/src/main/ts/plugin.ts:
--------------------------------------------------------------------------------
1 | import { tpl } from '@qiwi/semrel-common'
2 | import { createPlugin } from '@qiwi/semrel-plugin-creator'
3 |
4 | import { perform } from './actions'
5 | import { TPluginOptions } from './interface'
6 | import { normalizeOptions } from './options'
7 |
8 | export const plugin = createPlugin({
9 | debug: 'semantic-release:metabranch',
10 | async handler({ step, stepConfig, pluginConfig, context, debug }) {
11 | const stepOptions = stepConfig || pluginConfig[step]
12 |
13 | debug(
14 | 'handler exec:',
15 | 'step=',
16 | step,
17 | 'stepConfig=',
18 | JSON.stringify(stepOptions),
19 | )
20 |
21 | if (!stepOptions) {
22 | return
23 | }
24 |
25 | const user = {
26 | name: context.env.GIT_AUTHOR_NAME || context.env.GIT_COMMITTER_NAME,
27 | email: context.env.GIT_AUTHOR_EMAIL || context.env.GIT_COMMITTER_EMAIL,
28 | }
29 | const { branch, from, to, message, action } = stepOptions as TPluginOptions
30 | const actionOptions = normalizeOptions({
31 | branch,
32 | from,
33 | to,
34 | message,
35 | user,
36 | cwd: context.cwd,
37 | repo: context.options?.repositoryUrl + '',
38 | debug,
39 | })
40 | actionOptions.message = tpl(actionOptions.message, context, context.logger)
41 |
42 | if (context.options?.dryRun && action === 'push') {
43 | context.logger.log(
44 | '[metabranch] `push` action is disabled in dry-run mode',
45 | )
46 |
47 | return
48 | }
49 |
50 | await perform(action, actionOptions)
51 | },
52 | })
53 |
--------------------------------------------------------------------------------
/packages/metabranch/src/test/fixtures/basicPackage/inner/file.txt:
--------------------------------------------------------------------------------
1 | contents
2 |
--------------------------------------------------------------------------------
/packages/metabranch/src/test/fixtures/basicPackage/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "msr-test-basic",
3 | "author": "Dave Houlbrooke =8.3"
9 | },
10 | "release": {
11 | "plugins": [
12 | "@semantic-release/commit-analyzer",
13 | "@semantic-release/release-notes-generator"
14 | ],
15 | "noCi": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/metabranch/src/test/fixtures/foo/bar/foobar.txt:
--------------------------------------------------------------------------------
1 | foobar
2 |
--------------------------------------------------------------------------------
/packages/metabranch/src/test/fixtures/foo/baz/foobaz.txt:
--------------------------------------------------------------------------------
1 | foobaz
2 |
--------------------------------------------------------------------------------
/packages/metabranch/src/test/fixtures/foo/foo/foofoo.txt:
--------------------------------------------------------------------------------
1 | foofoo
2 |
--------------------------------------------------------------------------------
/packages/metabranch/src/test/ts/actions.ts:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs'
2 | import path, { dirname} from 'node:path'
3 | import { fileURLToPath } from 'node:url'
4 |
5 | import { Debugger } from '@qiwi/semrel-plugin-creator'
6 | import { gitCreateFakeRepo } from '@qiwi/semrel-testing-suite'
7 | import { execa } from 'execa'
8 | import { temporaryDirectory } from 'tempy'
9 |
10 | import { perform, push, TActionOptionsNormalized } from '../../main/ts'
11 |
12 | const __filename = fileURLToPath(import.meta.url)
13 | const __dirname = dirname(__filename)
14 | const fixtures = path.resolve(__dirname, '../fixtures')
15 |
16 | describe('actions', () => {
17 | const user = { name: 'Foo Bar', email: 'foo@bar.com' }
18 |
19 | describe('perform()', () => {
20 | it('Throws an error on unsupported action', () => {
21 | // @ts-ignore
22 | return expect(perform('foo', {})).rejects.toThrowError(
23 | /unsupported action 'foo'/,
24 | )
25 | })
26 | })
27 |
28 | describe('fetch()', () => {
29 | it('clones files from remote to target dir', async () => {
30 | const cwd = temporaryDirectory()
31 | const to = 'foo/bar/baz'
32 | const { url: repo } = gitCreateFakeRepo({
33 | sync: true,
34 | commits: [
35 | {
36 | message: 'feat: initial commit',
37 | from: `${fixtures}/basicPackage/`,
38 | },
39 | ],
40 | })
41 | const opts: TActionOptionsNormalized = {
42 | branch: 'master',
43 | from: '.',
44 | to,
45 | repo,
46 | cwd,
47 | temp: temporaryDirectory(),
48 | debug: console.log as Debugger,
49 | user,
50 | message: 'update meta',
51 | }
52 |
53 | await perform('fetch', opts)
54 |
55 | expect(
56 | fs
57 | .readFileSync(path.join(cwd, to, 'inner/file.txt'), {
58 | encoding: 'utf8',
59 | })
60 | .trim(),
61 | ).toBe('contents')
62 | })
63 | })
64 |
65 | describe('push()', () => {
66 | it('pushes files to remote', async () => {
67 | const cwd = `${fixtures}/foo/`
68 | const { cwd: _cwd, url: repo } = gitCreateFakeRepo({
69 | sync: true,
70 | commits: [
71 | {
72 | message: 'feat: initial commit',
73 | from: `${fixtures}/basicPackage/`,
74 | },
75 | ],
76 | })
77 | const opts = {
78 | cwd,
79 | temp: temporaryDirectory(),
80 | branch: 'metabranch',
81 | from: ['bar', 'unknown'],
82 | to: 'baz',
83 | repo,
84 | debug: console.log as Debugger,
85 | message: 'update meta',
86 | user,
87 | }
88 |
89 | const commitId = await perform('push', opts)
90 |
91 | await execa('git', ['fetch', 'origin', 'metabranch'], { cwd: _cwd })
92 | await execa('git', ['checkout', 'origin/metabranch'], { cwd: _cwd })
93 |
94 | expect(
95 | (await execa('git', ['rev-parse', 'HEAD'], { cwd: _cwd })).stdout,
96 | ).toBe(commitId)
97 | expect(
98 | fs
99 | .readFileSync(path.join(_cwd, 'baz', 'foobar.txt'), {
100 | encoding: 'utf8',
101 | })
102 | .trim(),
103 | ).toBe('foobar')
104 | })
105 |
106 | it('handles racing issues', async () => {
107 | const cwd = `${fixtures}/foo/`
108 | const { url: repo, cwd: _cwd } = gitCreateFakeRepo({
109 | sync: true,
110 | commits: [
111 | {
112 | message: 'feat: initial commit',
113 | from: `${fixtures}/basicPackage/`,
114 | },
115 | ],
116 | })
117 | const debug = console.log as Debugger
118 | const message = 'update meta'
119 | const opts0 = {
120 | cwd,
121 | temp: temporaryDirectory(),
122 | branch: 'metabranch',
123 | from: 'bar',
124 | to: 'scope',
125 | repo,
126 | debug,
127 | user,
128 | message,
129 | }
130 | const opts1 = {
131 | cwd,
132 | temp: temporaryDirectory(),
133 | branch: 'metabranch',
134 | from: 'foo',
135 | to: 'scope',
136 | repo,
137 | debug,
138 | user,
139 | message,
140 | }
141 | const opts2 = {
142 | cwd,
143 | temp: temporaryDirectory(),
144 | branch: 'metabranch',
145 | from: 'baz',
146 | to: 'scope',
147 | repo,
148 | debug,
149 | user,
150 | message,
151 | }
152 |
153 | const pushedCommits = await Promise.all([
154 | push(opts0),
155 | push(opts1),
156 | push(opts2),
157 | ])
158 |
159 | await execa('git', ['fetch', 'origin', 'metabranch'], { cwd: _cwd })
160 | await execa('git', ['checkout', 'origin/metabranch'], { cwd: _cwd })
161 | const commits = (
162 | await execa('git', ['log', '--pretty=%H', 'origin/metabranch'], {
163 | cwd: _cwd,
164 | })
165 | ).stdout.split('\n')
166 |
167 | expect(pushedCommits.every((hash) => commits.includes(hash))).toBeTruthy()
168 | expect(
169 | fs
170 | .readFileSync(path.join(_cwd, 'scope', 'foofoo.txt'), {
171 | encoding: 'utf8',
172 | })
173 | .trim(),
174 | ).toBe('foofoo')
175 | expect(
176 | fs
177 | .readFileSync(path.join(_cwd, 'scope', 'foobar.txt'), {
178 | encoding: 'utf8',
179 | })
180 | .trim(),
181 | ).toBe('foobar')
182 | expect(
183 | fs
184 | .readFileSync(path.join(_cwd, 'scope', 'foobaz.txt'), {
185 | encoding: 'utf8',
186 | })
187 | .trim(),
188 | ).toBe('foobaz')
189 | })
190 | })
191 | })
192 |
--------------------------------------------------------------------------------
/packages/metabranch/src/test/ts/index.ts:
--------------------------------------------------------------------------------
1 | import def, { plugin } from '../../main/ts'
2 |
3 | describe('index', () => {
4 | it('properly exports its inners', () => {
5 | expect(plugin).toBe(def)
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/packages/metabranch/src/test/ts/plugin.ts:
--------------------------------------------------------------------------------
1 | import { dirname, resolve } from 'node:path'
2 | import {fileURLToPath} from 'node:url'
3 |
4 | import { jest } from '@jest/globals'
5 | import { cleanPath, gitCreateFakeRepo } from '@qiwi/semrel-testing-suite'
6 | import { createRequire } from 'module'
7 | import resolveFrom from 'resolve-from'
8 | import semanticRelease from 'semantic-release'
9 |
10 | const require = createRequire(import.meta.url)
11 | const __filename = fileURLToPath(import.meta.url)
12 | const __dirname = dirname(__filename)
13 | const fixtures = resolve(__dirname, '../fixtures')
14 |
15 | describe('plugin', () => {
16 | const pluginName = 'some-plugin'
17 | const { cwd } = gitCreateFakeRepo({
18 | sync: true,
19 | commits: [
20 | {
21 | message: 'feat: initial commit',
22 | from: `${fixtures}/basicPackage/`,
23 | },
24 | ],
25 | })
26 | const perform = jest.fn()
27 |
28 | beforeAll(async () => {
29 | const resolveFromSilent = require('resolve-from').silent
30 |
31 | jest.unstable_mockModule(require.resolve('../../main/ts/actions'), () => ({ perform, __esModule: true }))
32 | const mockedPlugin = (await import('../../main/ts/plugin')).plugin
33 | jest.mock(pluginName, () => mockedPlugin, {
34 | virtual: true,
35 | })
36 | jest
37 | .spyOn(resolveFrom, 'silent')
38 | .mockImplementation((fromDir: string, moduleId: string) => {
39 | if (moduleId === pluginName) {
40 | return pluginName
41 | }
42 |
43 | return resolveFromSilent(fromDir, moduleId) as string
44 | })
45 | })
46 |
47 | afterAll(() => {
48 | jest.restoreAllMocks()
49 | jest.resetModules()
50 | })
51 |
52 | afterEach(jest.clearAllMocks)
53 |
54 | const env = {
55 | ...process.env,
56 | TRAVIS_PULL_REQUEST_BRANCH: 'master',
57 | TRAVIS_BRANCH: 'master',
58 | GITHUB_REF: 'master',
59 | GITHUB_BASE_REF: 'master',
60 | }
61 |
62 | it('plugin is compatible with semrel', async () => {
63 | await semanticRelease(
64 | {
65 | branches: ['master'],
66 | dryRun: true,
67 | plugins: [
68 | [
69 | pluginName,
70 | {
71 | verifyConditions: {
72 | action: 'fetch',
73 | branch: 'metabranch',
74 | from: 'foo',
75 | to: 'bar',
76 | message: 'commit message',
77 | },
78 | },
79 | ],
80 | ],
81 | },
82 | {
83 | cwd: cleanPath(cwd),
84 | env,
85 | },
86 | )
87 |
88 | expect(perform).toHaveBeenCalledWith('fetch', {
89 | branch: 'metabranch',
90 | from: 'foo',
91 | to: 'bar',
92 | cwd: expect.any(String),
93 | temp: expect.any(String),
94 | repo: expect.any(String),
95 | message: 'commit message',
96 | debug: expect.any(Function),
97 | user: {
98 | name: 'semantic-release-bot',
99 | email: 'semantic-release-bot@martynus.net',
100 | },
101 | })
102 | }, 15000)
103 |
104 | it('handles `dry-run` option', async () => {
105 | await semanticRelease(
106 | {
107 | branches: ['master'],
108 | dryRun: true,
109 | plugins: [
110 | [
111 | pluginName,
112 | {
113 | verifyConditions: {
114 | action: 'push',
115 | },
116 | },
117 | ],
118 | ],
119 | },
120 | {
121 | cwd: cleanPath(cwd),
122 | env,
123 | },
124 | )
125 | }, 5000)
126 |
127 | expect(perform).not.toHaveBeenCalled()
128 | })
129 |
--------------------------------------------------------------------------------
/packages/metabranch/tsconfig.es5.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es5",
5 | "outDir": "target/es5",
6 | "module": "ES2015"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/metabranch/tsconfig.es6.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es6",
5 | "outDir": "target/es6",
6 | "module": "ES6"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/metabranch/tsconfig.esnext.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "esnext",
6 | "outDir": "target/esnext"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/metabranch/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../packages/infra/tsconfig.compiler.json",
3 | "compilerOptions": {
4 | "rootDir": "./src/main/ts/",
5 | "baseUrl": "./src/main/ts/",
6 | "tsBuildInfoFile": "./buildcache/.tsbuildinfo"
7 | },
8 | "references": [
9 | { "path": "../testing-suite" },
10 | { "path": "../plugin-creator" }
11 | ],
12 | "include": [
13 | "src/main/**/*"
14 | ],
15 | "exclude": [
16 | "node_modules"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/metabranch/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "esnext",
6 | "importHelpers": false,
7 | "noEmitHelpers": false,
8 | "esModuleInterop": true
9 | },
10 | "include": [
11 | "src/**/*"
12 | ],
13 | "exclude": [
14 | "node_modules"
15 | ]
16 | }
--------------------------------------------------------------------------------
/packages/metabranch/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-metabranch",
3 | "out": "./docs",
4 | "exclude": ["src/test", "**/node_modules/**", "paralleljs"],
5 | "externalPattern": ["**/node_modules/**"],
6 | "excludePrivate": false,
7 | "hideGenerator": true,
8 | "readme": "README.md",
9 | "theme": "default",
10 | "tsconfig": "./tsconfig.es6.json"
11 | }
12 |
--------------------------------------------------------------------------------
/packages/plugin-actions/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'eslint-config-qiwi',
4 | 'prettier',
5 | ],
6 | overrides: [
7 | {
8 | files: ['./src/test/**/*.ts'],
9 | rules: {
10 | 'unicorn/consistent-function-scoping': 'off'
11 | }
12 | }
13 | ]
14 | };
15 |
--------------------------------------------------------------------------------
/packages/plugin-actions/README.md:
--------------------------------------------------------------------------------
1 | # @qiwi/semrel-actions
2 | [Semrel](https://github.com/semantic-release/semantic-release) plugin for data syncing with remote workspaces
3 |
4 | ## Motivation
5 | The main purpose of this plugin is to provide _non-blocking_ release flow (no commits, no conflicts),
6 | but keep the benefits of stateful operations like changelog appending, docs publishing and so on.
7 |
8 | ## Usage area
9 | * Shared build state
10 | * Cross-release semaphore
11 | * Build meta publishing: coverage, buildstamp
12 |
13 | ## Status
14 | 🚧 WIP 🚧
15 |
16 | ## Install
17 | ```shell script
18 | yarn add @qiwi/semrel-actions -D
19 | ```
20 |
21 | ## Config examples
22 | ```json
23 | {
24 | "plugins": [
25 | ["@qiwi/semrel-actions", {
26 | "providers": [
27 | {
28 | "provider": "metabranch",
29 | "options": {
30 | "branch": "metabranch",
31 | "url": "repoUrl"
32 | }
33 | }
34 | ]
35 | }]
36 | ],
37 | "prepare": [
38 | ["@qiwi/semrel-actions", {
39 | "actions": [{
40 | "provider": "metabranch",
41 | "options": {
42 | "from": "--changelog.md",
43 | "to": "changelog.md"
44 | }
45 | }]
46 | }]
47 | ],
48 | "publish": [
49 | ["@qiwi/semrel-actions", {
50 | "actions": [{
51 | "provider": "metabranch",
52 | "options": {
53 | "from": ["docs/*", "coverage/*", "buildstamp.json"],
54 | "to": "/"
55 | }
56 | }, {
57 | "provider": "metabranch",
58 | "options": {
59 | "from": "changelog.md",
60 | "to": "--changelog.md"
61 | }
62 | }]
63 | }]
64 | ]
65 | }
66 | ```
67 |
--------------------------------------------------------------------------------
/packages/plugin-actions/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {...require('../infra/jest.config.json')}
2 |
--------------------------------------------------------------------------------
/packages/plugin-actions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-actions",
3 | "version": "1.1.2",
4 | "publishConfig": {
5 | "access": "public"
6 | },
7 | "description": "Semrel actions provider",
8 | "keywords": [],
9 | "main": "target/es5/index.js",
10 | "source": "target/ts/index.ts",
11 | "types": "target/es5/index.d.ts",
12 | "typescript": {
13 | "definition": "target/es5/index.d.ts"
14 | },
15 | "files": [
16 | "README.md",
17 | "CHANGELOG.md",
18 | "target",
19 | "typings",
20 | "flow-typed"
21 | ],
22 | "scripts": {
23 | "clean": "rimraf target typings flow-typed buildcache coverage docs",
24 | "lint": "eslint 'src/**/*.ts'",
25 | "lint:fix": "yarn lint --fix",
26 | "format": "prettier --write 'src/**/*.ts'",
27 | "test": "echo 'WIP' && exit 0 || concurrently yarn:lint yarn:test:unit yarn:test:depcheck",
28 | "test:unit": "jest --runInBand",
29 | "test:depcheck": "npm_config_yes=true npx -p depcheck -p @babel/parser@7.16.4 depcheck --ignores '@types/*,tslib,eslint-*,prettier-*,@qiwi/semrel-infra,@qiwi/semrel-common' --ignore-patterns 'typings,flow-typed/*'",
30 | "build": "concurrently yarn:build:es5 yarn:build:es6 yarn:build:ts yarn:build:libdef yarn:docs",
31 | "build:es5": "mkdirp target/es5 && tsc -p tsconfig.es5.json",
32 | "build:es6": "mkdirp target/es6 && tsc -p tsconfig.es6.json",
33 | "build:ts": "cpy ./ ../../../target/ts/ --dot --cwd=./src/main/ts/",
34 | "build:libdef": "libdefkit --tsconfig=tsconfig.es5.json --tsconfig=tsconfig.es6.json",
35 | "docs": "typedoc --options ./typedoc.json ./src/main/ts",
36 | "uglify": "for f in $(find target -name '*.js'); do short=${f%.js}; terser -c -m -o $short.js -- $f; done",
37 | "postupdate": "yarn && yarn build && yarn test"
38 | },
39 | "dependencies": {
40 | "@qiwi/semrel-plugin-creator": "workspace:*",
41 | "tslib": "^2.4.0"
42 | },
43 | "devDependencies": {
44 | "@qiwi/semrel-infra": "workspace:*"
45 | },
46 | "repository": {
47 | "type": "git",
48 | "url": "git+https://github.com/qiwi/semantic-release-toolkit.git"
49 | },
50 | "author": "Anton Golub ",
51 | "license": "MIT",
52 | "bugs": {
53 | "url": "https://github.com/qiwi/semantic-release-toolkit/issues"
54 | },
55 | "homepage": "https://github.com/qiwi/semantic-release-toolkit/#readme",
56 | "prettier": "prettier-config-qiwi"
57 | }
58 |
--------------------------------------------------------------------------------
/packages/plugin-actions/src/main/ts/index.ts:
--------------------------------------------------------------------------------
1 | import { createPlugin } from '@qiwi/semrel-plugin-creator'
2 |
3 | export * from './interface'
4 |
5 | export const plugin = createPlugin({
6 | async handler({ step }) {
7 | console.log(step)
8 | },
9 | exclude: ['analyzeCommits', 'generateNotes'],
10 | })
11 |
12 | export default plugin
13 |
--------------------------------------------------------------------------------
/packages/plugin-actions/src/main/ts/interface.ts:
--------------------------------------------------------------------------------
1 | export interface TAction {
2 | provider: string
3 | options: Record
4 | }
5 |
6 | export type TActions = Array
7 |
8 | export type TPluginConfig = {
9 | actions: TActions
10 | }
11 |
--------------------------------------------------------------------------------
/packages/plugin-actions/src/main/ts/providers/metabranch.ts:
--------------------------------------------------------------------------------
1 | /*
2 | import { TAction } from '../interface'
3 |
4 | export type TSyncPoint = {
5 | direction: 'local' | 'remote'
6 | branch: string
7 | from: string | string[]
8 | to: string
9 | }
10 |
11 | export type TSyncBatch = {
12 | batch: Array
13 | }
14 |
15 | export interface TMetabranchPluginAction extends TAction {
16 | provider: 'metabranch'
17 | options: TSyncPoint | TSyncBatch
18 | }
19 | */
20 |
21 | export const foo = 'bar'
22 |
--------------------------------------------------------------------------------
/packages/plugin-actions/src/test/js/index.js:
--------------------------------------------------------------------------------
1 | // import def, { plugin } from '../../../target/es6'
2 | //
3 | // describe('export (es6)', () => {
4 | // it('`plugin` defined', () => {
5 | // expect(plugin).toEqual(expect.any(Object))
6 | // })
7 | //
8 | // it('`default` equals `plugin`', () => {
9 | // expect(def).toBe(plugin)
10 | // })
11 | // })
12 |
13 | describe('', () => {
14 | it('', () => {})
15 | })
16 |
--------------------------------------------------------------------------------
/packages/plugin-actions/src/test/ts/index.ts:
--------------------------------------------------------------------------------
1 | import def, { plugin } from '../../main/ts'
2 |
3 | describe('export', () => {
4 | it('`plugin` defined', () => {
5 | expect(plugin).toEqual(expect.any(Object))
6 | })
7 |
8 | it('`default` equals `plugin`', () => {
9 | expect(def).toBe(plugin)
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/packages/plugin-actions/tsconfig.es5.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es5",
5 | "outDir": "target/es5"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/plugin-actions/tsconfig.es6.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es6",
5 | "outDir": "target/es6"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/plugin-actions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../packages/infra/tsconfig.compiler.json",
3 | "compilerOptions": {
4 | "rootDir": "./src/main/ts/",
5 | "baseUrl": "./src/main/ts/",
6 | "target": "es5",
7 | "outDir": "target/es5",
8 | "tsBuildInfoFile": "./buildcache/.tsbuildinfo"
9 | },
10 | "references": [
11 | { "path": "../plugin-creator" },
12 | { "path": "../common" }
13 | ],
14 | "include": [
15 | "src/main/**/*"
16 | ],
17 | "exclude": [
18 | "node_modules"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/packages/plugin-actions/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": [
4 | "src/**/*"
5 | ],
6 | "exclude": [
7 | "node_modules"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/plugin-actions/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-actions",
3 | "out": "./docs",
4 | "exclude": ["src/test", "**/node_modules/**", "paralleljs"],
5 | "externalPattern": ["**/node_modules/**"],
6 | "excludePrivate": false,
7 | "hideGenerator": true,
8 | "readme": "README.md",
9 | "theme": "default",
10 | "tsconfig": "./tsconfig.es5.json"
11 | }
12 |
--------------------------------------------------------------------------------
/packages/plugin-creator/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'eslint-config-qiwi',
4 | 'prettier',
5 | ],
6 | overrides: [
7 | {
8 | files: ['./src/test/**/*.ts'],
9 | rules: {
10 | 'unicorn/consistent-function-scoping': 'off',
11 | 'sonarjs/no-duplicate-string': 'off'
12 | }
13 | }
14 | ]
15 | };
16 |
--------------------------------------------------------------------------------
/packages/plugin-creator/README.md:
--------------------------------------------------------------------------------
1 | # @qiwi/semrel-plugin-creator
2 | [Semrel](https://github.com/semantic-release/semantic-release) plugin creator
3 |
4 | ## Install
5 | ```shell script
6 | yarn add @qiwi/semrel-plugin-creator
7 | ```
8 |
9 | ## Usage
10 | ```typescript
11 | import {createPlugin} from '@qiwi/semrel-plugin-creator'
12 |
13 | const handler = async ({step, pluginConfig, context, name}) => {
14 | if (step === 'prepare') {
15 | pluginConfig.foo = 'bar'
16 | }
17 |
18 | if (step === 'publish') {
19 | await doSomething()
20 | }
21 | }
22 |
23 | const plugin = createPlugin({
24 | handler,
25 | name: 'plugin-name',
26 | include: ['prepare', 'publish']
27 | })
28 | ```
29 |
30 | ## API
31 | ```typescript
32 | export type TPluginHandlerContext = {
33 | pluginConfig: TPluginConfig
34 | stepConfig: TPluginConfig
35 | stepConfigs: TStepConfigs
36 | context: TSemrelContext
37 | step: TReleaseStep
38 | }
39 |
40 | export type TPluginFactoryOptionsNormalized = {
41 | handler: TReleaseHandler
42 | name?: string
43 | include: TReleaseStep[]
44 | exclude: TReleaseStep[]
45 | require: TReleaseStep[]
46 | }
47 |
48 | export type TPluginFactoryOptions = Partial
49 |
50 | export type TReleaseHandler = (context: TPluginHandlerContext) => Promise
51 |
52 | export type TPluginFactory = (
53 | handler: TPluginFactoryOptions | TReleaseHandler,
54 | ) => TPlugin
55 | ```
56 |
--------------------------------------------------------------------------------
/packages/plugin-creator/jest.config.mjs:
--------------------------------------------------------------------------------
1 | import { readFileSync } from 'node:fs'
2 | import { dirname, resolve } from 'node:path'
3 | import { fileURLToPath } from 'node:url'
4 |
5 | const _dirname = dirname(fileURLToPath(import.meta.url))
6 |
7 | export default JSON.parse(readFileSync(resolve(_dirname, '../infra/jest.config.json'), { encoding: 'utf8' }))
8 |
--------------------------------------------------------------------------------
/packages/plugin-creator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-plugin-creator",
3 | "version": "2.3.2",
4 | "publishConfig": {
5 | "access": "public"
6 | },
7 | "description": "Semrel plugin creator",
8 | "exports": {
9 | ".": {
10 | "module": "./target/exports/es6.mjs",
11 | "import": "./target/exports/es6.mjs",
12 | "require": "./target/exports/es5.cjs"
13 | }
14 | },
15 | "module": "./target/exports/es6.mjs",
16 | "source": "./target/ts/index.ts",
17 | "types": "./target/es6/index.d.ts",
18 | "keywords": [],
19 | "files": [
20 | "README.md",
21 | "CHANGELOG.md",
22 | "target",
23 | "typings",
24 | "flow-typed"
25 | ],
26 | "scripts": {
27 | "clean": "rimraf target typings flow-typed buildcache coverage docs",
28 | "lint": "eslint 'src/**/*.ts'",
29 | "lint:fix": "yarn lint --fix",
30 | "format": "prettier --write 'src/**/*.ts'",
31 | "test": "concurrently yarn:lint yarn:test:unit yarn:test:depcheck",
32 | "test:unit": "NODE_OPTIONS=--experimental-vm-modules jest --config=jest.config.mjs --runInBand",
33 | "test:depcheck": "npm_config_yes=true npx -p depcheck -p @babel/parser@7.16.4 depcheck --ignores '@qiwi/esm,@jest/globals,@types/*,tslib,eslint-*,prettier-*,@qiwi/semrel-infra,@qiwi/semrel-common,@qiwi/semrel-plugin-creator' --ignore-patterns 'typings,flow-typed/*'",
34 | "build": "concurrently yarn:build:es5 yarn:build:es6 yarn:build:exports yarn:build:ts yarn:build:esnext yarn:build:libdef yarn:docs && yarn build:esmfix",
35 | "build:esnext": "mkdirp target/esnext && tsc -p tsconfig.esnext.json",
36 | "build:es5": "mkdirp target/es5 && tsc -p tsconfig.es5.json",
37 | "build:es6": "mkdirp target/es6 && tsc -p tsconfig.es6.json",
38 | "build:ts": "cpy ./ ../../../target/ts/ --dot --cwd=./src/main/ts/",
39 | "build:esmfix": "yarn tsc-esm-fix --target=target/es6 --target=target/esnext --dirnameVar=false --ext=.mjs",
40 | "build:exports": "cpy src/main/exports/ target/exports/ --flat",
41 | "build:libdef": "libdefkit --tsconfig=tsconfig.es5.json --tsconfig=tsconfig.es6.json --tsconfig=tsconfig.esnext.json",
42 | "docs": "typedoc --options ./typedoc.json ./src/main/ts",
43 | "uglify": "for f in $(find target -name '*.js'); do short=${f%.js}; terser -c -m -o $short.js -- $f; done",
44 | "postupdate": "yarn && yarn build && yarn test"
45 | },
46 | "dependencies": {
47 | "@qiwi/esm": "^1.1.8",
48 | "@types/lodash-es": "^4.17.6",
49 | "@types/node": "^18.11.7",
50 | "@types/semantic-release": "^17.2.4",
51 | "debug": "^4.3.4",
52 | "lodash-es": "^4.17.21",
53 | "tslib": "^2.4.0"
54 | },
55 | "devDependencies": {
56 | "@qiwi/semrel-infra": "workspace:*",
57 | "@qiwi/semrel-testing-suite": "workspace:*",
58 | "resolve-from": "^5.0.0",
59 | "semantic-release": "^19.0.5"
60 | },
61 | "repository": {
62 | "type": "git",
63 | "url": "git+https://github.com/qiwi/semantic-release-toolkit.git"
64 | },
65 | "author": "Anton Golub ",
66 | "license": "MIT",
67 | "bugs": {
68 | "url": "https://github.com/qiwi/semantic-release-toolkit/issues"
69 | },
70 | "homepage": "https://github.com/qiwi/semantic-release-toolkit/#readme",
71 | "prettier": "prettier-config-qiwi",
72 | "main": "./target/exports/es5.cjs"
73 | }
74 |
--------------------------------------------------------------------------------
/packages/plugin-creator/src/main/exports/es5.cjs:
--------------------------------------------------------------------------------
1 | require = require('@qiwi/esm')(module, {
2 | mode: 'all',
3 | cjs: {
4 | cache:false,
5 | esModule:true,
6 | extensions:true,
7 | namedExports:true
8 | },
9 | force: false,
10 | cache: false,
11 | await: false,
12 | sourceMap: false,
13 | })
14 |
15 | module.exports = require('../es5/index.js')
16 |
--------------------------------------------------------------------------------
/packages/plugin-creator/src/main/exports/es6.mjs:
--------------------------------------------------------------------------------
1 | export * from '../es6/index.mjs'
2 |
--------------------------------------------------------------------------------
/packages/plugin-creator/src/main/ts/index.ts:
--------------------------------------------------------------------------------
1 | import debugFactory, { Debugger } from 'debug'
2 | import { castArray } from 'lodash-es'
3 |
4 | import {
5 | TPlugin,
6 | TPluginConfig,
7 | TPluginFactory,
8 | TPluginFactoryOptions,
9 | TPluginFactoryOptionsNormalized,
10 | TPluginMetaContext,
11 | TReleaseHandler,
12 | TReleaseStep,
13 | TSemrelContext,
14 | } from './interface'
15 |
16 | export * from './interface'
17 |
18 | export const releaseSteps: Array = [
19 | 'verifyConditions',
20 | 'analyzeCommits',
21 | 'verifyRelease',
22 | 'generateNotes',
23 | 'prepare',
24 | 'publish',
25 | 'addChannel',
26 | 'success',
27 | 'fail',
28 | ]
29 |
30 | export const defaultOptions = {
31 | include: releaseSteps,
32 | exclude: [],
33 | require: [],
34 | handler: async (): Promise => {
35 | /* async noop */
36 | },
37 | }
38 |
39 | const createDebugger = (scope: string | Debugger): Debugger => {
40 | if (typeof scope === 'string') {
41 | return debugFactory(scope)
42 | }
43 |
44 | return scope
45 | }
46 |
47 | export const normalizeOptions = (
48 | options: TReleaseHandler | TPluginFactoryOptions,
49 | ): TPluginFactoryOptionsNormalized => {
50 | const preOptions =
51 | typeof options === 'function' ? { handler: options } : options
52 | const name = preOptions.name || `semrel-plugin-${Math.random().toString().slice(-5)}`
53 | const debug = createDebugger(preOptions.debug || name)
54 |
55 | return { ...defaultOptions, ...preOptions, debug, name }
56 | }
57 |
58 | const checkPrevSteps = (
59 | { invoked }: TPluginMetaContext,
60 | { name, require }: TPluginFactoryOptionsNormalized,
61 | step: TReleaseStep,
62 | ): void => {
63 | if (require.length === 0) {
64 | return
65 | }
66 |
67 | const prevSteps = releaseSteps.slice(0, releaseSteps.indexOf(step))
68 | const missedStep = prevSteps.find(
69 | (step) => require.includes(step) && !invoked.includes(step),
70 | )
71 |
72 | if (missedStep) {
73 | throw new Error(
74 | `plugin '${name}' requires ${missedStep} to be invoked before ${step}`,
75 | )
76 | }
77 | }
78 |
79 | export const getStepConfig = (
80 | context: TSemrelContext,
81 | step: TReleaseStep,
82 | name = '',
83 | ): TPluginConfig | undefined =>
84 | castArray(context.options?.[step])
85 | .map((config) => {
86 | if (Array.isArray(config)) {
87 | const [path, opts] = config
88 |
89 | return { ...opts, path }
90 | }
91 |
92 | return config
93 | })
94 | .find((config) => config?.path === name)
95 |
96 | export const getStepConfigs = (
97 | context: TSemrelContext,
98 | name = '',
99 | ): Record =>
100 | releaseSteps.reduce>(
101 | (configs, step) => {
102 | configs[step] = getStepConfig(context, step, name)
103 |
104 | return configs
105 | },
106 | {} as Record,
107 | )
108 |
109 | const metaContexts: WeakMap = new WeakMap()
110 |
111 | const getMetaContext = (context: TSemrelContext): TPluginMetaContext => {
112 | let metaContext = metaContexts.get(context)
113 |
114 | if (!metaContext) {
115 | metaContext = {
116 | invoked: [],
117 | }
118 | metaContexts.set(context, metaContext)
119 | }
120 |
121 | return metaContext
122 | }
123 |
124 | export const createPlugin: TPluginFactory = (options) => {
125 | const normalizedOpions = normalizeOptions(options)
126 | const { handler, include, exclude, name, debug } = normalizedOpions
127 |
128 | return releaseSteps
129 | .filter((step) => include.includes(step) && !exclude.includes(step))
130 | .reduce((m, step) => {
131 | m[step] = (pluginConfig: TPluginConfig, context: TSemrelContext) => {
132 | const metaContext = getMetaContext(context)
133 |
134 | checkPrevSteps(metaContext, normalizedOpions, step)
135 |
136 | metaContext.invoked.push(step)
137 |
138 | const stepConfigs = getStepConfigs(context, name)
139 | const stepConfig = stepConfigs[step]
140 |
141 | return handler({
142 | pluginConfig,
143 | context,
144 | step,
145 | stepConfig,
146 | stepConfigs,
147 | debug,
148 | })
149 | }
150 |
151 | return m
152 | }, {})
153 | }
154 |
--------------------------------------------------------------------------------
/packages/plugin-creator/src/main/ts/interface.ts:
--------------------------------------------------------------------------------
1 | import { Debugger } from 'debug'
2 | import { Context } from 'semantic-release'
3 |
4 | export { Debugger } from 'debug'
5 |
6 | export type TReleaseType = 'patch' | 'minor' | 'major'
7 |
8 | export type TTag = {
9 | version: string
10 | channel: string
11 | gitTag: string
12 | gitHead: string
13 | }
14 |
15 | export type TBranch = {
16 | channel?: string
17 | tags: string[]
18 | type: string
19 | name: string
20 | range: string
21 | accept: TReleaseType[]
22 | main: boolean
23 | }
24 |
25 | export type TSemrelContext = Context & {
26 | cwd: string
27 | branch?: TBranch
28 | branches: string[]
29 | }
30 |
31 | export type TPluginConfig = Record
32 |
33 | export type TPluginMethod = (
34 | pluginConfig: TPluginConfig,
35 | context: TSemrelContext,
36 | ) => Promise
37 |
38 | export interface TPlugin {
39 | verifyConditions?: TPluginMethod
40 | analyzeCommits?: TPluginMethod
41 | verifyRelease?: TPluginMethod
42 | generateNotes?: TPluginMethod
43 | prepare?: TPluginMethod
44 | publish?: TPluginMethod
45 | addChannel?: TPluginMethod
46 | success?: TPluginMethod
47 | fail?: TPluginMethod
48 | }
49 |
50 | export type TReleaseStep = keyof TPlugin
51 |
52 | export type TStepConfigs = Record
53 |
54 | export type TPluginHandlerContext = {
55 | pluginConfig: TPluginConfig
56 | stepConfig?: TPluginConfig
57 | stepConfigs: TStepConfigs
58 | context: TSemrelContext
59 | step: TReleaseStep
60 | debug: Debugger
61 | }
62 |
63 | export type TReleaseHandler = (context: TPluginHandlerContext) => Promise
64 |
65 | export type TPluginFactoryOptionsNormalized = {
66 | handler: TReleaseHandler
67 | name?: string
68 | include: TReleaseStep[]
69 | exclude: TReleaseStep[]
70 | require: TReleaseStep[]
71 | debug: Debugger
72 | }
73 |
74 | export type TPluginFactoryOptions = Partial<
75 | Omit & {
76 | debug: string | Debugger
77 | }
78 | >
79 |
80 | export type TPluginFactory = (
81 | handler: TPluginFactoryOptions | TReleaseHandler,
82 | ) => TPlugin
83 |
84 | export type TPluginMetaContext = {
85 | invoked: TReleaseStep[]
86 | }
87 |
--------------------------------------------------------------------------------
/packages/plugin-creator/src/test/cjs/index.cjs:
--------------------------------------------------------------------------------
1 | describe('awaits', () => {
2 | it('https://github.com/facebook/jest/pull/11961', () => {
3 | expect(true).toBe(true)
4 | })
5 | })
6 |
7 | // const {createPlugin} = require('@qiwi/semrel-plugin-creator') // eslint-disable-line
8 | //
9 | // describe('cjs', () => {
10 | // it('createPlugin', () => {
11 | // expect(createPlugin).toEqual(expect.any(Function))
12 | // })
13 | // })
14 |
--------------------------------------------------------------------------------
/packages/plugin-creator/src/test/fixtures/yarnWorkspaces/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "msr-test-yarn",
3 | "author": "Dave Houlbrooke =8.3"
9 | },
10 | "workspaces": [
11 | "packages/*"
12 | ],
13 | "release": {
14 | "plugins": [
15 | "@semantic-release/commit-analyzer",
16 | "@semantic-release/release-notes-generator"
17 | ],
18 | "noCi": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/plugin-creator/src/test/fixtures/yarnWorkspaces/packages/a/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "msr-test-a",
3 | "version": "0.0.0",
4 | "peerDependencies": {
5 | "msr-test-c": "*",
6 | "left-pad": "latest"
7 | }
8 | }
--------------------------------------------------------------------------------
/packages/plugin-creator/src/test/fixtures/yarnWorkspaces/packages/b/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "msr-test-b",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "msr-test-a": "*"
6 | },
7 | "devDependencies": {
8 | "msr-test-c": "*",
9 | "left-pad": "latest"
10 | }
11 | }
--------------------------------------------------------------------------------
/packages/plugin-creator/src/test/fixtures/yarnWorkspaces/packages/c/.releaserc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tagFormat": "multi-semantic-release-test-c@v${version}"
3 | }
--------------------------------------------------------------------------------
/packages/plugin-creator/src/test/fixtures/yarnWorkspaces/packages/c/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "msr-test-c",
3 | "version": "0.0.0",
4 | "devDependencies": {
5 | "msr-test-b": "*",
6 | "msr-test-d": "*"
7 | }
8 | }
--------------------------------------------------------------------------------
/packages/plugin-creator/src/test/fixtures/yarnWorkspaces/packages/d/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "msr-test-d",
3 | "version": "0.0.0"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/plugin-creator/src/test/mjs/index.mjs:
--------------------------------------------------------------------------------
1 | import {createPlugin} from '@qiwi/semrel-plugin-creator'
2 |
3 | describe('esm/mjs', () => {
4 | it('createPlugin', () => {
5 | expect(createPlugin).toEqual(expect.any(Function))
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/packages/plugin-creator/src/test/ts/index.ts:
--------------------------------------------------------------------------------
1 | import { jest } from '@jest/globals'
2 |
3 | import {
4 | createPlugin,
5 | defaultOptions,
6 | normalizeOptions,
7 | releaseSteps,
8 | TBranch,
9 | TPluginFactoryOptions,
10 | TPluginMethod,
11 | TSemrelContext,
12 | } from '../../main/ts'
13 |
14 | describe('normalizeOptions()', () => {
15 | it('handles fn as input', () => {
16 | const handler: any = jest.fn()
17 | expect(normalizeOptions(handler).handler).toBe(handler)
18 | })
19 |
20 | it('merges `defaultOptions` with passed opts', () => {
21 | const handler: any = jest.fn()
22 | const name = 'fixtures.foobar'
23 | const options: TPluginFactoryOptions = {
24 | handler,
25 | include: ['fail'],
26 | exclude: ['success'],
27 | require: [],
28 | name,
29 | debug: 'scope',
30 | }
31 |
32 | expect(normalizeOptions(options)).toMatchObject({
33 | ...options,
34 | debug: expect.any(Function),
35 | })
36 | })
37 |
38 | it('uses `defaultOptions` otherwise', () => {
39 | expect(normalizeOptions({})).toMatchObject(defaultOptions)
40 | })
41 |
42 | it('resolves plugin`s pkg name', () => {
43 | expect(normalizeOptions({ name: 'foo' }).name).toBe('foo')
44 | expect(normalizeOptions({}).name).toMatch('semrel-plugin-')
45 | })
46 | })
47 |
48 | describe('createPlugin()', () => {
49 | it('factory returns a new plugin each time', () => {
50 | const handler: any = jest.fn()
51 | const plugin1 = createPlugin(handler)
52 | const plugin2 = createPlugin(handler)
53 |
54 | expect(plugin1).toBeInstanceOf(Object)
55 | expect(plugin2).toBeInstanceOf(Object)
56 | expect(plugin1).not.toBe(plugin2)
57 | })
58 |
59 | it('plugin properly invokes inner handler', () => {
60 | const handler: any = jest.fn()
61 | const pluginName = 'some-plugin'
62 | const plugin = createPlugin({ handler, name: pluginName })
63 | const pluginConfig = {}
64 | const branches = ['master']
65 | const logger: any = console
66 | const branch: TBranch = {
67 | tags: [],
68 | type: 'release',
69 | name: 'master',
70 | range: '>1.0.0',
71 | accept: ['patch', 'minor', 'major'],
72 | main: true,
73 | }
74 | const context: TSemrelContext = {
75 | cwd: process.cwd(),
76 | branch,
77 | branches,
78 | logger,
79 | env: {},
80 | options: {
81 | branch,
82 | branches,
83 | repositoryUrl: 'url',
84 | tagFormat: '@${version}', // eslint-disable-line no-template-curly-in-string
85 | plugins: [['some-plugin', { a: 'b' }]],
86 | publish: [
87 | {
88 | path: 'some-plugin',
89 | foo: 'bar',
90 | },
91 | {
92 | path: 'other-plugin',
93 | bar: 'baz',
94 | },
95 | ],
96 | },
97 | }
98 |
99 | releaseSteps.forEach((step) => {
100 | ;(plugin[step] as TPluginMethod)(pluginConfig, context)
101 |
102 | expect(handler).toHaveBeenCalledWith({
103 | pluginConfig,
104 | context,
105 | step,
106 | stepConfig:
107 | step === 'publish'
108 | ? {
109 | path: 'some-plugin',
110 | foo: 'bar',
111 | }
112 | : undefined,
113 | stepConfigs: {
114 | verifyConditions: undefined,
115 | analyzeCommits: undefined,
116 | verifyRelease: undefined,
117 | generateNotes: undefined,
118 | prepare: undefined,
119 | publish: {
120 | path: 'some-plugin',
121 | foo: 'bar',
122 | },
123 | addChannel: undefined,
124 | success: undefined,
125 | fail: undefined,
126 | },
127 | debug: expect.any(Function),
128 | })
129 | })
130 |
131 | expect(handler).toBeCalledTimes(releaseSteps.length)
132 | })
133 |
134 | describe('options', () => {
135 | it('plugin exposes `included` methods only if option passed', () => {
136 | const plugin = createPlugin({ include: ['success', 'prepare'] })
137 |
138 | expect(Object.keys(plugin)).toEqual(['prepare', 'success'])
139 | })
140 |
141 | it('plugin omits `excluded` methods if option passed', () => {
142 | const plugin = createPlugin({ exclude: ['success', 'prepare'] })
143 |
144 | expect(Object.keys(plugin)).toEqual(
145 | releaseSteps.filter((step) => step !== 'success' && step !== 'prepare'),
146 | )
147 | })
148 |
149 | it('`require` option asserts that all plugin steps have been called', () => {
150 | const plugin = createPlugin({ require: ['verifyConditions', 'prepare'] })
151 | const pluginConfig = {}
152 | const logger: any = console
153 | const context: TSemrelContext = {
154 | cwd: process.cwd(),
155 | branch: {
156 | tags: [],
157 | type: 'release',
158 | name: 'master',
159 | range: '>1.0.0',
160 | accept: ['patch', 'minor', 'major'],
161 | main: true,
162 | },
163 | branches: ['master'],
164 | logger,
165 | env: {},
166 | }
167 | const verifyConditions = plugin.verifyConditions as TPluginMethod
168 | const analyzeCommits = plugin.analyzeCommits as TPluginMethod
169 | const prepare = plugin.prepare as TPluginMethod
170 | const publish = plugin.publish as TPluginMethod
171 |
172 | expect(() => analyzeCommits(pluginConfig, context)).toThrowError(
173 | /^plugin 'semrel-plugin-\d{5}' requires verifyConditions to be invoked before analyzeCommits$/,
174 | )
175 |
176 | verifyConditions(pluginConfig, context)
177 | analyzeCommits(pluginConfig, context)
178 |
179 | expect(() => publish(pluginConfig, context)).toThrowError(
180 | /^plugin 'semrel-plugin-\d{5}' requires prepare to be invoked before publish$/,
181 | )
182 |
183 | prepare(pluginConfig, context)
184 | publish(pluginConfig, context)
185 | })
186 | })
187 |
188 | describe('metaContext', () => {
189 | it('is unique for each semrel context', () => {
190 | const plugin = createPlugin({ require: ['verifyConditions'] })
191 | const pluginConfig = {}
192 | const verifyConditions = plugin.verifyConditions as TPluginMethod
193 | const analyzeCommits = plugin.analyzeCommits as TPluginMethod
194 | const logger: any = console
195 | const context1: TSemrelContext = {
196 | cwd: process.cwd(),
197 | branch: {
198 | tags: [],
199 | type: 'release',
200 | name: 'master',
201 | range: '>1.0.0',
202 | accept: ['patch', 'minor', 'major'],
203 | main: true,
204 | },
205 | branches: ['master'],
206 | logger,
207 | env: {},
208 | }
209 | const context2: TSemrelContext = {
210 | cwd: process.cwd(),
211 | branch: {
212 | tags: [],
213 | type: 'release',
214 | name: 'master',
215 | range: '>1.0.0',
216 | accept: ['patch', 'minor', 'major'],
217 | main: true,
218 | },
219 | branches: ['master'],
220 | logger,
221 | env: {},
222 | }
223 |
224 | expect(() => analyzeCommits(pluginConfig, context1)).toThrowError(
225 | /^plugin 'semrel-plugin-\d{5}' requires verifyConditions to be invoked before analyzeCommits$/,
226 | )
227 | verifyConditions(pluginConfig, context1)
228 | analyzeCommits(pluginConfig, context1)
229 |
230 | expect(() => analyzeCommits(pluginConfig, context2)).toThrowError(
231 | /^plugin 'semrel-plugin-\d{5}' requires verifyConditions to be invoked before analyzeCommits$/,
232 | )
233 | verifyConditions(pluginConfig, context2)
234 | analyzeCommits(pluginConfig, context2)
235 | })
236 | })
237 | })
238 |
--------------------------------------------------------------------------------
/packages/plugin-creator/src/test/ts/integration.ts:
--------------------------------------------------------------------------------
1 | import { dirname, resolve } from 'node:path'
2 | import { fileURLToPath } from 'node:url'
3 |
4 | import { jest } from '@jest/globals'
5 | import {
6 | cleanPath,
7 | gitCreateFakeRepo,
8 | } from '@qiwi/semrel-testing-suite'
9 | import resolveFrom, { silent as resolveFromSilent } from 'resolve-from'
10 | import semanticRelease from 'semantic-release'
11 |
12 | import { createPlugin } from '../../main/ts'
13 |
14 | const __filename = fileURLToPath(import.meta.url)
15 | const __dirname = dirname(__filename)
16 | const fixtures = resolve(__dirname, '../fixtures')
17 |
18 | describe('integration', () => {
19 | const handler: any = jest.fn(({ step }) => {
20 | if (step === 'analyzeCommits') {
21 | return 'patch'
22 | }
23 | })
24 | const pluginName = 'some-plugin'
25 | const plugin = createPlugin({ handler, name: pluginName })
26 | const { cwd } = gitCreateFakeRepo({
27 | sync: true,
28 | commits: [
29 | {
30 | message: 'feat: initial commit',
31 | from: `${fixtures}/yarnWorkspaces/`,
32 | },
33 | ],
34 | })
35 |
36 | beforeAll(() => {
37 | jest.mock(pluginName, () => plugin, { virtual: true })
38 | jest
39 | .spyOn(resolveFrom, 'silent')
40 | .mockImplementation((fromDir: string, moduleId: string) => {
41 | if (moduleId === pluginName) {
42 | return pluginName
43 | }
44 |
45 | return resolveFromSilent(fromDir, moduleId) as string
46 | })
47 | })
48 |
49 | afterAll(() => {
50 | jest.restoreAllMocks()
51 | jest.resetModules()
52 | })
53 |
54 | afterEach(jest.clearAllMocks)
55 |
56 | const env = {
57 | ...process.env,
58 | TRAVIS_PULL_REQUEST_BRANCH: 'master',
59 | TRAVIS_BRANCH: 'master',
60 | GITHUB_REF: 'master',
61 | GITHUB_BASE_REF: 'master',
62 | }
63 |
64 | it('plugin is compatible with semrel', async () => {
65 | await semanticRelease(
66 | {
67 | branches: ['master'],
68 | dryRun: true,
69 | plugins: [pluginName],
70 | },
71 | {
72 | cwd: cleanPath(cwd),
73 | env,
74 | },
75 | )
76 |
77 | expect(handler).toBeCalledTimes(4)
78 | }, 15000)
79 |
80 | it('release handler is invoked with proper context', async () => {
81 | const commonPluginConfig = { common: true }
82 | const preparePluginConfig = { prepare: true }
83 | const publishPluginConfig = { publish: true }
84 | const stepConfigs = {
85 | prepare: preparePluginConfig,
86 | publish: publishPluginConfig,
87 | }
88 |
89 | await semanticRelease(
90 | {
91 | branches: ['master'],
92 | dryRun: false,
93 | plugins: [[pluginName, commonPluginConfig]],
94 | prepare: [[pluginName, preparePluginConfig]],
95 | publish: [
96 | {
97 | path: pluginName,
98 | ...publishPluginConfig,
99 | },
100 | ],
101 | },
102 | {
103 | cwd: cleanPath(cwd),
104 | env,
105 | },
106 | )
107 |
108 | const expectedContext = {
109 | env,
110 | }
111 | // prettier-ignore
112 | const expectedArgs = [
113 | {step: 'verifyConditions', pluginConfig: commonPluginConfig, context: expectedContext},
114 | {step: 'analyzeCommits', context: expectedContext},
115 | {step: 'verifyRelease', context: expectedContext},
116 | {step: 'generateNotes'},
117 | {
118 | step: 'prepare',
119 | pluginConfig: preparePluginConfig,
120 | stepConfig: preparePluginConfig,
121 | stepConfigs,
122 | context: {
123 | ...expectedContext,
124 | nextRelease: {
125 | type: 'patch',
126 | version: '1.0.0',
127 | gitTag: 'v1.0.0',
128 | name: 'v1.0.0',
129 | notes: ''
130 | }
131 | }
132 | },
133 | {step: 'publish', pluginConfig: publishPluginConfig, stepConfig: publishPluginConfig, stepConfigs},
134 | {step: 'success'},
135 | ]
136 | expectedArgs.forEach((handlerContext, index) =>
137 | expect(handler.mock.calls[index][0]).toMatchObject(handlerContext),
138 | )
139 |
140 | expect(handler).toBeCalledTimes(7)
141 | }, 15000)
142 | })
143 |
--------------------------------------------------------------------------------
/packages/plugin-creator/tsconfig.es5.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es5",
5 | "outDir": "target/es5",
6 | "module": "ES2015"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/plugin-creator/tsconfig.es6.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es6",
5 | "outDir": "target/es6",
6 | "module": "ES6"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/plugin-creator/tsconfig.esnext.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "esnext",
6 | "outDir": "target/esnext"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/plugin-creator/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../packages/infra/tsconfig.compiler.json",
3 | "compilerOptions": {
4 | "rootDir": "./src/main/ts/",
5 | "baseUrl": "./src/main/ts/",
6 | "tsBuildInfoFile": "./buildcache/.tsbuildinfo"
7 | },
8 | "references": [
9 | { "path": "../testing-suite" }
10 | ],
11 | "include": [
12 | "src/main/**/*"
13 | ],
14 | "exclude": [
15 | "node_modules"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/packages/plugin-creator/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "esnext",
6 | "importHelpers": false,
7 | "noEmitHelpers": false,
8 | "esModuleInterop": true
9 | },
10 | "include": [
11 | "src/**/*"
12 | ],
13 | "exclude": [
14 | "node_modules"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/packages/plugin-creator/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-plugin-creator",
3 | "out": "./docs",
4 | "exclude": ["src/test", "**/node_modules/**", "paralleljs"],
5 | "externalPattern": ["**/node_modules/**"],
6 | "excludePrivate": false,
7 | "hideGenerator": true,
8 | "readme": "README.md",
9 | "theme": "default",
10 | "tsconfig": "./tsconfig.es5.json"
11 | }
12 |
--------------------------------------------------------------------------------
/packages/preset/README.md:
--------------------------------------------------------------------------------
1 | # @qiwi/semrel-preset
2 | Shared QIWI OSS plugin preset for [semantic-release](https://github.com/semantic-release/semantic-release)
3 | What's inside:
4 | * [@qiwi/semantic-release-gh-pages-plugin](https://github.com/qiwi/semantic-release-gh-pages-plugin)
5 | * [@semantic-release/commit-analyzer](https://github.com/semantic-release/commit-analyzer)
6 | * [@semantic-release/release-notes-generator](https://github.com/semantic-release/release-notes-generator)
7 | * [@semantic-release/changelog](https://github.com/semantic-release/changelog)
8 | * [@semantic-release/npm](https://github.com/semantic-release/npm)
9 | * [@semantic-release/github](https://github.com/semantic-release/github)
10 | * [@semantic-release/git](https://github.com/semantic-release/git)
11 | * [@semantic-release/exec](https://github.com/semantic-release/exec)
12 |
13 | ## Usage
14 | ```shell script
15 | yarn add @qiwi/semrel-preset -D
16 | ```
17 |
--------------------------------------------------------------------------------
/packages/preset/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {...require('../infra/jest.config.json')}
2 |
--------------------------------------------------------------------------------
/packages/preset/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-preset",
3 | "version": "3.1.10",
4 | "publishConfig": {
5 | "access": "public"
6 | },
7 | "description": "QIWI OSS semrel plugins preset",
8 | "keywords": [],
9 | "files": [
10 | "README.md",
11 | "CHANGELOG.md"
12 | ],
13 | "scripts": {
14 | "test": "yarn test:unit",
15 | "test:unit": "jest --runInBand",
16 | "clean": "rimraf target",
17 | "postupdate": "yarn && yarn build && yarn test",
18 | "format": "prettier --write 'src/**/*.ts'"
19 | },
20 | "dependencies": {
21 | "@qiwi/semantic-release-gh-pages-plugin": "^5.2.3",
22 | "@qiwi/semrel-metabranch": "workspace:*",
23 | "@semantic-release/changelog": "^6.0.1",
24 | "@semantic-release/commit-analyzer": "^9.0.2",
25 | "@semantic-release/exec": "^6.0.3",
26 | "@semantic-release/git": "^10.0.1",
27 | "@semantic-release/github": "^8.0.6",
28 | "@semantic-release/npm": "^9.0.1",
29 | "@semantic-release/release-notes-generator": "^10.0.3",
30 | "@semrel-extra/npm": "^1.2.2"
31 | },
32 | "peerDependencies": {
33 | "semantic-release": "*"
34 | },
35 | "repository": {
36 | "type": "git",
37 | "url": "git+https://github.com/qiwi/semantic-release-toolkit.git"
38 | },
39 | "author": "Anton Golub ",
40 | "license": "MIT",
41 | "bugs": {
42 | "url": "https://github.com/qiwi/semantic-release-toolkit/issues"
43 | },
44 | "homepage": "https://github.com/qiwi/semantic-release-toolkit/#readme"
45 | }
46 |
--------------------------------------------------------------------------------
/packages/preset/src/test/js/index.js:
--------------------------------------------------------------------------------
1 | describe('@qiwi/semrel-preset', () => {
2 | const plugins = [
3 | '@qiwi/semantic-release-gh-pages-plugin',
4 | '@semantic-release/commit-analyzer',
5 | '@semantic-release/release-notes-generator',
6 | '@semantic-release/changelog',
7 | '@semantic-release/npm',
8 | '@semantic-release/github',
9 | '@semantic-release/git'
10 | ]
11 |
12 | plugins.forEach((plugin) => {
13 | it(`contains ${plugin}`, () => {
14 | expect(require(plugin)).not.toBeUndefined()
15 | })
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/packages/testing-suite/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'eslint-config-qiwi',
4 | 'prettier',
5 | ],
6 | overrides: [
7 | {
8 | files: ['./src/test/**/*.ts'],
9 | rules: {
10 | 'unicorn/consistent-function-scoping': 'off',
11 | 'sonarjs/no-duplicate-string': 'off'
12 | }
13 | },
14 | {
15 | files: ['./src/main/ts/git.ts'],
16 | rules: {
17 | 'sonarjs/no-duplicate-string': 'off'
18 | }
19 | }
20 | ]
21 | };
22 |
--------------------------------------------------------------------------------
/packages/testing-suite/README.md:
--------------------------------------------------------------------------------
1 | # @qiwi/semrel-testing-suite
2 | Semrel/msr testing helpers
3 |
4 | ## Install
5 | ```shell script
6 | yarn add @qiwi/semrel-testing-suite -D
7 | ```
8 |
9 | ## Usage
10 | ```ts
11 | import { resolve } from 'path'
12 | import resolveFrom, { silent as resolveFromSilent } from 'resolve-from'
13 | import semanticRelease from 'semantic-release'
14 | import {
15 | cleanPath,
16 | copyDirectory,
17 | gitCommitAll,
18 | gitInit,
19 | gitInitOrigin,
20 | gitPush
21 | } from '@qiwi/semrel-testing-suite'
22 | import { createPlugin } from '@qiwi/semrel-plugin-creator'
23 |
24 | const fixtures = resolve(__dirname, '../fixtures')
25 |
26 | describe('integration', () => {
27 | const handler: any = jest.fn(({step}) => {
28 | if (step === 'analyzeCommits') {
29 | return 'patch'
30 | }
31 | })
32 | const pluginName = 'some-plugin'
33 | const plugin = createPlugin({handler, name: pluginName})
34 | const cwd = gitInit()
35 |
36 | copyDirectory(`${fixtures}/yarnWorkspaces/`, cwd)
37 | gitCommitAll(cwd, 'feat: Initial release')
38 | gitInitOrigin(cwd)
39 | gitPush(cwd)
40 |
41 | beforeAll(() => {
42 | jest.mock(pluginName, () => plugin, {virtual: true})
43 | jest
44 | .spyOn(resolveFrom, 'silent')
45 | .mockImplementation((fromDir: string, moduleId: string) => {
46 | if (moduleId === pluginName) {
47 | return pluginName
48 | }
49 |
50 | return resolveFromSilent(fromDir, moduleId) as string
51 | })
52 | })
53 |
54 | afterAll(() => {
55 | jest.restoreAllMocks()
56 | jest.resetModules()
57 | })
58 |
59 | afterEach(jest.clearAllMocks)
60 |
61 | const env = {
62 | ...process.env,
63 | TRAVIS_PULL_REQUEST_BRANCH: 'master',
64 | TRAVIS_BRANCH: 'master'
65 | }
66 |
67 | it('plugin is compatible with semrel', async () => {
68 | await semanticRelease(
69 | {
70 | branches: ['master'],
71 | dryRun: true,
72 | plugins: [pluginName],
73 | },
74 | {
75 | cwd: cleanPath(cwd),
76 | env,
77 | },
78 | )
79 |
80 | expect(handler).toBeCalledTimes(4)
81 | }, 15000)
82 | })
83 | ```
84 |
--------------------------------------------------------------------------------
/packages/testing-suite/jest.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {...require('../infra/jest.config.json')}
2 |
--------------------------------------------------------------------------------
/packages/testing-suite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-testing-suite",
3 | "version": "3.1.2",
4 | "publishConfig": {
5 | "access": "public"
6 | },
7 | "description": "Semrel/msr testing helpers",
8 | "keywords": [],
9 | "type": "module",
10 | "module": "./target/es6/index.mjs",
11 | "exports": "./target/es6/index.mjs",
12 | "source": "./target/ts/index.ts",
13 | "types": "./target/es6/index.d.ts",
14 | "typescript": {
15 | "definition": "./typings/index.d.ts"
16 | },
17 | "files": [
18 | "README.md",
19 | "CHANGELOG.md",
20 | "target",
21 | "typings",
22 | "flow-typed"
23 | ],
24 | "scripts": {
25 | "clean": "rimraf target typings flow-typed buildcache coverage docs",
26 | "lint": "eslint 'src/**/*.ts'",
27 | "lint:fix": "yarn lint --fix",
28 | "format": "prettier --write 'src/**/*.ts'",
29 | "test": "concurrently yarn:lint yarn:test:unit yarn:test:depcheck",
30 | "test:unit": "NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles --forceExit --runInBand",
31 | "test:depcheck": "npm_config_yes=true npx -p depcheck -p @babel/parser@7.16.4 depcheck --ignores '@types/*,tslib,eslint-*,prettier-*,@qiwi/semrel-infra,@qiwi/semrel-common' --ignore-patterns 'typings,flow-typed/*'",
32 | "build": "concurrently yarn:build:esnext yarn:build:es6 yarn:build:ts yarn:build:libdef yarn:docs && yarn build:esmfix",
33 | "build:esnext": "mkdirp target/esnext && tsc -p tsconfig.esnext.json",
34 | "build:es6": "mkdirp target/es6 && tsc -p tsconfig.es6.json",
35 | "build:ts": "cpy ./ ../../../target/ts/ --dot --cwd=./src/main/ts/ --flat",
36 | "build:libdef": "libdefkit --tsconfig=tsconfig.esnext.json --tsconfig=tsconfig.es6.json --entry=@qiwi/semrel-testing-suite/target/es6",
37 | "build:esmfix": "yarn tsc-esm-fix --target=target/es6 --target=target/esnext --ext=.mjs",
38 | "docs": "typedoc --options ./typedoc.json ./src/main/ts",
39 | "uglify": "for f in $(find target -name '*.js'); do short=${f%.js}; terser -c -m -o $short.js -- $f; done",
40 | "postupdate": "yarn && yarn build && yarn test"
41 | },
42 | "dependencies": {
43 | "@antongolub/git-root": "^1.5.7",
44 | "@qiwi/git-utils": "workspace:*",
45 | "@qiwi/semrel-common": "workspace:*",
46 | "@qiwi/substrate": "^1.20.15",
47 | "execa": "^6.1.0",
48 | "fs-extra": "^10.1.0",
49 | "tempy": "^3.0.0",
50 | "tslib": "^2.4.0"
51 | },
52 | "devDependencies": {
53 | "@qiwi/semrel-infra": "workspace:*",
54 | "@types/node": "^18.11.7"
55 | },
56 | "repository": {
57 | "type": "git",
58 | "url": "git+https://github.com/qiwi/semantic-release-toolkit.git"
59 | },
60 | "author": "Anton Golub ",
61 | "license": "MIT",
62 | "bugs": {
63 | "url": "https://github.com/qiwi/semantic-release-toolkit/issues"
64 | },
65 | "homepage": "https://github.com/qiwi/semantic-release-toolkit/#readme",
66 | "prettier": "prettier-config-qiwi"
67 | }
68 |
--------------------------------------------------------------------------------
/packages/testing-suite/src/main/ts/file.ts:
--------------------------------------------------------------------------------
1 | import { existsSync, lstatSync, writeFileSync } from 'node:fs'
2 | import { isAbsolute, join, normalize } from 'node:path'
3 |
4 | import fse from 'fs-extra'
5 |
6 | const { ensureFileSync, copySync: copyDirectory } = fse
7 |
8 | export { copyDirectory }
9 |
10 | // Is given path a directory?
11 | export const isDirectory = (path: string): boolean => {
12 | // String path that exists and is a directory.
13 | return (
14 | typeof path === 'string' &&
15 | existsSync(path) &&
16 | lstatSync(path).isDirectory()
17 | )
18 | }
19 |
20 | // Creates testing files on all specified folders.
21 | export const createTestingFiles = (cwd: string, folders: string[]): void =>
22 | folders.forEach((fld) => {
23 | const target = join(cwd, fld, 'test.txt')
24 |
25 | ensureFileSync(target)
26 | writeFileSync(target, fld)
27 | })
28 |
29 | /**
30 | * Normalize and make a path absolute, optionally using a custom CWD.
31 | * Trims any trailing slashes from the path.
32 | *
33 | * @param {string} path The path to normalize and make absolute.
34 | * @param {string} cwd=process.cwd() The CWD to prepend to the path to make it absolute.
35 | * @returns {string} The absolute and normalized path.
36 | *
37 | * @internal
38 | */
39 | export const cleanPath = (path: string, cwd = process.cwd()): string => {
40 | // Checks.
41 | // check(path, "path: path");
42 | // check(cwd, "cwd: absolute");
43 |
44 | // Normalize, absolutify, and trim trailing slashes from the path.
45 | return normalize(isAbsolute(path) ? path : join(cwd, path)).replace(
46 | /[/\\]+$/,
47 | '',
48 | )
49 | }
50 |
--------------------------------------------------------------------------------
/packages/testing-suite/src/main/ts/git.ts:
--------------------------------------------------------------------------------
1 | import { gitRoot } from '@antongolub/git-root'
2 | import {
3 | exec,
4 | format,
5 | gitAddAll,
6 | gitCheckout,
7 | gitCommit,
8 | gitConfigAdd,
9 | gitExec,
10 | gitInit,
11 | gitInitOrigin,
12 | gitInitRemote,
13 | gitPush,
14 | gitRemoteAdd,
15 | gitSetUser,
16 | gitTag,
17 | IGitCommit,
18 | IGitInit,
19 | TGitResult,
20 | } from '@qiwi/git-utils'
21 | import { ICallable } from '@qiwi/substrate'
22 | import { temporaryDirectory } from 'tempy'
23 |
24 | import { copyDirectory } from './file'
25 |
26 | export * from '@qiwi/git-utils'
27 |
28 | export type TGitCommitDigest = {
29 | message: string
30 | from?: string
31 | tag?: string
32 | branch?: string
33 | }
34 |
35 | export interface IGitInitTestingRepo extends IGitInit {
36 | branch?: string
37 | commits?: TGitCommitDigest[]
38 | }
39 |
40 | /**
41 | * Create a Git repository.
42 | * _Created in a temp folder._
43 | *
44 | * @param {string} opts.branch='master' The branch to initialize the repository to.
45 | * @return {Promise} Promise that resolves to string pointing to the CWD for the created Git repository.
46 | */
47 | export const gitInitTestingRepo = ({
48 | branch = 'master',
49 | sync,
50 | commits,
51 | }: T): TGitResult => {
52 | const cwd = temporaryDirectory()
53 |
54 | return exec(
55 | () => gitInit({ sync, cwd }),
56 | () => gitSetUser({ sync, cwd, name: 'Foo Bar', email: 'foo@bar.com' }),
57 | () =>
58 | gitCheckout({
59 | cwd,
60 | sync,
61 | branch,
62 | b: true,
63 | }),
64 | // Disable GPG signing for commits.
65 | () => gitConfigAdd({ cwd, sync, key: 'commit.gpgsign', value: false }),
66 | () => gitCreateFakeCommits({ sync, cwd, commits }),
67 | () => gitInitOrigin({ cwd, sync }),
68 | // () => gitPush({ cwd, sync }),
69 | () => format(sync as T['sync'], cwd),
70 | )
71 | }
72 |
73 | /**
74 | * `git add .` followed by `git commit`
75 | * _Allows empty commits without any files added._
76 | *
77 | * @param {string} cwd The CWD of the Git repository.
78 | * @param {string} message Commit message.
79 | * @returns {Promise} Promise that resolves to the SHA for the commit.
80 | */
81 | export const gitCommitAll = ({
82 | cwd,
83 | message,
84 | sync,
85 | }: T): TGitResult => {
86 | // Check params.
87 | // check(cwd, 'cwd: absolute')
88 | // check(message, 'message: string+')
89 |
90 | return exec(
91 | () => gitAddAll({ cwd, sync }),
92 | () => gitCommit({ cwd, message, sync: sync as T['sync'] }),
93 | )
94 | }
95 |
96 | export interface IGitFakeRepo extends IGitInit {
97 | commits: TGitCommitDigest[]
98 | }
99 |
100 | export type TGitFakeCommitsDigest = {
101 | commits: string[]
102 | }
103 |
104 | export type TGitFakeRepoDigest = TGitFakeCommitsDigest & {
105 | cwd: string
106 | url: string
107 | }
108 |
109 | export interface IGitPushFakeCommits {
110 | cwd: string
111 | sync?: boolean
112 | commits?: TGitCommitDigest[]
113 | }
114 |
115 | export const gitCreateFakeCommits = ({
116 | commits = [{ message: 'empty commit' }],
117 | sync,
118 | cwd,
119 | }: T): TGitResult => {
120 | const res: TGitFakeCommitsDigest = {
121 | commits: [],
122 | }
123 |
124 | return exec(
125 | ...commits.reduce(
126 | (
127 | cb,
128 | { message = 'feat: initial commit', from, tag, branch = 'master' },
129 | ) => {
130 | cb.push(
131 | () => gitCheckout({ cwd, sync, branch, b: true }),
132 | () => from && copyDirectory(from, cwd),
133 | () =>
134 | gitCommitAll({
135 | cwd,
136 | message,
137 | sync,
138 | }),
139 | (commit) => {
140 | res.commits.push(commit)
141 | },
142 | (commit) => {
143 | if (tag) {
144 | return gitTag({ cwd, tag, hash: commit, sync })
145 | }
146 | },
147 | )
148 | return cb
149 | },
150 | [],
151 | ),
152 | () => format(sync as T['sync'], res),
153 | )
154 | }
155 |
156 | export const gitPushFakeCommits = ({
157 | commits,
158 | sync,
159 | cwd,
160 | }: T): TGitResult => {
161 | let _res: TGitFakeCommitsDigest
162 |
163 | return exec(
164 | () => gitCreateFakeCommits({ cwd, sync, commits }),
165 | (res) => {
166 | _res = res
167 | if (res.commits.length > 0) {
168 | return gitPush({ cwd, sync })
169 | }
170 | },
171 | () => format(sync as T['sync'], _res),
172 | )
173 | }
174 |
175 | export const gitCreateFakeRepo = ({
176 | sync,
177 | commits,
178 | }: T): TGitResult => {
179 | const cwd = temporaryDirectory()
180 | const res: TGitFakeRepoDigest = {
181 | cwd,
182 | url: '',
183 | commits: [],
184 | }
185 |
186 | return exec(
187 | () => gitInit({ sync, cwd }),
188 | () => gitConfigAdd({ cwd, sync, key: 'commit.gpgsign', value: false }),
189 | () => gitSetUser({ sync, cwd, name: 'Foo Bar', email: 'foo@bar.com' }),
190 | () => gitInitRemote({ sync }),
191 | (url: string) => {
192 | res.url = url
193 | },
194 | () => gitRemoteAdd({ sync, cwd, url: res.url }),
195 | () => gitPushFakeCommits({ commits, sync, cwd }),
196 | ({ commits }) => format(sync as T['sync'], { ...res, commits }),
197 | )
198 | }
199 |
200 | export interface IGitClone {
201 | cwd?: string
202 | sync?: boolean
203 | url: string
204 | }
205 |
206 | export const gitClone = ({
207 | cwd = temporaryDirectory(),
208 | sync,
209 | url,
210 | }: T): TGitResult =>
211 | exec(
212 | () => gitRoot(cwd, sync),
213 | (parentGitDir: string) => {
214 | if (parentGitDir) {
215 | throw new Error(
216 | `${cwd} belongs to repo ${parentGitDir as string} already`,
217 | )
218 | }
219 | },
220 | () =>
221 | gitExec({
222 | cwd: cwd as string,
223 | sync,
224 | args: ['clone', url, cwd],
225 | }),
226 | () => format(sync as T['sync'], cwd),
227 | )
228 |
--------------------------------------------------------------------------------
/packages/testing-suite/src/main/ts/index.ts:
--------------------------------------------------------------------------------
1 | export * from './file'
2 | export * from './git'
3 |
--------------------------------------------------------------------------------
/packages/testing-suite/src/test/fixtures/basicPackage/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-basic-package",
3 | "version": "0.0.0",
4 | "private": true,
5 | "license": "0BSD",
6 | "engines": {
7 | "node": ">=8.3"
8 | },
9 | "workspaces": [
10 | "packages/*"
11 | ],
12 | "release": {
13 | "plugins": [
14 | "@semantic-release/commit-analyzer",
15 | "@semantic-release/release-notes-generator"
16 | ],
17 | "noCi": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/testing-suite/src/test/fixtures/foo/bar/foobar.txt:
--------------------------------------------------------------------------------
1 | foobar
2 |
--------------------------------------------------------------------------------
/packages/testing-suite/src/test/fixtures/foo/baz/foobaz.txt:
--------------------------------------------------------------------------------
1 | foobaz
2 |
--------------------------------------------------------------------------------
/packages/testing-suite/src/test/fixtures/foo/foo/foofoo.txt:
--------------------------------------------------------------------------------
1 | foofoo
2 |
--------------------------------------------------------------------------------
/packages/testing-suite/src/test/ts/it/git.ts:
--------------------------------------------------------------------------------
1 | import { dirname, resolve } from 'node:path'
2 | import { fileURLToPath } from 'node:url'
3 |
4 | import { execaSync } from 'execa'
5 |
6 | import {
7 | copyDirectory,
8 | gitAdd,
9 | gitCommit,
10 | gitCommitAll,
11 | gitGetTagHash,
12 | gitGetTags,
13 | // gitInitOrigin,
14 | gitInitTestingRepo,
15 | gitPush,
16 | gitTag,
17 | } from '../../../main/ts'
18 |
19 | const __filename = fileURLToPath(import.meta.url)
20 | const __dirname = dirname(__filename)
21 | const fixtures = resolve(__dirname, '../../fixtures')
22 |
23 | describe('gitInitOrigin()', () => {
24 | it('configures origin', () => {
25 | const sync = true
26 | const cwd = gitInitTestingRepo({ sync })
27 | copyDirectory(`${fixtures}/basicPackage/`, cwd)
28 | const commitId = gitCommitAll({
29 | cwd,
30 | message: 'feat: initial commit',
31 | sync,
32 | })
33 | // const url = gitInitOrigin({ cwd, branch: 'release', sync })
34 | // expect(url).toEqual(expect.any(String))
35 |
36 | expect(commitId).toEqual(expect.any(String))
37 | expect(
38 | execaSync('git', ['remote', 'show', 'origin'], { cwd }).stdout,
39 | ).toMatch(/Remote branch:\n\s+master\s+tracked/)
40 | // ).toMatch(/master\s+tracked\n\s+release\s+tracked/)
41 | })
42 | })
43 |
44 | describe('gitAdd()', () => {
45 | it('adds files to git', () => {
46 | const sync = true
47 | const cwd = gitInitTestingRepo({ sync })
48 | copyDirectory(`${fixtures}/basicPackage/`, cwd)
49 | gitAdd({ cwd, sync, file: 'package.json' })
50 | const commitId = gitCommit({
51 | cwd,
52 | message: 'chore: add package.json',
53 | sync,
54 | })
55 |
56 | expect(commitId).toEqual(expect.any(String))
57 | })
58 | })
59 |
60 | describe('gitPush()', () => {
61 | it('pushes to remote', () => {
62 | const sync = true
63 | const cwd = gitInitTestingRepo({ sync })
64 | copyDirectory(`${fixtures}/basicPackage/`, cwd)
65 | gitCommitAll({ cwd, message: 'feat: initial commit', sync })
66 |
67 | expect(() => gitPush({ cwd, sync })).not.toThrowError()
68 | })
69 | })
70 |
71 | describe('gitTag()', () => {
72 | it('adds tag to commit', () => {
73 | const sync = true
74 | const cwd = gitInitTestingRepo({ sync })
75 | const tag1 = 'foo@1.0.0'
76 | const tag2 = 'bar@1.0.0'
77 | copyDirectory(`${fixtures}/basicPackage/`, cwd)
78 | const commitId = gitCommitAll({
79 | cwd,
80 | message: 'feat: initial commit',
81 | sync,
82 | })
83 |
84 | gitTag({ cwd, tag: tag1, hash: commitId, sync })
85 | gitTag({ cwd, tag: tag2, hash: commitId, sync })
86 | gitPush({ cwd, sync })
87 |
88 | const tagHash = gitGetTagHash({ cwd, tag: tag1, sync })
89 | const tags = gitGetTags({ cwd, hash: commitId, sync })
90 |
91 | expect(tagHash).toBe(commitId)
92 | expect(tags).toEqual([tag2, tag1])
93 | })
94 | })
95 |
--------------------------------------------------------------------------------
/packages/testing-suite/src/test/ts/unit/file.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync } from 'node:fs'
2 | import { dirname, resolve } from 'node:path'
3 | import { fileURLToPath } from 'node:url'
4 |
5 | import { temporaryDirectory } from 'tempy'
6 |
7 | import { cleanPath, createTestingFiles, isDirectory } from '../../../main/ts'
8 |
9 | const __filename = fileURLToPath(import.meta.url)
10 | const __dirname = dirname(__filename)
11 | const fixtures = resolve(__dirname, '../../fixtures')
12 |
13 | describe('cleanPath()', () => {
14 | it('Relative without CWD', () => {
15 | expect(cleanPath('aaa')).toBe(`${process.cwd()}/aaa`)
16 | expect(cleanPath('aaa/')).toBe(`${process.cwd()}/aaa`)
17 | })
18 | it('Relative with CWD', () => {
19 | expect(cleanPath('ccc', '/a/b/')).toBe(`/a/b/ccc`)
20 | expect(cleanPath('ccc', '/a/b')).toBe(`/a/b/ccc`)
21 | })
22 | it('Absolute without CWD', () => {
23 | expect(cleanPath('/aaa')).toBe(`/aaa`)
24 | expect(cleanPath('/aaa/')).toBe(`/aaa`)
25 | expect(cleanPath('/a/b/c')).toBe(`/a/b/c`)
26 | expect(cleanPath('/a/b/c/')).toBe(`/a/b/c`)
27 | })
28 | it('Absolute with CWD', () => {
29 | expect(cleanPath('/aaa', '/x/y/z')).toBe(`/aaa`)
30 | expect(cleanPath('/aaa/', '/x/y/z')).toBe(`/aaa`)
31 | expect(cleanPath('/a/b/c', '/x/y/z')).toBe(`/a/b/c`)
32 | expect(cleanPath('/a/b/c/', '/x/y/z')).toBe(`/a/b/c`)
33 | })
34 | })
35 |
36 | describe('isDirectory()', () => {
37 | it('differs dirs from other dst types', () => {
38 | const cases: [string, boolean][] = [
39 | [`${fixtures}/basicPackage`, true],
40 | [`${fixtures}/foofoo`, false],
41 | [`${fixtures}/basicPackage/package.json`, false],
42 | ]
43 |
44 | cases.forEach(([value, result]) => expect(isDirectory(value)).toBe(result))
45 | })
46 | })
47 |
48 | describe('createTestingFiles', () => {
49 | it('populates cwd with specified subfolders with `test.txt`', () => {
50 | const cwd = temporaryDirectory()
51 | const folters = ['foo', 'bar']
52 |
53 | createTestingFiles(cwd, folters)
54 |
55 | expect(
56 | readFileSync(resolve(cwd, 'foo/test.txt'), { encoding: 'utf-8' }),
57 | ).toBe('foo')
58 | expect(
59 | readFileSync(resolve(cwd, 'bar/test.txt'), { encoding: 'utf-8' }),
60 | ).toBe('bar')
61 | })
62 | })
63 |
--------------------------------------------------------------------------------
/packages/testing-suite/src/test/ts/unit/git.ts:
--------------------------------------------------------------------------------
1 | import { dirname,resolve } from 'node:path'
2 | import {fileURLToPath} from 'node:url'
3 |
4 | import { execaSync } from 'execa'
5 |
6 | import {
7 | gitClone,
8 | gitCreateFakeRepo,
9 | gitGetTags,
10 | gitPushFakeCommits,
11 | } from '../../../main/ts'
12 |
13 | const __filename = fileURLToPath(import.meta.url)
14 | const __dirname = dirname(__filename)
15 | const fixtures = resolve(__dirname, '../../fixtures')
16 | const sync = true
17 |
18 | describe('testing suite', () => {
19 | describe('gitCreateFakeRepo()', () => {
20 | it('creates new fake repo', () => {
21 | const { cwd, url, commits } = gitCreateFakeRepo({
22 | sync,
23 | commits: [
24 | {
25 | message: 'chore: initial commit',
26 | from: `${fixtures}/basicPackage/`,
27 | tag: 'foobar',
28 | branch: 'foo',
29 | },
30 | ],
31 | })
32 | expect(cwd).toEqual(expect.any(String))
33 | expect(
34 | execaSync('git', ['rev-parse', 'HEAD'], { cwd }).stdout,
35 | ).toEqual(commits[0])
36 |
37 | const _cwd = gitClone({ sync, url })
38 |
39 | execaSync('git', ['fetch', '--all'], { cwd: _cwd })
40 | expect(
41 | execaSync('git', ['rev-parse', 'remotes/origin/foo'], { cwd: _cwd })
42 | .stdout,
43 | ).toEqual(commits[0])
44 | expect(gitGetTags({ cwd, hash: commits[0], sync })).toEqual(['foobar'])
45 | })
46 | })
47 | describe('gitPushFakeCommits()', () => {
48 | it('adds commit to repo', () => {
49 | const { cwd, url } = gitCreateFakeRepo({
50 | sync,
51 | commits: [],
52 | })
53 | const { commits } = gitPushFakeCommits({
54 | cwd,
55 | sync,
56 | commits: [
57 | {
58 | message: 'feat: foo bar baz',
59 | from: `${fixtures}/foo/`,
60 | branch: 'foo',
61 | },
62 | {
63 | message: 'feat: initial commit',
64 | from: `${fixtures}/basicPackage/`,
65 | },
66 | ],
67 | })
68 |
69 | const _cwd = gitClone({ sync, url })
70 | const _commits = execaSync('git', ['log', '-10', '--format=format:%H'], {
71 | cwd: _cwd,
72 | })
73 | .stdout.split('\n')
74 |
75 | expect(commits).toEqual(_commits.reverse())
76 | expect(
77 | execaSync('git', ['rev-parse', 'remotes/origin/master'], {
78 | cwd: _cwd,
79 | }).stdout,
80 | ).toEqual(commits[1])
81 | })
82 | })
83 | })
84 |
--------------------------------------------------------------------------------
/packages/testing-suite/src/test/ts/unit/index.ts:
--------------------------------------------------------------------------------
1 | import { gitClone } from '../../../main/ts'
2 |
3 | describe('export', () => {
4 | it('`gitClone` is defined', () => {
5 | expect(gitClone).toEqual(expect.any(Function))
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/packages/testing-suite/tsconfig.es5.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es5",
5 | "outDir": "target/es5"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/testing-suite/tsconfig.es6.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "ES6",
5 | "target": "es6",
6 | "outDir": "target/es6"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/testing-suite/tsconfig.esnext.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "esnext",
6 | "outDir": "target/esnext"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/testing-suite/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../packages/infra/tsconfig.compiler.json",
3 | "compilerOptions": {
4 | "rootDir": "./src/main/ts/",
5 | "baseUrl": "./src/main/ts/",
6 | "tsBuildInfoFile": "./buildcache/.tsbuildinfo"
7 | },
8 | "references": [],
9 | "include": [
10 | "src/main/**/*"
11 | ],
12 | "exclude": [
13 | "node_modules"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/testing-suite/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "esnext",
6 | "importHelpers": false,
7 | "noEmitHelpers": false,
8 | "esModuleInterop": false
9 | },
10 | "include": [
11 | "src/**/*"
12 | ],
13 | "exclude": [
14 | "node_modules"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/packages/testing-suite/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-testing-suite",
3 | "out": "./docs",
4 | "exclude": ["src/test", "**/node_modules/**", "paralleljs"],
5 | "externalPattern": ["**/node_modules/**"],
6 | "excludePrivate": false,
7 | "hideGenerator": true,
8 | "readme": "README.md",
9 | "theme": "default",
10 | "tsconfig": "./tsconfig.es5.json"
11 | }
12 |
--------------------------------------------------------------------------------
/packages/toolkit/README.md:
--------------------------------------------------------------------------------
1 | # @qiwi/semrel-toolkit
2 | All-in-one package to run [semantic-release](https://github.com/semantic-release/semantic-release) and [multi-semantic-release](https://github.com/qiwi/multi-semantic-release) releases with QIWI OSS plugin presets
3 |
4 | ## Usage
5 | ```shell script
6 | npx -p @qiwi/semrel-toolkit semrel
7 | npx -p @qiwi/semrel-toolkit multi-semrel
8 | ```
9 |
--------------------------------------------------------------------------------
/packages/toolkit/msr.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import('@qiwi/multi-semantic-release/bin/cli.js')
4 |
--------------------------------------------------------------------------------
/packages/toolkit/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@qiwi/semrel-toolkit",
3 | "version": "3.2.11",
4 | "publishConfig": {
5 | "access": "public"
6 | },
7 | "description": "Semantic release tools, plugins and configs for QIWI OSS projects",
8 | "keywords": [],
9 | "files": [
10 | "README.md",
11 | "CHANGELOG.md",
12 | "msr.js",
13 | "semrel.js"
14 | ],
15 | "bin": {
16 | "multi-semrel": "./msr.js",
17 | "semrel": "./semrel.js"
18 | },
19 | "dependencies": {
20 | "@qiwi/multi-semantic-release": "~6.5.1",
21 | "@qiwi/semrel-config": "workspace:*",
22 | "@qiwi/semrel-config-monorepo": "workspace:*",
23 | "@qiwi/semrel-preset": "workspace:*",
24 | "semantic-release": "~19.0.5"
25 | },
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/qiwi/semantic-release-toolkit.git"
29 | },
30 | "author": "Anton Golub ",
31 | "license": "MIT",
32 | "bugs": {
33 | "url": "https://github.com/qiwi/semantic-release-toolkit/issues"
34 | },
35 | "homepage": "https://github.com/qiwi/semantic-release-toolkit/#readme"
36 | }
37 |
--------------------------------------------------------------------------------
/packages/toolkit/semrel.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | require('semantic-release/bin/semantic-release.js')
4 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ],
5 | "automerge": true,
6 | "automergeType": "pr",
7 | "rangeStrategy": "replace"
8 | }
9 |
--------------------------------------------------------------------------------
/scripts/js/coverage-merge.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const findGitRoot = require('find-git-root')
4 | const { resolve } = require('path')
5 |
6 | const {
7 | mkdirSync,
8 | existsSync,
9 | readFileSync,
10 | readdirSync,
11 | writeFileSync
12 | } = require('fs')
13 |
14 | const ROOT = resolve(findGitRoot(), '..')
15 | const PACKAGES_ROOT = resolve(ROOT, 'packages')
16 | const COV_DIR = 'coverage'
17 | const COV_FINAL = 'coverage-final.json'
18 | const LCOV = 'lcov.info'
19 |
20 | const getDirectories = source =>
21 | readdirSync(source, { withFileTypes: true })
22 | .filter(dirent => dirent.isDirectory())
23 | .map(dirent => resolve(source, dirent.name))
24 |
25 | const read = (file, cb) => {
26 | if (existsSync(file)) {
27 | const data = readFileSync(file, { encoding: 'utf-8' })
28 |
29 | try {
30 | cb(data)
31 |
32 | } catch (e) {
33 | console.warn(e)
34 | }
35 | }
36 | }
37 |
38 | const write = (dir, name, data) => {
39 | mkdirSync(dir, { recursive: true })
40 | writeFileSync(resolve(dir, name), data)
41 | }
42 |
43 | const mergeCoverage = () => {
44 | let lcov = ''
45 | const covFinal = {}
46 | const packages = getDirectories(PACKAGES_ROOT)
47 |
48 | packages.forEach(pack => {
49 | read(resolve(pack, COV_DIR, COV_FINAL), data => Object.assign(covFinal, JSON.parse(data)))
50 | read(resolve(pack, COV_DIR, LCOV), data => {lcov = lcov + data})
51 | })
52 |
53 | write(resolve(ROOT, COV_DIR), LCOV, lcov)
54 | write(resolve(ROOT, COV_DIR), COV_FINAL, JSON.stringify(covFinal))
55 | }
56 |
57 | mergeCoverage()
58 |
--------------------------------------------------------------------------------
/scripts/sh/patch-pkg-main.sh:
--------------------------------------------------------------------------------
1 | packages="node_modules/tslib/package.json packages/plugin-creator/package.json packages/metabranch/package.json"
2 |
3 | if [ $# -eq 1 ]; then
4 | for p in $packages;
5 | do jq 'if (.main) then ._main = .main | del(.main) else . end' $p | sponge $p;
6 | done;
7 | echo "package.json main disabled: https://github.com/facebook/jest/pull/11961";
8 | else
9 | for p in $packages;
10 | do jq 'if (._main) then .main = ._main | del(._main) else . end' $p | sponge $p;
11 | done;
12 | echo "package.json main restored: https://github.com/facebook/jest/pull/11961";
13 | fi
14 |
--------------------------------------------------------------------------------
/scripts/sh/update.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | NPM_UPGRADE="npm-upgrade"
4 | PACKAGES=$(cat package.json | jq -r '.workspaces | join(" ")')
5 |
6 | eval $NPM_UPGRADE
7 |
8 | for f in $PACKAGES; do
9 | if [ -d "$f" ]; then
10 | cd $f
11 | eval $NPM_UPGRADE
12 | fi
13 | done
14 |
--------------------------------------------------------------------------------
/tsconfig.esnext.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "references": [
4 | { "path": "./packages/common/tsconfig.esnext.json" },
5 | { "path": "./packages/plugin-creator/tsconfig.esnext.json" },
6 | { "path": "./packages/plugin-actions/tsconfig.es6.json" },
7 | { "path": "./packages/metabranch/tsconfig.esnext.json" },
8 | { "path": "./packages/testing-suite/tsconfig.esnext.json" },
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "temp/prebuild",
4 | "emitDeclarationOnly": true,
5 | "esModuleInterop": true,
6 | "importHelpers": false,
7 | "noEmitHelpers": true,
8 | "declaration": true,
9 | "module": "ESNext",
10 | "moduleResolution": "Node"
11 | },
12 | "include": [
13 | "./packages/*/src/main/**/*"
14 | ],
15 | "references": [
16 | { "path": "./packages/common/tsconfig.es6.json" },
17 | { "path": "./packages/plugin-creator/tsconfig.es6.json" },
18 | { "path": "./packages/plugin-actions/tsconfig.es6.json" },
19 | { "path": "./packages/metabranch/tsconfig.es6.json" },
20 | { "path": "./packages/testing-suite/tsconfig.es6.json" },
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------