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