Skip to content

Commit e948aa7

Browse files
committed
feat: add KB style selector and update WP version requirements
1 parent acdfe9d commit e948aa7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+4971
-391
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
[![Required PHP](https://img.shields.io/wordpress/plugin/required-php/knowledgebase?style=flat-square)](https://wordpress.org/plugins/knowledgebase/)
99
[![Active installs](https://img.shields.io/wordpress/plugin/installs/knowledgebase?style=flat-square)](https://wordpress.org/plugins/knowledgebase/)
1010

11-
__Requires:__ 6.3
11+
__Requires:__ 6.6
1212

1313
__Tested up to:__ 6.8
1414

build-assets.js

Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
const { execSync } = require( 'child_process' );
2+
const { readdirSync, statSync, readFileSync, writeFileSync, existsSync, unlinkSync } = require( 'fs' );
3+
const { join, dirname } = require( 'path' );
4+
const { mkdirSync } = require( 'fs' );
5+
6+
/**
7+
* Configuration for asset processing.
8+
*/
9+
const config = {
10+
// Directories to exclude from processing.
11+
excludeDirs: ['node_modules', 'vendor', 'build', '.git', 'includes/blocks'],
12+
13+
// File patterns to exclude from discovery.
14+
// Note: We exclude -rtl.css but not .min files to allow re-minification.
15+
excludePatterns: [
16+
/-rtl\.css$/,
17+
/^build-assets\.js$/,
18+
],
19+
20+
// CSS files to combine - order matters!
21+
// Example: 'path/to/output.css': ['path/to/source1.css', 'path/to/source2.css']
22+
combineCss: {},
23+
24+
// JS files to combine - order matters!
25+
// Example: 'path/to/output.js': ['path/to/source1.js', 'path/to/source2.js']
26+
combineJs: {},
27+
28+
// If true, minify source files before combining (results in smaller bundles).
29+
minifyBeforeCombine: false,
30+
31+
// If true, keep individual .min versions of combined output files.
32+
createMinifiedCombinedFiles: true,
33+
};
34+
35+
// Track errors for final exit code.
36+
let errorCount = 0;
37+
38+
// Paths to local binaries.
39+
const binaries = {
40+
cleancss: './node_modules/.bin/cleancss',
41+
terser: './node_modules/.bin/terser',
42+
rtlcss: './node_modules/.bin/rtlcss',
43+
wpscripts: './node_modules/.bin/wp-scripts',
44+
};
45+
46+
/**
47+
* Ensure directory exists.
48+
*
49+
* @param {string} filePath File path to check.
50+
*/
51+
function ensureDirectoryExists( filePath ) {
52+
const dir = dirname( filePath );
53+
if ( ! existsSync( dir ) ) {
54+
mkdirSync( dir, { recursive: true } );
55+
}
56+
}
57+
58+
/**
59+
* Check if a path should be excluded.
60+
*
61+
* @param {string} filePath File path to check.
62+
* @return {boolean} True if should be excluded.
63+
*/
64+
function shouldExclude( filePath ) {
65+
// Check directory exclusions.
66+
for ( const excludeDir of config.excludeDirs ) {
67+
if ( filePath.includes( `/${excludeDir}/` ) || filePath.startsWith( `${excludeDir}/` ) ) {
68+
return true;
69+
}
70+
}
71+
72+
// Check pattern exclusions.
73+
for ( const pattern of config.excludePatterns ) {
74+
if ( pattern.test( filePath ) ) {
75+
return true;
76+
}
77+
}
78+
79+
return false;
80+
}
81+
82+
/**
83+
* Recursively find all files with a specific extension.
84+
*
85+
* @param {string} dir Directory to search.
86+
* @param {string} extension File extension to match.
87+
* @param {Array} fileList Accumulated file list.
88+
* @return {Array} List of file paths.
89+
*/
90+
function findFiles( dir, extension, fileList = [] ) {
91+
if ( ! existsSync( dir ) ) {
92+
return fileList;
93+
}
94+
95+
const files = readdirSync( dir );
96+
97+
files.forEach( ( file ) => {
98+
const filePath = join( dir, file );
99+
100+
if ( shouldExclude( filePath ) ) {
101+
return;
102+
}
103+
104+
const stat = statSync( filePath );
105+
106+
if ( stat.isDirectory() ) {
107+
findFiles( filePath, extension, fileList );
108+
} else if ( file.endsWith( extension ) && ! shouldExclude( filePath ) ) {
109+
fileList.push( filePath );
110+
}
111+
} );
112+
113+
return fileList;
114+
}
115+
116+
/**
117+
* Minify a CSS file.
118+
*
119+
* @param {string} inputFile Input file path.
120+
* @param {string} outputFile Output file path.
121+
* @return {boolean} True if successful.
122+
*/
123+
function minifyCss( inputFile, outputFile ) {
124+
try {
125+
ensureDirectoryExists( outputFile );
126+
execSync( `${binaries.cleancss} -o ${outputFile} ${inputFile}`, { stdio: 'pipe' } );
127+
return true;
128+
} catch ( error ) {
129+
console.error( ` ✗ Error minifying ${inputFile}:`, error.message );
130+
errorCount++;
131+
return false;
132+
}
133+
}
134+
135+
/**
136+
* Minify a JS file.
137+
*
138+
* @param {string} inputFile Input file path.
139+
* @param {string} outputFile Output file path.
140+
* @return {boolean} True if successful.
141+
*/
142+
function minifyJs( inputFile, outputFile ) {
143+
try {
144+
ensureDirectoryExists( outputFile );
145+
execSync( `${binaries.terser} ${inputFile} -o ${outputFile} -c -m`, { stdio: 'pipe' } );
146+
return true;
147+
} catch ( error ) {
148+
console.error( ` ✗ Error minifying ${inputFile}:`, error.message );
149+
errorCount++;
150+
return false;
151+
}
152+
}
153+
154+
/**
155+
* Generate RTL version of CSS file.
156+
*
157+
* @param {string} inputFile Input file path.
158+
* @param {string} outputFile Output file path.
159+
* @return {boolean} True if successful.
160+
*/
161+
function generateRtl( inputFile, outputFile ) {
162+
try {
163+
ensureDirectoryExists( outputFile );
164+
execSync( `${binaries.rtlcss} ${inputFile} ${outputFile}`, { stdio: 'pipe' } );
165+
return true;
166+
} catch ( error ) {
167+
console.error( ` ✗ Error creating RTL for ${inputFile}:`, error.message );
168+
errorCount++;
169+
return false;
170+
}
171+
}
172+
173+
/**
174+
* Format a CSS file with wp-scripts (uses WordPress Prettier config).
175+
*
176+
* @param {string} file File path to format.
177+
* @return {boolean} True if successful.
178+
*/
179+
function formatCss( file ) {
180+
try {
181+
execSync( `${binaries.wpscripts} format ${file}`, { stdio: 'pipe' } );
182+
return true;
183+
} catch ( error ) {
184+
console.error( ` ✗ Error formatting ${file}:`, error.message );
185+
errorCount++;
186+
return false;
187+
}
188+
}
189+
190+
/**
191+
* Combine multiple files into one.
192+
*
193+
* @param {string} outputFile Output file path.
194+
* @param {Array} inputFiles Array of input file paths.
195+
* @param {boolean} isJs Whether files are JavaScript.
196+
*/
197+
function combineFiles( outputFile, inputFiles, isJs = false ) {
198+
console.log( `\nCombining files into: ${outputFile}` );
199+
200+
let combinedContent = '';
201+
const tempFiles = [];
202+
203+
inputFiles.forEach( ( file ) => {
204+
try {
205+
let content;
206+
207+
// Minify before combining if configured.
208+
if ( config.minifyBeforeCombine ) {
209+
const tempMinFile = file.replace( /\.(css|js)$/, '.temp.min.$1' );
210+
tempFiles.push( tempMinFile );
211+
212+
if ( isJs ) {
213+
if ( ! minifyJs( file, tempMinFile ) ) {
214+
return;
215+
}
216+
} else {
217+
if ( ! minifyCss( file, tempMinFile ) ) {
218+
return;
219+
}
220+
}
221+
222+
content = readFileSync( tempMinFile, 'utf8' );
223+
} else {
224+
content = readFileSync( file, 'utf8' );
225+
}
226+
227+
combinedContent += `\n/* Source: ${file} */\n`;
228+
combinedContent += content;
229+
combinedContent += '\n';
230+
console.log( ` ✓ Added: ${file}` );
231+
} catch ( error ) {
232+
console.error( ` ✗ Error reading ${file}:`, error.message );
233+
errorCount++;
234+
}
235+
} );
236+
237+
try {
238+
ensureDirectoryExists( outputFile );
239+
writeFileSync( outputFile, combinedContent );
240+
console.log( ` ✓ Created: ${outputFile}` );
241+
242+
// Clean up temp files (cross-platform).
243+
tempFiles.forEach( ( tempFile ) => {
244+
try {
245+
if ( existsSync( tempFile ) ) {
246+
unlinkSync( tempFile );
247+
}
248+
} catch ( error ) {
249+
// Silently ignore cleanup errors.
250+
}
251+
} );
252+
} catch ( error ) {
253+
console.error( ` ✗ Error writing ${outputFile}:`, error.message );
254+
errorCount++;
255+
}
256+
}
257+
258+
// Step 1: Combine CSS files.
259+
console.log( '=== Combining CSS Files ===' );
260+
Object.entries( config.combineCss ).forEach( ( [output, inputs] ) => {
261+
combineFiles( output, inputs, false );
262+
} );
263+
264+
// Step 2: Combine JS files.
265+
console.log( '\n=== Combining JS Files ===' );
266+
Object.entries( config.combineJs ).forEach( ( [output, inputs] ) => {
267+
combineFiles( output, inputs, true );
268+
} );
269+
270+
// Step 3: Build exclusion lists.
271+
const combinedSourceFiles = [
272+
...Object.values( config.combineCss ).flat(),
273+
...Object.values( config.combineJs ).flat(),
274+
];
275+
276+
const combinedOutputFiles = [
277+
...Object.keys( config.combineCss ),
278+
...Object.keys( config.combineJs ),
279+
];
280+
281+
// Step 4: Find all CSS and JS files.
282+
// Note: This includes .min files to allow re-minification of existing minified files.
283+
const allCssFiles = findFiles( '.', '.css' );
284+
const allJsFiles = findFiles( '.', '.js' );
285+
286+
// Filter out source files used in combinations.
287+
// Also filter out .min and combined output files based on config.
288+
const cssFilesToMinify = allCssFiles.filter( ( file ) => {
289+
// Skip source files that are part of combinations.
290+
if ( combinedSourceFiles.includes( file ) ) {
291+
return false;
292+
}
293+
// Skip already-minified files (we don't want to minify .min.css again).
294+
if ( file.endsWith( '.min.css' ) ) {
295+
return false;
296+
}
297+
// Skip combined output files if not creating minified versions.
298+
if ( ! config.createMinifiedCombinedFiles && combinedOutputFiles.includes( file ) ) {
299+
return false;
300+
}
301+
return true;
302+
} );
303+
304+
const jsFilesToMinify = allJsFiles.filter( ( file ) => {
305+
// Skip source files that are part of combinations.
306+
if ( combinedSourceFiles.includes( file ) ) {
307+
return false;
308+
}
309+
// Skip already-minified files (we don't want to minify .min.js again).
310+
if ( file.endsWith( '.min.js' ) ) {
311+
return false;
312+
}
313+
// Skip combined output files if not creating minified versions.
314+
if ( ! config.createMinifiedCombinedFiles && combinedOutputFiles.includes( file ) ) {
315+
return false;
316+
}
317+
return true;
318+
} );
319+
320+
console.log( `\n=== Processing Assets ===` );
321+
console.log( `Found ${cssFilesToMinify.length} CSS files and ${jsFilesToMinify.length} JS files to minify.\n` );
322+
323+
// Step 5: Minify CSS files.
324+
console.log( '=== Minifying CSS ===' );
325+
cssFilesToMinify.forEach( ( file ) => {
326+
const minFile = file.replace( '.css', '.min.css' );
327+
console.log( ` ${file}${minFile}` );
328+
minifyCss( file, minFile );
329+
} );
330+
331+
// Step 6: Minify JS files.
332+
console.log( '\n=== Minifying JS ===' );
333+
jsFilesToMinify.forEach( ( file ) => {
334+
const minFile = file.replace( '.js', '.min.js' );
335+
console.log( ` ${file}${minFile}` );
336+
minifyJs( file, minFile );
337+
} );
338+
339+
// Step 7: Generate RTL versions for all CSS files (including minified and combined).
340+
console.log( '\n=== Generating RTL CSS ===' );
341+
const cssFilesForRtl = allCssFiles.filter( ( file ) => ! file.includes( '-rtl.' ) && ! file.endsWith( '-rtl.css' ) );
342+
343+
const rtlFilesGenerated = [];
344+
cssFilesForRtl.forEach( ( file ) => {
345+
// Handle .min.css files correctly: name.min.css → name-rtl.min.css
346+
const rtlFile = file.endsWith( '.min.css' )
347+
? file.replace( '.min.css', '-rtl.min.css' )
348+
: file.replace( '.css', '-rtl.css' );
349+
console.log( ` ${file}${rtlFile}` );
350+
if ( generateRtl( file, rtlFile ) ) {
351+
// Only format non-minified RTL files.
352+
if ( ! rtlFile.endsWith( '.min.css' ) ) {
353+
rtlFilesGenerated.push( rtlFile );
354+
}
355+
}
356+
} );
357+
358+
// Step 8: Format RTL CSS files for better readability.
359+
if ( rtlFilesGenerated.length > 0 ) {
360+
console.log( '\n=== Formatting RTL CSS ===' );
361+
rtlFilesGenerated.forEach( ( file ) => {
362+
console.log( ` Formatting: ${file}` );
363+
formatCss( file );
364+
} );
365+
}
366+
367+
// Final summary.
368+
console.log( '\n==================================' );
369+
if ( errorCount > 0 ) {
370+
console.error( `✗ Completed with ${errorCount} error(s).` );
371+
process.exit( 1 );
372+
} else {
373+
console.log( '✓ All assets processed successfully!' );
374+
process.exit( 0 );
375+
}

0 commit comments

Comments
 (0)