@@ -49,6 +49,125 @@ function findDudestackThemeCss() {
4949
5050const globalCssPath = findGlobalCss ( ) ;
5151
52+ // Find theme.json and extract WordPress custom properties
53+ // WordPress generates --wp--preset--*--{slug} and --wp--custom--* at runtime
54+ // from theme.json, so they never appear in compiled CSS files
55+ // Reference: https://linear.app/dude/issue/DEV-758
56+ function findThemeJson ( ) {
57+ const possiblePaths = [
58+ 'theme.json' ,
59+ ...findDudestackThemeJson ( )
60+ ] ;
61+
62+ for ( const jsonPath of possiblePaths ) {
63+ if ( fs . existsSync ( jsonPath ) ) {
64+ return jsonPath ;
65+ }
66+ }
67+
68+ return null ;
69+ }
70+
71+ function findDudestackThemeJson ( ) {
72+ const themesDir = 'content/themes' ;
73+ if ( ! fs . existsSync ( themesDir ) ) {
74+ return [ ] ;
75+ }
76+
77+ try {
78+ return fs . readdirSync ( themesDir , { withFileTypes : true } )
79+ . filter ( dirent => dirent . isDirectory ( ) )
80+ . map ( dirent => path . join ( themesDir , dirent . name , 'theme.json' ) ) ;
81+ } catch {
82+ return [ ] ;
83+ }
84+ }
85+
86+ function flattenCustomSettings ( obj , prefix , result ) {
87+ for ( const [ key , value ] of Object . entries ( obj ) ) {
88+ const kebabKey = key . replace ( / ( [ a - z ] ) ( [ A - Z ] ) / g, '$1-$2' ) . toLowerCase ( ) ;
89+ const varName = `${ prefix } --${ kebabKey } ` ;
90+
91+ if ( typeof value === 'object' && value !== null && ! Array . isArray ( value ) ) {
92+ flattenCustomSettings ( value , varName , result ) ;
93+ } else {
94+ result [ varName ] = String ( value ) ;
95+ }
96+ }
97+ }
98+
99+ function getThemeJsonCustomProperties ( ) {
100+ const themeJsonPath = findThemeJson ( ) ;
101+ if ( ! themeJsonPath ) {
102+ return null ;
103+ }
104+
105+ try {
106+ const themeJson = JSON . parse ( fs . readFileSync ( themeJsonPath , 'utf8' ) ) ;
107+ const settings = themeJson . settings || { } ;
108+ const customProperties = { } ;
109+
110+ // --wp--preset--color--{slug}
111+ const palette = ( settings . color && settings . color . palette ) || [ ] ;
112+ for ( const item of palette ) {
113+ customProperties [ `--wp--preset--color--${ item . slug } ` ] = item . color || '' ;
114+ }
115+
116+ // --wp--preset--gradient--{slug}
117+ const gradients = ( settings . color && settings . color . gradients ) || [ ] ;
118+ for ( const item of gradients ) {
119+ customProperties [ `--wp--preset--gradient--${ item . slug } ` ] = item . gradient || '' ;
120+ }
121+
122+ // --wp--preset--font-family--{slug}
123+ const fontFamilies = ( settings . typography && settings . typography . fontFamilies ) || [ ] ;
124+ for ( const item of fontFamilies ) {
125+ customProperties [ `--wp--preset--font-family--${ item . slug } ` ] = item . fontFamily || '' ;
126+ }
127+
128+ // --wp--preset--font-size--{slug}
129+ const fontSizes = ( settings . typography && settings . typography . fontSizes ) || [ ] ;
130+ for ( const item of fontSizes ) {
131+ customProperties [ `--wp--preset--font-size--${ item . slug } ` ] = item . size || '' ;
132+ }
133+
134+ // --wp--preset--spacing--{slug}
135+ const spacingSizes = ( settings . spacing && settings . spacing . spacingSizes ) || [ ] ;
136+ for ( const item of spacingSizes ) {
137+ customProperties [ `--wp--preset--spacing--${ item . slug } ` ] = item . size || '' ;
138+ }
139+
140+ // --wp--preset--shadow--{slug}
141+ const shadows = ( settings . shadow && settings . shadow . presets ) || [ ] ;
142+ for ( const item of shadows ) {
143+ customProperties [ `--wp--preset--shadow--${ item . slug } ` ] = item . shadow || '' ;
144+ }
145+
146+ // --wp--custom--{key} (nested objects with -- separator)
147+ if ( settings . custom ) {
148+ flattenCustomSettings ( settings . custom , '--wp--custom' , customProperties ) ;
149+ }
150+
151+ // --wp--style--global--content-size and --wp--style--global--wide-size
152+ if ( settings . layout ) {
153+ if ( settings . layout . contentSize ) {
154+ customProperties [ '--wp--style--global--content-size' ] = settings . layout . contentSize ;
155+ }
156+ if ( settings . layout . wideSize ) {
157+ customProperties [ '--wp--style--global--wide-size' ] = settings . layout . wideSize ;
158+ }
159+ }
160+
161+ return Object . keys ( customProperties ) . length > 0
162+ ? { customProperties }
163+ : null ;
164+ } catch {
165+ return null ;
166+ }
167+ }
168+
169+ const themeJsonProperties = getThemeJsonCustomProperties ( ) ;
170+
52171module . exports = {
53172 defaultSeverity : 'warning' ,
54173 plugins : [
@@ -166,11 +285,11 @@ module.exports = {
166285 ]
167286 }
168287 ] ,
169- 'csstools/value-no-unknown-custom-properties' : globalCssPath
288+ 'csstools/value-no-unknown-custom-properties' : globalCssPath || themeJsonProperties
170289 ? [
171290 true ,
172291 {
173- importFrom : [ globalCssPath ]
292+ importFrom : [ globalCssPath , themeJsonProperties ] . filter ( Boolean )
174293 }
175294 ]
176295 : null ,
0 commit comments