Add .deployignore support for Preview Sites - #2924
Conversation
Allow users to place a .deployignore file at the site root to control which files are excluded when pushing to Preview Sites or syncing to WordPress.com/Pressable. Uses gitignore pattern syntax via the `ignore` npm package. Built-in defaults (.git, node_modules, .DS_Store, Thumbs.db) are always applied regardless of whether a .deployignore file exists.
There was a problem hiding this comment.
Pull request overview
Adds shared .deployignore-based filtering (gitignore syntax via ignore npm package) so Preview Site uploads and Site Sync file-tree building can consistently exclude user-specified paths plus a set of built-in defaults.
Changes:
- Introduces
createDeployIgnoreFilterutility in@studio/commonand adds unit tests for it. - Updates CLI Preview Site archiving + size validation to apply deploy-ignore rules when scanning/archiving
wp-content. - Updates Studio Site Sync file-tree building to use deploy-ignore rules, while keeping dotfile hiding as a UI-specific rule; refactors sync exclusion constants accordingly.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/common/package.json | Adds ignore dependency used to parse .deployignore. |
| tools/common/lib/deploy-ignore.ts | New shared utility to build ignore filter from defaults + .deployignore. |
| tools/common/lib/tests/deploy-ignore.test.ts | Unit coverage for default rules, patterns, negation, globs, additional defaults. |
| tools/common/lib/fs-utils.ts | Extends archive size calculation to accept an ignore filter + path prefix. |
| apps/cli/lib/archive.ts | Applies deploy-ignore filtering when archiving wp-content for preview uploads. |
| apps/cli/lib/validation.ts | Applies deploy-ignore filtering when computing wp-content size for limits. |
| apps/cli/lib/tests/validation.test.ts | Updates mocks/assertions for new deploy-ignore usage in validation. |
| apps/studio/src/modules/sync/constants.ts | Renames/reshapes sync exclusions into SYNC_ADDITIONAL_DEFAULTS. |
| apps/studio/src/modules/sync/lib/tree-utils.ts | Uses deploy-ignore filter when deciding whether to exclude sync tree entries. |
| apps/studio/src/ipc-handlers.ts | Builds deploy-ignore filter once per tree request and threads it through recursion. |
| package-lock.json | Locks ignore@7.0.5 for tools/common. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Log a warning for unexpected errors (e.g., permission issues) instead of silently falling back to defaults. Uses the existing isErrnoException helper, matching the pattern in fs-utils.ts.
Site Sync integration with .deployignore will be handled in a follow-up PR that also updates the DefaultExporter filtering.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * Default patterns that are always excluded from deploys, | ||
| * regardless of whether a .deployignore file exists. | ||
| */ | ||
| const DEPLOY_IGNORE_DEFAULTS = [ '.git', 'node_modules', '.DS_Store', 'Thumbs.db' ]; |
There was a problem hiding this comment.
The .DS_Store and Thumbs.db are a new addition - as I don't think we need those synced to the preview site.
fredrikekelund
left a comment
There was a problem hiding this comment.
This is a great addition 👍 I left a couple of comments on the implementation, but the core functionality looks good and tests well for me. I think we should address my comments, but approving now to unblock you, @ivan-ottinger
Remove the separate DEFAULT_EXCLUDED_DIRECTORIES fallback and the Ignore type re-export. Instead, construct a default ignore instance from the exported DEPLOY_IGNORE_DEFAULTS when no filter is provided.
📊 Performance Test ResultsComparing be4ce31 vs trunk app-size
site-editor
site-startup
Results are median values from multiple test runs. Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff) |
Thanks for the review and feedback, Fredrik. I have updated the PR. Next I will retest the logic on both macOS and Windows and then merge. |
Everything is testing well. The files are properly excluded on both macOS and Windows. I did not observe any regressions. |
|
@ivan-ottinger I was just looking for something similar on this -- does this / could this also support For example, if I manage a theme or plugin that I've got checked out inside of a local synced site in Studio, could I have a |
Great idea, George! Currently It could be a nice enhancement though. I have opened up a follow-up issue (STU-1499) for it so we can explore that approach further. |
Integrate the deploy-ignore filter (from PR #2924) into the Site Sync flow. Files matching .deployignore patterns are now hidden from the sync tree UI and excluded from the push archive. - Add additionalDefaults parameter to createDeployIgnoreFilter for sync-specific Studio-internal exclusions - Update shouldExcludeFromSync to use the ignore filter alongside the existing dotfile check - Thread the deploy-ignore filter through listLocalFileTree recursion - Pass the filter via ExportOptions to DefaultExporter for archive filtering during sync push - Downgrade ignore package from v7 to v5 to match the rest of the dependency tree and avoid TypeScript type conflicts
* Add .deployignore support for Preview Sites and Site Sync Allow users to place a .deployignore file at the site root to control which files are excluded when pushing to Preview Sites or syncing to WordPress.com/Pressable. Uses gitignore pattern syntax via the `ignore` npm package. Built-in defaults (.git, node_modules, .DS_Store, Thumbs.db) are always applied regardless of whether a .deployignore file exists. * Only silence ENOENT when reading .deployignore Log a warning for unexpected errors (e.g., permission issues) instead of silently falling back to defaults. Uses the existing isErrnoException helper, matching the pattern in fs-utils.ts. * Revert sync changes to focus PR on Preview Sites only Site Sync integration with .deployignore will be handled in a follow-up PR that also updates the DefaultExporter filtering. * Remove unused additionalDefaults parameter from createDeployIgnoreFilter * Update comment to clarify defaults can be overridden via negation * Use single filtering path with shared DEPLOY_IGNORE_DEFAULTS Remove the separate DEFAULT_EXCLUDED_DIRECTORIES fallback and the Ignore type re-export. Instead, construct a default ignore instance from the exported DEPLOY_IGNORE_DEFAULTS when no filter is provided. * Simplify relative path computation with path.relative
* Add .deployignore support to Site Sync Integrate the deploy-ignore filter (from PR #2924) into the Site Sync flow. Files matching .deployignore patterns are now hidden from the sync tree UI and excluded from the push archive. - Add additionalDefaults parameter to createDeployIgnoreFilter for sync-specific Studio-internal exclusions - Update shouldExcludeFromSync to use the ignore filter alongside the existing dotfile check - Thread the deploy-ignore filter through listLocalFileTree recursion - Pass the filter via ExportOptions to DefaultExporter for archive filtering during sync push - Downgrade ignore package from v7 to v5 to match the rest of the dependency tree and avoid TypeScript type conflicts * Check deployIgnore on top-level archive paths When specificSelectionPaths is undefined, the exporter reads wp-content from disk directly. Top-level directories like wordpress-seo need to be checked against the deploy-ignore filter before archiving, not just their children in the callback. * Filter meta.json plugin and theme lists against deployIgnore The server-side import uses meta.json to install plugins/themes from WordPress.org. Without filtering this list, excluded plugins would be reinstalled by the server even though their files were excluded from the archive. * Update SYNC_ADDITIONAL_DEFAULTS comment to clarify overridability * Add tests for additionalDefaults parameter and negation override * Apply .deployignore to CLI sync push Mirror the desktop app's sync push changes in the CLI: - Add optional deployIgnore to CLI ExportOptions - Use the filter in CLI DefaultExporter addWpContent and meta.json - Create the filter with SYNC_ADDITIONAL_DEFAULTS in the push command * Simplify createDeployIgnoreFilter with a single basePatterns argument Replace the additionalDefaults parameter with basePatterns, which completely overrides the built-in DEPLOY_IGNORE_DEFAULTS when provided. Callers that want Studio-internal sync exclusions on top of the base defaults now spread both arrays explicitly. * Move DEPLOY_IGNORE_DEFAULTS into SYNC_IGNORE_DEFAULTS Spread DEPLOY_IGNORE_DEFAULTS into the sync base patterns so sync callers can pass a single constant instead of building the combined array at each call site. Rename SYNC_ADDITIONAL_DEFAULTS to SYNC_IGNORE_DEFAULTS to parallel DEPLOY_IGNORE_DEFAULTS and reflect that it is the complete base set for sync (no longer "additional" to a function-internal default). * Inline deploy defaults in SYNC_IGNORE_DEFAULTS to fix renderer build Spreading DEPLOY_IGNORE_DEFAULTS into SYNC_IGNORE_DEFAULTS caused Vite to pull deploy-ignore.ts (and its `fs` import) into the renderer bundle via sync/constants.ts consumers. Inline the four patterns instead and add a unit test that asserts SYNC_IGNORE_DEFAULTS is a superset of DEPLOY_IGNORE_DEFAULTS to catch drift automatically. * Drop redundant Windows path normalization around ignore.ignores() The `ignore` library already converts backslashes to forward slashes internally on Windows (via its `makePosix` converter, active when `process.platform === 'win32'`). Our `.replace(/\\/g, '/')` calls at each `.ignores()` call site were no-ops on macOS/Linux and redundant duplication on Windows. * Fix import order in CLI export types * Extract DEPLOY_IGNORE_DEFAULTS to a renderer-safe module Move the constant to deploy-ignore-defaults.ts so sync/constants.ts can spread it via ...DEPLOY_IGNORE_DEFAULTS without pulling in fs. * Show dotfiles in the sync tree UI Remove the blanket startsWith('.') check from shouldExcludeFromSync. Dotfiles were previously hidden from the sync file selector but still pushed inside selected directories, making them easy to overlook. They are now visible so users can see what gets deployed. Dotfiles matched by deploy-ignore defaults (.git, .DS_Store) are still excluded. * Revert dotfile visibility in sync tree The server-side restore system automatically skips dotfiles, so showing them in the sync tree gives users a false expectation that they will be deployed. Restore the trunk behavior of hiding dotfiles from the tree. * Document dotfile handling in sync filter and tree UI * Add ignore as a direct dependency and upgrade to v7 * Use path.join for consistent separators when building ignore paths * Rename exporter option deployIgnore to ignoreFilter * Fix import order in CLI export command * Move shouldExcludeFromSync to its own module and use path.basename
Related issues
How AI was used in this PR
AI-assisted implementation with human review of all changes, architecture decisions, and edge cases. Step-by-step iteration. I have tested the PR as well.
Proposed Changes
createDeployIgnoreFilterutility (tools/common/lib/deploy-ignore.ts) that reads a.deployignorefile from the site root and creates a filter using theignorenpm package (gitignore pattern syntax)ignorepackage is widely used and I consider it is better to use it instead of re-inventing the wheel.git,node_modules,.DS_Store,Thumbs.db) are excluded by defaultarchiveSiteContentandvalidateSiteSizeto use the deploy-ignore filter when archivingwp-contentfor uploadisExcludedFromArchivehardcoded filter fromfs-utils.ts, replacing it with the new deploy-ignore utility.calculateDirectorySizeForArchiveretains a fallback to the original.git/node_modulesexclusion when called without a filterNote: Site Sync integration with
.deployignorewill be handled in a follow-up PR that also updates theDefaultExporterfiltering. The shared utility introduced here is designed to be reused for that.Testing Instructions
Tip
It is probably easier to ask your AI assistant to perform the steps 2 and 3 for you, so you don't have to create the testing files manually.
wordpress-seoplugin.deployignorefile at the site root (e.g.,~/Studio/my-site/.deployignore) with the following content:wp-content:vendor/some-lib/should be excludedvendor/important-lib/should be included (negation pattern)*.logfiles should be excludeduploads/2024/should be excluded,uploads/2025/should be presentwordpress-seoplugin should be excluded.gitandnode_modulesinsidemy-pluginare excluded by built-in defaults (even without.deployignoreentries for them).deployignore. Please note that the backup already excludes files/folders like.git,node_modules,cacheby default (unrelated to this PR).Pre-merge Checklist