Compare commits

..

No commits in common. "main" and "v2.0.0" have entirely different histories.
main ... v2.0.0

21 changed files with 9458 additions and 30606 deletions

View File

@ -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": {}
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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:

View File

@ -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 () => {

View File

@ -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"

27531
dist/index.js generated vendored

File diff suppressed because one or more lines are too long

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

82
dist/licenses.txt generated vendored
View File

@ -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 &lt;julian@juliangruber.com&gt;
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.

2
dist/sourcemap-register.js generated vendored

File diff suppressed because one or more lines are too long

11908
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -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}"

View File

@ -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) {

View File

@ -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);
}

View File

@ -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

View File

@ -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}

View File

@ -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. */