Skip to content

Add .deployignore support to Site Sync - #2955

Merged
ivan-ottinger merged 27 commits into
trunkfrom
stu-16-add-deployignore-to-site-sync
Apr 23, 2026
Merged

Add .deployignore support to Site Sync#2955
ivan-ottinger merged 27 commits into
trunkfrom
stu-16-add-deployignore-to-site-sync

Conversation

@ivan-ottinger

@ivan-ottinger ivan-ottinger commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Related issues

How AI was used in this PR

AI-assisted implementation with human iteration and review of all changes, architecture decisions, and edge cases. I have manually tested the changes as well.

Proposed Changes

Follow-up to #2924, which added .deployignore support for Preview Sites. This PR extends it to Site Sync (push to WordPress.com / Pressable).

  • Extend createDeployIgnoreFilter to accept a basePatterns argument so the sync path can pass Studio-internal exclusions (database, db.php, debug.log, sqlite-database-integration, cache) alongside DEPLOY_IGNORE_DEFAULTS
  • Sync tree UI: Update listLocalFileTree and shouldExcludeFromSync to use the deploy-ignore filter. Files matching .deployignore patterns are hidden from the sync dialog
  • Sync push: CLI's DefaultExporter accepts an optional ignoreFilter in ExportOptions, used by addWpContent() alongside existing hardcoded exclusions (isExactPathExcluded, isPathExcludedByPattern). The studio export command gains a hidden --apply-deploy-ignore flag; Studio's sync push sets it when shelling to the CLI. Regular site exports (Studio UI, direct studio export CLI) do not apply the filter, preserving pre-existing backup behavior.
  • meta.json filtering: Filter the plugin and theme lists in meta.json against the deploy-ignore filter. Without this, the server-side import would reinstall excluded plugins from WordPress.org even though their files were excluded from the archive
  • Rename SYNC_EXCLUSIONS to SYNC_IGNORE_DEFAULTS — a superset of DEPLOY_IGNORE_DEFAULTS plus Studio-internal items (database, db.php, debug.log, sqlite-database-integration, cache).
  • Remove redundant .replace(/\\/g, '/') path normalization in fs-utils.ts — the ignore library handles Windows backslash conversion internally
  • Add ignore as a direct dependency in apps/cli and apps/studio (previously relied on transitive resolution)

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.

  1. Create a local site in Studio
  2. Add a .deployignore file at the site root (e.g., ~/Studio/my-site/.deployignore) with the following content:
    # Ignore vendor dependencies in my-plugin, except important-lib
    wp-content/plugins/my-plugin/vendor/*
    !wp-content/plugins/my-plugin/vendor/important-lib
    
    # Ignore all log files
    *.log
    
    # Ignore old uploads
    wp-content/uploads/2024
    
    # Ignore a specific plugin
    wp-content/plugins/wordpress-seo
    
  3. Create matching test files/folders in the site's wp-content:
    # Plugin with build artifacts
    mkdir -p wp-content/plugins/my-plugin/vendor/some-lib
    mkdir -p wp-content/plugins/my-plugin/vendor/important-lib
    echo '<?php // plugin entry' > wp-content/plugins/my-plugin/my-plugin.php
    echo '<?php // vendor file' > wp-content/plugins/my-plugin/vendor/some-lib/autoload.php
    echo '<?php // important' > wp-content/plugins/my-plugin/vendor/important-lib/loader.php
    echo 'build log' > wp-content/plugins/my-plugin/build.log
    
    # .git and node_modules inside the plugin (built-in defaults)
    mkdir -p wp-content/plugins/my-plugin/.git
    echo 'ref: refs/heads/main' > wp-content/plugins/my-plugin/.git/HEAD
    mkdir -p wp-content/plugins/my-plugin/node_modules/some-package
    echo '{"name":"some-package"}' > wp-content/plugins/my-plugin/node_modules/some-package/package.json
    
    # Old and new uploads
    mkdir -p wp-content/uploads/2024
    echo 'old photo' > wp-content/uploads/2024/photo.jpg
    mkdir -p wp-content/uploads/2025
    echo 'new photo' > wp-content/uploads/2025/photo.jpg
    
  4. Install the wordpress-seo plugin on the site
  5. Connect the local site to a target remote site (WordPress.com or Pressable)
  6. Consider checking the target site to confirm it does not yet include any directories/files we want to ignore during the sync.
  7. Open the Sync dialog and verify:
    • wordpress-seo plugin is hidden from the tree
    • akismet and my-plugin are still visible
    • uploads/2024 directory is hidden
    • uploads/2025 is still visible
  8. Push to WordPress.com / Pressable and verify (e.g through a SSH connection to the WoA site):
    • vendor/some-lib/ is excluded from the push
    • vendor/important-lib/ is included (negation pattern)
    • *.log files are excluded
    • uploads/2024/ is excluded, uploads/2025/ is present
    • wordpress-seo plugin is excluded and not reinstalled by the server
  9. Test the CLI push with the same .deployignore file:
    npm run cli:build && node apps/cli/dist/cli/main.mjs push --path {path-to-your-studio-site}
    
  10. Verify the same exclusions apply as in step 7 & step 8 (check the target site again after the CLI push).
  11. Verify that .git and node_modules inside my-plugin are excluded by built-in defaults
  12. Verify that site export (backup) is unaffected by .deployignore — the filter is only applied during sync push, not when using Studio UI "Export site" or running studio export directly

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?
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
@ivan-ottinger ivan-ottinger self-assigned this Apr 1, 2026
Comment thread tools/common/package.json Outdated
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.
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.
Comment on lines +306 to +317
studioJson.plugins = this.options.deployIgnore
? plugins.filter(
( p: StudioJsonPluginOrTheme ) =>
! this.options.deployIgnore!.ignores( `wp-content/plugins/${ p.name }` )
)
: plugins;
studioJson.themes = this.options.deployIgnore
? themes.filter(
( t: StudioJsonPluginOrTheme ) =>
! this.options.deployIgnore!.ignores( `wp-content/themes/${ t.name }` )
)
: themes;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are required because otherwise the .deployignored plugins/themes would be reinstalled on the target site.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Extends existing .deployignore support to Studio’s Site Sync flow (sync tree UI + push/export), ensuring ignored files are consistently hidden/excluded and that meta.json doesn’t re-trigger server-side reinstalls of excluded plugins/themes.

Changes:

  • Adds additionalDefaults support to the shared deploy-ignore filter and updates sync defaults to use it.
  • Updates Sync tree listing/exclusion logic to rely on the deploy-ignore filter (while keeping dotfile hiding UI-only).
  • Passes deploy-ignore filtering through the export pipeline and filters meta.json plugin/theme lists accordingly; downgrades ignore to v5 to avoid type conflicts.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tools/common/package.json Downgrades ignore dependency to ^5.3.2.
tools/common/lib/deploy-ignore.ts Adds additionalDefaults parameter to createDeployIgnoreFilter.
package-lock.json Updates lockfile entries to reflect ignore@5.3.2 usage.
apps/studio/src/modules/sync/lib/tree-utils.ts Reworks sync exclusion logic to use a deploy-ignore filter + dotfile hiding.
apps/studio/src/modules/sync/lib/ipc-handlers.ts Creates deploy-ignore filter during push export and passes it into ExportOptions.
apps/studio/src/modules/sync/constants.ts Renames/adjusts sync exclusion defaults to Studio-internal items only.
apps/studio/src/lib/import-export/export/types.ts Adds optional deployIgnore?: Ignore to ExportOptions.
apps/studio/src/lib/import-export/export/exporters/default-exporter.ts Applies deploy-ignore filtering during wp-content archive creation and filters meta.json plugins/themes.
apps/studio/src/ipc-handlers.ts Updates listLocalFileTree to build/reuse deploy-ignore filter when listing sync tree.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/studio/src/modules/sync/constants.ts Outdated
Comment thread tools/common/lib/deploy-ignore.ts Outdated
Comment thread apps/studio/src/lib/import-export/export/exporters/default-exporter.ts Outdated
@ivan-ottinger
ivan-ottinger marked this pull request as ready for review April 1, 2026 13:00
@ivan-ottinger
ivan-ottinger requested a review from a team April 1, 2026 13:02
@fredrikekelund

Copy link
Copy Markdown
Contributor

Noting that #3010 changed a lot of the sync logic on trunk. The CLI now also supports sync, and a lot of the internals moved to tools/common. We should update this PR accordingly

@fredrikekelund fredrikekelund left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned in #2955 (comment), a lot of the sync internals were moved around when we added sync support to the CLI. We should update this PR to reflect that.

Also, we currently have two copies of the exporter implementation: one for the CLI and another for the app. We should make sure this PR updates both places.

Lastly, I left some more detailed comments on the implementation.

Comment thread tools/common/lib/deploy-ignore.ts Outdated
Comment thread tools/common/lib/sync/constants.ts Outdated
Comment thread tools/common/package.json Outdated
Comment thread apps/studio/src/modules/sync/lib/tree-utils.ts Outdated
Comment thread apps/studio/src/lib/import-export/export/exporters/default-exporter.ts Outdated
…ore-to-site-sync

# Conflicts:
#	apps/studio/src/ipc-handlers.ts
#	apps/studio/src/modules/sync/constants.ts
#	apps/studio/src/modules/sync/lib/ipc-handlers.ts
#	apps/studio/src/modules/sync/lib/tree-utils.ts
@wpmobilebot

wpmobilebot commented Apr 15, 2026

Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing e01525a vs trunk

app-size

Metric trunk e01525a Diff Change
App Size (Mac) 1482.75 MB 1482.71 MB 0.04 MB ⚪ 0.0%

site-editor

Metric trunk e01525a Diff Change
load 1806 ms 1850 ms +44 ms ⚪ 0.0%

site-startup

Metric trunk e01525a Diff Change
siteCreation 8086 ms 8079 ms 7 ms ⚪ 0.0%
siteStartup 4958 ms 4956 ms 2 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

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
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.
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).
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.
@ivan-ottinger

Copy link
Copy Markdown
Contributor Author

I will continue with updates and testing tomorrow with fresh brain.

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.
Comment thread tools/common/lib/sync/constants.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

tools/common/lib/fs-utils.ts:39

  • path.relative() returns OS-specific separators (e.g., \ on Windows). Passing ignorePath with backslashes into ig.ignores() can cause .deployignore patterns that use / (like wp-content/plugins/**) to stop matching on Windows, and it can also break the built-in defaults when scanning nested paths. Normalize fileRelativeToRoot/ignorePath to POSIX (replace \\ with /) before calling ignores() (and keep pathPrefix joins consistent).
						const fileRelativeToRoot = path.relative( directoryPath, filePath );
						const ignorePath = pathPrefix
							? `${ pathPrefix }/${ fileRelativeToRoot }`
							: fileRelativeToRoot;
						try {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/studio/src/lib/import-export/export/exporters/default-exporter.ts Outdated
Comment thread apps/cli/lib/import-export/export/exporters/default-exporter.ts Outdated
Comment thread apps/cli/lib/import-export/export/exporters/default-exporter.ts
Comment thread apps/studio/src/lib/import-export/export/exporters/default-exporter.ts Outdated
@ivan-ottinger

Copy link
Copy Markdown
Contributor Author

Alright, I have addressed the feedback and tested the changes on both macOS and Windows (pushing through the UI and the Studio CLI. Everything is running well, the files are getting excluded as described in the .deployignore.

From what I see this is the main remaining change to agree on. We can wait for the response from Antonio and/or Kat.

Apart from that, the PR will be ready for another review/test round.

…ore-to-site-sync

# Conflicts:
#	apps/cli/commands/push.ts
#	apps/cli/lib/import-export/export/exporters/default-exporter.ts
#	apps/cli/lib/import-export/export/types.ts
Move the constant to deploy-ignore-defaults.ts so sync/constants.ts
can spread it via ...DEPLOY_IGNORE_DEFAULTS without pulling in fs.
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.
…ore-to-site-sync

# Conflicts:
#	apps/studio/src/ipc-handlers.ts
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.
@ivan-ottinger

Copy link
Copy Markdown
Contributor Author

Hey @fredrikekelund 👋🏼🙂 I think we should be good merging this one. Feel free to let me know what you think.

…ore-to-site-sync

# Conflicts:
#	apps/studio/src/ipc-handlers.ts
#	apps/studio/src/modules/sync/lib/ipc-handlers.ts
@fredrikekelund

Copy link
Copy Markdown
Contributor

Taking another look at this now

@fredrikekelund fredrikekelund left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 I left a couple of comments that'd be nice to address before landing this, not least the ignore dependency management.

Also interested to hear what you think about reading .gitignore, too, but that can easily be a follow-up.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for not thinking of this sooner, but I feel like it'd make sense to look for a .gitignore file here, too. Thoughts, @ivan-ottinger? I guess this could also be a follow-up PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worried. Do you mean to merge records from both files and use that to decide what should get synced to WordPress.com / Pressable? 🤔 Wouldn't it be mixing two different concepts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean to merge records from both files and use that to decide what should get synced to WordPress.com / Pressable?

Yep, exactly. npm is an interesting example to look at. It uses an .npmignore file by default to determine what to include when publishing a package, and falls back to .gitignore (docs).

Wouldn't it be mixing two different concepts?

.gitignore might not be comprehensive in terms of what you want and don't want to sync to your remote site, but it's still a good indicator – especially if there's no .deployignore file.

.gitignore is specifically mentioned in #137, too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. 👍🏼 I have created a follow-up task: STU-1633.

.gitignore is specifically mentioned in #137, too.

Yes, I am aware of that. Just when we discussed the scope in STU-16, we shifted to .deployignore at the end.

Comment on lines +301 to +312
studioJson.plugins = this.options.deployIgnore
? plugins.filter(
( p: StudioJsonPluginOrTheme ) =>
! this.options.deployIgnore!.ignores( `wp-content/plugins/${ p.name }` )
)
: plugins;
studioJson.themes = this.options.deployIgnore
? themes.filter(
( t: StudioJsonPluginOrTheme ) =>
! this.options.deployIgnore!.ignores( `wp-content/themes/${ t.name }` )
)
: themes;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we should really do, instead of asserting the array entry types, is use zod to parse the JSON returned by WP-CLI in getSitePlugins and getSiteThemes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I first tried to put it directly into this PR, but then decided to leave it for a new follow-up task: STU-1630 task - so we can unblock this PR.

Comment thread tools/common/package.json Outdated
Comment thread tools/common/lib/fs-utils.ts Outdated
Comment thread tools/common/lib/sync/tree-utils.ts Outdated
Comment thread apps/cli/lib/import-export/export/types.ts Outdated
…ore-to-site-sync

# Conflicts:
#	apps/cli/commands/push.ts
#	apps/cli/lib/import-export/export/exporters/default-exporter.ts
#	apps/studio/src/lib/import-export/export/exporters/default-exporter.ts
#	apps/studio/src/lib/import-export/export/types.ts
#	apps/studio/src/modules/sync/lib/ipc-handlers.ts
@ivan-ottinger

Copy link
Copy Markdown
Contributor Author

Hey @fredrikekelund 👋🏼 I have applied the latest changes that also required adjustment related to recent #3129 PR. Now we have a new --apply-deploy-ignore flag for the studio export to ensure we don't filter out files when the user is doing just a regular site export.

I have tested everything on macOS and Windows and haven't observed any regressions.

@ivan-ottinger
ivan-ottinger merged commit 20ec417 into trunk Apr 23, 2026
16 checks passed
@ivan-ottinger
ivan-ottinger deleted the stu-16-add-deployignore-to-site-sync branch April 23, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

4 participants