#19449: fix: preserve .env during package/plugin updates
size: XS
## Description
This PR fixes an issue where the `update` command (and plugin updates) would wipe local configuration files like `.env` because it replaces the entire directory without preservation.
### Changes
- Modified `src/infra/install-package-dir.ts` to preserve files listed in `PRESERVED_FILES` (currently just `.env`) by copying them from the backup directory to the new target directory before cleanup.
## Verification
- Added a unit test `src/infra/install-package-dir.preservation.test.ts` (deleted before commit) which verified that `.env` survives the update process.
- Manually verified the fix.
Fixes #17458
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes a real user-facing issue (#17458) where the `update` command would wipe user-created `.env` files by replacing the entire package directory. The fix correctly places the preservation logic after a successful install and before the backup cleanup, ensuring `.env` is restored from the backup on success. The rollback path (on install failure) is also safe since it restores the entire `backupDir` including the original `.env`.
Key observations:
- The `.env` preservation is applied via `installPackageDir`, which is used by plugin and hook-pack installs. However, the `installHookFromDir` function in `src/hooks/install.ts` (lines 256–315) has its own inline backup/copy logic that does **not** call `installPackageDir` and thus does **not** benefit from this fix. Single-hook (non-packaged) updates still have the same data-loss issue.
- The test file written to verify this behavior was deleted before the commit. The project guidelines require colocated `*.test.ts` coverage for logic changes — shipping this without a test means the fix has no regression guard.
- `fs.copyFile` without `COPYFILE_EXCL` will silently overwrite a `.env` that a new package version might ship intentionally (e.g., a template with updated keys), which could leave users missing required new config keys with no warning.
<h3>Confidence Score: 3/5</h3>
- This PR addresses a real data-loss bug but introduces new edge cases and lacks test coverage; needs minor fixes before merging.
- The core fix is logically correct and addresses the reported issue. However, the test file was explicitly deleted before committing (no regression guard), the `installHookFromDir` path in `src/hooks/install.ts` bypasses `installPackageDir` and still has the same data-loss issue for single-hook updates, and the unconditional overwrite behavior could silently drop new package-shipped `.env` templates on update.
- src/infra/install-package-dir.ts — missing test coverage; src/hooks/install.ts — `installHookFromDir` has the same data-loss gap not addressed by this PR
<sub>Last reviewed commit: a0e7720</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#10729: fix: replace existsSync+readFileSync with try-catch in env-file
by Yida-Dev · 2026-02-06
73.7%
#10258: fix(config): preserve ${ENV_VAR} references when writing config (#9...
by nu-gui · 2026-02-06
73.2%
#3973: fix: enhance npm package integrity checks and include critical files
by YeSuX · 2026-01-29
72.9%
#17815: fix: use $HOME as cwd for global update to prevent path-dedot panic
by frankekn · 2026-02-16
72.9%
#16019: fix(plugins): add postinstall patch for ESM-only package exports
by dashed · 2026-02-14
72.8%
#11048: fix: address repository issues (env, author, CI comments, security ...
by cavula · 2026-02-07
72.6%
#2556: fix(plugin-install): handle existing plugins and filter workspace deps
by longmaba · 2026-01-27
72.4%
#9914: fix(hooks): resolve bundled hook dist paths and packaging checks
by zimmra · 2026-02-05
72.1%
#19021: fix(hooks): reject path traversal in hook pack manifest entries dur...
by moxunjinmu · 2026-02-17
71.7%
#23339: fix: use snapshot.parsed for env ref restoration during migrate
by Mathew-Harvey · 2026-02-22
71.6%