sdc-build-wp 5.0.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,6 +23,7 @@ While watch is enabled, use the following keyboard commands to control the build
23
23
 
24
24
  ```sh
25
25
  [r] Restart
26
+ [c] Clear cache
26
27
  [p] Pause/Resume
27
28
  [q] Quit
28
29
  ````
package/lib/build.js CHANGED
@@ -3,7 +3,7 @@ import * as utils from './utils.js';
3
3
  import log from './logging.js';
4
4
 
5
5
  export async function build(watch = false) {
6
- if (project.components.cache && project.builds.includes('cache')) {
6
+ if (project.builds.includes('cache')) {
7
7
  try {
8
8
  await project.components.cache.init();
9
9
  } catch (error) {
@@ -48,7 +48,7 @@ export async function build(watch = false) {
48
48
  project.builds.splice(project.builds.indexOf('server'), 1);
49
49
  project.builds.push('server');
50
50
  log('info', `Started watching [${project.builds.join(', ')}]`);
51
- log('info', `[r] to restart, [p] to pause/resume, [q] to quit`);
51
+ log('info', `[r] to restart, [c] to clear cache, [p] to pause/resume, [q] to quit`);
52
52
 
53
53
  for (let build of project.builds) {
54
54
  try {
@@ -63,10 +63,8 @@ export async function build(watch = false) {
63
63
  }
64
64
  }
65
65
 
66
- export function restartBuild() {
67
- utils.stopActiveComponents();
68
- setTimeout(() => {
69
- utils.clearScreen();
70
- build(true);
71
- }, 100);
66
+ export async function restartBuild() {
67
+ await utils.stopActiveComponents();
68
+ utils.clearScreen();
69
+ build(true);
72
70
  }
@@ -54,7 +54,10 @@ export default class ScriptsComponent extends BaseComponent {
54
54
 
55
55
  try {
56
56
  const result = await esbuild.build({
57
- platform: 'node',
57
+ platform: 'browser',
58
+ format: 'iife',
59
+ globalName: 'sdcBuild',
60
+ treeShaking: true,
58
61
  entryPoints: [entry],
59
62
  bundle: true,
60
63
  minify: true,
@@ -97,7 +100,8 @@ export default class ScriptsComponent extends BaseComponent {
97
100
  try {
98
101
  await this.process();
99
102
  } catch (error) {
100
- this.log('error', `Failed to process scripts: ${error.message}`);
103
+ console.error(error);
104
+ this.log('error', `Failed to process scripts`);
101
105
  }
102
106
  });
103
107
  }
@@ -11,14 +11,7 @@ export default class ServerComponent extends BaseComponent {
11
11
  this.description = `Run a dev proxy server for live reloading`;
12
12
  this.sessions = {};
13
13
  this.server = create('SDC WP Build Server');
14
- this.watchedFiles = [
15
- `${this.project.paths.dist}/**/*`,
16
- `**/*.html`,
17
- `**/*.json`
18
- ];
19
- if (!this.project.shouldPHPLint) {
20
- this.watchedFiles.push(`**/*.php`);
21
- }
14
+ this.watchedFiles = [];
22
15
  this.ignoredFiles = [
23
16
  `.sdc-build-wp/cache/**`,
24
17
  `node_modules/**`,
@@ -28,6 +21,14 @@ export default class ServerComponent extends BaseComponent {
28
21
  }
29
22
 
30
23
  async init() {
24
+ this.watchedFiles = [
25
+ `${this.project.paths.dist}/**/*`,
26
+ `**/*.html`,
27
+ `**/*.json`
28
+ ];
29
+ if (!this.project.config.php.enabled) {
30
+ this.watchedFiles.push(`**/*.php`);
31
+ }
31
32
  this.project.pageScript = await readFile(path.join(path.dirname(fileURLToPath(import.meta.url)), '../page-script.js'), 'utf8');
32
33
  }
33
34
 
@@ -0,0 +1,104 @@
1
+ import log from './logging.js';
2
+
3
+ const configSchema = {
4
+ imagesPath: { type: 'string', optional: true },
5
+ errorLogPath: { type: 'string', optional: true },
6
+ entries: { type: 'object', optional: true },
7
+ php: {
8
+ type: 'object',
9
+ optional: true,
10
+ properties: {
11
+ enabled: { type: 'boolean', optional: true }
12
+ }
13
+ }
14
+ };
15
+
16
+ function validateType(value, expectedType, path = '') {
17
+ const actualType = Array.isArray(value) ? 'array' : typeof value;
18
+ if (actualType !== expectedType) {
19
+ throw new Error(`Configuration error at '${path}': expected ${expectedType}, got ${actualType}`);
20
+ }
21
+ }
22
+
23
+ function validateObject(obj, schema, path = '') {
24
+ if (!obj || typeof obj !== 'object') {
25
+ if (!schema.optional) {
26
+ throw new Error(`Configuration error at '${path}': expected object, got ${typeof obj}`);
27
+ }
28
+ return;
29
+ }
30
+
31
+ const allowedKeys = Object.keys(schema.properties || {});
32
+ const objKeys = Object.keys(obj);
33
+ const unknownKeys = objKeys.filter(key => !allowedKeys.includes(key));
34
+
35
+ if (unknownKeys.length > 0) {
36
+ log('warn', `Unknown configuration properties: ${unknownKeys.map(key => `${path}.${key}`).join(', ')}`);
37
+ }
38
+
39
+ for (const [key, subSchema] of Object.entries(schema.properties || {})) {
40
+ const currentPath = path ? `${path}.${key}` : key;
41
+ const value = obj[key];
42
+
43
+ if (value === undefined || value === null) {
44
+ if (!subSchema.optional) {
45
+ throw new Error(`Configuration error: required property '${currentPath}' is missing`);
46
+ }
47
+ continue;
48
+ }
49
+
50
+ if (subSchema.type === 'object' && subSchema.properties) {
51
+ validateObject(value, subSchema, currentPath);
52
+ } else {
53
+ validateType(value, subSchema.type, currentPath);
54
+ }
55
+ }
56
+ }
57
+
58
+ function validateConfig(config) {
59
+ try {
60
+ for (const [key, schema] of Object.entries(configSchema)) {
61
+ const value = config[key];
62
+ const currentPath = key;
63
+
64
+ if (value === undefined || value === null) {
65
+ if (!schema.optional) {
66
+ throw new Error(`Configuration error: required property '${currentPath}' is missing`);
67
+ }
68
+ continue;
69
+ }
70
+
71
+ if (schema.type === 'object' && schema.properties) {
72
+ validateObject(value, schema, currentPath);
73
+ } else {
74
+ validateType(value, schema.type, currentPath);
75
+ }
76
+ }
77
+ return true;
78
+ } catch (error) {``
79
+ console.error(error);
80
+ log('error', `Configuration validation failed`);
81
+ return false;
82
+ }
83
+ }
84
+
85
+ function getDefaultConfig() {
86
+ return {
87
+ errorLogPath: '../../../../../logs/php/error.log', // default Local by Flywheel error log path
88
+ php: {
89
+ enabled: true
90
+ }
91
+ };
92
+ }
93
+
94
+ function mergeWithDefaults(userConfig) {
95
+ const defaults = getDefaultConfig();
96
+
97
+ return {
98
+ ...defaults,
99
+ ...userConfig,
100
+ php: { ...defaults.php, ...userConfig.php }
101
+ };
102
+ }
103
+
104
+ export { validateConfig, getDefaultConfig, mergeWithDefaults };
package/lib/help.js CHANGED
@@ -1,34 +1,48 @@
1
1
  import project from './project.js';
2
+ import chalk from 'chalk';
2
3
 
3
4
  export default function() {
4
5
  console.log(`
5
- Usage: sdc-build-wp [options] [arguments]
6
+ ${chalk.bold.blue('SDC Build WP')} - Custom WordPress build process
6
7
 
7
- Options:
8
- -h, --help Show help message and exit
9
- -v, --version Version
10
- -w, --watch Build and watch
11
- -b, --builds BUILDS Build with specific components
12
- --no-cache Disable build caching
13
- --clear-cache Clear build cache and exit
8
+ ${chalk.yellow('Usage:')} sdc-build-wp [options] [arguments]
14
9
 
15
- Components:
10
+ ${chalk.yellow('Options:')}
11
+ ${chalk.green('-h, --help')} Show this help message and exit
12
+ ${chalk.green('-v, --version')} Show version number and exit
13
+ ${chalk.green('-w, --watch')} Build and watch for changes
14
+ ${chalk.green('-b, --builds BUILDS')} Build with specific components (comma-separated)
15
+ ${chalk.green('--no-cache')} Disable build caching for this run
16
+ ${chalk.green('--clear-cache')} Clear all cached data and exit
16
17
 
18
+ ${chalk.yellow('Available Components:')}
17
19
  ${Object.entries(project.components).map(([key, component]) => {
18
- return `${key}\t\t${component.description}\r\n`;
19
- }).join('')}
20
- Examples:
20
+ return ` ${chalk.cyan(key.padEnd(12))} ${component.description}`;
21
+ }).join('\n')}
21
22
 
22
- sdc-build-wp
23
- sdc-build-wp --watch
24
- sdc-build-wp --watch --builds=style,scripts
25
- sdc-build-wp --no-cache
26
- sdc-build-wp --clear-cache
23
+ ${chalk.yellow('Examples:')}
24
+ ${chalk.dim('# Basic build')}
25
+ sdc-build-wp
27
26
 
28
- While watch is enabled, use the following keyboard commands to control the build process:
27
+ ${chalk.dim('# Build and watch for changes')}
28
+ sdc-build-wp --watch
29
29
 
30
- [r] Restart
31
- [p] Pause/Resume
32
- [q] Quit
30
+ ${chalk.dim('# Build only specific components')}
31
+ sdc-build-wp --watch --builds=style,scripts
32
+
33
+ ${chalk.dim('# Build without cache')}
34
+ sdc-build-wp --no-cache
35
+
36
+ ${chalk.dim('# Clear cache and exit')}
37
+ sdc-build-wp --clear-cache
38
+
39
+ ${chalk.yellow('Watch Mode Controls:')}
40
+ ${chalk.green('[r]')} Restart build process
41
+ ${chalk.green('[p]')} Pause/Resume watching
42
+ ${chalk.green('[q]')} Quit and exit
43
+
44
+ ${chalk.yellow('Configuration:')}
45
+ Place your configuration in ${chalk.cyan('.sdc-build-wp/config.json')}
46
+ See documentation for available options.
33
47
  `);
34
48
  }
package/lib/project.js CHANGED
@@ -9,6 +9,7 @@ import * as utils from './utils.js';
9
9
  import log from './logging.js';
10
10
  import * as LibComponents from './components/index.js';
11
11
  import help from './help.js';
12
+ import { validateConfig, mergeWithDefaults } from './config-validator.js';
12
13
 
13
14
  let project = {
14
15
  config: {},
@@ -69,7 +70,16 @@ project.chokidarOpts = {
69
70
 
70
71
  export async function init() {
71
72
 
72
- project.argv = yargs(hideBin(process.argv)).parse();
73
+ project.components = Object.fromEntries(Object.entries(LibComponents).map(([name, Class]) => [name, new Class()]));
74
+
75
+ project.argv = yargs(hideBin(process.argv))
76
+ .help(false)
77
+ .argv;
78
+
79
+ if (project.argv.help || project.argv.h) {
80
+ help();
81
+ process.exit(0);
82
+ }
73
83
 
74
84
  if (project.package.sdc) {
75
85
  await convertPackageToConfig();
@@ -77,24 +87,18 @@ export async function init() {
77
87
 
78
88
  await loadConfig();
79
89
 
80
- project.components = Object.fromEntries(Object.entries(LibComponents).map(([name, Class]) => [name, new Class()]));
81
90
  const styleDir = `${project.path}/${project.paths.src.src}/${project.paths.src.style}`;
82
91
 
83
- if (project.argv.help || project.argv.h) {
84
- help();
85
- process.exit(0);
86
- } else if (project.argv.version || project.argv.v) {
92
+ if (project.argv.version || project.argv.v) {
87
93
  console.log(await utils.getThisPackageVersion());
88
94
  process.exit(0);
89
95
  } else if (project.argv['clear-cache']) {
90
- if (project.components.cache) {
91
- try {
92
- await project.components.cache.init();
93
- await project.components.cache.clearCache();
94
- } catch (error) {
95
- console.error(error);
96
- log('error', `Failed to clear cache`);
97
- }
96
+ try {
97
+ await project.components.cache.init();
98
+ await project.components.cache.clearCache();
99
+ } catch (error) {
100
+ console.error(error);
101
+ log('error', `Failed to clear cache`);
98
102
  }
99
103
  process.exit(0);
100
104
  }
@@ -139,17 +143,17 @@ export async function init() {
139
143
  log('warn', 'Continuing build process despite error');
140
144
  });
141
145
 
142
- process.on('uncaughtException', (error) => {
146
+ process.on('uncaughtException', async (error) => {
143
147
  log('error', `Uncaught Exception: ${error.message}`);
144
148
  log('warn', 'Attempting graceful shutdown');
145
- utils.stopActiveComponents();
149
+ await utils.stopActiveComponents();
146
150
  process.exit(1);
147
151
  });
148
152
 
149
153
  process.on('SIGINT', async function() {
150
154
  console.log(`\r`);
151
155
  if (project.isRunning) {
152
- utils.stopActiveComponents();
156
+ await utils.stopActiveComponents();
153
157
  project.isRunning = false;
154
158
  utils.clearScreen();
155
159
  }
@@ -174,7 +178,7 @@ export function keypressListen() {
174
178
  process.stdin.resume();
175
179
  process.stdin.setEncoding('utf8');
176
180
 
177
- process.stdin.on('data', (key) => {
181
+ process.stdin.on('data', async (key) => {
178
182
  switch (key) {
179
183
  case '\r': // [Enter]/[Return]
180
184
  console.log('\r');
@@ -192,9 +196,12 @@ export function keypressListen() {
192
196
  log('warn', 'Paused build process');
193
197
  }
194
198
  break;
199
+ case 'c':
200
+ await project.components.cache.clearCache();
201
+ break;
195
202
  case 'r':
196
203
  log('info', 'Restarted build process');
197
- restartBuild();
204
+ await restartBuild();
198
205
  break;
199
206
  }
200
207
  });
@@ -217,16 +224,27 @@ export async function convertPackageToConfig() {
217
224
  export async function loadConfig() {
218
225
  try {
219
226
  const configFile = await fs.readFile(configPath, 'utf-8');
220
- project.config = JSON.parse(configFile);
221
- project.paths.images = project.config.imagesPath || `${project.path}/${project.paths.src.src}/${project.paths.src.images}`
222
- project.paths.errorLog = process.env.ERROR_LOG_PATH || project.config.error_log_path || '../../../../../logs/php/error.log'
227
+ const rawConfig = JSON.parse(configFile);
228
+ if (!validateConfig(rawConfig)) {
229
+ log('error', 'Configuration validation failed');
230
+ process.exit(1);
231
+ }
232
+ project.config = mergeWithDefaults(rawConfig);
233
+ project.paths.images = project.config.imagesPath || `${project.path}/${project.paths.src.src}/${project.paths.src.images}`;
234
+ project.paths.errorLog = project.config.errorLogPath;
223
235
  project.entries = project.config.entries || {};
224
- project.shouldPHPLint = typeof project.config.php === 'undefined' || typeof project.config.php.enabled === 'undefined' || project.config.php.enabled == true;
225
236
  setupConfigWatcher();
226
237
  } catch (error) {
227
- console.error(error);
228
- log('error', `Failed to load config`);
229
- process.exit(1);
238
+ if (error.code === 'ENOENT') {
239
+ log('warn', 'No config file found, using defaults');
240
+ project.config = mergeWithDefaults({});
241
+ project.paths.images = `${project.path}/${project.paths.src.src}/${project.paths.src.images}`;
242
+ project.entries = {};
243
+ } else {
244
+ console.error(error);
245
+ log('error', `Failed to load config: ${error.message}`);
246
+ process.exit(1);
247
+ }
230
248
  }
231
249
  }
232
250
 
@@ -239,7 +257,7 @@ export function setupConfigWatcher() {
239
257
  configWatcher.on('change', async () => {
240
258
  if (!project.isRunning) { return; }
241
259
  await loadConfig();
242
- restartBuild();
260
+ await restartBuild();
243
261
  });
244
262
  configWatcher.on('error', (error) => {
245
263
  console.error(error);
package/lib/utils.js CHANGED
@@ -13,10 +13,10 @@ export function clearScreen() {
13
13
  process.stdout.write('\x1B[2J\x1B[0f');
14
14
  }
15
15
 
16
- export function stopActiveComponents() {
16
+ export async function stopActiveComponents() {
17
17
  if (project.configWatcher) {
18
18
  try {
19
- project.configWatcher.close();
19
+ await project.configWatcher.close();
20
20
  } catch (error) {
21
21
  console.error(error);
22
22
  log('error', 'Failed to stop config file watcher');
@@ -36,7 +36,7 @@ export function stopActiveComponents() {
36
36
  }
37
37
  if (component.watcher) {
38
38
  try {
39
- component.watcher.close();
39
+ await component.watcher.close();
40
40
  } catch (error) {
41
41
  console.warn(`Failed to stop watcher for ${component.constructor.name}:`, error.message);
42
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdc-build-wp",
3
- "version": "5.0.0",
3
+ "version": "5.0.2",
4
4
  "description": "Custom WordPress build process.",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -32,14 +32,14 @@
32
32
  "chalk": "^5.4.1",
33
33
  "chokidar": "^4.0.3",
34
34
  "esbuild": "^0.25.8",
35
- "eslint": "^9.31.0",
35
+ "eslint": "^9.32.0",
36
36
  "fs-extra": "^11.3.0",
37
37
  "postcss": "^8.5.6",
38
38
  "postcss-scss": "^4.0.9",
39
39
  "postcss-sort-media-queries": "^5.2.0",
40
40
  "sass": "^1.89.2",
41
41
  "sharp": "^0.34.3",
42
- "stylelint": "^16.22.0",
42
+ "stylelint": "^16.23.0",
43
43
  "svgo": "^4.0.0",
44
44
  "tail": "^2.2.6",
45
45
  "yargs": "^18.0.0"