Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Test repocopy

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
bash-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: sudo apt update && sudo apt install -y jq xclip bats
- name: Run Bash tests
run: bats tests/bash/rpcp.bats

pester-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install PowerShell & Pester
uses: actions/setup-python@v4 # just to get pwsh; GitHub image already has pwsh
- name: Install Pester
shell: pwsh
run: |
Install-Module Pester -Force -Scope CurrentUser
- name: Run PowerShell tests
shell: pwsh
run: |
Invoke-Pester -Path tests/powershell -CI -Output Detailed

6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "tests/test_helper/bats-support"]
path = tests/test_helper/bats-support
url = https://github.com/bats-core/bats-support
[submodule "tests/test_helper/bats-assert"]
path = tests/test_helper/bats-assert
url = https://github.com/bats-core/bats-assert
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,22 @@ After running `rpcp`, your clipboard contains all relevant files with context. P

## 🧪 Testing & Linting

- **Pester**: Write tests for PowerShell functions.
- **PSScriptAnalyzer**: Validate PowerShell style.
- **ShellCheck**: Lint the Bash script.
# Running tests locally

## Bash (Bats)

```bash
sudo apt install bats jq xclip # or the equivalent for your OS
bats tests/bash/repocopy.bats
```

## PowerShell (Pester)

```
Install-Module Pester -Force -Scope CurrentUser # once
Invoke-Pester -Path tests/powershell -Output Detailed
```


---

Expand Down
3 changes: 2 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"repoPath": ".",
"maxFileSize": 204800,
"ignoreFolders": [".git", ".github", ".terraform", "node_modules","plugin-cache", "terraform-provider*", "logo-drafts","build", ".archive"],
"ignoreFolders": [".git", ".github", ".terraform", "node_modules","plugin-cache", "terraform-provider*", "logo-drafts","build", ".archive","test_helper"],
"ignoreFiles": ["manifest.json", "package-lock.json","*.png","*.jpg","*.jpeg","*.gif","*.svg","*.zip","*.tar.gz","*.tgz","*.tfstate", "*.tfstate.backup", "*.tfvars", "*.tfvars.json", "*.tfplan", "*.tfplan.json","*.avif","*.webp"],
"replacements": { "PARENT_COMPANY":"pca","Bob":"Redacted_name","PROJECT_ACRONYM":"wla" },
"replacements": { "PARENT_COMPANY":"pca","CLIENT_NAME":"ClientName","PROJECT_ACRONYM":"wla" },
"showCopiedFiles": true,
"autoInstallDeps": true
Expand Down
52 changes: 38 additions & 14 deletions rpcp.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,16 @@ Param(
[long] $MaxFileSize,

[Parameter()]
[ValidateNotNullOrEmpty()]
[AllowNull()]
[string[]] $IgnoreFolders,

[Parameter()]
[ValidateNotNullOrEmpty()]
[AllowNull()]
[string[]] $IgnoreFiles,

[Parameter()]
[AllowNull()]

[hashtable]$Replacements,

[Parameter()]
Expand All @@ -103,17 +105,20 @@ function Get-Config {
function Get-FilesToInclude {
[CmdletBinding()]
Param(
[Parameter(Mandatory)]
[Parameter()]
[ValidateScript({ Test-Path $_ -PathType Container })]
[string] $RepoRoot,

[Parameter(Mandatory)]
[string[]] $IgnoreFolders,
# ↓↓↓ CHANGE #1 – remove Mandatory, give default @()
[Parameter()]
[string[]] $IgnoreFolders = @(),

[Parameter(Mandatory)]
[string[]] $IgnoreFiles,
# ↓↓↓ CHANGE #2 – remove Mandatory, give default @()
[Parameter()]
[string[]] $IgnoreFiles = @(),

[Parameter()]

[Parameter(Mandatory)]
[ValidateRange(0, [long]::MaxValue)]
[long] $MaxFileSize
)
Expand All @@ -125,8 +130,15 @@ function Get-FilesToInclude {
# Folder pattern check
$dirs = $f.DirectoryName.Split([IO.Path]::DirectorySeparatorChar)
foreach ($pat in $IgnoreFolders) {
if ($dirs -like $pat) {
$reason = "matched ignore-folder '$pat'"; break
$sepRegex = [Regex]::Escape([IO.Path]::DirectorySeparatorChar)
$segments = $f.DirectoryName -split $sepRegex # safe on Win & *nix

foreach ($pat in $IgnoreFolders) {
if ($segments -like $pat) {
$reason = "matched ignore-folder '$pat'"
break
}

}
}
# File name check
Expand Down Expand Up @@ -228,15 +240,27 @@ $config = Get-Config -ConfigFilePath $ConfigFile
# Merge CLI parameters over config values
$rp = if ($PSBoundParameters.ContainsKey('RepoPath')) { $RepoPath } else { $config.repoPath }
$mf = if ($PSBoundParameters.ContainsKey('MaxFileSize')) { $MaxFileSize } else { [long]$config.maxFileSize }
$if = if ($PSBoundParameters.ContainsKey('IgnoreFolders')) { $IgnoreFolders } else { @($config.ignoreFolders) }
$ifl = if ($PSBoundParameters.ContainsKey('IgnoreFiles')) { $IgnoreFiles } else { @($config.ignoreFiles) }
$if = if ($PSBoundParameters.ContainsKey('IgnoreFolders') -and $IgnoreFolders) { $IgnoreFolders } else { @($config.ignoreFolders) }
$ifl = if ($PSBoundParameters.ContainsKey('IgnoreFiles') -and $IgnoreFiles) { $IgnoreFiles } else { @($config.ignoreFiles) }
$rep = if ($PSBoundParameters.ContainsKey('Replacements')) { $Replacements } else {
$h = @{}; foreach ($p in $config.replacements.PSObject.Properties) { $h[$p.Name] = $p.Value }; $h
}
$scf = if ($PSBoundParameters.ContainsKey('ShowCopiedFiles')) { $ShowCopiedFiles.IsPresent } else { [bool]$config.showCopiedFiles }
$scf = if ($PSBoundParameters.ContainsKey('ShowCopiedFiles')) {
$ShowCopiedFiles.IsPresent
} else {
[bool]$config.showCopiedFiles
}

if ($null -eq $if) { $if = @() }
if ($null -eq $ifl) { $ifl = @() }

# Gather, filter, and log
$filesToCopy = Get-FilesToInclude -RepoRoot $rp -IgnoreFolders $if -IgnoreFiles $ifl -MaxFileSize $mf
$filesToCopy = Get-FilesToInclude `
-RepoRoot $rp `
-IgnoreFolders $if `
-IgnoreFiles $ifl `
-MaxFileSize $mf


if ($filesToCopy.Count -eq 0) {
Write-Warning 'No files passed the filters; nothing to copy.'
Expand Down
111 changes: 111 additions & 0 deletions tests/bash/rpcp.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env bats
#
# End-to-end tests for the Bash version of repocopy (rpcp.sh)
# ─────────────────────────────────────────────────────────────
# • Spins-up a temp repo each run (safe & hermetic)
# • Stubs xclip/pbcopy so we can inspect what hits the clipboard
# • Verifies:
# 1. happy-path copy & token replacement
# 2. max-file-size exclusion
# 3. override of max-file-size via CLI
# 4. folder-ignore pattern ("build")
# 5. behaviour when --show-copied-files is used
#
export BATS_LIB_PATH="$PWD/tests/test_helper"
load 'test_helper/bats-support/load'
load 'test_helper/bats-assert/load'

setup() {
# ── ① disposable sandbox repo ──────────────────────────────
TMP_REPO="$(mktemp -d)"
mkdir -p "$TMP_REPO/src" "$TMP_REPO/build"

# source files
printf 'hello ClientName\n' >"$TMP_REPO/src/include.txt"
printf 'ignore me\n' >"$TMP_REPO/manifest.json"
head -c 10 </dev/urandom >"$TMP_REPO/image.png"

# a 300-KiB file to test the size filter
dd if=/dev/zero of="$TMP_REPO/src/big.bin" bs=1k count=300 2>/dev/null

# something inside an ignored folder
printf 'ignore me\n' >"$TMP_REPO/build/output.txt"

# config.json that matches the Pester suite
cat >"$TMP_REPO/config.json" <<'JSON'
{
"repoPath": ".",
"maxFileSize": 204800,
"ignoreFolders": [ "build" ],
"ignoreFiles": [ "manifest.json", "*.png", "config.json" ],
"replacements": { "ClientName": "Bob" },
"showCopiedFiles": false,
"autoInstallDeps": false
}
JSON

# ── ② stub clipboard (xclip) ───────────────────────────────
CLIP_FILE="$(mktemp)"
STUB_DIR="$(mktemp -d)"
cat >"$STUB_DIR/xclip" <<STUB
#!/usr/bin/env bash
cat > "$CLIP_FILE"
STUB
chmod +x "$STUB_DIR/xclip"
PATH="$STUB_DIR:$PATH"
}

teardown() {
rm -rf "$TMP_REPO" "$CLIP_FILE" "$STUB_DIR"
}

# helper to run rpcp.sh and slurp clipboard into $CLIP variable
run_rpcp() {
run bash ./rpcp.sh "$@"
# copy the clipboard text into a shell variable for assertions
CLIP="$(cat "$CLIP_FILE")"
}

# ─────────────────────────────────────────────────────────────
@test "default run: copies only permitted files & replaces tokens" {
run_rpcp --repo-path "$TMP_REPO" --config-file "$TMP_REPO/config.json"

assert_success
assert_line --partial "✅ Copied" # sanity

assert_regex "$CLIP" "include\\.txt"
assert_regex "$CLIP" "hello Bob"

refute_regex "$CLIP" "manifest\\.json"
refute_regex "$CLIP" "image\\.png"
refute_regex "$CLIP" "config\\.json"
refute_regex "$CLIP" "output\\.txt"
refute_regex "$CLIP" "big\\.bin"
}

@test "size filter: big.bin is excluded by default" {
run_rpcp --repo-path "$TMP_REPO" --config-file "$TMP_REPO/config.json"
refute_regex "$CLIP" "big\\.bin"
}

@test "size override: big.bin appears when --max-file-size 0 is used" {
run_rpcp --repo-path "$TMP_REPO" \
--config-file "$TMP_REPO/config.json" \
--max-file-size 0
assert_regex "$CLIP" "big\\.bin"
}

@test "folder ignore: anything under build/ is skipped" {
run_rpcp --repo-path "$TMP_REPO" --config-file "$TMP_REPO/config.json"
refute_regex "$CLIP" "build/output\\.txt"
}

@test "--show-copied-files does not affect clipboard content" {
run_rpcp --repo-path "$TMP_REPO" \
--config-file "$TMP_REPO/config.json" \
--show-copied-files

# The script prints the file list to stdout;
# we just need to ensure normal data is still on the clipboard
assert_regex "$CLIP" "include\\.txt"
}
1 change: 1 addition & 0 deletions tests/fixtures/sample-repo/build/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ignore me
8 changes: 8 additions & 0 deletions tests/fixtures/sample-repo/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"repoPath": ".",
"maxFileSize": 204800,
"ignoreFolders": [ "build" ],
"ignoreFiles" : [ "manifest.json", "*.png", "config.json" ],
"replacements" : { "ClientName": "Bob" },
"showCopiedFiles": false
}
Binary file added tests/fixtures/sample-repo/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions tests/fixtures/sample-repo/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "note": "this file should be ignored by rpcp" }
Binary file added tests/fixtures/sample-repo/src/big.bin
Binary file not shown.
1 change: 1 addition & 0 deletions tests/fixtures/sample-repo/src/include.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello Bob
Loading
Loading