@@ -16,27 +16,29 @@ const Inspector = (() => {
1616 function _getSingleRoot ( zipFiles ) {
1717 const topLevelEntries = new Set ( ) ;
1818 Object . keys ( zipFiles ) . forEach ( path => {
19- const topLevel = path . split ( '/' ) [ 0 ] ;
20- if ( topLevel ) topLevelEntries . add ( topLevel ) ;
19+ const parts = path . split ( '/' ) ;
20+ // If it has a slash, it's in a folder. If no slash, check if it's a file.
21+ if ( parts . length > 1 ) {
22+ topLevelEntries . add ( parts [ 0 ] ) ;
23+ } else if ( ! zipFiles [ path ] . dir ) {
24+ topLevelEntries . add ( '__ROOT_FILE__' ) ;
25+ }
2126 } ) ;
2227
23- if ( topLevelEntries . size === 1 ) {
28+ if ( topLevelEntries . size === 1 && ! topLevelEntries . has ( '__ROOT_FILE__' ) ) {
2429 const root = Array . from ( topLevelEntries ) [ 0 ] ;
25- // Ensure it's actually a directory
26- if ( zipFiles [ root + '/' ] ) return root + '/' ;
30+ // CRITICAL FIX: Never flatten the .github directory!
31+ if ( root . toLowerCase ( ) === '.github' ) return null ;
32+ return root + '/' ;
2733 }
2834 return null ;
2935 }
3036
3137 /**
3238 * Inspect a ZIP file and return its contents analysis.
33- * @param {File|Blob } file
34- * @returns {Promise<InspectResult> }
3539 */
3640 async function inspect ( file ) {
37- if ( typeof JSZip === 'undefined' ) {
38- throw new Error ( 'JSZip library not loaded. Check your CDN link.' ) ;
39- }
41+ if ( typeof JSZip === 'undefined' ) throw new Error ( 'JSZip library not loaded.' ) ;
4042
4143 const zip = await JSZip . loadAsync ( file ) . catch ( err => {
4244 throw new Error ( `Failed to read ZIP: ${ err . message } ` ) ;
@@ -54,7 +56,6 @@ const Inspector = (() => {
5456 const size = entry . _data ?. uncompressedSize || 0 ;
5557 totalSize += size ;
5658
57- // Adjust path if the backend will flatten it
5859 let actualPath = relativePath ;
5960 if ( rootToFlatten && actualPath . startsWith ( rootToFlatten ) ) {
6061 actualPath = actualPath . substring ( rootToFlatten . length ) ;
@@ -64,14 +65,13 @@ const Inspector = (() => {
6465 path : actualPath ,
6566 name : actualPath . split ( '/' ) . pop ( ) ,
6667 size,
67- isWorkflow : / ^ \. g i t h u b \/ w o r k f l o w s \/ .* \. y a ? m l $ / i. test ( actualPath ) ,
68+ isWorkflow : / (?: ^ | \/ ) \. g i t h u b \/ w o r k f l o w s \/ .* \. y a ? m l $ / i. test ( actualPath ) ,
6869 } ;
6970
7071 files . push ( info ) ;
7172 if ( info . isWorkflow ) workflowFiles . push ( info ) ;
7273 } ) ;
7374
74- // Sort by path
7575 files . sort ( ( a , b ) => a . path . localeCompare ( b . path ) ) ;
7676
7777 return {
@@ -87,9 +87,7 @@ const Inspector = (() => {
8787 * Create a ZIP blob from a FileList (single or multiple files).
8888 */
8989 async function createZipFromFiles ( fileList ) {
90- if ( typeof JSZip === 'undefined' ) {
91- throw new Error ( 'JSZip library not loaded.' ) ;
92- }
90+ if ( typeof JSZip === 'undefined' ) throw new Error ( 'JSZip library not loaded.' ) ;
9391
9492 const zip = new JSZip ( ) ;
9593 for ( const file of fileList ) {
@@ -103,16 +101,19 @@ const Inspector = (() => {
103101 * Create a ZIP blob from folder entries (from webkitdirectory input).
104102 */
105103 async function createZipFromFolder ( fileList ) {
106- if ( typeof JSZip === 'undefined' ) {
107- throw new Error ( 'JSZip library not loaded.' ) ;
108- }
104+ if ( typeof JSZip === 'undefined' ) throw new Error ( 'JSZip library not loaded.' ) ;
109105
110106 const zip = new JSZip ( ) ;
111107 for ( const file of fileList ) {
112108 const path = file . webkitRelativePath || file . name ;
113- // Remove the root folder name to flatten
114109 const parts = path . split ( '/' ) ;
115- const relativePath = parts . length > 1 ? parts . slice ( 1 ) . join ( '/' ) : path ;
110+ let relativePath = path ;
111+
112+ // CRITICAL FIX: Only flatten if the root folder is NOT .github
113+ if ( parts . length > 1 && parts [ 0 ] . toLowerCase ( ) !== '.github' ) {
114+ relativePath = parts . slice ( 1 ) . join ( '/' ) ;
115+ }
116+
116117 const data = await file . arrayBuffer ( ) ;
117118 zip . file ( relativePath , data ) ;
118119 }
@@ -121,12 +122,9 @@ const Inspector = (() => {
121122
122123 /**
123124 * Extract workflow file contents from a ZIP for direct API push.
124- * Returns an array of { path, contentBase64 } objects.
125125 */
126126 async function extractWorkflowFiles ( file ) {
127- if ( typeof JSZip === 'undefined' ) {
128- throw new Error ( 'JSZip library not loaded.' ) ;
129- }
127+ if ( typeof JSZip === 'undefined' ) throw new Error ( 'JSZip library not loaded.' ) ;
130128
131129 const zip = await JSZip . loadAsync ( file ) ;
132130 const results = [ ] ;
@@ -135,13 +133,12 @@ const Inspector = (() => {
135133 for ( const [ path , entry ] of Object . entries ( zip . files ) ) {
136134 if ( entry . dir ) continue ;
137135
138- // Adjust path if the backend will flatten it
139136 let actualPath = path ;
140137 if ( rootToFlatten && actualPath . startsWith ( rootToFlatten ) ) {
141138 actualPath = actualPath . substring ( rootToFlatten . length ) ;
142139 }
143140
144- if ( / ^ \. g i t h u b \/ w o r k f l o w s \/ .* \. y a ? m l $ / i. test ( actualPath ) ) {
141+ if ( / (?: ^ | \/ ) \. g i t h u b \/ w o r k f l o w s \/ .* \. y a ? m l $ / i. test ( actualPath ) ) {
145142 const content = await entry . async ( 'base64' ) ;
146143 results . push ( { path : actualPath , contentBase64 : content } ) ;
147144 }
@@ -161,14 +158,12 @@ const Inspector = (() => {
161158 return ;
162159 }
163160
164- // Build a tree structure
165161 const tree = { } ;
166162 result . files . forEach ( f => {
167163 const parts = f . path . split ( '/' ) ;
168164 let node = tree ;
169165 parts . forEach ( ( part , i ) => {
170166 if ( i === parts . length - 1 ) {
171- // File leaf
172167 if ( ! node . __files ) node . __files = [ ] ;
173168 node . __files . push ( f ) ;
174169 } else {
@@ -178,7 +173,6 @@ const Inspector = (() => {
178173 } ) ;
179174 } ) ;
180175
181- // Stats bar
182176 const statsHtml = `
183177 <div class="inspector-stats">
184178 <span class="inspector-stat"><i data-lucide="file" class="inspector-stat-icon"></i>${ result . fileCount } files</span>
@@ -189,14 +183,12 @@ const Inspector = (() => {
189183 </div>
190184 ` ;
191185
192- // Render the file tree
193186 const treeHtml = _renderNode ( tree , '' ) ;
194187
195188 container . innerHTML = statsHtml + `<div class="inspector-tree">${ treeHtml } </div>` ;
196189
197190 if ( typeof lucide !== 'undefined' ) lucide . createIcons ( { nodes : [ container ] } ) ;
198191
199- // Bind collapsible folders
200192 container . querySelectorAll ( '.inspector-folder-toggle' ) . forEach ( btn => {
201193 btn . addEventListener ( 'click' , ( ) => {
202194 const parent = btn . closest ( '.inspector-folder' ) ;
@@ -208,7 +200,6 @@ const Inspector = (() => {
208200 function _renderNode ( node , prefix ) {
209201 let html = '' ;
210202
211- // Render directories first
212203 const dirs = Object . keys ( node ) . filter ( k => k !== '__files' ) . sort ( ) ;
213204 dirs . forEach ( dir => {
214205 const childCount = _countFiles ( node [ dir ] ) ;
@@ -227,7 +218,6 @@ const Inspector = (() => {
227218 ` ;
228219 } ) ;
229220
230- // Render files
231221 const files = node . __files || [ ] ;
232222 files . forEach ( f => {
233223 const sizeClass = f . size > 10_000_000 ? 'size-danger' :
0 commit comments