Skip to content
Open
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
18 changes: 14 additions & 4 deletions apps/studio/src/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from '@studio/common/lib/agent-skills';
import { validateBlueprintData } from '@studio/common/lib/blueprint-validation';
import { parseCliError, errorMessageContains } from '@studio/common/lib/cli-error';
import { createDeployIgnoreFilter } from '@studio/common/lib/deploy-ignore';
import {
calculateDirectorySizeForArchive,
isWordPressDirectory,
Expand Down Expand Up @@ -93,6 +94,7 @@ import {
import { editSiteViaCli, EditSiteOptions } from 'src/modules/cli/lib/cli-site-editor';
import { isStudioCliInstalled } from 'src/modules/cli/lib/ipc-handlers';
import { STABLE_BIN_DIR_PATH } from 'src/modules/cli/lib/windows-installation-manager';
import { SYNC_ADDITIONAL_DEFAULTS } from 'src/modules/sync/constants';
import { shouldExcludeFromSync, shouldLimitDepth } from 'src/modules/sync/lib/tree-utils';
import { supportedEditorConfig, SupportedEditor } from 'src/modules/user-settings/lib/editor';
import { getUserEditor, getUserTerminal } from 'src/modules/user-settings/lib/ipc-handlers';
Expand All @@ -107,6 +109,7 @@ import {
updateAppdata,
} from 'src/storage/user-data';
import { Blueprint } from 'src/stores/wpcom-api';
import type { Ignore } from 'ignore';
import type { RawDirectoryEntry } from 'src/modules/sync/types';
import type { WpCliResult } from 'src/site-server';

Expand Down Expand Up @@ -1635,24 +1638,30 @@ export async function listLocalFileTree(
siteId: string,
path: string,
maxDepth: number = 3,
currentDepth: number = 0
currentDepth: number = 0,
deployIgnore?: Ignore
): Promise< RawDirectoryEntry[] > {
const server = SiteServer.get( siteId );
if ( ! server ) throw new Error( 'Site not found' );

if ( ! deployIgnore ) {
deployIgnore = await createDeployIgnoreFilter( server.details.path, SYNC_ADDITIONAL_DEFAULTS );
}

const fullPath = nodePath.join( server.details.path, path );

try {
const entries = await fs.promises.readdir( fullPath, { withFileTypes: true } );
const result = [];

for ( const entry of entries ) {
if ( shouldExcludeFromSync( entry.name ) ) {
const itemPath = nodePath.join( path, entry.name ).replace( /\\/g, '/' );

if ( shouldExcludeFromSync( itemPath, deployIgnore ) ) {
continue;
}

const isDirectory = entry.isDirectory();
const itemPath = nodePath.join( path, entry.name ).replace( /\\/g, '/' );

const directoryEntry: RawDirectoryEntry = {
name: entry.name,
Expand All @@ -1668,7 +1677,8 @@ export async function listLocalFileTree(
siteId,
itemPath,
maxDepth,
currentDepth + 1
currentDepth + 1,
deployIgnore
);
} catch ( childErr ) {
console.warn( `Failed to load children for ${ itemPath }:`, childErr );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
Exporter,
BackupCreateProgressEventData,
StudioJson,
StudioJsonPluginOrTheme,
} from 'src/lib/import-export/export/types';
import { getWordPressVersionFromInstallation } from 'src/lib/wp-versions';
import { SiteServer } from 'src/site-server';
Expand Down Expand Up @@ -216,6 +217,10 @@ export class DefaultExporter extends EventEmitter implements Exporter {
continue;
}

if ( this.options.deployIgnore?.ignores( archivePath.replace( /\\/g, '/' ) ) ) {
continue;
}

const stat = await fs.promises.stat( fullPath );
if ( stat.isDirectory() ) {
this.archiveBuilder.directory( fullPath, archivePath, ( entry ) => {
Expand All @@ -226,14 +231,20 @@ export class DefaultExporter extends EventEmitter implements Exporter {
);
if (
this.isExactPathExcluded( entryPathRelativeToArchiveRoot ) ||
this.isPathExcludedByPattern( fullEntryPathOnDisk )
this.isPathExcludedByPattern( fullEntryPathOnDisk ) ||
this.options.deployIgnore?.ignores(
entryPathRelativeToArchiveRoot.replace( /\\/g, '/' )
)
) {
return false;
}
return entry;
} );
} else {
if ( this.isExactPathExcluded( archivePath ) ) {
if (
this.isExactPathExcluded( archivePath ) ||
this.options.deployIgnore?.ignores( archivePath.replace( /\\/g, '/' ) )
) {
continue;
}
this.archiveBuilder.file( fullPath, { name: archivePath } );
Expand Down Expand Up @@ -292,8 +303,18 @@ export class DefaultExporter extends EventEmitter implements Exporter {
this.getSiteThemes( this.options.site.id ),
] );

studioJson.plugins = plugins;
studioJson.themes = themes;
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;
Comment on lines +306 to +317
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.


const tempDir = await fs.promises.mkdtemp( path.join( os.tmpdir(), 'studio-export-' ) );
const studioJsonPath = path.join( tempDir, 'meta.json' );
Expand Down
2 changes: 2 additions & 0 deletions apps/studio/src/lib/import-export/export/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ProgressData } from 'archiver';
import type { EventEmitter } from 'events';
import type { Ignore } from 'ignore';

export interface ExportOptions {
site: SiteDetails;
Expand All @@ -8,6 +9,7 @@ export interface ExportOptions {
phpVersion: string;
splitDatabaseDumpByTable?: boolean;
specificSelectionPaths?: string[];
deployIgnore?: Ignore;
}

export type ExportOptionsIncludes = 'wpContent' | 'database';
Expand Down
11 changes: 6 additions & 5 deletions apps/studio/src/modules/sync/constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
export const SYNC_EXCLUSIONS = [
/**
* Studio-internal exclusions excluded from sync by default,
* in addition to the base deploy-ignore defaults. These are pre-seeded
* but can be overridden via negation patterns in .deployignore.
*/
export const SYNC_ADDITIONAL_DEFAULTS = [
'database',
'db.php',
'debug.log',
'sqlite-database-integration',
'.DS_Store',
'Thumbs.db',
'.git',
'node_modules',
'cache',
];
8 changes: 8 additions & 0 deletions apps/studio/src/modules/sync/lib/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from 'fs';
import fsPromises from 'fs/promises';
import { randomUUID } from 'node:crypto';
import path from 'node:path';
import { createDeployIgnoreFilter } from '@studio/common/lib/deploy-ignore';
import { getCurrentUserId } from '@studio/common/lib/shared-config';
import wpcomFactory from '@studio/common/lib/wpcom-factory';
import wpcomXhrRequest from '@studio/common/lib/wpcom-xhr-request-factory';
Expand All @@ -20,6 +21,7 @@ import { exportBackup } from 'src/lib/import-export/export/export-manager';
import { ExportOptions } from 'src/lib/import-export/export/types';
import { getAuthenticationToken } from 'src/lib/oauth';
import { keepSqliteIntegrationUpdated } from 'src/lib/sqlite-versions';
import { SYNC_ADDITIONAL_DEFAULTS } from 'src/modules/sync/constants';
import { SyncSite } from 'src/modules/sync/types';
import { SiteServer } from 'src/site-server';
import { loadUserData, lockAppdata, saveUserData, unlockAppdata } from 'src/storage/user-data';
Expand Down Expand Up @@ -163,6 +165,11 @@ export async function exportSiteForPush(

await keepSqliteIntegrationUpdated( site.details.path );

const deployIgnore = await createDeployIgnoreFilter(
site.details.path,
SYNC_ADDITIONAL_DEFAULTS
);

const shouldIncludeSyncOption = (
optionsToSync: SyncOption[] | undefined,
option: SyncOption
Expand All @@ -186,6 +193,7 @@ export async function exportSiteForPush(
phpVersion: site.details.phpVersion,
splitDatabaseDumpByTable: true,
specificSelectionPaths: configuration?.specificSelectionPaths,
deployIgnore,
};

const onEvent = () => {};
Expand Down
12 changes: 4 additions & 8 deletions apps/studio/src/modules/sync/lib/tree-utils.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { TreeNode } from 'src/components/tree-view';
import { SYNC_EXCLUSIONS } from '../constants';
import type { RawDirectoryEntry } from '../types';
import type { Ignore } from 'ignore';

export const shouldExcludeFromSync = ( itemName: string ): boolean => {
export const shouldExcludeFromSync = ( relativePath: string, deployIgnore: Ignore ): boolean => {
const itemName = relativePath.split( '/' ).pop() || '';
if ( itemName.startsWith( '.' ) ) {
return true;
}

if ( SYNC_EXCLUSIONS.includes( itemName ) ) {
return true;
}

return false;
return deployIgnore.ignores( relativePath );
};

export const shouldLimitDepth = ( relativePath: string ): boolean => {
Expand Down
11 changes: 1 addition & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion tools/common/lib/deploy-ignore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,16 @@ const DEPLOY_IGNORE_FILENAME = '.deployignore';
* and any patterns from a .deployignore file at the given root.
*
* @param rootPath - The site root directory to look for .deployignore in
* @param additionalDefaults - Extra patterns to include as built-in defaults
*/
export async function createDeployIgnoreFilter( rootPath: string ): Promise< Ignore > {
export async function createDeployIgnoreFilter(
rootPath: string,
additionalDefaults?: string[]
): Promise< Ignore > {
const ig = ignore().add( DEPLOY_IGNORE_DEFAULTS );
if ( additionalDefaults ) {
ig.add( additionalDefaults );
}

const deployIgnorePath = path.join( rootPath, DEPLOY_IGNORE_FILENAME );
try {
Expand Down
14 changes: 14 additions & 0 deletions tools/common/lib/tests/deploy-ignore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ describe( 'createDeployIgnoreFilter', () => {
expect( ig.ignores( 'uploads/2025/photo.jpg' ) ).toBe( false );
} );

it( 'should apply additional defaults when provided', async () => {
const ig = await createDeployIgnoreFilter( tempDir, [ 'cache', 'database' ] );
expect( ig.ignores( 'cache' ) ).toBe( true );
expect( ig.ignores( 'database' ) ).toBe( true );
expect( ig.ignores( '.git' ) ).toBe( true );
} );

it( 'should allow .deployignore to override additional defaults via negation', async () => {
fs.writeFileSync( path.join( tempDir, '.deployignore' ), '!cache\n' );
const ig = await createDeployIgnoreFilter( tempDir, [ 'cache', 'database' ] );
expect( ig.ignores( 'cache' ) ).toBe( false );
expect( ig.ignores( 'database' ) ).toBe( true );
} );

it( 'should handle empty .deployignore file', async () => {
fs.writeFileSync( path.join( tempDir, '.deployignore' ), '' );
const ig = await createDeployIgnoreFilter( tempDir );
Expand Down
2 changes: 1 addition & 1 deletion tools/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"cross-port-killer": "^1.4.0",
"date-fns": "^3.3.1",
"fast-deep-equal": "^3.1.3",
"ignore": "^7.0.5",
"ignore": "^5.3.2",
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 have made this change because other packages we are already using in Studio rely on the ignore v5. This version provides everything we need.

Using v7 would require several workarounds that would make the code less maintainable and more difficult to read

"lockfile": "^1.0.4",
"wpcom": "^7.1.1",
"wpcom-xhr-request": "^1.3.0",
Expand Down
Loading