sdc-build-wp 4.9.2 → 5.0.1

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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { default as project, init, keypressListen } from './lib/project.js';
3
- import build from './lib/build.js';
3
+ import { build } from './lib/build.js';
4
4
 
5
5
  (async () => {
6
6
  await init();
package/lib/build.js CHANGED
@@ -2,7 +2,7 @@ import { default as project } from './project.js';
2
2
  import * as utils from './utils.js';
3
3
  import log from './logging.js';
4
4
 
5
- export default async function(watch = false) {
5
+ export async function build(watch = false) {
6
6
  if (project.components.cache && project.builds.includes('cache')) {
7
7
  try {
8
8
  await project.components.cache.init();
@@ -62,3 +62,11 @@ export default async function(watch = false) {
62
62
  process.emit('SIGINT');
63
63
  }
64
64
  }
65
+
66
+ export function restartBuild() {
67
+ utils.stopActiveComponents();
68
+ setTimeout(() => {
69
+ utils.clearScreen();
70
+ build(true);
71
+ }, 100);
72
+ }
@@ -37,10 +37,10 @@ export default class BlocksComponent extends BaseComponent {
37
37
 
38
38
  for (const file of srcFiles) {
39
39
  if (/\.(js|jsx|ts|tsx)$/.test(file)) {
40
- const jsDependencies = this.utils.getAllJSDependencies(file);
40
+ const jsDependencies = await this.utils.getAllJSDependencies(file);
41
41
  dependencies.push(...jsDependencies);
42
42
  } else if (/\.(scss|sass)$/.test(file)) {
43
- const scssDependencies = this.utils.getImportedSASSFiles(file);
43
+ const scssDependencies = await this.utils.getImportedSASSFiles(file);
44
44
  dependencies.push(...scssDependencies);
45
45
  }
46
46
  }
@@ -11,7 +11,7 @@ export default class FontsComponent extends BaseComponent {
11
11
 
12
12
  async init() {
13
13
  this.globs = await Array.fromAsync(
14
- this.glob(this.project.package?.sdc?.fontsPath ||
14
+ this.glob(this.project.config.fontsPath ||
15
15
  `${this.project.path}/${this.project.paths.src.src}/${this.project.paths.src.fonts}`)
16
16
  );
17
17
  await this.process();
@@ -13,7 +13,7 @@ export default class ImagesComponent extends BaseComponent {
13
13
 
14
14
  async init() {
15
15
  this.globs = await Array.fromAsync(
16
- this.glob(this.project.package?.sdc?.imagesPath ||
16
+ this.glob(this.project.config.imagesPath ||
17
17
  `${this.project.paths.images}/**/*`)
18
18
  );
19
19
  this.globsDirectories = [
@@ -12,7 +12,7 @@ export default class PHPComponent extends BaseComponent {
12
12
 
13
13
  async init() {
14
14
  this.globs = await Array.fromAsync(
15
- this.glob(this.project.package?.sdc?.phpGlobPath ||
15
+ this.glob(this.project.config.phpGlobPath ||
16
16
  `${this.project.path}/**/*.php`)
17
17
  );
18
18
  // await this.process(null, { lintType: 'warn' }); // this errors "Fatal error: Allowed memory size"
@@ -14,7 +14,7 @@ export default class ScriptsComponent extends BaseComponent {
14
14
  async init() {
15
15
  this.files = this.utils.addEntriesByFiletypes(['.js', '.jsx', '.ts', '.tsx']);
16
16
  this.globs = await Array.fromAsync(
17
- this.glob(this.project.package?.sdc?.scriptsGlobPath ||
17
+ this.glob(this.project.config.scriptsGlobPath ||
18
18
  `${this.project.path}/${this.project.paths.src.src}/${this.project.paths.src.scripts}/**/*.{js,jsx,ts,tsx}`)
19
19
  );
20
20
  await this.process();
@@ -35,12 +35,12 @@ export default class ScriptsComponent extends BaseComponent {
35
35
  throw thisLint;
36
36
  }
37
37
  } catch (error) {
38
- console.log(error);
38
+ console.error(error);
39
39
  this.log('error', `Failed linting ${entry.replace(`${this.project.path}/${this.project.paths.src.src}/${this.project.paths.src.scripts}/`, '')} - See above error.`);
40
40
  return false;
41
41
  }
42
42
 
43
- const dependencies = this.utils.getAllJSDependencies(entry);
43
+ const dependencies = await this.utils.getAllJSDependencies(entry);
44
44
 
45
45
  this.clearHashCache([entry, ...(options.entriesToLint || []), ...dependencies]);
46
46
 
@@ -116,6 +116,7 @@ export default class ScriptsComponent extends BaseComponent {
116
116
  if (formatterOutput) { console.log(formatterOutput.replace(`${this.project.path}/${this.project.paths.src.src}/${this.project.paths.src.scripts}/`, '')); }
117
117
  return true;
118
118
  } catch (error) {
119
+ console.error(error);
119
120
  return error;
120
121
  }
121
122
  }
@@ -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
 
@@ -37,8 +38,8 @@ export default class ServerComponent extends BaseComponent {
37
38
  let bsOptions = {
38
39
  logPrefix: '',
39
40
  logFileChanges: false,
40
- port: this.project.package.sdc?.port || 3000,
41
- proxy: this.project.package.sdc?.browsersync?.localProxyURL,
41
+ port: this.project.config.port || 3000,
42
+ proxy: this.project.config.browsersync?.localProxyURL,
42
43
  files: watch ? this.watchedFiles : [],
43
44
  watchOptions: {
44
45
  cwd: this.project.path,
@@ -48,14 +49,14 @@ export default class ServerComponent extends BaseComponent {
48
49
  reloadDelay: 250,
49
50
  reloadDebounce: 1000,
50
51
  reloadOnRestart: true,
51
- watchEvents: this.project.package.sdc?.browsersync?.watchEvents || ['add', 'change', 'unlink', 'addDir', 'unlinkDir'],
52
- open: this.project.package.sdc?.open || false,
52
+ watchEvents: this.project.config.browsersync?.watchEvents || ['add', 'change', 'unlink', 'addDir', 'unlinkDir'],
53
+ open: this.project.config.browsersync?.open || false,
53
54
  https: (process.env.SSL_KEY_PATH && process.env.SSL_CRT_PATH ? {
54
55
  key: process.env.SSL_KEY_PATH,
55
56
  cert: process.env.SSL_CRT_PATH
56
57
  } : false),
57
58
  ui: false,
58
- tunnel: this.project.package.sdc?.browsersync?.tunnel,
59
+ tunnel: this.project.config.browsersync?.tunnel,
59
60
  notify: {
60
61
  styles: {
61
62
  pointerEvents: 'none',
@@ -68,11 +69,11 @@ export default class ServerComponent extends BaseComponent {
68
69
  },
69
70
  snippetOptions: {
70
71
  rule: {
71
- match: thisProject.package.sdc?.browsersync?.location == 'end' ? /<\/body>/ : /<body[^>]*>/,
72
+ match: thisProject.config.browsersync?.location == 'end' ? /<\/body>/ : /<body[^>]*>/,
72
73
  fn: function (snippet, match) {
73
74
  const customScript = `<script async>${thisProject.pageScript}</script>`;
74
75
  const allScripts = snippet + customScript;
75
- return thisProject.package.sdc?.browsersync?.location == 'end' ? allScripts + match : match + allScripts;
76
+ return thisProject.config.browsersync?.location == 'end' ? allScripts + match : match + allScripts;
76
77
  }
77
78
  }
78
79
  }
@@ -17,7 +17,7 @@ export default class StyleComponent extends BaseComponent {
17
17
  async init() {
18
18
  this.files = this.utils.addEntriesByFiletypes(['.scss']);
19
19
  this.globs = await Array.fromAsync(
20
- this.glob(this.project.package?.sdc?.sassGlobPath ||
20
+ this.glob(this.project.config.sassGlobPath ||
21
21
  `${this.project.path}{/${this.project.paths.src.src}/${this.project.paths.src.style},/blocks}/**/*.scss`)
22
22
  );
23
23
  await this.process();
@@ -129,7 +129,7 @@ export default class StyleComponent extends BaseComponent {
129
129
 
130
130
  this.clearHashCache([entry, ...(options.entriesToLint || [])]);
131
131
 
132
- const sassDependencies = this.utils.getImportedSASSFiles(entry);
132
+ const sassDependencies = await this.utils.getImportedSASSFiles(entry);
133
133
  if (await this.shouldSkipBuild(entry, outFile, sassDependencies)) {
134
134
  this.end({
135
135
  itemLabel: entryLabel,
@@ -202,7 +202,7 @@ export default class StyleComponent extends BaseComponent {
202
202
  try {
203
203
  let hasRanSingle = false;
204
204
  for (let group of this.files) {
205
- if (path == group.file || this.utils.getImportedSASSFiles(group.file).includes(path)) {
205
+ if (path == group.file || (await this.utils.getImportedSASSFiles(group.file)).includes(path)) {
206
206
  await this.process(group.file, { buildTheme: path == this.project.paths.theme.json });
207
207
  hasRanSingle = true;
208
208
  }
@@ -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
@@ -1,12 +1,18 @@
1
- import parseArgs from 'minimist';
1
+ import yargs from 'yargs';
2
+ import { hideBin } from 'yargs/helpers';
2
3
  import { readFile } from 'fs/promises';
3
- import build from './build.js';
4
+ import path from 'path';
5
+ import { promises as fs } from 'fs';
6
+ import chokidar from 'chokidar';
7
+ import { restartBuild } from './build.js';
4
8
  import * as utils from './utils.js';
5
9
  import log from './logging.js';
6
10
  import * as LibComponents from './components/index.js';
7
11
  import help from './help.js';
12
+ import { validateConfig, mergeWithDefaults } from './config-validator.js';
8
13
 
9
14
  let project = {
15
+ config: {},
10
16
  argv: null,
11
17
  isRunning: false,
12
18
  path: process.cwd(),
@@ -21,7 +27,7 @@ let project = {
21
27
  }
22
28
  };
23
29
 
24
- project.shouldPHPLint = typeof project.package.sdc?.php === 'undefined' || typeof project.package.sdc?.php.enabled === 'undefined' || project.package.sdc?.php.enabled == true;
30
+ const configPath = path.join(project.path, '.sdc-build-wp', 'config.json');
25
31
 
26
32
  project.paths = {
27
33
  src: {
@@ -44,8 +50,8 @@ project.paths = {
44
50
  composer: {
45
51
  vendor: `${project.path}/vendor`
46
52
  },
47
- images: project.package?.sdc?.imagesPath || `${project.path}/${project.paths.src.src}/${project.paths.src.images}`,
48
- errorLog: process.env.ERROR_LOG_PATH || project.package.sdc?.error_log_path || '../../../../../logs/php/error.log'
53
+ images: null,
54
+ errorLog: null
49
55
  };
50
56
 
51
57
  project.chokidarOpts = {
@@ -58,42 +64,41 @@ project.chokidarOpts = {
58
64
  `${project.paths.composer.vendor}/**/*`,
59
65
  project.paths.theme.scss,
60
66
  `${project.path}/blocks/*/build/*.php`,
61
- `${project.path}/.sdc-build-wp/**/*`,
67
+ `${project.path}/.sdc-build-wp/cache/**/*`,
62
68
  ]
63
69
  };
64
70
 
65
- export default project;
66
-
67
71
  export async function init() {
68
- project.argv = parseArgs(process.argv.slice(2));
72
+
69
73
  project.components = Object.fromEntries(Object.entries(LibComponents).map(([name, Class]) => [name, new Class()]));
70
74
 
71
- process.on('unhandledRejection', (reason, promise) => {
72
- log('error', `Unhandled Promise Rejection: ${reason}`);
73
- log('warn', 'Continuing build process despite error');
74
- });
75
-
76
- process.on('uncaughtException', (error) => {
77
- log('error', `Uncaught Exception: ${error.message}`);
78
- log('warn', 'Attempting graceful shutdown');
79
- utils.stopActiveComponents();
80
- process.exit(1);
81
- });
75
+ project.argv = yargs(hideBin(process.argv))
76
+ .help(false)
77
+ .argv;
82
78
 
83
79
  if (project.argv.help || project.argv.h) {
84
80
  help();
85
81
  process.exit(0);
86
- } else if (project.argv.version || project.argv.v) {
82
+ }
83
+
84
+ if (project.package.sdc) {
85
+ await convertPackageToConfig();
86
+ }
87
+
88
+ await loadConfig();
89
+
90
+ const styleDir = `${project.path}/${project.paths.src.src}/${project.paths.src.style}`;
91
+
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.log(`Error clearing cache: ${error.message}`);
96
- }
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`);
97
102
  }
98
103
  process.exit(0);
99
104
  }
@@ -112,13 +117,50 @@ export async function init() {
112
117
  project.builds.unshift('cache');
113
118
  }
114
119
 
115
- process.on('SIGINT', function () {
120
+ if (Object.keys(project.entries).length === 0) {
121
+ const styleFiles = await utils.getAllFiles(styleDir, ['.scss', '.css']);
122
+ const jsFiles = await utils.getAllFiles(`${project.path}/${project.paths.src.src}/${project.paths.src.scripts}`, ['.js', '.ts']);
123
+ for (const file of [...styleFiles, ...jsFiles]) {
124
+ let thisFiletype = path.extname(file);
125
+ let replaceString;
126
+ if (['.scss', '.css'].includes(thisFiletype)) {
127
+ replaceString = project.paths.src.style;
128
+ } else if (['.js', '.ts'].includes(thisFiletype)) {
129
+ replaceString = project.paths.src.scripts;
130
+ }
131
+ if (!replaceString) {
132
+ continue;
133
+ }
134
+ const entryName = utils.entryBasename(file).replace(/\.(css|scss|js|ts)$/, '')
135
+ if (!project.entries[`${replaceString}/${entryName}`]) {
136
+ project.entries[`${replaceString}/${entryName}`] = [ file.replace(project.path, '') ];
137
+ }
138
+ }
139
+ }
140
+
141
+ process.on('unhandledRejection', (reason, promise) => {
142
+ log('error', `Unhandled Promise Rejection: ${reason}`);
143
+ log('warn', 'Continuing build process despite error');
144
+ });
145
+
146
+ process.on('uncaughtException', (error) => {
147
+ log('error', `Uncaught Exception: ${error.message}`);
148
+ log('warn', 'Attempting graceful shutdown');
149
+ utils.stopActiveComponents();
150
+ process.exit(1);
151
+ });
152
+
153
+ process.on('SIGINT', async function() {
116
154
  console.log(`\r`);
117
155
  if (project.isRunning) {
118
156
  utils.stopActiveComponents();
119
157
  project.isRunning = false;
120
158
  utils.clearScreen();
121
159
  }
160
+ if (project.configWatcher) {
161
+ await project.configWatcher.close();
162
+ project.configWatcher = null;
163
+ }
122
164
  log('info', `Exited sdc-build-wp`);
123
165
  if (process.stdin.isTTY) {
124
166
  process.stdin.setRawMode(false);
@@ -138,7 +180,10 @@ export function keypressListen() {
138
180
 
139
181
  process.stdin.on('data', (key) => {
140
182
  switch (key) {
141
- case '\u0003': // Ctrl+C
183
+ case '\r': // [Enter]/[Return]
184
+ console.log('\r');
185
+ break;
186
+ case '\u0003': // [Ctrl]+C
142
187
  case 'q':
143
188
  process.emit('SIGINT');
144
189
  return;
@@ -153,12 +198,69 @@ export function keypressListen() {
153
198
  break;
154
199
  case 'r':
155
200
  log('info', 'Restarted build process');
156
- utils.stopActiveComponents();
157
- setTimeout(() => {
158
- utils.clearScreen();
159
- build(true);
160
- }, 100);
201
+ restartBuild();
161
202
  break;
162
203
  }
163
204
  });
164
205
  }
206
+
207
+ export async function convertPackageToConfig() {
208
+ if (!project.package.sdc) { return; }
209
+ try {
210
+ await fs.writeFile(configPath, JSON.stringify(project.package.sdc, null, '\t'));
211
+ log('success', 'Converted package.json sdc to .sdc-build-wp/config.json');
212
+ delete project.package.sdc;
213
+ await fs.writeFile(path.join(project.path, 'package.json'), JSON.stringify(project.package, null, '\t'));
214
+ log('success', 'Updated package.json to remove sdc');
215
+ } catch (error) {
216
+ log('error', `Failed to convert package.json sdc to .sdc-build-wp/config.json: ${error.message}`);
217
+ process.exit(1);
218
+ }
219
+ }
220
+
221
+ export async function loadConfig() {
222
+ try {
223
+ const configFile = await fs.readFile(configPath, 'utf-8');
224
+ const rawConfig = JSON.parse(configFile);
225
+ if (!validateConfig(rawConfig)) {
226
+ log('error', 'Configuration validation failed');
227
+ process.exit(1);
228
+ }
229
+ project.config = mergeWithDefaults(rawConfig);
230
+ project.paths.images = project.config.imagesPath || `${project.path}/${project.paths.src.src}/${project.paths.src.images}`;
231
+ project.paths.errorLog = project.config.errorLogPath;
232
+ project.entries = project.config.entries || {};
233
+ setupConfigWatcher();
234
+ } catch (error) {
235
+ if (error.code === 'ENOENT') {
236
+ log('warn', 'No config file found, using defaults');
237
+ project.config = mergeWithDefaults({});
238
+ project.paths.images = `${project.path}/${project.paths.src.src}/${project.paths.src.images}`;
239
+ project.entries = {};
240
+ } else {
241
+ console.error(error);
242
+ log('error', `Failed to load config: ${error.message}`);
243
+ process.exit(1);
244
+ }
245
+ }
246
+ }
247
+
248
+ export function setupConfigWatcher() {
249
+ if (!project.argv.watch) { return; }
250
+ const configWatcher = chokidar.watch(configPath, {
251
+ ignoreInitial: true,
252
+ persistent: true
253
+ });
254
+ configWatcher.on('change', async () => {
255
+ if (!project.isRunning) { return; }
256
+ await loadConfig();
257
+ restartBuild();
258
+ });
259
+ configWatcher.on('error', (error) => {
260
+ console.error(error);
261
+ log('warn', `Config file watcher error`);
262
+ });
263
+ project.configWatcher = configWatcher;
264
+ }
265
+
266
+ export default project;
package/lib/utils.js CHANGED
@@ -1,12 +1,11 @@
1
1
  import path from 'path';
2
- import fs from 'fs';
3
- import * as fsPromises from 'fs/promises';
2
+ import { promises as fs } from 'fs';
4
3
  import { readdir } from 'node:fs/promises';
5
4
  import { fileURLToPath } from 'url';
6
5
  import project from './project.js';
7
6
 
8
7
  export async function getThisPackageVersion() {
9
- return JSON.parse(await fsPromises.readFile(path.join(path.dirname(fileURLToPath(import.meta.url)), '../package.json'))).version
8
+ return JSON.parse(await fs.readFile(path.join(path.dirname(fileURLToPath(import.meta.url)), '../package.json'))).version
10
9
  }
11
10
 
12
11
  export function clearScreen() {
@@ -15,14 +14,26 @@ export function clearScreen() {
15
14
  }
16
15
 
17
16
  export function stopActiveComponents() {
17
+ if (project.configWatcher) {
18
+ try {
19
+ project.configWatcher.close();
20
+ } catch (error) {
21
+ console.error(error);
22
+ log('error', 'Failed to stop config file watcher');
23
+ }
24
+ }
18
25
  if (project.components.server?.server) {
19
26
  try {
20
27
  project.components.server.server.exit();
21
28
  } catch (error) {
22
- console.warn('Failed to stop server:', error.message);
29
+ console.error(error);
30
+ log('error', 'Failed to stop server');
23
31
  }
24
32
  }
25
- Object.values(project.components).forEach(component => {
33
+ for (const component of Object.values(project.components)) {
34
+ if (component.isBuilding) {
35
+ component.isBuilding = false;
36
+ }
26
37
  if (component.watcher) {
27
38
  try {
28
39
  component.watcher.close();
@@ -30,9 +41,6 @@ export function stopActiveComponents() {
30
41
  console.warn(`Failed to stop watcher for ${component.constructor.name}:`, error.message);
31
42
  }
32
43
  }
33
- });
34
- if (project.components.scripts) {
35
- project.components.scripts.isBuilding = false;
36
44
  }
37
45
  }
38
46
 
@@ -44,6 +52,24 @@ export function entryBasename(entry) {
44
52
  return path.parse(entry).base;
45
53
  }
46
54
 
55
+ export async function getAllFiles(dir, filetypes = false) {
56
+ let files = [];
57
+ const entries = await readdir(dir, { withFileTypes: true });
58
+ if (filetypes && !Array.isArray(filetypes)) {
59
+ filetypes = [filetypes];
60
+ }
61
+ for (const entry of entries) {
62
+ const fullPath = path.join(dir, entry.name);
63
+ if (
64
+ !entry.isDirectory() &&
65
+ (!filetypes || filetypes.length == 0 || filetypes.includes(path.extname(fullPath)))
66
+ ) {
67
+ files.push(fullPath);
68
+ }
69
+ }
70
+ return files;
71
+ }
72
+
47
73
  export async function getAllSubdirectories(dir) {
48
74
  let subdirectories = [];
49
75
  const subdirectoriesEntries = await readdir(dir, { withFileTypes: true });
@@ -58,8 +84,8 @@ export async function getAllSubdirectories(dir) {
58
84
  return subdirectories;
59
85
  }
60
86
 
61
- export function getImportedSASSFiles(filePath) {
62
- const content = fs.readFileSync(filePath, 'utf8');
87
+ export async function getImportedSASSFiles(filePath) {
88
+ const content = await fs.readFile(filePath, 'utf8');
63
89
  const regex = /@(?:import|use)\s+['"]([^'"]+)['"]/g;
64
90
  const imports = [];
65
91
  let match;
@@ -75,9 +101,9 @@ export function getImportedSASSFiles(filePath) {
75
101
  return imports;
76
102
  }
77
103
 
78
- export function getImportedJSFiles(filePath) {
104
+ export async function getImportedJSFiles(filePath) {
79
105
  try {
80
- const content = fs.readFileSync(filePath, 'utf8');
106
+ const content = await fs.readFile(filePath, 'utf8');
81
107
  const regex = /(?:import\s+[^'"]*\s+from\s+['"`]([^'">`]+)['"`]|import\s*\(\s*['"`]([^'">`]+)['"`]\s*\)|require\s*\(\s*['"`]([^'">`]+)['"`]\s*\))/g;
82
108
  const imports = [];
83
109
  let match;
@@ -95,43 +121,61 @@ export function getImportedJSFiles(filePath) {
95
121
  if (!path.extname(resolvedPath)) {
96
122
  for (const ext of extensions) {
97
123
  const pathWithExt = resolvedPath + ext;
98
- if (fs.existsSync(pathWithExt)) {
124
+ try {
125
+ await fs.access(pathWithExt);
99
126
  imports.push(pathWithExt);
100
127
  break;
128
+ } catch {
129
+ // File does not exist
101
130
  }
102
131
  }
103
132
  const indexPath = path.join(resolvedPath, 'index');
104
133
  for (const ext of extensions) {
105
134
  const pathWithExt = indexPath + ext;
106
- if (fs.existsSync(pathWithExt)) {
135
+ try {
136
+ await fs.access(pathWithExt);
107
137
  imports.push(pathWithExt);
108
138
  break;
139
+ } catch {
140
+ // File does not exist
109
141
  }
110
142
  }
111
143
  } else {
112
- if (fs.existsSync(resolvedPath)) {
144
+ try {
145
+ await fs.access(resolvedPath);
113
146
  imports.push(resolvedPath);
147
+ } catch {
148
+ // File does not exist
114
149
  }
115
150
  }
116
151
  }
117
152
  }
118
- return [...new Set(imports)].filter(importPath =>
119
- importPath.startsWith(project.path) && fs.existsSync(importPath)
120
- );
153
+ const unique = [...new Set(imports)];
154
+ const finalList = [];
155
+ for (const imp of unique) {
156
+ if (!imp.startsWith(project.path)) { continue; }
157
+ try {
158
+ await fs.access(imp);
159
+ finalList.push(imp);
160
+ } catch {
161
+ // File does not exist
162
+ }
163
+ }
164
+ return finalList;
121
165
  } catch (error) {
122
166
  return [];
123
167
  }
124
168
  }
125
169
 
126
- export function getAllJSDependencies(filePath, visited = new Set()) {
170
+ export async function getAllJSDependencies(filePath, visited = new Set()) {
127
171
  if (visited.has(filePath)) {
128
172
  return [];
129
173
  }
130
174
  visited.add(filePath);
131
- const directDeps = getImportedJSFiles(filePath);
175
+ const directDeps = await getImportedJSFiles(filePath);
132
176
  const allDeps = [...directDeps];
133
177
  for (const dep of directDeps) {
134
- const nestedDeps = getAllJSDependencies(dep, visited);
178
+ const nestedDeps = await getAllJSDependencies(dep, visited);
135
179
  allDeps.push(...nestedDeps);
136
180
  }
137
181
  return [...new Set(allDeps)];
@@ -139,7 +183,7 @@ export function getAllJSDependencies(filePath, visited = new Set()) {
139
183
 
140
184
  export function addEntriesByFiletypes(filetypes = []) {
141
185
  let finalFiles = [];
142
- for (const [name, files] of Object.entries(project.package.sdc.entries)) {
186
+ for (const [name, files] of Object.entries(project.entries)) {
143
187
  for (let file of files) {
144
188
  let fullPath = project.path + file;
145
189
  let extension = path.parse(fullPath).ext;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdc-build-wp",
3
- "version": "4.9.2",
3
+ "version": "5.0.1",
4
4
  "description": "Custom WordPress build process.",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -34,7 +34,6 @@
34
34
  "esbuild": "^0.25.8",
35
35
  "eslint": "^9.31.0",
36
36
  "fs-extra": "^11.3.0",
37
- "minimist": "^1.2.8",
38
37
  "postcss": "^8.5.6",
39
38
  "postcss-scss": "^4.0.9",
40
39
  "postcss-sort-media-queries": "^5.2.0",
@@ -42,6 +41,7 @@
42
41
  "sharp": "^0.34.3",
43
42
  "stylelint": "^16.22.0",
44
43
  "svgo": "^4.0.0",
45
- "tail": "^2.2.6"
44
+ "tail": "^2.2.6",
45
+ "yargs": "^18.0.0"
46
46
  }
47
47
  }