1- const { existsSync, readdirSync, readFileSync } = require ( "fs" ) ;
2- const { join } = require ( "path" ) ;
3- const { execSync } = require ( "child_process" ) ;
4- const fg = require ( "fast-glob" ) ;
1+ import { existsSync , readdirSync , readFileSync } from "fs" ;
2+ import { join } from "path" ;
3+ import { execSync } from "child_process" ;
4+ import * as fg from "fast-glob" ;
55
66/**
77 * CI Check: Detect New Native Dependencies
@@ -11,39 +11,68 @@ const fg = require("fast-glob");
1111 * which forces customers to rebuild their mobile apps.
1212 *
1313 * How it works:
14- * - Compares package.json dependencies against previous commit (HEAD)
14+ * - Compares package.json dependencies against parent commit (HEAD^ )
1515 * - For any NEW dependencies, checks if they contain native code
1616 * - Warns if the new dependency has ios/android folders
1717 *
1818 * Bypass: Include "NATIVE_DEPENDENCY_APPROVED" in the commit message.
1919 */
2020
21+ interface PackageJson {
22+ dependencies ?: Record < string , string > ;
23+ peerDependencies ?: Record < string , string > ;
24+ }
25+
26+ interface Violation {
27+ widget : string ;
28+ dependency : string ;
29+ path : string ;
30+ }
31+
2132try {
22- validateNativeDependencies ( ) ;
33+ await validateNativeDependencies ( ) ;
2334} catch ( error ) {
2435 console . error ( error ) ;
2536 process . exit ( 1 ) ;
2637}
2738
28- async function validateNativeDependencies ( ) {
39+ async function validateNativeDependencies ( ) : Promise < void > {
2940 // Check for bypass approval
30- // In CI: read from environment variable
31- // In commit-msg hook: read from file path passed as argument
32- let commitMessage = process . env . CI_COMMIT_MESSAGE || "" ;
41+ let isApproved = false ;
3342
34- const commitMsgFile = process . argv [ 2 ] ; // Path from commit-msg hook
43+ // In commit-msg hook: read from file path passed as argument
44+ const commitMsgFile = process . argv [ 2 ] ;
3545 if ( commitMsgFile && existsSync ( commitMsgFile ) ) {
36- commitMessage = readFileSync ( commitMsgFile , "utf-8" ) ;
46+ const commitMessage = readFileSync ( commitMsgFile , "utf-8" ) ;
47+ isApproved = commitMessage . includes ( "NATIVE_DEPENDENCY_APPROVED" ) ;
48+ } else {
49+ // In CI: check all commits between HEAD^ and HEAD
50+ try {
51+ const commitMessages = execSync ( "git log HEAD^..HEAD --format=%B" , {
52+ encoding : "utf-8" ,
53+ stdio : [ "pipe" , "pipe" , "ignore" ]
54+ } ) ;
55+ isApproved = commitMessages . includes ( "NATIVE_DEPENDENCY_APPROVED" ) ;
56+ } catch ( error ) {
57+ // If git log fails (e.g., no parent commit), fall back to checking HEAD only
58+ try {
59+ const commitMessage = execSync ( "git log -1 --format=%B" , {
60+ encoding : "utf-8" ,
61+ stdio : [ "pipe" , "pipe" , "ignore" ]
62+ } ) ;
63+ isApproved = commitMessage . includes ( "NATIVE_DEPENDENCY_APPROVED" ) ;
64+ } catch ( fallbackError ) {
65+ // If even that fails, continue with validation
66+ }
67+ }
3768 }
3869
39- const isApproved = commitMessage . includes ( "NATIVE_DEPENDENCY_APPROVED" ) ;
40-
4170 if ( isApproved ) {
4271 console . log ( "✅ Native dependency changes approved via commit message" ) ;
4372 return ;
4473 }
4574
46- const violations = [ ] ;
75+ const violations : Violation [ ] = [ ] ;
4776 const widgetsDir = join ( process . cwd ( ) , "packages/pluggableWidgets" ) ;
4877
4978 if ( ! existsSync ( widgetsDir ) ) {
@@ -64,21 +93,22 @@ async function validateNativeDependencies() {
6493
6594 try {
6695 // Get current package.json
67- const currentPackageJson = require ( packageJsonPath ) ;
68- const currentDeps = {
96+ const currentPackageJson : PackageJson = JSON . parse ( readFileSync ( packageJsonPath , "utf-8" ) ) ;
97+ const currentDeps : Record < string , string > = {
6998 ...currentPackageJson . dependencies ,
7099 ...currentPackageJson . peerDependencies
71100 } ;
72101
73- // Get previous package.json from git (HEAD)
74- let previousDeps = { } ;
102+ // Get previous package.json from git (HEAD^ )
103+ let previousDeps : Record < string , string > = { } ;
75104 try {
76- const relativePath = join ( "packages/pluggableWidgets" , widget , "package.json" ) ;
77- const previousContent = execSync ( `git show HEAD:${ relativePath } ` , {
105+ // Git always uses forward slashes, even on Windows
106+ const relativePath = join ( "packages/pluggableWidgets" , widget , "package.json" ) . replace ( / \\ / g, "/" ) ;
107+ const previousContent = execSync ( `git show HEAD^:${ relativePath } ` , {
78108 encoding : "utf-8" ,
79109 stdio : [ "pipe" , "pipe" , "ignore" ]
80110 } ) ;
81- const previousPackageJson = JSON . parse ( previousContent ) ;
111+ const previousPackageJson : PackageJson = JSON . parse ( previousContent ) ;
82112 previousDeps = {
83113 ...previousPackageJson . dependencies ,
84114 ...previousPackageJson . peerDependencies
@@ -110,7 +140,7 @@ async function validateNativeDependencies() {
110140 }
111141 }
112142 } catch ( error ) {
113- console . warn ( `⚠️ Could not check ${ widget } : ${ error . message } ` ) ;
143+ console . warn ( `⚠️ Could not check ${ widget } : ${ error instanceof Error ? error . message : String ( error ) } ` ) ;
114144 }
115145 }
116146
@@ -140,11 +170,12 @@ async function validateNativeDependencies() {
140170 * Check if a dependency contains native code
141171 * Same logic as the patched hasNativeCode function
142172 */
143- async function hasNativeCode ( dir ) {
173+ async function hasNativeCode ( dir : string ) : Promise < boolean > {
144174 try {
145- const files = await fg ( [ "**/{android,ios}/*" , "**/*.podspec" ] , {
175+ const files = await fg . default ( [ "**/{android,ios}/*" , "**/*.podspec" ] , {
146176 cwd : dir ,
147- ignore : [ "**/example*/**" , "**/__tests__/**" , "**/docs/**" ]
177+ ignore : [ "**/example*/**" , "**/__tests__/**" , "**/docs/**" , "**/.github/**" ] ,
178+ caseSensitiveMatch : false
148179 } ) ;
149180 return files . length > 0 ;
150181 } catch ( error ) {
0 commit comments