Building a GitHub Action Text Replacement Tool with JavaScript: From Principles to Practice

A deep dive into building a JavaScript-based GitHub Action for automated text find-and-replace in CI/CD.
This article explores how to build a GitHub Action for text replacement using JavaScript, covering why JS is ideal for this task, practical use cases like version updates and config injection, key implementation details such as ReDoS prevention and atomic file writes, and broader insights into the GitHub Actions ecosystem.
Starting from a Simple Need
In day-to-day open source project maintenance, we frequently encounter scenarios that require batch text replacement — updating version numbers, modifying documentation links, replacing outdated configuration items, or dynamically injecting build information during the release process. These operations seem simple enough, but doing them manually every time or writing complex Shell scripts to handle them is both error-prone and hard to maintain.
Recently, a developer shared their solution on Reddit: a JavaScript-based GitHub Action specifically designed for finding and replacing text. The tool has a very clear purpose — wrapping the common text processing needs in CI/CD workflows into a reusable, configurable automation step.

While the functionality may sound modest, it embodies the core value proposition of the GitHub Actions ecosystem: standardizing, automating, and modularizing repetitive tasks.
Why JavaScript for a GitHub Action
Three Ways to Implement GitHub Actions
GitHub Actions supports three main Action types: Docker container-based, JavaScript-based, and Composite. The author's choice to implement the text replacement tool in JavaScript has solid technical reasoning behind it.
To understand this choice, you first need to understand how GitHub Actions works. GitHub provides virtual machine execution environments called "runners," each pre-installed with common development toolchains, including the Node.js runtime. When a workflow is triggered, the runner executes each step sequentially according to the YAML configuration. Docker container-based Actions need to spin up a separate container within the runner to execute their logic, while JavaScript Actions run directly in the runner's Node.js process, eliminating the containerization overhead. Composite Actions combine multiple existing steps together, essentially providing reusable encapsulation of workflow fragments.
Compared to Docker container-based Actions, JavaScript Actions have several notable advantages:
- Faster startup: No need to pull and build Docker images — they run directly in the runner's Node.js environment, dramatically reducing cold start time. In practical testing, Docker Actions typically need an additional 10-30 seconds for image pulling and container startup, while JavaScript Actions start almost instantaneously.
- Cross-platform compatibility: They can run on Linux, Windows, and macOS runners simultaneously, whereas Docker Actions can only run on Linux runners. This limitation stems from Docker's own compatibility issues on Windows and macOS — GitHub's Windows and macOS runners don't come with the Docker daemon pre-installed.
- Mature development ecosystem: With official toolkits like
@actions/coreand@actions/github, you can easily read input parameters, set outputs, and manipulate workflow context. Specifically,@actions/coreprovides standardized interfaces likegetInput(),setOutput(), andsetFailed(), so Action developers don't need to worry about underlying environment variable and file I/O details.
The Technical Core of Text Replacement
Text find-and-replace has naturally strong support in JavaScript. JS's string replace() method and regex engine make implementing complex match-and-replace logic remarkably concise. The author specifically mentioned "using the great power of JS" in the title, which is partly tongue-in-cheek — using a language renowned for string processing to handle text replacement is a natural fit.
JavaScript's regex engine is based on the Perl 5 style, supporting advanced features like capture groups, lookahead/lookbehind assertions, and Unicode property matching. String.prototype.replace() supports not only string arguments but also regular expressions and replacement functions, meaning users can reference capture groups (like $1, $2) or even execute dynamic computation logic during replacement. By comparison, traditional Unix text processing tools like sed and awk perform excellently in Linux environments but require additional installation on Windows, and sed's behavior across different operating systems (particularly GNU sed vs. BSD sed) has subtle differences — this is precisely where the JavaScript approach shines in cross-platform scenarios.
The typical implementation approach goes roughly like this: accept user-configured search patterns (supporting literals or regex), replacement content, and target file paths, then iterate over matching files, perform the replacement, and write back. The implementation typically uses glob pattern matching to locate target files, while Node.js's fs module handles file I/O — the entire process is clean and efficient.
Practical Use Cases for a Text Replacement Action
Version Number Updates in Automated Release Workflows
The most common use case is updating version numbers in files during a release. For example, when you create a new Git tag, the Action can automatically replace version strings in package.json, README.md, or documentation with the latest version.
An important concept here is Semantic Versioning (SemVer). SemVer defines a version format of MAJOR.MINOR.PATCH (e.g., 2.1.3), where the major version indicates incompatible API changes, the minor version indicates backward-compatible feature additions, and the patch version indicates backward-compatible bug fixes. In real-world CI/CD workflows, version numbers are often scattered across multiple files — the version field in package.json, installation instructions in documentation, version headings in the Changelog, and so on. Relying on manual updates one by one is not only inefficient but also highly prone to version inconsistencies. A text replacement Action combined with a Git tag-triggered workflow can achieve globally consistent version number updates during the release process, eliminating human error.
Dynamic Configuration Injection
When deploying to different environments (development, staging, production), you often need to replace API endpoints, secret placeholders, or feature flags in configuration files. A text replacement Action can handle these injections in a pre-deployment step.
This approach aligns closely with The Twelve-Factor App methodology's principles on configuration management. The twelve-factor methodology advocates strict separation of configuration from code, passing environment-specific configuration values through environment variables. However, in practice, not all scenarios are suited for environment variables — for example, build artifacts for static websites, mobile app configuration files, or constant values that need to be inlined at compile time. In these scenarios, the "placeholder + text replacement" approach becomes a pragmatic alternative: keep placeholders like __API_BASE_URL__ or {{BUILD_HASH}} in the code, and inject actual values through the text replacement Action in the CI/CD pipeline. GitHub Actions' Secrets mechanism also ensures that sensitive information (like API keys) is automatically masked in logs, further enhancing security.
Batch Documentation Maintenance
For large open source projects, documentation may contain numerous links, brand names, or code examples that need unified updates. Batch replacement via an Action prevents the omissions that come with manual, one-by-one edits.
Consider a documentation site with hundreds of Markdown files. When the project domain migrates from docs.example.io to docs.example.com, manual find-and-replace is not only time-consuming but also prone to missing references nested within code blocks, HTML tags, or hyperlinks. With a text replacement Action that supports regex and glob file matching, a single workflow configuration line can complete the full replacement, and the results can be reviewed through a Pull Request diff — balancing automation efficiency with change controllability.
Insights from the GitHub Actions Ecosystem
The Value of Small, Focused Automation Tools
This project reminds us that not all valuable tools need complex functionality. The GitHub Marketplace currently hosts over 20,000 Actions, many of which are "micro-tools" that solve a single, well-defined problem. Their value lies in solving a specific pain point well enough and making it easy enough to use.
For the developer community, the challenges for such tools also include "discoverability" and "trustworthiness." For a text replacement Action to gain widespread adoption, beyond reliable functionality, it needs clear documentation, well-designed inputs and outputs, and security considerations — after all, it may modify file contents in the repository. In recent years, supply chain security in GitHub Actions has drawn increasing attention. In 2023, multiple incidents of popular Actions being injected with malicious code were exposed, where attackers executed unauthorized operations by tampering with an Action's mutable tags. Therefore, the industry-recommended best practice when using third-party Actions is to pin to a specific commit SHA rather than a tag version (e.g., uses: owner/action@a1b2c3d instead of uses: owner/action@v1) to prevent supply chain attacks. GitHub has also introduced the "Verified Creator" badge to help users identify trustworthy sources.
The Significance of Building Your Own Action
Some might ask: there are already similar text replacement Actions on the market — why reinvent the wheel? The answer often lies in learning and control. Building your own Action not only deepens your understanding of GitHub Actions' execution mechanisms, input/output models, and execution context, but also allows you to make customizations tailored to your project's specific needs.
The GitHub Actions execution context is a concept system worth understanding deeply. Every Action at runtime can access rich contextual information, including trigger event details (github.event), repository information (github.repository), current runner environment (runner.os), and more. Understanding how these contexts are passed between Actions and how to build data flows between steps through inputs and outputs is key to mastering GitHub Actions automation orchestration.
For developers looking to enter the CI/CD automation space, starting with a simple JavaScript Action is an excellent learning path. GitHub officially provides two template repositories — actions/typescript-action and actions/javascript-action — where developers can quickly scaffold their own Action project, complete with TypeScript compilation, testing frameworks, automated publishing, and other best practice configurations.
Key Implementation Details for Building a Text Replacement Action
If you plan to build or use this type of tool, several aspects deserve attention:
-
Regex safety: When allowing users to pass in regular expressions, be aware of ReDoS (Regular Expression Denial of Service) risks to prevent malicious or pathological regex from overwhelming the runner. ReDoS attacks exploit the backtracking mechanism of regex engines — when a regular expression contains nested quantifiers or overlapping alternation branches (e.g.,
(a+)+$,(a|a)*$), the engine's matching time can grow exponentially against specially crafted input strings, potentially causing the process to hang. In a GitHub Actions context, if the Action accepts regex parameters from external Pull Requests, malicious users could exhaust the runner's computing resources through ReDoS. Defense measures include: setting matching timeouts for regular expressions, using safe regex detection libraries (such assafe-regexorrecheck) to validate pattern safety before execution, or restricting support to simple literal matching and predefined regex patterns. -
Atomic file writes: During batch replacement, if the process fails midway, you should ensure files don't end up in a corrupted intermediate state. The common approach is to write replaced content to a temporary file first, then atomically replace the original file via a
renameoperation once the write is confirmed successful. In Node.js,fs.rename()is typically an atomic operation within the same filesystem, which is much safer than direct overwrite writes (fs.writeFile()). -
Committing changes: After replacing files, you typically need to pair the operation with
git commitandgit pushsteps to persist changes to the repository. Note that the defaultGITHUB_TOKENin GitHub Actions has write permissions to the repository, but pushes made with it won't trigger subsequent workflow runs — this is a safety mechanism designed by GitHub to prevent infinite loops. If you need commits from the replacement to trigger downstream workflows, you'll need to use a Personal Access Token (PAT) or a GitHub App token. -
Dry run mode: Providing a "preview" mode that lets users see what will be replaced before deciding whether to actually execute significantly improves the user experience. A well-implemented dry run should output the list of files to be modified, the context around each match (a few lines before and after), and a before-and-after diff comparison — similar to
git diffpresentation logic.
Conclusion
This JavaScript-based text replacement GitHub Action may seem like an unassuming little tool, but it reflects the ongoing evolution of "tool reuse" and "process standardization" in the open source automation ecosystem. In today's world where DevOps is increasingly prevalent, encapsulating every repetitive action into a reusable automation step is key to improving team efficiency.
Whether you want to use it directly to simplify your workflows or study it as an example of how to develop your own GitHub Action, this is a practical case worth paying attention to. Sometimes, the simplest tools are the ones that best demonstrate the beauty of engineering.
Related articles

Claude Code vs Cursor: Which Is Better? Core Differences & Real-World Comparison
In-depth comparison of Claude Code vs Cursor across accuracy, context capability, and auto-debugging. Covers AI coding tool evolution and selection advice.

Dify + Ollama Local Deployment for Smart Knowledge Bases: Build Private AI Apps with Zero Code
Complete guide to deploying a smart knowledge base locally with Dify + Ollama, covering model selection, RAG construction, and workflow orchestration for private AI apps.

AI Values Under the Microscope: When Models Face the "Most Woke Sentence" Challenge
Reddit users test AI value boundaries with the "most woke sentence" prompt, revealing how LLM alignment, cultural bias, and safety guardrails shape model behavior.