Skip to content

Instantly share code, notes, and snippets.

@johnnymo87
Created June 25, 2025 18:46
Show Gist options
  • Save johnnymo87/055680e716f8a822ef318ececa467f40 to your computer and use it in GitHub Desktop.
Save johnnymo87/055680e716f8a822ef318ececa467f40 to your computer and use it in GitHub Desktop.
Concatenates code files output from `madge` into a single output file.
#!/usr/bin/env bash
: <<'END'
Script Name: madge_concatenator.sh
Purpose:
This script uses 'madge' to find all dependencies for a given TypeScript/JavaScript
entry file, and then concatenates all found files into a single output file.
The output file will contain the absolute file paths and contents of each code file,
separated by a delimiter (```). This is useful for providing a large codebase
context to tools that accept a single file input.
Usage:
./madge_concatenator.bash <ENTRY_FILE>
Arguments:
- ENTRY_FILE: The path to the entry file (e.g., a .ts or .js file) for madge to analyze.
Features:
- Runs madge to build a dependency graph for the specified entry file.
- Parses madge's JSON output to get a complete and unique list of all dependent files.
- Correctly resolves file paths, which madge outputs relative to the entry file's directory.
- Concatenates the absolute file path and full content for each file.
- Outputs the final concatenated content to standard output, which can be redirected to a file.
Dependencies:
- bash: The script is written for Bash shell environments.
- madge: Must be available in the environment (e.g., `npm install -g madge` or as a project dependency).
- jq: A command-line JSON processor. Must be installed.
Note:
This script should be run from the root of your project, where `tsconfig.json` is located.
END
set -euo pipefail
# --- Dependency Checks ---
if ! command -v npx &> /dev/null; then
echo "Error: npx is not installed. Please install Node.js and npm." >&2
exit 1
fi
# Check for madge via npx
if ! npx madge --version &> /dev/null; then
echo "Error: madge could not be found by npx. Please install it in your project ('npm install madge') or globally ('npm install -g madge')." >&2
exit 1
fi
if ! command -v jq &> /dev/null; then
echo "Error: jq is not installed. Please install it (e.g., 'brew install jq' or 'sudo apt-get install jq')." >&2
exit 1
fi
# --- End Dependency Checks ---
# --- Argument Parsing ---
if [ -z "${1-}" ]; then
echo "Usage: $0 <ENTRY_FILE>" >&2
echo "Example: $0 src/index.ts" >&2
exit 1
fi
ENTRY_FILE="$1"
if [ ! -f "$ENTRY_FILE" ]; then
echo "Error: Entry file not found at '$ENTRY_FILE'" >&2
exit 1
fi
# The directory of the entry file is used as the base for resolving relative paths from madge.
ENTRY_DIR=$(dirname "$ENTRY_FILE")
# --- End Argument Parsing ---
# --- Main Logic ---
# Create a temporary file for the final output
output_file=$(mktemp)
# Ensure cleanup on exit
trap 'rm -f -- "$output_file"' EXIT
echo "1. Running madge to analyze dependencies for '$ENTRY_FILE'..." >&2
# These flags are based on your example. You can modify them as needed.
madge_output=$(npx madge --ts-config tsconfig.json --extensions ts,tsx "$ENTRY_FILE" --json)
echo "2. Parsing madge's JSON output to create a file list..." >&2
# The jq command extracts all unique file paths (both keys and values in the dependency arrays).
file_list=$(echo "$madge_output" | jq -r '(keys_unsorted[], .[][]) | select(length > 0)' | sort -u)
# If madge failed to produce a list, fall back to just the entry file.
if [ -z "$file_list" ]; then
echo "Warning: madge found no dependencies. Processing only the entry file." >&2
# The path madge would have returned for the entry file is its basename.
file_list=$(basename "$ENTRY_FILE")
fi
echo "3. Concatenating all dependent files..." >&2
# Loop through the list of files and concatenate them
while IFS= read -r file; do
# madge paths are relative to the entry file's directory. We construct the full path from CWD.
full_path="$ENTRY_DIR/$file"
# Normalize the path (e.g., resolve ../) and check for existence.
if [ -f "$full_path" ]; then
# `realpath` gives the canonical, absolute path.
canonical_path=$(realpath "$full_path")
printf " - Adding %s\n" "$canonical_path" >&2
# We use a temporary delimiter '###' to avoid conflicts with '```' in source code.
{
printf "%s\n" "$canonical_path"
printf "###\n"
cat "$canonical_path"
# Ensure there's a newline at the end of the file content before the delimiter
if [ "$(tail -c1 "$canonical_path" | wc -l)" -eq 0 ]; then
printf "\n"
fi
printf "###\n"
} >> "$output_file"
else
echo " - Warning: File not found, skipping: $full_path" >&2
fi
done <<< "$file_list"
echo "4. Finalizing output..." >&2
# Print the contents of the output file to stdout, replacing the temporary delimiter with the final one.
sed 's/^###$/```/g' "$output_file"
# The trap will handle removing the temp file.
exit 0
@johnnymo87
Copy link
Author

How to Use

  1. Install madge.
  2. Save the script as madge_concatenator.bash in the root of your project.
  3. Make it executable: chmod +x madge_concatenator.bash.
  4. Run it with your entry file as the argument. The output will be printed to your terminal.
    ./madge_concatenator.bash src/templates/Cooking/CookingLayout/PodLayout/shared/TaskSlots/usePersistedTasks.ts
  5. To save the output to a file, redirect it:
    ./madge_concatenator.bash src/templates/Cooking/CookingLayout/PodLayout/shared/TaskSlots/usePersistedTasks.ts > context.txt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment