chore(repo): add a clean script for build outputs and caches - #377
chore(repo): add a clean script for build outputs and caches#377omridevk wants to merge 1 commit into
Conversation
📝 WalkthroughWalkthroughThe root ChangesRepository cleanup script
Estimated code review effort: 1 (Trivial) | ~2 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@package.json`:
- Line 19: Update the clean script’s find expression so the *.tsbuildinfo match
requires -type f, while retaining directory matching for dist and the existing
cleanup behavior.
- Line 19: Update the clean script to preserve NUL-delimited paths throughout
the pipeline: change find to -print0, invoke git check-ignore with -z, and keep
xargs -0 while passing paths safely to rm -rf --. Preserve the existing
node_modules pruning, artifact matching, and .turbo removal behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| "dev:site": "turbo run dev --filter=site", | ||
| "storybook": "turbo run storybook --filter=conciv-storybook", | ||
| "build": "turbo run build", | ||
| "clean": "find packages apps e2e -name node_modules -prune -o \\( -name dist -type d -o -name '*.tsbuildinfo' \\) -print | git check-ignore --stdin | tr '\\n' '\\0' | xargs -0 rm -rf; rm -rf .turbo", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- candidate skill files ---'
fd -HI -t f '(SKILL|skill|AGENTS|CLAUDE|CONTRIBUTING|README)' . | head -200
printf '%s\n' '--- repository root files ---'
git ls-files | sed -n '1,120p'
printf '%s\n' '--- package.json ---'
cat -n package.json | sed -n '1,80p'Repository: conciv-dev/conciv
Length of output: 24022
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- root instructions ---'
cat -n AGENTS.md
printf '%s\n' '--- review skill ---'
cat -n .claude/skills/conciv-review/SKILL.md
printf '%s\n' '--- repository review instructions ---'
cat -n .github/skills/code-review/SKILL.md
printf '%s\n' '--- intent-related guidance ---'
rg -n -A8 -B4 'intent|skill check|TanStack Intent' AGENTS.md CLAUDE.md .claude .github package.json --glob '!**/node_modules/**'Repository: conciv-dev/conciv
Length of output: 40023
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- TanStack Intent skill check ---'
pnpm dlx `@tanstack/intent`@latest list
printf '%s\n' '--- matching repository entries and types ---'
find packages apps e2e \
-name node_modules -prune -o \
\( -name dist -type d -o -name '*.tsbuildinfo' \) -print |
while IFS= read -r path; do
if [ -d "$path" ]; then type=directory
elif [ -f "$path" ]; then type=file
else type=other
fi
printf '%s\t%s\n' "$type" "$path"
done | sed -n '1,160p'
printf '%s\n' '--- ignore status for current matches ---'
find packages apps e2e \
-name node_modules -prune -o \
\( -name dist -type d -o -name '*.tsbuildinfo' \) -print |
git check-ignore --stdin | sed -n '1,160p'
printf '%s\n' '--- verifier for the find predicates ---'
python3 - <<'PY'
from dataclasses import dataclass
`@dataclass`(frozen=True)
class Entry:
name: str
kind: str
def current_predicate(entry: Entry) -> bool:
return entry.name == 'dist' and entry.kind == 'directory' or entry.name.endswith('.tsbuildinfo')
def proposed_predicate(entry: Entry) -> bool:
return entry.name == 'dist' and entry.kind == 'directory' or (
entry.name.endswith('.tsbuildinfo') and entry.kind == 'file'
)
cases = [
Entry('dist', 'directory'),
Entry('dist', 'file'),
Entry('cache.tsbuildinfo', 'file'),
Entry('cache.tsbuildinfo', 'directory'),
Entry('cache.tsbuildinfo', 'other'),
]
for entry in cases:
print(f'{entry.kind:9} {entry.name:20} current={current_predicate(entry)!s:5} proposed={proposed_predicate(entry)!s:5}')
PYRepository: conciv-dev/conciv
Length of output: 49831
Restrict *.tsbuildinfo matches to files.
If a directory name ends with .tsbuildinfo, the command passes it to rm -rf and deletes its contents. Add -type f to this match.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@package.json` at line 19, Update the clean script’s find expression so the
*.tsbuildinfo match requires -type f, while retaining directory matching for
dist and the existing cleanup behavior.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
printf '%s\n' '--- root files ---'
git ls-files | sed -n '1,120p'
printf '%s\n' '--- skill files ---'
find .. -iname '*skill*' -o -iname 'AGENTS.md' -o -iname 'CONTRIBUTING.md' | sed -n '1,120p'
printf '%s\n' '--- package.json command ---'
sed -n '1,45p' package.jsonRepository: conciv-dev/conciv
Length of output: 15260
🏁 Script executed:
printf '%s\n' '--- root files ---'
git ls-files | sed -n '1,120p'
printf '%s\n' '--- skill files ---'
find .. \( -iname '*skill*' -o -iname 'AGENTS.md' -o -iname 'CONTRIBUTING.md' \) -print | sed -n '1,120p'
printf '%s\n' '--- package.json command ---'
sed -n '1,45p' package.jsonRepository: conciv-dev/conciv
Length of output: 15260
🏁 Script executed:
printf '%s\n' '--- root instructions ---'
cat AGENTS.md
printf '%s\n' '--- review skill ---'
cat .claude/skills/conciv-review/SKILL.md
printf '%s\n' '--- repository review instructions ---'
cat .github/instructions/conciv-review.instructions.md
printf '%s\n' '--- code-review skill ---'
cat .github/skills/code-review/SKILL.mdRepository: conciv-dev/conciv
Length of output: 33140
🏁 Script executed:
set -eu
printf '%s\n' '--- TanStack Intent skill check ---'
pnpm dlx `@tanstack/intent`@latest list
printf '%s\n' '--- Git version and check-ignore options ---'
git --version
git check-ignore -h 2>&1 | sed -n '1,100p'
printf '%s\n' '--- newline-path verifier ---'
python3 - <<'PY'
import os
import subprocess
import tempfile
from pathlib import Path
with tempfile.TemporaryDirectory() as root:
repo = Path(root)
subprocess.run(['git', 'init', '-q', str(repo)], check=True)
(repo / '.gitignore').write_text('**/dist/\n', encoding='utf-8')
target = repo / 'apps' / 'part\nwith-newline' / 'dist'
target.mkdir(parents=True)
path = 'apps/part\nwith-newline/dist'
def run(args, data):
return subprocess.run(
args,
cwd=repo,
input=data,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
line_result = run(['git', 'check-ignore', '--stdin'], (path + '\n').encode())
nul_result = run(['git', 'check-ignore', '--stdin', '-z'], (path + '\0').encode())
print('input:', repr(path))
print('newline mode:', repr(line_result.stdout), 'status:', line_result.returncode)
print('NUL mode:', repr(nul_result.stdout), 'status:', nul_result.returncode)
PYRepository: conciv-dev/conciv
Length of output: 46743
🏁 Script executed:
set -eu
printf '%s\n' '--- TanStack Intent skill check ---'
pnpm dlx `@tanstack/intent`@latest list
printf '%s\n' '--- Git version and check-ignore options ---'
git --version
git check-ignore -h 2>&1 | sed -n '1,100p'
printf '%s\n' '--- newline-path verifier ---'
python3 - <<'PY'
import subprocess
import tempfile
from pathlib import Path
with tempfile.TemporaryDirectory() as root:
repo = Path(root)
subprocess.run(['git', 'init', '-q', str(repo)], check=True)
(repo / '.gitignore').write_text('**/dist/\n', encoding='utf-8')
target = repo / 'apps' / 'part\nwith-newline' / 'dist'
target.mkdir(parents=True)
path = 'apps/part\nwith-newline/dist'
def run(args, data):
return subprocess.run(
args,
cwd=repo,
input=data,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
line_result = run(['git', 'check-ignore', '--stdin'], (path + '\n').encode())
nul_result = run(['git', 'check-ignore', '--stdin', '-z'], (path + '\0').encode())
print('input:', repr(path))
print('newline mode:', repr(line_result.stdout), 'status:', line_result.returncode)
print('NUL mode:', repr(nul_result.stdout), 'status:', nul_result.returncode)
PYRepository: conciv-dev/conciv
Length of output: 46743
🏁 Script executed:
printf '%s\n' '--- ignore rules relevant to clean ---'
cat .gitignore
printf '%s\n' '--- all ignore files ---'
find . -name .gitignore -print -exec sh -c 'echo "--- $1"; cat "$1"' _ {} \;
printf '%s\n' '--- exact current clean pipeline, without deletion ---'
python3 - <<'PY'
import subprocess
result = subprocess.run(
"find packages apps e2e -name node_modules -prune -o \\( -name dist -type d -o -name '*.tsbuildinfo' \\) -print | git check-ignore --stdin | tr '\\n' '\\0'",
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
print('status:', result.returncode)
print('output bytes:', repr(result.stdout[:2000]))
print('stderr:', repr(result.stderr))
PYRepository: conciv-dev/conciv
Length of output: 10483
🏁 Script executed:
printf '%s\n' '--- ignore rules relevant to clean ---'
cat .gitignore
printf '%s\n' '--- all ignore files ---'
find . -name .gitignore -print -exec sh -c 'echo "--- $1"; cat "$1"' _ {} \;
printf '%s\n' '--- exact current clean pipeline, without deletion ---'
python3 - <<'PY'
import subprocess
result = subprocess.run(
"find packages apps e2e -name node_modules -prune -o \\( -name dist -type d -o -name '*.tsbuildinfo' \\) -print | git check-ignore --stdin | tr '\\n' '\\0'",
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
print('status:', result.returncode)
print('output bytes:', repr(result.stdout[:2000]))
print('stderr:', repr(result.stderr))
PYRepository: conciv-dev/conciv
Length of output: 10483
🏁 Script executed:
printf '%s\n' '--- destructive-path reproduction in an isolated temporary repository ---'
python3 - <<'PY'
import subprocess
import tempfile
from pathlib import Path
current = (
"find packages apps e2e -name node_modules -prune -o "
"\\( -name dist -type d -o -name '*.tsbuildinfo' \\) -print "
"| git check-ignore --stdin | tr '\\n' '\\0' "
"| xargs -0 rm -rf"
)
proposed = (
"find packages apps e2e -name node_modules -prune -o "
"\\( -name dist -type d -o -name '*.tsbuildinfo' \\) -print0 "
"| git check-ignore --stdin -z "
"| xargs -0 sh -c 'for path do rm -rf -- \"$path\"; done' _"
)
with tempfile.TemporaryDirectory() as root:
repo = Path(root)
subprocess.run(['git', 'init', '-q', str(repo)], check=True)
(repo / '.gitignore').write_text('dist/\n*.tsbuildinfo\n', encoding='utf-8')
candidate = repo / 'apps' / 'foo\npackages' / 'client' / 'dist'
unrelated = repo / 'packages' / 'client' / 'dist'
candidate.mkdir(parents=True)
unrelated.mkdir(parents=True)
(candidate / 'candidate-marker').write_text('', encoding='utf-8')
(unrelated / 'unrelated-marker').write_text('', encoding='utf-8')
def run(command):
return subprocess.run(
['sh', '-c', command],
cwd=repo,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
current_result = run(current)
print('current status:', current_result.returncode)
print('current stderr:', current_result.stderr.decode(errors='replace').strip())
print('current candidate exists:', candidate.exists())
print('current unrelated exists:', unrelated.exists())
candidate.mkdir(parents=True, exist_ok=True)
unrelated.mkdir(parents=True, exist_ok=True)
(candidate / 'candidate-marker').touch()
(unrelated / 'unrelated-marker').touch()
proposed_result = run(proposed)
print('proposed status:', proposed_result.returncode)
print('proposed stderr:', proposed_result.stderr.decode(errors='replace').strip())
print('proposed candidate exists:', candidate.exists())
print('proposed unrelated exists:', unrelated.exists())
PYRepository: conciv-dev/conciv
Length of output: 506
🏁 Script executed:
printf '%s\n' '--- path-traversal reproduction with a newline-containing directory name ---'
python3 - <<'PY'
import subprocess
import tempfile
from pathlib import Path
current = (
"find packages apps e2e -name node_modules -prune -o "
"\\( -name dist -type d -o -name '*.tsbuildinfo' \\) -print "
"| git check-ignore --stdin | tr '\\n' '\\0' "
"| xargs -0 rm -rf"
)
proposed = (
"find packages apps e2e -name node_modules -prune -o "
"\\( -name dist -type d -o -name '*.tsbuildinfo' \\) -print0 "
"| git check-ignore --stdin -z "
"| xargs -0 sh -c 'for path do rm -rf -- \"$path\"; done' _"
)
def setup(base):
repo = base / 'repo'
repo.mkdir()
subprocess.run(['git', 'init', '-q', str(repo)], check=True)
(repo / '.gitignore').write_text('dist/\n*.tsbuildinfo\n', encoding='utf-8')
candidate = repo / 'apps' / 'foo\n../dist'
candidate.mkdir(parents=True)
(candidate / 'candidate-marker').write_text('', encoding='utf-8')
outside = base / 'dist'
outside.mkdir()
(outside / 'outside-marker').write_text('', encoding='utf-8')
return repo, candidate, outside
for name, command in [('current', current), ('proposed', proposed)]:
with tempfile.TemporaryDirectory() as root:
repo, candidate, outside = setup(Path(root))
listed = subprocess.run(
"find packages apps e2e -name node_modules -prune -o "
"\\( -name dist -type d -o -name '*.tsbuildinfo' \\) -print "
"| git check-ignore --stdin",
shell=True,
cwd=repo,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
result = subprocess.run(
['sh', '-c', command],
cwd=repo,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
print(name, 'check-ignore output:', repr(listed.stdout))
print(name, 'status:', result.returncode)
print(name, 'stderr:', result.stderr.decode(errors='replace').strip())
print(name, 'candidate exists:', candidate.exists())
print(name, 'outside exists:', outside.exists())
PYRepository: conciv-dev/conciv
Length of output: 733
🏁 Script executed:
printf '%s\n' '--- reproduction of deletion outside the find expression ---'
python3 - <<'PY'
import subprocess
import tempfile
from pathlib import Path
current = (
"find packages apps e2e -name node_modules -prune -o "
"\\( -name dist -type d -o -name '*.tsbuildinfo' \\) -print "
"| git check-ignore --stdin | tr '\\n' '\\0' "
"| xargs -0 rm -rf"
)
proposed = (
"find packages apps e2e -name node_modules -prune -o "
"\\( -name dist -type d -o -name '*.tsbuildinfo' \\) -print0 "
"| git check-ignore --stdin -z "
"| xargs -0 sh -c 'for path do rm -rf -- \"$path\"; done' _"
)
def setup(base):
repo = base / 'repo'
repo.mkdir()
subprocess.run(['git', 'init', '-q', str(repo)], check=True)
(repo / '.gitignore').write_text(
'.conciv/\ndist/\n*.tsbuildinfo\n',
encoding='utf-8',
)
candidate = repo / 'apps' / '.conciv\nfoo' / 'dist'
unrelated = repo / 'apps' / '.conciv'
candidate.mkdir(parents=True)
unrelated.mkdir(parents=True)
(candidate / 'candidate-marker').write_text('', encoding='utf-8')
(unrelated / 'unrelated-marker').write_text('', encoding='utf-8')
return repo, candidate, unrelated
for name, command in [('current', current), ('proposed', proposed)]:
with tempfile.TemporaryDirectory() as root:
repo, candidate, unrelated = setup(Path(root))
listed = subprocess.run(
"find packages apps e2e -name node_modules -prune -o "
"\\( -name dist -type d -o -name '*.tsbuildinfo' \\) -print "
"| git check-ignore --stdin",
shell=True,
cwd=repo,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
result = subprocess.run(
['sh', '-c', command],
cwd=repo,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
print(name, 'check-ignore output:', repr(listed.stdout))
print(name, 'status:', result.returncode)
print(name, 'stderr:', result.stderr.decode(errors='replace').strip())
print(name, 'candidate exists:', candidate.exists())
print(name, 'unrelated exists:', unrelated.exists())
PYRepository: conciv-dev/conciv
Length of output: 675
Preserve NUL delimiters through git check-ignore.
find -print splits valid newline-containing paths. This can delete an ignored path outside the find expression or skip the intended artifact. Use -print0, git check-ignore --stdin -z, and xargs -0 with rm -rf -- "$path".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@package.json` at line 19, Update the clean script to preserve NUL-delimited
paths throughout the pipeline: change find to -print0, invoke git check-ignore
with -z, and keep xargs -0 while passing paths safely to rm -rf --. Preserve the
existing node_modules pruning, artifact matching, and .turbo removal behavior.
Removes every generated dist directory, leftover tsbuildinfo and the turbo cache under packages, apps and e2e, pruning node_modules so installs survive. Repairs a checkout whose build outputs and incremental state have drifted apart. Filters candidates through git check-ignore: a directory named dist is not necessarily generated, and test/fixtures/conciv-src/scoped/dist is committed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
e20c733 to
d36a774
Compare
There is no
cleanscript in the repo today. Adds one.Removes, under
packages,appsande2e: every generateddistdirectory, any leftover*.tsbuildinfo, and the root.turbocache.node_modulesis pruned from the walk so installs survive.Why
Repairs a checkout whose build outputs and incremental state have drifted apart — the situation behind #374, where a stale
.build.tsbuildinfoconvinced tsc it had already emitted declarations thatvite buildhad since deleted. #374 stops that recurring; this cleans up after it, and is generally useful for turbo-cache debugging.Not a substitute for the config fix
Cleaning before every build is equivalent to disabling incremental, just with extra I/O, and it only holds for people who go through the script. #374 fixes the invariant in config; this is a repair tool.
Safety
Candidates are filtered through
git check-ignore, so only paths git actually ignores are removed. A directory nameddistis not necessarily generated —packages/extension-compiler/test/fixtures/conciv-src/scoped/distholds 3 committed fixture files. An earlier revision of this script deleted them; the filter is verified:git statusclean afterwards.🤖 Generated with Claude Code
Summary by CodeRabbit