Compare commits
No commits in common. "main" and "v2.0.0" have entirely different histories.
|
|
@ -1,43 +0,0 @@
|
|||
{
|
||||
"name": "GitHub Actions (TypeScript)",
|
||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:20",
|
||||
"postCreateCommand": "npm install",
|
||||
"customizations": {
|
||||
"codespaces": {
|
||||
"openFiles": [
|
||||
"README.md"
|
||||
]
|
||||
},
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"bierner.markdown-preview-github-styles",
|
||||
"davidanson.vscode-markdownlint",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"github.copilot",
|
||||
"github.vscode-github-actions",
|
||||
"github.vscode-pull-request-github",
|
||||
"me-dutour-mathieu.vscode-github-actions",
|
||||
"redhat.vscode-yaml",
|
||||
"rvest.vs-code-prettier-eslint",
|
||||
"yzhang.markdown-all-in-one",
|
||||
"eamodio.gitlens"
|
||||
],
|
||||
"settings": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnSave": true,
|
||||
"markdown.extension.list.indentationSize": "adaptive",
|
||||
"markdown.extension.italic.indicator": "_",
|
||||
"markdown.extension.orderedList.marker": "one"
|
||||
}
|
||||
}
|
||||
},
|
||||
"remoteEnv": {
|
||||
"GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}"
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {},
|
||||
"ghcr.io/devcontainers-community/npm-features/prettier:1": {}
|
||||
}
|
||||
}
|
||||
132
.eslintrc.json
132
.eslintrc.json
|
|
@ -1,78 +1,56 @@
|
|||
{
|
||||
"plugins": [
|
||||
"jest",
|
||||
"@typescript-eslint",
|
||||
"@stylistic"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:github/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 9,
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"rules": {
|
||||
"i18n-text/no-en": "off",
|
||||
"eslint-comments/no-use": "off",
|
||||
"import/no-namespace": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
"error",
|
||||
{
|
||||
"accessibility": "no-public"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-require-imports": "error",
|
||||
"@typescript-eslint/array-type": "error",
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
"@typescript-eslint/ban-ts-comment": "error",
|
||||
"camelcase": "off",
|
||||
"@typescript-eslint/consistent-type-assertions": "error",
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"error",
|
||||
{
|
||||
"allowExpressions": true
|
||||
}
|
||||
],
|
||||
"@stylistic/func-call-spacing": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"@typescript-eslint/no-array-constructor": "error",
|
||||
"@typescript-eslint/no-empty-interface": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/no-extraneous-class": "error",
|
||||
"@typescript-eslint/no-for-in-array": "error",
|
||||
"@typescript-eslint/no-inferrable-types": "error",
|
||||
"@typescript-eslint/no-misused-new": "error",
|
||||
"@typescript-eslint/no-namespace": "error",
|
||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
"@typescript-eslint/no-useless-constructor": "error",
|
||||
"@typescript-eslint/no-var-requires": "error",
|
||||
"@typescript-eslint/prefer-for-of": "warn",
|
||||
"@typescript-eslint/prefer-function-type": "warn",
|
||||
"@typescript-eslint/prefer-includes": "error",
|
||||
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
||||
"@typescript-eslint/promise-function-async": "error",
|
||||
"@typescript-eslint/require-array-sort-compare": "error",
|
||||
"@typescript-eslint/restrict-plus-operands": "error",
|
||||
"semi": "off",
|
||||
"@stylistic/semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"@typescript-eslint/trailingComma": "off",
|
||||
"@stylistic/type-annotation-spacing": "error",
|
||||
"@typescript-eslint/unbound-method": "error"
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true,
|
||||
"jest/globals": true
|
||||
}
|
||||
}
|
||||
"plugins": ["jest", "@typescript-eslint"],
|
||||
"extends": ["plugin:github/recommended"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 9,
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"rules": {
|
||||
"i18n-text/no-en": "off",
|
||||
"eslint-comments/no-use": "off",
|
||||
"import/no-namespace": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
||||
"@typescript-eslint/no-require-imports": "error",
|
||||
"@typescript-eslint/array-type": "error",
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
"@typescript-eslint/ban-ts-comment": "error",
|
||||
"camelcase": "off",
|
||||
"@typescript-eslint/consistent-type-assertions": "error",
|
||||
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
|
||||
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||
"@typescript-eslint/no-array-constructor": "error",
|
||||
"@typescript-eslint/no-empty-interface": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/no-extraneous-class": "error",
|
||||
"@typescript-eslint/no-for-in-array": "error",
|
||||
"@typescript-eslint/no-inferrable-types": "error",
|
||||
"@typescript-eslint/no-misused-new": "error",
|
||||
"@typescript-eslint/no-namespace": "error",
|
||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
"@typescript-eslint/no-useless-constructor": "error",
|
||||
"@typescript-eslint/no-var-requires": "error",
|
||||
"@typescript-eslint/prefer-for-of": "warn",
|
||||
"@typescript-eslint/prefer-function-type": "warn",
|
||||
"@typescript-eslint/prefer-includes": "error",
|
||||
"@typescript-eslint/prefer-string-starts-ends-with": "error",
|
||||
"@typescript-eslint/promise-function-async": "error",
|
||||
"@typescript-eslint/require-array-sort-compare": "error",
|
||||
"@typescript-eslint/restrict-plus-operands": "error",
|
||||
"semi": "off",
|
||||
"@typescript-eslint/semi": ["error", "always"],
|
||||
"@typescript-eslint/type-annotation-spacing": "error",
|
||||
"@typescript-eslint/unbound-method": "error",
|
||||
"@typescript-eslint/trailingComma": "off"
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true,
|
||||
"jest/globals": true
|
||||
}
|
||||
}
|
||||
|
|
@ -3,20 +3,13 @@ updates:
|
|||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
commit-message:
|
||||
prefix: "chore: "
|
||||
prefix: "chore(deps): "
|
||||
schedule:
|
||||
interval: weekly
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
commit-message:
|
||||
prefix: "chore: "
|
||||
schedule:
|
||||
interval: weekly
|
||||
|
||||
- package-ecosystem: devcontainers
|
||||
directory: /
|
||||
commit-message:
|
||||
prefix: "chore: "
|
||||
prefix: "chore(deps): "
|
||||
schedule:
|
||||
interval: weekly
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set Node.js 20.x
|
||||
uses: actions/setup-node@v4.4.0
|
||||
- name: Set Node.js 16.x
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 16.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
|
@ -48,7 +48,7 @@ jobs:
|
|||
id: diff
|
||||
|
||||
# If index.js was different than expected, upload the expected version as an artifact
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
|
||||
with:
|
||||
name: dist
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
source-root: src
|
||||
|
|
@ -47,7 +47,7 @@ jobs:
|
|||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
|
@ -61,4 +61,4 @@ jobs:
|
|||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ jobs:
|
|||
files: '**/*.txt'
|
||||
replacement-text: '1.0.2'
|
||||
exclude: '**/*.check.txt'
|
||||
max-parallelism: '1'
|
||||
|
||||
- name: Replace in Files - replace using special characters
|
||||
uses: ./
|
||||
|
|
@ -92,26 +91,6 @@ jobs:
|
|||
encoding: 'invalid'
|
||||
exclude: '**/*.check.txt'
|
||||
|
||||
- name: Replace in Files - error on invalid max-parallelism (invalid number)
|
||||
uses: ./
|
||||
continue-on-error: true
|
||||
with:
|
||||
search-text: 'foo'
|
||||
files: '**/*.txt'
|
||||
replacement-text: 'bar'
|
||||
exclude: '**/*.check.txt'
|
||||
max-parallelism: '-1'
|
||||
|
||||
- name: Replace in Files - error on invalid max-parallelism (invalid string)
|
||||
uses: ./
|
||||
continue-on-error: true
|
||||
with:
|
||||
search-text: 'foo'
|
||||
files: '**/*.txt'
|
||||
replacement-text: 'bar'
|
||||
exclude: '**/*.check.txt'
|
||||
max-parallelism: 'invalid'
|
||||
|
||||
- name: Verify changes
|
||||
run: |
|
||||
echo " > Actual - test1.txt"
|
||||
|
|
|
|||
56
README.md
56
README.md
|
|
@ -28,81 +28,29 @@ It can be useful for automating repetitive tasks such as updating version number
|
|||
- `exclude`:
|
||||
(Optional) The files to be excluded from the search. It can be the path to a file or a glob pattern matching one or more files (e.g. `**/*.md`). Defaults to an empty string.
|
||||
|
||||
- `max-parallelism`:
|
||||
(Optional) The maximum number of files that will be processed in parallel. This can be used to control the performance impact of the operation on the system. It should be a positive integer. Defaults to `10`.
|
||||
|
||||
## Example usage
|
||||
|
||||
```yaml
|
||||
# Replace all the occurrences of 'hello' with 'world' in all the txt files,
|
||||
# excluding the node_modules folder
|
||||
- name: Replace multiple files
|
||||
uses: richardrigutins/replace-in-files@v2
|
||||
uses: richardrigutins/replace-in-files@v1
|
||||
with:
|
||||
files: '**/*.txt'
|
||||
search-text: 'hello'
|
||||
replacement-text: 'world'
|
||||
exclude: 'node_modules/**'
|
||||
encoding: 'utf8'
|
||||
max-parallelism: 10
|
||||
|
||||
# Replace all the occurrences of '{0}' with '42' in the README.md file
|
||||
- name: Replace single file
|
||||
uses: richardrigutins/replace-in-files@v2
|
||||
uses: richardrigutins/replace-in-files@v1
|
||||
with:
|
||||
files: 'README.md'
|
||||
search-text: '{0}'
|
||||
replacement-text: '42'
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Update the Action Code
|
||||
|
||||
The [`src/`](./src/) directory contains the source code that will be run when
|
||||
the action is invoked.
|
||||
|
||||
After making changes to the action code, make sure to run the following command
|
||||
to run all tests, lint the code, and build the final JavaScript action code:
|
||||
|
||||
```bash
|
||||
npm run all
|
||||
```
|
||||
|
||||
> This step is important! It will run [`ncc`](https://github.com/vercel/ncc) to
|
||||
> build the final JavaScript action code with all dependencies included. If you
|
||||
> do not run this step, the action will not work correctly when it is used in a
|
||||
> workflow. This step also includes the `--license` option for `ncc`, which will
|
||||
> create a license file for all of the production node modules used in your
|
||||
> project.
|
||||
|
||||
### Publishing a New Release
|
||||
|
||||
This project includes a helper script, [`script/release`](./script/release)
|
||||
designed to streamline the process of tagging and pushing new releases for
|
||||
GitHub Actions.
|
||||
|
||||
GitHub Actions allows users to select a specific version of the action to use,
|
||||
based on release tags. This script simplifies this process by performing the
|
||||
following steps:
|
||||
|
||||
1. **Retrieving the latest release tag:** The script starts by fetching the most
|
||||
recent release tag by looking at the local data available in your repository.
|
||||
1. **Prompting for a new release tag:** The user is then prompted to enter a new
|
||||
release tag. To assist with this, the script displays the latest release tag
|
||||
and provides a regular expression to validate the format of the new tag.
|
||||
1. **Tagging the new release:** Once a valid new tag is entered, the script tags
|
||||
the new release.
|
||||
1. **Pushing the new tag to the remote:** Finally, the script pushes the new tag
|
||||
to the remote repository. From here, you will need to create a new release in
|
||||
GitHub and users can easily reference the new tag in their workflows.
|
||||
|
||||
To use the script, run the following command:
|
||||
|
||||
```bash
|
||||
./script/release
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Here are some ways you can contribute:
|
||||
|
|
|
|||
|
|
@ -1,51 +1,6 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import {
|
||||
getFiles,
|
||||
isPositiveInteger,
|
||||
isValidEncoding,
|
||||
processInChunks,
|
||||
replaceTextInFile,
|
||||
} from '../src/utils';
|
||||
|
||||
describe('isPositiveInteger', () => {
|
||||
it('should return true for positive integers', () => {
|
||||
expect(isPositiveInteger('1')).toBe(true);
|
||||
expect(isPositiveInteger('123')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for zero', () => {
|
||||
expect(isPositiveInteger('0')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for negative integers', () => {
|
||||
expect(isPositiveInteger('-1')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for non-integer numbers', () => {
|
||||
expect(isPositiveInteger('1.5')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for non-numeric strings', () => {
|
||||
expect(isPositiveInteger('abc')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for empty strings', () => {
|
||||
expect(isPositiveInteger('')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for whitespace strings', () => {
|
||||
expect(isPositiveInteger(' ')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for null', () => {
|
||||
expect(isPositiveInteger(null as any)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for undefined', () => {
|
||||
expect(isPositiveInteger(undefined as any)).toBe(false);
|
||||
});
|
||||
});
|
||||
import { getFiles, isValidEncoding, replaceTextInFile } from '../src/utils';
|
||||
|
||||
describe('isValidEncoding', () => {
|
||||
it('should return true for valid encodings', () => {
|
||||
|
|
@ -74,36 +29,9 @@ describe('getFiles', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('processInChunks', () => {
|
||||
it('should process an array in chunks', async () => {
|
||||
const totalItems = 999;
|
||||
const array = Array.from({ length: totalItems }, (_, i) => i);
|
||||
const func = jest.fn(async (item: number) => {
|
||||
item++;
|
||||
});
|
||||
const chunkSize = 100;
|
||||
|
||||
await processInChunks(array, func, chunkSize);
|
||||
|
||||
expect(func).toHaveBeenCalledTimes(totalItems);
|
||||
});
|
||||
|
||||
it('should process an array with less items than chunk size', async () => {
|
||||
const array = Array.from({ length: 50 }, (_, i) => i);
|
||||
const func = jest.fn(async (item: number) => {
|
||||
item++;
|
||||
});
|
||||
const chunkSize = 100;
|
||||
|
||||
await processInChunks(array, func, chunkSize);
|
||||
|
||||
expect(func).toHaveBeenCalledTimes(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceTextInFile', () => {
|
||||
const testFilePath = path.join(__dirname, 'test-file.txt');
|
||||
const testFileContent = '{0}, foo, {0}!';
|
||||
const testFileContent = '{0}, world!';
|
||||
|
||||
beforeEach(async () => {
|
||||
await fs.promises.writeFile(testFilePath, testFileContent);
|
||||
|
|
@ -114,10 +42,10 @@ describe('replaceTextInFile', () => {
|
|||
});
|
||||
|
||||
it('should replace all instances of the given text with the given value in the file', async () => {
|
||||
await replaceTextInFile(testFilePath, '{0}', 'run'); // Replace text with special characters
|
||||
await replaceTextInFile(testFilePath, 'foo', 'test'); // Replace text with letters
|
||||
await replaceTextInFile(testFilePath, '{0}', 'Hello'); // Replace text with special characters
|
||||
await replaceTextInFile(testFilePath, 'world', 'universe'); // Replace text with no special characters
|
||||
const updatedContent = await fs.promises.readFile(testFilePath, 'utf-8');
|
||||
expect(updatedContent).toBe('run, test, run!');
|
||||
expect(updatedContent).toBe('Hello, universe!');
|
||||
});
|
||||
|
||||
it('should not modify the file if the search text is not found', async () => {
|
||||
|
|
|
|||
|
|
@ -30,12 +30,6 @@ inputs:
|
|||
It can be the path to a file or a glob pattern (e.g. `**/*.md`).
|
||||
default: ""
|
||||
required: false
|
||||
max-parallelism:
|
||||
description: |-
|
||||
(Optional) The maximum number of files to process in parallel.
|
||||
Defaults to `10`.
|
||||
default: "10"
|
||||
required: false
|
||||
|
||||
branding:
|
||||
icon: "edit"
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -10,18 +10,6 @@ The above copyright notice and this permission notice shall be included in all c
|
|||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@actions/exec
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@actions/http-client
|
||||
MIT
|
||||
Actions Http Client for Node.js
|
||||
|
|
@ -47,47 +35,11 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
@actions/io
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@fastify/busboy
|
||||
MIT
|
||||
Copyright Brian White. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
|
||||
@isaacs/balanced-match
|
||||
balanced-match
|
||||
MIT
|
||||
(MIT)
|
||||
|
||||
Original code Copyright Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
Port to TypeScript Copyright Isaac Z. Schlueter <i@izs.me>
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
|
@ -108,13 +60,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||
SOFTWARE.
|
||||
|
||||
|
||||
@isaacs/brace-expansion
|
||||
brace-expansion
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
TypeScript port Copyright Isaac Z. Schlueter <i@izs.me>
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -295,26 +245,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||
THE SOFTWARE.
|
||||
|
||||
|
||||
undici
|
||||
uuid
|
||||
MIT
|
||||
MIT License
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Matteo Collina and Undici contributors
|
||||
Copyright (c) 2010-2020 Robert Kieffer and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
|
|
@ -24,25 +24,23 @@
|
|||
"author": "richardrigutins",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.11.1",
|
||||
"glob": "^11.0.3"
|
||||
"@actions/core": "^1.10.1",
|
||||
"glob": "^10.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@stylistic/eslint-plugin": "^2.13.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/node": "^24.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.33.1",
|
||||
"@typescript-eslint/parser": "^8.38.0",
|
||||
"@vercel/ncc": "^0.38.3",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-plugin-github": "^5.1.8",
|
||||
"eslint-plugin-jest": "^28.11.0",
|
||||
"eslint-plugin-prettier": "^5.5.3",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/lodash": "^4.14.199",
|
||||
"@types/node": "^20.6.5",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"@vercel/ncc": "^0.38.0",
|
||||
"eslint": "^8.50.0",
|
||||
"eslint-plugin-github": "^4.10.0",
|
||||
"eslint-plugin-jest": "^27.4.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"ts-jest": "^29.4.0",
|
||||
"typescript": "^5.9.2"
|
||||
"prettier": "^3.0.3",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# About:
|
||||
#
|
||||
# This is a helper script to tag and push a new release. GitHub Actions use
|
||||
# release tags to allow users to select a specific version of the action to use.
|
||||
#
|
||||
# See: https://github.com/actions/typescript-action#publishing-a-new-release
|
||||
#
|
||||
# This script will do the following:
|
||||
#
|
||||
# 1. Get the latest release tag
|
||||
# 2. Prompt the user for a new release tag
|
||||
# 3. Check if the tag exists and ask for confirmation to overwrite
|
||||
# 4. Tag the new release
|
||||
# 5. Push the new tag to the remote
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# script/release
|
||||
|
||||
# Terminal colors
|
||||
OFF='\033[0m'
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
|
||||
# Get the latest release tag
|
||||
latest_tag=$(git describe --tags "$(git rev-list --tags --max-count=1)")
|
||||
|
||||
if [[ -z "$latest_tag" ]]; then
|
||||
# There are no existing release tags
|
||||
echo -e "No tags found (yet) - Continue to create and push your first tag"
|
||||
latest_tag="[unknown]"
|
||||
fi
|
||||
|
||||
# Display the latest release tag
|
||||
echo -e "The latest release tag is: ${BLUE}${latest_tag}${OFF}"
|
||||
|
||||
# Prompt the user for the new release tag
|
||||
read -r -p 'Enter a new release tag (vX.X.X format): ' new_tag
|
||||
|
||||
# Validate the new release tag
|
||||
tag_regex='v[0-9]+(\.[0-9]+){0,2}$'
|
||||
if echo "$new_tag" | grep -q -E "$tag_regex"; then
|
||||
echo -e "Tag: ${BLUE}$new_tag${OFF} is valid"
|
||||
else
|
||||
# Release tag is not `vX.X.X` format
|
||||
echo -e "Tag: ${BLUE}$new_tag${OFF} is ${RED}not valid${OFF} (must be in vX.X.X format)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if the tag exists; if it does, ask the user for confirmation to overwrite
|
||||
if git rev-parse -q --verify "refs/tags/$new_tag" >/dev/null; then
|
||||
read -r -p "The tag already exists. Overwrite? [y/N] " response
|
||||
if [[ ! "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
|
||||
echo -e "${RED}Aborted${OFF}"
|
||||
exit 1
|
||||
else
|
||||
# Delete the tag locally
|
||||
git tag -d "$new_tag"
|
||||
echo -e "${GREEN}Deleted local tag: $new_tag${OFF}"
|
||||
# Delete the tag remotely
|
||||
git push origin --delete "$new_tag"
|
||||
echo -e "${GREEN}Deleted remote tag: $new_tag${OFF}"
|
||||
fi
|
||||
else
|
||||
read -r -p "The tag does not exist and will be created. Continue? [y/N] " response
|
||||
if [[ ! "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
|
||||
echo -e "${RED}Aborted${OFF}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Tag the new release
|
||||
git tag -a "$new_tag" -m "$new_tag Release"
|
||||
echo -e "${GREEN}Tagged: $new_tag${OFF}"
|
||||
|
||||
# Push the new tag to the remote
|
||||
git push --tags
|
||||
echo -e "${GREEN}Release tag pushed to remote${OFF}"
|
||||
echo -e "${GREEN}Done!${OFF}"
|
||||
22
src/main.ts
22
src/main.ts
|
|
@ -2,37 +2,23 @@ import { debug, getInput, info, setFailed, warning } from '@actions/core';
|
|||
import {
|
||||
Encoding,
|
||||
getFiles,
|
||||
isPositiveInteger,
|
||||
isValidEncoding,
|
||||
processInChunks,
|
||||
replaceTextInFile,
|
||||
} from './utils';
|
||||
|
||||
// Entry point for the action
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
// Get the input parameters
|
||||
const filesPattern = getInput('files');
|
||||
const searchText = getInput('search-text');
|
||||
const replaceText = getInput('replacement-text');
|
||||
const excludePattern = getInput('exclude');
|
||||
const inputEncoding = getInput('encoding');
|
||||
const maxParallelism = getInput('max-parallelism');
|
||||
|
||||
// Validate the encoding
|
||||
if (!isValidEncoding(inputEncoding)) {
|
||||
throw new Error(`Invalid encoding: ${inputEncoding}`);
|
||||
}
|
||||
|
||||
// Validate that maxParallelism is a positive integer
|
||||
if (!isPositiveInteger(maxParallelism)) {
|
||||
throw new Error(`Invalid max-parallelism: ${maxParallelism}`);
|
||||
}
|
||||
|
||||
// Get the file paths that match the files pattern and do not match the exclude pattern
|
||||
const filePaths = await getFiles(filesPattern, excludePattern);
|
||||
|
||||
// If no file paths were found, log a warning and exit
|
||||
if (filePaths.length === 0) {
|
||||
warning(`No files found for the given pattern.`);
|
||||
return;
|
||||
|
|
@ -41,19 +27,15 @@ async function run(): Promise<void> {
|
|||
info(`Found ${filePaths.length} files for the given pattern.`);
|
||||
info(`Replacing "${searchText}" with "${replaceText}".`);
|
||||
|
||||
// Process the file paths in chunks, replacing the search text with the replace text in each file
|
||||
// This is done to avoid opening too many files at once
|
||||
const encoding = inputEncoding as Encoding;
|
||||
const chunkSize = parseInt(maxParallelism);
|
||||
await processInChunks(
|
||||
filePaths,
|
||||
const promises: Promise<void>[] = filePaths.map(
|
||||
async (filePath: string) => {
|
||||
info(`Replacing text in file ${filePath}`);
|
||||
await replaceTextInFile(filePath, searchText, replaceText, encoding);
|
||||
},
|
||||
chunkSize,
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
info(`Done!`);
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
|
|
|
|||
36
src/utils.ts
36
src/utils.ts
|
|
@ -12,16 +12,6 @@ const encodings = [
|
|||
|
||||
export type Encoding = (typeof encodings)[number];
|
||||
|
||||
/**
|
||||
* Checks if a given string represents a positive integer.
|
||||
*
|
||||
* @param value - The string to check.
|
||||
* @returns True if the string represents a positive integer, false otherwise.
|
||||
*/
|
||||
export function isPositiveInteger(value: string): boolean {
|
||||
return /^[1-9]\d*$/.test(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given encoding is supported.
|
||||
* @param encoding The encoding to check.
|
||||
|
|
@ -49,30 +39,6 @@ export async function getFiles(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an array in chunks, applying a given function to each item.
|
||||
* @param array The array to process.
|
||||
* @param func The function to apply to each item.
|
||||
* @param chunkSize The number of items to process at a time.
|
||||
* @returns A Promise that resolves when all items have been processed.
|
||||
*/
|
||||
export async function processInChunks<T>(
|
||||
array: T[],
|
||||
func: (item: T) => Promise<void>,
|
||||
chunkSize: number,
|
||||
): Promise<void> {
|
||||
// Split the array into chunks
|
||||
const chunks = Array(Math.ceil(array.length / chunkSize))
|
||||
.fill(0)
|
||||
.map((_, index) => index * chunkSize)
|
||||
.map(begin => array.slice(begin, begin + chunkSize));
|
||||
|
||||
// Process each chunk
|
||||
for (const chunk of chunks) {
|
||||
await Promise.all(chunk.map(func));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all instances of the given text with the given value in the file.
|
||||
* @param filePath The path of the file to modify.
|
||||
|
|
@ -94,7 +60,7 @@ export async function replaceTextInFile(
|
|||
}
|
||||
|
||||
const fileContent = await readFileContent(filePath, encoding);
|
||||
const updatedContent = fileContent.replaceAll(searchText, replacementText);
|
||||
const updatedContent = fileContent.replace(searchText, replacementText);
|
||||
await saveFileContent(filePath, updatedContent);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,4 +3,3 @@ Hello world!
|
|||
This should be twenty-three: 23.
|
||||
This line has been changed: false.
|
||||
This value comes from environment variables: environment
|
||||
Multiple occurrences in the same file: world
|
||||
|
|
|
|||
|
|
@ -3,4 +3,3 @@ Hello {0}!
|
|||
This should be twenty-three: 23.
|
||||
This line has been changed: false.
|
||||
This value comes from environment variables: _ENV_
|
||||
Multiple occurrences in the same file: {0}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
"target": "ES2021", /* Specify ECMAScript target version. */
|
||||
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
"outDir": "./lib", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
|
|
|
|||
Loading…
Reference in New Issue