Advanced file manipulation utilities for updating, appending, modifying, and patching file contents programmatically with pattern matching and conditional operations.
Functions for verifying the existence of patterns and content within files.
/**
* Check if a pattern exists in a file
* @param filename - Path to file to check
* @param pattern - String or regex pattern to search for
* @returns Promise resolving to true if pattern exists
*/
exists(filename: string, pattern: string | RegExp): Promise<boolean>;Pattern Checking Examples:
import { patching } from "gluegun";
// Check for string patterns
const hasReactImport = await patching.exists("src/App.js", "import React");
const hasApiKey = await patching.exists(".env", "API_KEY=");
// Check for regex patterns
const hasVersion = await patching.exists("package.json", /\"version\":\s*\"[\d\.]+\"/);
const hasExport = await patching.exists("index.js", /^export\s+/m);
if (!hasReactImport) {
// Add React import
}Functions for programmatically updating file contents using callback functions.
/**
* Update file contents using a callback function
* @param filename - Path to file to update
* @param callback - Function that receives current contents and returns new contents
* @returns Promise resolving to updated contents, object, or false if failed
*/
update(filename: string, callback: (contents: any) => any): Promise<string | object | boolean>;Content Update Examples:
import { patching } from "gluegun";
// Update JSON file
await patching.update("package.json", (contents) => {
const pkg = JSON.parse(contents);
pkg.version = "1.2.0";
pkg.scripts.deploy = "npm run build && npm publish";
return JSON.stringify(pkg, null, 2);
});
// Update text file with string manipulation
await patching.update("README.md", (contents) => {
return contents.replace(/# Old Title/, "# New Title");
});
// Conditional updates
await patching.update("config.js", (contents) => {
if (!contents.includes("production: true")) {
return contents.replace(
"module.exports = {",
"module.exports = {\n production: true,"
);
}
return contents;
});Functions for adding content to files at the beginning or end.
/**
* Append content to the end of a file
* @param filename - Path to target file
* @param contents - Content to append
* @returns Promise resolving to new contents or false if failed
*/
append(filename: string, contents: string): Promise<string | boolean>;
/**
* Prepend content to the beginning of a file
* @param filename - Path to target file
* @param contents - Content to prepend
* @returns Promise resolving to new contents or false if failed
*/
prepend(filename: string, contents: string): Promise<string | boolean>;Content Insertion Examples:
import { patching } from "gluegun";
// Add import to beginning of file
await patching.prepend("src/App.js", "import './App.css';\n");
// Add export to end of file
await patching.append("src/utils.js", "\nexport { newFunction };");
// Add configuration entry
await patching.append(".gitignore", "\n# Build outputs\ndist/\nbuild/\n");
// Add documentation
await patching.append("README.md", `
## New Feature
This version includes a new feature for handling...
`);Simple find-and-replace operations for file contents.
/**
* Replace all occurrences of a search pattern with replacement text
* @param filename - Path to target file
* @param search - String to search for
* @param replace - Replacement string
* @returns Promise resolving to new contents or false if failed
*/
replace(filename: string, search: string, replace: string): Promise<string | boolean>;Search and Replace Examples:
import { patching } from "gluegun";
// Update API endpoints
await patching.replace(
"src/config.js",
"api.staging.com",
"api.production.com"
);
// Update version references
await patching.replace(
"docs/README.md",
"version 1.0.0",
"version 1.1.0"
);
// Replace placeholder values
await patching.replace(
"template.html",
"{{APP_NAME}}",
"My Application"
);Sophisticated patching operations with multiple options and conditional logic.
/**
* Apply multiple patch operations to a file
* @param filename - Path to target file
* @param options - Array of patch operation objects
* @returns Promise resolving to new contents or false if failed
*/
patch(filename: string, ...options: GluegunPatchingPatchOptions[]): Promise<string | boolean>;
interface GluegunPatchingPatchOptions {
/** String to insert */
insert?: string;
/** Insert before this pattern (string or regex) */
before?: string | RegExp;
/** Insert after this pattern (string or regex) */
after?: string | RegExp;
/** Replace this pattern (string or regex) */
replace?: string | RegExp;
/** Delete this pattern (string or regex) */
delete?: string | RegExp;
/** Force operation even if pattern already exists */
force?: boolean;
}Force Option Behavior:
The force option controls whether insertions are applied when the content already exists in the file:
force: false (default): Skip insertion if the content already exists, preventing duplicatesforce: true: Always perform the insertion, even if content already exists// Safe insertion (default behavior)
await patching.patch("config.js", {
insert: "const API_URL = 'https://api.example.com';",
before: /^module\.exports/,
force: false // Won't duplicate if line already exists
});
// Forced insertion (always inserts)
await patching.patch("config.js", {
insert: "/* Updated configuration */",
before: /^module\.exports/,
force: true // Will insert even if comment already exists
});Force Option Use Cases:
// Idempotent setup scripts
await patching.patch("package.json", {
insert: '"build": "webpack --mode=production",',
after: /"scripts":\s*{/,
force: false // Safe for repeated script runs
});
// Version stamping (always update)
await patching.patch("src/version.js", {
replace: /VERSION = ['"][^'"]*['"]/,
insert: `VERSION = '${newVersion}'`,
force: true // Always update version
});
// Development vs Production differences
await patching.patch("config.js", {
insert: `const DEBUG = ${isDevelopment};`,
before: /^module\.exports/,
force: isDevelopment // Force in dev, safe in prod
});Advanced Patching Examples:
import { patching } from "gluegun";
// Complex package.json patching
await patching.patch("package.json",
{
// Add new script after existing scripts
insert: ' "deploy": "npm run build && npm publish",',
after: /"scripts":\s*{/
},
{
// Add new dependency
insert: ' "axios": "^1.0.0",',
after: /"dependencies":\s*{/
},
{
// Update existing field
replace: /"version":\s*"[\d\.]+"/,
insert: '"version": "2.0.0"'
}
);
// Modify configuration file with multiple operations
await patching.patch("webpack.config.js",
{
// Add import if not present
insert: "const path = require('path');\n",
before: /^module\.exports/m,
force: false // Don't add if already exists
},
{
// Replace output path
replace: /output:\s*{[^}]*}/,
insert: `output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash].js'
}`
},
{
// Delete old configuration
delete: /\/\*\s*old config\s*\*\/[\s\S]*?\/\*\s*end old config\s*\*\//
}
);
// Conditional insertion based on file content
await patching.patch("src/App.js",
{
insert: "import React from 'react';\n",
before: /^import/m
},
{
insert: "\nexport default App;",
after: /^function App\(\)/m,
force: false
}
);The patching system supports both string and regular expression patterns for flexible content matching.
String Patterns:
Regular Expression Patterns:
m flagg flagi flagPattern Examples:
import { patching } from "gluegun";
// String pattern matching
await patching.exists("config.js", "production: true");
// Regex pattern matching
await patching.exists("src/App.js", /^import\s+React/m);
await patching.exists("package.json", /"version":\s*"[\d\.]+"/);
// Complex regex for multiline matching
await patching.patch("server.js",
{
replace: /app\.listen\(\d+,\s*\(\)\s*=>\s*{[\s\S]*?}\);/,
insert: `app.listen(process.env.PORT || 3000, () => {
console.log('Server started on port', process.env.PORT || 3000);
});`
}
);interface GluegunPatching {
/**
* Check if pattern exists in file
* @param filename - File path
* @param pattern - Pattern to search for
* @returns True if pattern exists
*/
exists(filename: string, pattern: string | RegExp): Promise<boolean>;
/**
* Update file using callback function
* @param filename - File path
* @param callback - Update function
* @returns Updated contents or false
*/
update(filename: string, callback: (contents: any) => any): Promise<string | object | boolean>;
/**
* Append content to file
* @param filename - File path
* @param contents - Content to append
* @returns New contents or false
*/
append(filename: string, contents: string): Promise<string | boolean>;
/**
* Prepend content to file
* @param filename - File path
* @param contents - Content to prepend
* @returns New contents or false
*/
prepend(filename: string, contents: string): Promise<string | boolean>;
/**
* Replace pattern in file
* @param filename - File path
* @param search - Pattern to find
* @param replace - Replacement text
* @returns New contents or false
*/
replace(filename: string, search: string, replace: string): Promise<string | boolean>;
/**
* Apply multiple patch operations
* @param filename - File path
* @param options - Patch operations
* @returns New contents or false
*/
patch(filename: string, ...options: GluegunPatchingPatchOptions[]): Promise<string | boolean>;
}
interface GluegunPatchingPatchOptions {
insert?: string;
before?: string | RegExp;
after?: string | RegExp;
replace?: string | RegExp;
delete?: string | RegExp;
force?: boolean;
}Comprehensive Usage Example:
// CLI command for setting up a new project
export = {
name: "setup",
description: "Setup project configuration files",
run: async (toolbox) => {
const { patching, filesystem, print } = toolbox;
print.info("Setting up project configuration...");
// Update package.json with new scripts and dependencies
await patching.patch("package.json",
{
insert: ' "dev": "webpack serve --mode development",',
after: /"scripts":\s*{/
},
{
insert: ' "build": "webpack --mode production",',
after: /"dev":/
},
{
insert: ' "webpack": "^5.0.0",',
after: /"devDependencies":\s*{/
}
);
// Add gitignore entries if not present
if (!(await patching.exists(".gitignore", "node_modules"))) {
await patching.append(".gitignore", "\n# Dependencies\nnode_modules/\n");
}
if (!(await patching.exists(".gitignore", "dist/"))) {
await patching.append(".gitignore", "\n# Build output\ndist/\nbuild/\n");
}
// Update existing config file or create new one
if (filesystem.exists("webpack.config.js")) {
await patching.patch("webpack.config.js",
{
replace: /entry:\s*['"][^'"]*['"]/,
insert: "entry: './src/index.js'"
},
{
replace: /mode:\s*['"][^'"]*['"]/,
insert: "mode: process.env.NODE_ENV || 'development'"
}
);
} else {
filesystem.write("webpack.config.js", `
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
mode: process.env.NODE_ENV || 'development'
};
`.trim());
}
print.success("Project configuration completed!");
}
};