11import type { ApiExtensionContext } from '@directus/extensions' ;
2- import type { Collection , ExtensionsServices , Snapshot , SnapshotField , SnapshotRelation } from '@directus/types' ;
2+ import type { Collection , ExtensionsServices , Snapshot , SnapshotDiff , SnapshotField , SnapshotRelation } from '@directus/types' ;
33import { mkdir , readFile , rm , writeFile } from 'fs/promises' ;
44import { glob } from 'glob' ;
55import { condenseAction } from './condenseAction.js' ;
66import { exportHook } from './schemaExporterHooks.js' ;
77import type { IExporter } from './types' ;
88import { ExportHelper } from './utils.js' ;
99
10+ /**
11+ * Removes all destructive (DELETE) operations from a schema diff.
12+ * Used with SCHEMA_SYNC_SAFE=true to prevent project-specific collections,
13+ * fields, and relations from being dropped when importing a base snapshot.
14+ */
15+ function filterNonDestructive ( diff : SnapshotDiff ) : SnapshotDiff {
16+ const isTopLevelDelete = ( diffs : ReadonlyArray < { kind : string ; path ?: unknown } > ) =>
17+ diffs . some ( d => d . kind === 'D' && ! d . path ) ;
18+
19+ const deletedCollections = new Set (
20+ diff . collections . filter ( c => isTopLevelDelete ( c . diff ) ) . map ( c => c . collection )
21+ ) ;
22+
23+ return {
24+ ...diff ,
25+ collections : diff . collections . filter ( c => ! deletedCollections . has ( c . collection ) ) ,
26+ fields : diff . fields . filter (
27+ f => ! deletedCollections . has ( f . collection ) && ! isTopLevelDelete ( f . diff )
28+ ) ,
29+ systemFields : ( diff . systemFields ?? [ ] ) . filter (
30+ f => ! deletedCollections . has ( f . collection ) && ! isTopLevelDelete ( f . diff )
31+ ) ,
32+ relations : diff . relations . filter (
33+ r => ! deletedCollections . has ( r . collection ) && ! isTopLevelDelete ( r . diff )
34+ ) ,
35+ } ;
36+ }
37+
1038export class SchemaExporter implements IExporter {
1139 protected _filePath : string ;
1240 protected _exportHandler = condenseAction ( ( ) => this . createAndSaveSnapshot ( ) ) ;
@@ -15,7 +43,7 @@ export class SchemaExporter implements IExporter {
1543 constructor (
1644 protected getSchemaService : ( ) => Promise < InstanceType < ExtensionsServices [ 'SchemaService' ] > > ,
1745 protected logger : ApiExtensionContext [ 'logger' ] ,
18- protected options = { split : true }
46+ protected options = { split : true , safe : false }
1947 ) {
2048 this . _filePath = `${ ExportHelper . dataDir } /schema.json` ;
2149 }
@@ -112,8 +140,12 @@ export class SchemaExporter implements IExporter {
112140 }
113141
114142 this . logger . info ( `Diffing schema with hash: ${ currentHash } and hash: ${ hash } ` ) ;
115- const diff = await svc . diff ( snapshot , { currentSnapshot, force : true } ) ;
143+ let diff = await svc . diff ( snapshot , { currentSnapshot, force : true } ) ;
116144 if ( diff !== null ) {
145+ if ( this . options . safe ) {
146+ diff = filterNonDestructive ( diff ) ;
147+ this . logger . info ( 'SCHEMA_SYNC_SAFE: filtered destructive operations from diff' ) ;
148+ }
117149 this . logger . info ( `Applying schema diff...` ) ;
118150 await svc . apply ( { diff, hash : currentHash } ) ;
119151 this . logger . info ( `Schema updated` ) ;
0 commit comments