@@ -11,7 +11,7 @@ import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilitie
1111import { fsUtil , schemaTemplate , lookupExtension , lookUpTaxonomy , fileHelper } from '../../utils' ;
1212import { ImportConfig , ModuleClassParams } from '../../types' ;
1313import BaseClass , { ApiOptions } from './base-class' ;
14- import { updateFieldRules } from '../../utils/content-type-helper' ;
14+ import { updateFieldRules , isGlobalFieldRule } from '../../utils/content-type-helper' ;
1515
1616export default class ContentTypesImport extends BaseClass {
1717 private cTsMapperPath : string ;
@@ -34,7 +34,7 @@ export default class ContentTypesImport extends BaseClass {
3434 private reqConcurrency : number ;
3535 private ignoredFilesInContentTypesFolder : Map < string , string > ;
3636 private titleToUIdMap : Map < string , string > ;
37- private fieldRules : Array < Record < string , unknown > > ;
37+ private fieldRules : string [ ] ;
3838 private installedExtensions : Record < string , unknown > ;
3939 private cTsConfig : {
4040 dirName : string ;
@@ -206,13 +206,103 @@ export default class ContentTypesImport extends BaseClass {
206206 this . pendingGFs = fsUtil . readFile ( this . gFsPendingPath ) as any ;
207207 if ( ! this . pendingGFs || isEmpty ( this . pendingGFs ) ) {
208208 log . info ( 'No pending global fields found to update.' , this . importConfig . context ) ;
209- return ;
209+ } else {
210+ await this . updatePendingGFs ( ) . catch ( ( error ) => {
211+ handleAndLogError ( error , { ...this . importConfig . context } ) ;
212+ } ) ;
213+ log . success ( 'Updated pending global fields with content type with references' , this . importConfig . context ) ;
210214 }
211- await this . updatePendingGFs ( ) . catch ( ( error ) => {
215+
216+ // Global field rules were skipped during the content type update (see updateFieldRules) because
217+ // the embedded global field schema was not yet complete on the stack. By this point every global
218+ // field is complete — deferred ones via updatePendingGFs above, non-deferred ones already applied
219+ // in the global-fields module, and pre-existing ones already on the stack for module-only imports.
220+ // So re-apply the global field rules now. This runs UNCONDITIONALLY (outside the pending check):
221+ // non-deferred and module-only imports have no pending global fields but still need their rules.
222+ const failedGFFieldRuleCTs = await this . updateGFFieldRules ( ) . catch ( ( error ) => {
212223 handleAndLogError ( error , { ...this . importConfig . context } ) ;
224+ return [ ] as string [ ] ;
213225 } ) ;
214- log . success ( 'Updated pending global fields with content type with references' , this . importConfig . context ) ;
215- log . success ( 'Content types have been imported successfully!' , this . importConfig . context ) ;
226+
227+ if ( failedGFFieldRuleCTs . length ) {
228+ // Surface the partial failure instead of claiming an unqualified success.
229+ log . error (
230+ `Content types imported, but failed to apply global field rules for: ${ failedGFFieldRuleCTs . join ( ', ' ) } ` ,
231+ this . importConfig . context ,
232+ ) ;
233+ } else {
234+ log . success ( 'Content types have been imported successfully!' , this . importConfig . context ) ;
235+ }
236+ }
237+
238+ /**
239+ * Applies the global field rules that were skipped during the content type update (updateFieldRules
240+ * strips rules flagged is_global_field_rule, because their paths reference an embedded global field
241+ * whose schema is not yet complete when the content type is first updated). By the time this runs,
242+ * every embedded global field is complete, so the rules validate. Runs for deferred, non-deferred
243+ * and module-only imports alike.
244+ * @returns the uids of content types whose global field rule update failed.
245+ */
246+ async updateGFFieldRules ( ) : Promise < string [ ] > {
247+ const failedCTs : string [ ] = [ ] ;
248+
249+ if ( ! this . fieldRules ?. length ) {
250+ log . debug ( 'No content types with field rules; skipping global field rules update.' , this . importConfig . context ) ;
251+ return failedCTs ;
252+ }
253+
254+ const cTs = ( fsUtil . readFile ( path . join ( this . cTsFolderPath , 'schema.json' ) ) || [ ] ) as Record < string , any > [ ] ;
255+
256+ for ( const cTUid of this . fieldRules ) {
257+ const contentType : any = find ( cTs , { uid : cTUid } ) ;
258+ if ( ! contentType ?. field_rules ?. length ) {
259+ continue ;
260+ }
261+
262+ // Only content types carrying a global field rule need re-applying; the rest were fully
263+ // updated (schema + their own rules) in updateCTs.
264+ const hasGFFieldRule = contentType . field_rules . some ( ( rule : any ) => isGlobalFieldRule ( rule ) ) ;
265+ if ( ! hasGFFieldRule ) {
266+ continue ;
267+ }
268+
269+ log . info ( `Re-applying global field rules for content type: ${ contentType . uid } ` , this . importConfig . context ) ;
270+
271+ const contentTypeResponse : any = await this . stack
272+ . contentType ( contentType . uid )
273+ . fetch ( )
274+ . catch ( ( error : unknown ) => {
275+ handleAndLogError ( error , { ...this . importConfig . context , uid : contentType . uid } ) ;
276+ } ) ;
277+ if ( ! contentTypeResponse ) {
278+ log . debug (
279+ `Skipping global field rules update for ${ contentType . uid } - content type not found` ,
280+ this . importConfig . context ,
281+ ) ;
282+ failedCTs . push ( contentType . uid ) ;
283+ continue ;
284+ }
285+
286+ // Send the global field rules together with the content type's own non-reference rules,
287+ // NOT the raw on-disk set. updateFieldRules(..., { keepGlobalFieldRules: true }) keeps the
288+ // now-valid global field rules while still dropping reference-condition rules, which are
289+ // owned by the entries module (it remaps their entry-uid values post entry-import). Sending
290+ // the raw set here would resurrect those reference rules prematurely with stale uids.
291+ // NOTE: field_rules is a whole-array PUT — if any single rule is invalid the API rejects the
292+ // entire array, so a malformed rule would take the global field rules down with it.
293+ contentTypeResponse . field_rules = updateFieldRules ( contentType , { keepGlobalFieldRules : true } ) ;
294+ await contentTypeResponse
295+ . update ( )
296+ . then ( ( ) => {
297+ log . success ( `Re-applied global field rules for content type: ${ contentType . uid } ` , this . importConfig . context ) ;
298+ } )
299+ . catch ( ( error : Error ) => {
300+ handleAndLogError ( error , { ...this . importConfig . context , uid : contentType . uid } ) ;
301+ failedCTs . push ( contentType . uid ) ;
302+ } ) ;
303+ }
304+
305+ return failedCTs ;
216306 }
217307
218308 async seedCTs ( ) : Promise < any > {
0 commit comments