@@ -18,6 +18,10 @@ interface ExtensionMetadata {
1818 uuid : string ;
1919}
2020
21+ const currentDir = dirname ( fileURLToPath ( import . meta. url ) ) ;
22+ const metadataPath = resolve ( currentDir , 'metadata.json' ) ;
23+ const metadata = JSON . parse ( readFileSync ( metadataPath , 'utf8' ) ) as ExtensionMetadata ;
24+
2125// Externalize local .ts imports and rewrite paths to .js
2226const localExternals : Plugin = {
2327 name : 'local-externals' ,
@@ -34,9 +38,137 @@ const localExternals: Plugin = {
3438 } ,
3539} ;
3640
37- const currentDir = dirname ( fileURLToPath ( import . meta. url ) ) ;
38- const metadataPath = resolve ( currentDir , 'metadata.json' ) ;
39- const metadata = JSON . parse ( readFileSync ( metadataPath , 'utf8' ) ) as ExtensionMetadata ;
41+ // Resolve @girs /* imports directly to gi:// and resource:// runtime paths,
42+ // bypassing node_modules to avoid re-export shims and version query strings
43+ const girsResolver : Plugin = {
44+ name : 'girs-resolver' ,
45+ setup ( ctx ) {
46+ // @girs /gjs is a type-only shim; drop it entirely
47+ ctx . onResolve ( { filter : / ^ @ g i r s \/ g j s $ / } , ( ) => ( {
48+ path : 'gjs' ,
49+ namespace : 'girs-empty' ,
50+ } ) ) ;
51+ ctx . onLoad ( { filter : / .* / , namespace : 'girs-empty' } , ( ) => ( {
52+ contents : '' ,
53+ loader : 'js' ,
54+ } ) ) ;
55+
56+ // @girs /gnome-shell/* → resource:///org/gnome/shell/*.js
57+ ctx . onResolve ( { filter : / ^ @ g i r s \/ g n o m e - s h e l l \/ / } , ( args ) => {
58+ const subpath = args . path . replace ( '@girs/gnome-shell/' , '' ) ;
59+ const distFile = resolve (
60+ currentDir ,
61+ 'node_modules/@girs/gnome-shell/dist' ,
62+ `${ subpath } .js` ,
63+ ) ;
64+ // Read the actual resource path from the package when the file exists,
65+ // otherwise fall back to the standard resource path pattern
66+ if ( existsSync ( distFile ) ) {
67+ const content = readFileSync ( distFile , 'utf8' ) ;
68+ const match = content . match ( / f r o m [ ' " ] ( .+ ) [ ' " ] / ) ;
69+ if ( match ) return { path : match [ 1 ] , external : true } ;
70+ }
71+ return {
72+ path : `resource:///org/gnome/shell/${ subpath } .js` ,
73+ external : true ,
74+ } ;
75+ } ) ;
76+
77+ // @girs /<name>-<version> → gi://<Namespace> (without ?version=)
78+ ctx . onResolve ( { filter : / ^ @ g i r s \/ / } , ( args ) => {
79+ if ( args . path === '@girs/gjs' || args . path . startsWith ( '@girs/gnome-shell/' ) )
80+ return ;
81+ const pkgName = args . path . replace ( '@girs/' , '' ) ;
82+ const mainFile = resolve (
83+ currentDir ,
84+ `node_modules/@girs/${ pkgName } /${ pkgName } .js` ,
85+ ) ;
86+ if ( existsSync ( mainFile ) ) {
87+ const content = readFileSync ( mainFile , 'utf8' ) ;
88+ const match = content . match ( / f r o m [ " ' ] ( g i : \/ \/ [ ^ ? " ' ] + ) / ) ;
89+ if ( match ) return { path : match [ 1 ] , external : true } ;
90+ }
91+ } ) ;
92+ } ,
93+ } ;
94+
95+ // JS keywords that should NOT be treated as method declarations
96+ const JS_KEYWORDS = new Set ( [
97+ 'if' , 'for' , 'while' , 'switch' , 'return' , 'throw' , 'try' , 'catch' ,
98+ 'finally' , 'else' , 'do' , 'new' , 'typeof' , 'void' , 'delete' , 'await' ,
99+ 'yield' , 'debugger' , 'with' , 'break' , 'continue' , 'case' , 'default' ,
100+ 'super' , 'this' , 'true' , 'false' , 'null' , 'undefined' ,
101+ ] ) ;
102+
103+ // Add blank lines between class methods, between the last field and first
104+ // method, and between top-level declarations. esbuild strips these blank
105+ // lines during compilation and prettier does not re-add them.
106+ function addBlankLinesBetweenMembers ( code : string ) : string {
107+ const lines = code . split ( '\n' ) ;
108+ const result : string [ ] = [ ] ;
109+
110+ const isMethodDecl = ( trimmed : string ) : boolean => {
111+ const m = trimmed . match (
112+ / ^ (?: (?: g e t | s e t | s t a t i c | a s y n c ) * ( [ a - z A - Z _ $ # ] \w * ) \s * \( ) / ,
113+ ) ;
114+ return m !== null && ! JS_KEYWORDS . has ( m [ 1 ] ) ;
115+ } ;
116+
117+ const isComment = ( trimmed : string ) : boolean =>
118+ trimmed . startsWith ( '//' ) || trimmed . startsWith ( '/*' ) || trimmed . startsWith ( '*' ) ;
119+
120+ for ( let i = 0 ; i < lines . length ; i ++ ) {
121+ if ( i > 0 && lines [ i ] . trim ( ) !== '' && lines [ i - 1 ] . trim ( ) !== '' ) {
122+ const prev = lines [ i - 1 ] ;
123+ const curr = lines [ i ] ;
124+ const prevTrimmed = prev . trimStart ( ) ;
125+ const currTrimmed = curr . trimStart ( ) ;
126+ const prevIndent = prev . search ( / \S | $ / ) ;
127+ const currIndent = curr . search ( / \S | $ / ) ;
128+ let needsBlank = false ;
129+
130+ const isClosingBrace = / ^ \} ; ? $ / . test ( prevTrimmed ) ;
131+
132+ // Between methods: closing } at indent N → method decl at indent N
133+ if (
134+ isClosingBrace &&
135+ currIndent === prevIndent &&
136+ ( isMethodDecl ( currTrimmed ) || isComment ( currTrimmed ) )
137+ ) {
138+ needsBlank = true ;
139+ }
140+
141+ // Between last field and first method at the same indent level
142+ if (
143+ ! needsBlank &&
144+ / ; $ / . test ( prevTrimmed ) &&
145+ / ^ [ a - z A - Z _ $ # ] \w * / . test ( prevTrimmed ) &&
146+ currIndent === prevIndent &&
147+ isMethodDecl ( currTrimmed )
148+ ) {
149+ needsBlank = true ;
150+ }
151+
152+ // Between top-level declarations (indent 0) after block closures
153+ if (
154+ ! needsBlank &&
155+ currIndent === 0 &&
156+ / [ } ) \] ] ; ? \s * $ / . test ( prevTrimmed ) &&
157+ / ^ (?: v a r | l e t | c o n s t | f u n c t i o n | c l a s s | e x p o r t ) / . test ( currTrimmed )
158+ ) {
159+ needsBlank = true ;
160+ }
161+
162+ if ( needsBlank ) {
163+ result . push ( '' ) ;
164+ }
165+ }
166+
167+ result . push ( lines [ i ] ) ;
168+ }
169+
170+ return result . join ( '\n' ) ;
171+ }
40172
41173const srcDir = resolve ( currentDir , 'src' ) ;
42174const entryPoints = ( readdirSync ( srcDir , { recursive : true } ) as string [ ] )
@@ -49,7 +181,7 @@ const sharedOptions = {
49181 outdir : 'dist' ,
50182 outbase : 'src' ,
51183 bundle : true ,
52- plugins : [ localExternals ] ,
184+ plugins : [ localExternals , girsResolver ] ,
53185 // Do not remove the functions `enable()`, `disable()` and `init()`
54186 treeShaking : false ,
55187 // firefox60 // Since GJS 1.53.90
@@ -59,10 +191,6 @@ const sharedOptions = {
59191 // firefox102 // Since GJS 1.73.2
60192 target : 'firefox102' as const ,
61193 format : 'esm' as const ,
62- alias : {
63- // Not exported in @girs /gnome-shell v49; map directly to runtime resource
64- '@girs/gnome-shell/ui/dash' : 'resource:///org/gnome/shell/ui/dash.js' ,
65- } ,
66194 external : [ 'gi://*' , 'resource://*' , 'system' , 'gettext' , 'cairo' ] ,
67195} ;
68196
@@ -91,7 +219,7 @@ Promise.all(
91219 const filePath = resolve ( distDir , file ) ;
92220 const content = readFileSync ( filePath , 'utf8' ) ;
93221 const formatted = await format ( content , { parser : 'babel' , filepath : filePath } ) ;
94- writeFileSync ( filePath , formatted ) ;
222+ writeFileSync ( filePath , addBlankLinesBetweenMembers ( formatted ) ) ;
95223 }
96224
97225 copyFileSync ( metadataPath , metaDist ) ;
0 commit comments