sdc-build-wp 5.2.0 → 5.3.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/lib/build.js CHANGED
@@ -2,9 +2,20 @@ import { default as project } from './project.js';
2
2
  import * as utils from './utils.js';
3
3
  import log from './logging.js';
4
4
  import chalk from 'chalk';
5
+ import tui from './tui.js';
5
6
 
6
7
 
7
8
  export async function build(watch = false) {
9
+
10
+ if (watch) {
11
+ tui.init();
12
+ tui.setComponents(project.builds, true);
13
+ const commands = `Commands: ${chalk.underline.green('r')}estart, ${chalk.underline.green('c')}lear cache, ${chalk.underline.green('p')}ause/resume, ${chalk.underline.green('n')}ew component, ${chalk.underline.green('q')}uit`;
14
+ tui.setCommands(commands);
15
+ }
16
+
17
+ log('info', `Started sdc-build-wp`);
18
+
8
19
  if (project.builds.includes('cache')) {
9
20
  try {
10
21
  await project.components.cache.init();
@@ -28,7 +39,7 @@ export async function build(watch = false) {
28
39
 
29
40
  promisesBuilds.push(
30
41
  project.components[build].init().catch(error => {
31
- console.error(error);
42
+ log(null, error);
32
43
  log('error', `Failed to initialize ${build} component`);
33
44
  return { failed: true, component: build, error };
34
45
  })
@@ -44,15 +55,18 @@ export async function build(watch = false) {
44
55
  }
45
56
 
46
57
  utils.clearScreen();
47
- log('info', `Finished initial build in ${Math.round((performance.now() - initialBuildTimerStart) / 1000)} seconds`);
48
58
 
49
59
  if (watch && project.builds.includes('server')) {
50
60
  project.isRunning = true;
51
61
  project.builds.splice(project.builds.indexOf('server'), 1);
52
62
  project.builds.push('server');
63
+
64
+ log('info', `Finished initial build in ${Math.round((performance.now() - initialBuildTimerStart) / 1000)} seconds`);
53
65
  log('info', `Started watching [${project.builds.join(', ')}]`);
66
+
67
+ tui.setComponents(project.builds, true);
68
+
54
69
  project.components.server.logURLs();
55
- log('info', `${chalk.underline.green('r')}estart, ${chalk.underline.green('c')}lear cache, ${chalk.underline.green('p')}ause/resume, ${chalk.underline.green('n')}ew component, ${chalk.underline.green('q')}uit`);
56
70
 
57
71
  for (let build of project.builds) {
58
72
  try {
@@ -63,6 +77,7 @@ export async function build(watch = false) {
63
77
  }
64
78
  }
65
79
  } else {
80
+ log('info', `Finished initial build in ${Math.round((performance.now() - initialBuildTimerStart) / 1000)} seconds`);
66
81
  process.emit('SIGINT');
67
82
  }
68
83
  }
@@ -156,7 +156,7 @@ export default class BlocksComponent extends BaseComponent {
156
156
 
157
157
  await this.updateBuildCache(workingBlockJson, cacheOutputFile, dependencies);
158
158
  } catch (error) {
159
- console.error(error.stdout || error.stderr || error.message);
159
+ this.log(null, error.stdout || error.stderr || error.message);
160
160
  this.log('error', `Failed building ${entryLabel} block - See above error.`);
161
161
  return false;
162
162
  }
@@ -183,8 +183,8 @@ export default class BlocksComponent extends BaseComponent {
183
183
  if (this.watcher) {
184
184
  this.watcher.add([`${blockPath}/src`, `${blockPath}/src/**/*`]);
185
185
  }
186
- this.build(blockPath).catch(err => {
187
- console.error(err);
186
+ this.build(blockPath).catch(error => {
187
+ this.log(null, error);
188
188
  this.log('error', `Failed initial build for new block ${blockPath}`);
189
189
  });
190
190
  }
@@ -290,7 +290,7 @@ export default class BlocksComponent extends BaseComponent {
290
290
  if (path.endsWith('.js')) {
291
291
  if (!this.project.components.scripts.isBuilding) {
292
292
  this.project.components.scripts.lint(path).catch(lintError => {
293
- console.error(lintError);
293
+ this.log(null, lintError);
294
294
  this.log('warn', `Linting failed for ${path}`);
295
295
  });
296
296
  }
@@ -27,7 +27,7 @@ export default class FontsComponent extends BaseComponent {
27
27
  if (fontsDir.length == 0) { throw new Error('No files present'); }
28
28
  await fs.copy(entry, `${this.project.path}${entryLabel}`);
29
29
  entryLabel += ` (${fontsDir.filter(file => !file.startsWith('.')).length} files)`;
30
- } catch(error) {
30
+ } catch (error) {
31
31
  this.log('error', `${error} at ${entry.replace(this.project.path, '')}/. Skipping font copy`);
32
32
  return false;
33
33
  }
@@ -78,7 +78,7 @@ export default class HTMLComponent extends BaseComponent {
78
78
  entryLabel = `${formattedCount} html file${formattedCount > 1 ? 's' : ''}`;
79
79
  }
80
80
  } catch (error) {
81
- console.error(error);
81
+ this.log(null, error);
82
82
  this.log('error', `Failed formatting ${entryLabel.replace(this.project.path, '')} - ${error.message}`);
83
83
  return false;
84
84
  }
@@ -58,7 +58,7 @@ export default class ImagesComponent extends BaseComponent {
58
58
  }
59
59
  convertedImagesCount++;
60
60
  } catch (error) {
61
- console.error(error);
61
+ this.log(null, error);
62
62
  this.log('error', `Failed optimizing ${filePath.replace(this.project.path, '')} - See above error.`);
63
63
  }
64
64
 
@@ -29,7 +29,7 @@ export default class PHPComponent extends BaseComponent {
29
29
  this.log('warn', 'PHP syntax checker not found. Skipping syntax check.');
30
30
  return true;
31
31
  }
32
- console.error(error.stderr.replace(this.project.path, ''));
32
+ this.log(null, error.stderr.replace(this.project.path, ''));
33
33
  this.log('error', `Failed to validate ${entry.replace(this.project.path, '')} - See above error.`);
34
34
  return false;
35
35
  }
@@ -93,7 +93,7 @@ export default class PHPComponent extends BaseComponent {
93
93
  )
94
94
  )
95
95
  ) {
96
- console.error(error.stderr?.length ? error.stderr : error.stdout);
96
+ this.log(null, error.stderr?.length ? error.stderr : error.stdout);
97
97
  this.log('error', `Failed linting ${entryLabel.replace(this.project.path, '')} - See above error.`);
98
98
  return false;
99
99
  }
@@ -35,7 +35,7 @@ export default class ScriptsComponent extends BaseComponent {
35
35
  throw thisLint;
36
36
  }
37
37
  } catch (error) {
38
- console.error(error);
38
+ this.log(null, 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
  }
@@ -59,6 +59,7 @@ export default class ScriptsComponent extends BaseComponent {
59
59
  globalName: 'sdcBuild',
60
60
  treeShaking: true,
61
61
  entryPoints: [entry],
62
+ logLevel: 'silent',
62
63
  bundle: true,
63
64
  minify: true,
64
65
  outdir: `${this.project.paths.dist}/${this.project.paths.src.scripts}/`,
@@ -72,7 +73,7 @@ export default class ScriptsComponent extends BaseComponent {
72
73
 
73
74
  await this.updateBuildCache(entry, outFile, dependencies);
74
75
  } catch (error) {
75
- console.error(error);
76
+ this.log(null, String(error.message || error.stack || error));
76
77
  this.log('error', `Failed building ${entryLabel} - See above error.`);
77
78
  return false;
78
79
  }
@@ -103,7 +104,7 @@ export default class ScriptsComponent extends BaseComponent {
103
104
  await this.checkAndRebuildAffectedBlocks(path);
104
105
  }
105
106
  } catch (error) {
106
- console.error(error);
107
+ this.log(null, String(error.stack || error));
107
108
  this.log('error', `Failed to process scripts`);
108
109
  }
109
110
  });
@@ -121,7 +122,7 @@ export default class ScriptsComponent extends BaseComponent {
121
122
  affectedBlocks.add(blockPath);
122
123
  }
123
124
  } catch (error) {
124
- console.error(error);
125
+ this.log(null, String(error.stack || error));
125
126
  }
126
127
  }
127
128
 
@@ -149,10 +150,13 @@ export default class ScriptsComponent extends BaseComponent {
149
150
  await ESLint.outputFixes(lintresults);
150
151
  const formatter = await eslint.loadFormatter('stylish');
151
152
  const formatterOutput = formatter.format(lintresults);
152
- if (formatterOutput) { console.log(formatterOutput.replace(`${this.project.path}/${this.project.paths.src.src}/${this.project.paths.src.scripts}/`, '')); }
153
+ if (formatterOutput) {
154
+ const cleanedOutput = formatterOutput.replace(`${this.project.path}/${this.project.paths.src.src}/${this.project.paths.src.scripts}/`, '');
155
+ this.log(null, cleanedOutput)
156
+ }
153
157
  return true;
154
158
  } catch (error) {
155
- console.error(error);
159
+ this.log(null, error.stack || error);
156
160
  return error;
157
161
  }
158
162
  }
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'url';
4
4
  import { readFile } from 'fs/promises';
5
5
  import { create } from 'browser-sync';
6
6
  import chalk from 'chalk';
7
+ import tui from '../tui.js';
7
8
 
8
9
  export default class ServerComponent extends BaseComponent {
9
10
 
@@ -115,9 +116,14 @@ export default class ServerComponent extends BaseComponent {
115
116
  if (this.usedOptions.proxy) {
116
117
  localURL = localURL.replace('localhost', this.usedOptions.proxy.url.host);
117
118
  }
118
- this.log('info', `Local server: ${chalk.green(localURL)} (${this.usedOptions.urls.local})`);
119
- if (this.usedOptions.urls.external) {
120
- this.log('info', `External server: ${chalk.green(this.usedOptions.urls.external)}`);
119
+
120
+ if (tui.isInitialized) {
121
+ tui.setURLs(localURL, this.usedOptions.urls.external || '');
122
+ } else {
123
+ this.log('info', `Local server: ${chalk.green(localURL)} (${this.usedOptions.urls.local})`);
124
+ if (this.usedOptions.urls.external) {
125
+ this.log('info', `External server: ${chalk.green(this.usedOptions.urls.external)}`);
126
+ }
121
127
  }
122
128
  }
123
129
 
@@ -84,12 +84,12 @@ export default class StyleComponent extends BaseComponent {
84
84
  }
85
85
  try {
86
86
  await fs.writeFile(this.project.paths.theme.scss, themeFileData);
87
- } catch(error) {
88
- console.error(error);
87
+ } catch (error) {
88
+ this.log(null, error);
89
89
  this.log('error', `Failed to write auto-generated _theme.scss - See above error.`);
90
90
  }
91
- } catch(error) {
92
- console.error(error);
91
+ } catch (error) {
92
+ this.log(null, error);
93
93
  this.log('error', `Failed to read theme.json - See above error.`);
94
94
  }
95
95
  }
@@ -110,7 +110,7 @@ export default class StyleComponent extends BaseComponent {
110
110
 
111
111
  try {
112
112
  await fs.access(`${this.project.path}/${this.project.paths.dist}/${this.project.paths.src.style}`);
113
- } catch(error) {
113
+ } catch (error) {
114
114
  await fs.mkdir(`${this.project.path}/${this.project.paths.dist}/${this.project.paths.src.style}`, { recursive: true });
115
115
  }
116
116
 
@@ -123,7 +123,7 @@ export default class StyleComponent extends BaseComponent {
123
123
  fix: true
124
124
  });
125
125
  if (stylelinted.errored) {
126
- console.error(stylelinted.report);
126
+ this.log(null, stylelinted.report);
127
127
  throw Error('Linting error');
128
128
  }
129
129
 
@@ -162,8 +162,8 @@ export default class StyleComponent extends BaseComponent {
162
162
  thisClass.end({
163
163
  itemLabel: entryLabel
164
164
  });
165
- } catch(error) {
166
- console.error(error);
165
+ } catch (error) {
166
+ this.log(null, error);
167
167
  this.log('error', `Failed building ${entryLabel} - See above error.`);
168
168
  return false;
169
169
  }
@@ -75,8 +75,8 @@ function validateConfig(config) {
75
75
  }
76
76
  }
77
77
  return true;
78
- } catch (error) {``
79
- console.error(error);
78
+ } catch (error) {
79
+ log(null, error);
80
80
  log('error', `Configuration validation failed`);
81
81
  return false;
82
82
  }
package/lib/help.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import project from './project.js';
2
2
  import chalk from 'chalk';
3
+ import log from './logging.js';
3
4
 
4
5
  export default function() {
5
- console.log(`
6
+ log(null, `
6
7
  ${chalk.bold.blue('SDC Build WP')} - Custom WordPress build process
7
8
 
8
9
  ${chalk.yellow('Usage:')} sdc-build-wp [options] [arguments]
@@ -38,9 +39,9 @@ ${chalk.yellow('Examples:')}
38
39
 
39
40
  ${chalk.yellow('Watch Mode Controls:')}
40
41
  ${chalk.green('[r]')} Restart build process
41
- ${chalk.green('[c]')} Clear cache
42
+ ${chalk.green('[c]')} Clear cache
42
43
  ${chalk.green('[p]')} Pause/Resume watching
43
- ${chalk.green('[n]')} New component
44
+ ${chalk.green('[n]')} New component
44
45
  ${chalk.green('[q]')} Quit and exit
45
46
 
46
47
  ${chalk.yellow('Configuration:')}
package/lib/logging.js CHANGED
@@ -2,59 +2,68 @@
2
2
  // https://github.com/callmenick/node-pretty-log
3
3
  import chalk from 'chalk';
4
4
  import { default as project } from './project.js';
5
+ import tui from './tui.js';
5
6
 
6
7
  function getTime() {
7
8
  return new Date().toLocaleTimeString('en-US');
8
9
  }
9
10
 
10
11
  function log(type, ...messages) {
12
+ let icon, time = null;
13
+ let prefix = '';
14
+
11
15
  switch (type) {
12
16
  case 'success':
13
- console.log.call(
14
- console,
15
- chalk.green('✔'),
16
- chalk.gray(getTime()),
17
- ...messages
18
- );
17
+ icon = chalk.green('✔');
18
+ time = chalk.gray(getTime());
19
19
  break;
20
20
  case 'error':
21
- console.log.call(
22
- console,
23
- chalk.red('✖'),
24
- chalk.bgRed.gray(getTime()),
25
- ...messages
26
- );
21
+ icon = chalk.red('✖');
22
+ time = chalk.bgRed.gray(getTime());
27
23
  if (project.builds.includes('server') && project.isRunning) {
28
24
  project.components.server.server.notify('ERROR', 2500);
29
25
  }
30
26
  break;
31
27
  case 'warn':
32
- console.log.call(
33
- console,
34
- chalk.yellow('⚠'),
35
- chalk.bgYellow.gray(getTime()),
36
- ...messages
37
- );
28
+ icon = chalk.yellow('⚠');
29
+ time = chalk.bgYellow.gray(getTime());
38
30
  break;
39
31
  case 'php':
40
- console.log.call(
41
- console,
42
- chalk.blue(''),
43
- chalk.gray(getTime()),
44
- chalk.gray('PHP: '),
45
- ...messages
46
- );
32
+ icon = chalk.blue('ℹ');
33
+ time = chalk.gray(getTime());
34
+ prefix = chalk.gray('PHP: ');
47
35
  break;
48
36
  case 'info':
49
- default:
50
- console.log.call(
51
- console,
52
- chalk.blue('ℹ'),
53
- chalk.bgBlue.gray(getTime()),
54
- ...messages
55
- );
37
+ icon = chalk.blue('ℹ');
38
+ time = chalk.bgBlue.gray(getTime());
56
39
  break;
57
40
  }
41
+
42
+ let messagesString = messages.join(' ');
43
+
44
+ const logMessage = [icon, time, prefix, messagesString].filter(Boolean).join(' ');
45
+ if (tui.isInitialized) {
46
+ if (!type && messagesString.includes('\n')) {
47
+ messagesString.split('\n').forEach(line => {
48
+ if (line.trim()) {
49
+ tui.log(line);
50
+ }
51
+ });
52
+ return;
53
+ }
54
+ tui.log(String(logMessage));
55
+ } else {
56
+ switch (type) {
57
+ case 'error':
58
+ console.error(logMessage);
59
+ break;
60
+ case 'warn':
61
+ console.warn(logMessage);
62
+ break;
63
+ default:
64
+ console.log(logMessage);
65
+ }
66
+ }
58
67
  }
59
68
 
60
69
  export default log;
package/lib/project.js CHANGED
@@ -11,7 +11,7 @@ import log from './logging.js';
11
11
  import * as LibComponents from './components/index.js';
12
12
  import help from './help.js';
13
13
  import { validateConfig, mergeWithDefaults } from './config-validator.js';
14
- import { input, select } from '@inquirer/prompts';
14
+ import tui from './tui.js';
15
15
 
16
16
  let project = {
17
17
  config: {},
@@ -92,14 +92,14 @@ export async function init() {
92
92
  const styleDir = `${project.path}/${project.paths.src.src}/${project.paths.src.style}`;
93
93
 
94
94
  if (project.argv.version || project.argv.v) {
95
- console.log(await utils.getThisPackageVersion());
95
+ log(null, await utils.getThisPackageVersion());
96
96
  process.exit(0);
97
97
  } else if (project.argv['clear-cache']) {
98
98
  try {
99
99
  await project.components.cache.init();
100
100
  await project.components.cache.clearCache();
101
101
  } catch (error) {
102
- console.error(error);
102
+ log(null, error);
103
103
  log('error', `Failed to clear cache`);
104
104
  }
105
105
  process.exit(0);
@@ -153,7 +153,6 @@ export async function init() {
153
153
  });
154
154
 
155
155
  process.on('SIGINT', async function() {
156
- console.log(`\r`);
157
156
  if (project.isRunning) {
158
157
  await utils.stopActiveComponents();
159
158
  project.isRunning = false;
@@ -163,12 +162,19 @@ export async function init() {
163
162
  await project.configWatcher.close();
164
163
  project.configWatcher = null;
165
164
  }
165
+ tui.destroy();
166
+ if (tui.getLogHistory) {
167
+ const logDump = tui.getLogHistory();
168
+ if (logDump && logDump.trim()) {
169
+ log(null, logDump);
170
+ }
171
+ }
166
172
  log('info', `Exited sdc-build-wp`);
167
173
  if (process.stdin.isTTY) {
168
174
  process.stdin.setRawMode(false);
169
175
  process.stdin.pause();
170
176
  }
171
- process.exit(0);
177
+ setTimeout(() => process.exit(0), 100);
172
178
  });
173
179
 
174
180
  }
@@ -180,7 +186,9 @@ export function keypressListen() {
180
186
 
181
187
  const installRaw = () => {
182
188
  if (!process.stdin.isTTY) return;
183
- try { process.stdin.setRawMode(true); } catch {}
189
+ if (!tui.isInitialized) {
190
+ try { process.stdin.setRawMode(true); } catch {}
191
+ }
184
192
  process.stdin.resume();
185
193
  process.stdin.setEncoding('utf8');
186
194
  };
@@ -191,7 +199,7 @@ export function keypressListen() {
191
199
  if (isPrompting) { return; }
192
200
  switch (key) {
193
201
  case '\r': // [Enter]/[Return]
194
- console.log('\r');
202
+ log(null, '');
195
203
  break;
196
204
  case '\u0003': // [Ctrl]+C
197
205
  case 'q':
@@ -202,8 +210,10 @@ export function keypressListen() {
202
210
  utils.clearScreen();
203
211
  if (project.isRunning) {
204
212
  log('success', 'Resumed build process');
213
+ tui.setPaused(false);
205
214
  } else {
206
215
  log('warn', 'Paused build process');
216
+ tui.setPaused(true);
207
217
  }
208
218
  break;
209
219
  case 'c':
@@ -216,8 +226,8 @@ export function keypressListen() {
216
226
  case 'n': // New menu
217
227
  isPrompting = true;
218
228
  process.stdin.removeListener('data', handler);
229
+
219
230
  try {
220
- try { process.stdin.setRawMode(false); } catch {}
221
231
  await handleCreateNew();
222
232
  } finally {
223
233
  isPrompting = false;
@@ -235,28 +245,21 @@ async function handleCreateNew() {
235
245
  if (!project.isRunning) {
236
246
  log('warn', 'Build process paused. Press [p] to resume if needed. Continuing creation.');
237
247
  }
238
- let typeKey;
239
- try {
240
- typeKey = await select({
241
- message: 'Create new:',
242
- choices: [
243
- { name: 'Block', value: 'b' },
244
- { name: 'Pattern', value: 'p' },
245
- { name: 'Style variation', value: 's' },
246
- { name: 'Cancel', value: 'cancel' }
247
- ]
248
- });
249
- } catch (error) {
250
- return;
251
- }
252
- if (typeKey === 'cancel') { return; }
248
+
249
+
250
+ const menuOptions = ['Block', 'Pattern', 'Style variation', 'Cancel'];
251
+ const menuMap = { Block: 'b', Pattern: 'p', 'Style variation': 's', Cancel: 'cancel' };
252
+ const menuPrompt = 'Create new:';
253
+ const blockPrompt = 'Block name:';
254
+ const patternPrompt = 'Pattern name:';
255
+ const stylePrompt = 'Style variation name:';
256
+
257
+ let menuResult = await tui.showMenu(menuOptions, menuPrompt);
258
+ if (!menuResult || menuMap[menuResult.value] === 'cancel') return;
259
+ let typeKey = menuMap[menuResult.value];
260
+
253
261
  if (typeKey === 'b') {
254
- let name;
255
- try {
256
- name = await input({ message: 'Block name:' });
257
- } catch (error) {
258
- return;
259
- }
262
+ let name = await tui.showInput(blockPrompt);
260
263
  if (!name) { log('warn', 'No name provided.'); return; }
261
264
  const slug = utils.slugify(name);
262
265
  const blockDir = `${project.path}/blocks/${slug}`;
@@ -286,12 +289,11 @@ async function handleCreateNew() {
286
289
  project.components.blocks.addBlock(blockDir);
287
290
  }
288
291
  } catch (error) {
289
- console.error(error);
292
+ log(null, error);
290
293
  log('error', `Failed to scaffold block`);
291
294
  }
292
295
  } else if (typeKey === 'p') {
293
- let name;
294
- try { name = await input({ message: 'Pattern name:' }); } catch (error) { return; }
296
+ let name = await tui.showInput(patternPrompt);
295
297
  if (!name) { log('warn', 'No name provided.'); return; }
296
298
  const slug = utils.slugify(name);
297
299
  const patternsDir = `${project.path}/patterns`;
@@ -318,12 +320,11 @@ async function handleCreateNew() {
318
320
  project.components.php.globs.push(filePath);
319
321
  }
320
322
  } catch (error) {
321
- console.error(error);
323
+ log(null, error);
322
324
  log('error', `Failed to create pattern`);
323
325
  }
324
326
  } else if (typeKey === 's') {
325
- let name;
326
- try { name = await input({ message: 'Style variation name:' }); } catch (error) { return; }
327
+ let name = await tui.showInput(stylePrompt);
327
328
  if (!name) { log('warn', 'No name provided.'); return; }
328
329
  const slug = utils.slugify(name);
329
330
  const stylesDir = `${project.path}/styles`;
@@ -334,7 +335,7 @@ async function handleCreateNew() {
334
335
  log('warn', `Style variation ${slug}.json already exists.`);
335
336
  return;
336
337
  } catch (error) {
337
-
338
+ //
338
339
  }
339
340
  try {
340
341
  const libDir = path.dirname(fileURLToPath(import.meta.url));
@@ -344,7 +345,7 @@ async function handleCreateNew() {
344
345
  try {
345
346
  templateObj = JSON.parse(templateRaw);
346
347
  } catch (error) {
347
- console.error(error);
348
+ log(null, error);
348
349
  throw new Error('Invalid style variation template JSON');
349
350
  }
350
351
  templateObj.title = name;
@@ -352,7 +353,7 @@ async function handleCreateNew() {
352
353
  await fs.writeFile(filePath, JSON.stringify(templateObj, null, '\t'));
353
354
  log('success', `Created style variation at styles/${slug}.json`);
354
355
  } catch (error) {
355
- console.error(error);
356
+ log(null, error);
356
357
  log('error', `Failed to create style variation`);
357
358
  }
358
359
  }
@@ -393,7 +394,7 @@ export async function loadConfig() {
393
394
  project.paths.images = `${project.path}/${project.paths.src.src}/${project.paths.src.images}`;
394
395
  project.entries = {};
395
396
  } else {
396
- console.error(error);
397
+ log(null, error);
397
398
  log('error', `Failed to load config: ${error.message}`);
398
399
  process.exit(1);
399
400
  }
@@ -412,7 +413,7 @@ export function setupConfigWatcher() {
412
413
  await restartBuild();
413
414
  });
414
415
  configWatcher.on('error', (error) => {
415
- console.error(error);
416
+ log(null, error);
416
417
  log('warn', `Config file watcher error`);
417
418
  });
418
419
  project.configWatcher = configWatcher;
package/lib/tui.js ADDED
@@ -0,0 +1,345 @@
1
+ import blessed from 'blessed';
2
+ import chalk from 'chalk';
3
+ import log from './logging.js';
4
+
5
+ class TUI {
6
+ constructor() {
7
+ this.screen = null;
8
+ this.headerBox = null;
9
+ this.logBox = null;
10
+ this.isInitialized = false;
11
+ this.urls = {
12
+ local: '',
13
+ external: ''
14
+ };
15
+ this.commands = '';
16
+ this.components = [];
17
+ this.watchMode = false;
18
+ this.isPaused = false;
19
+ this._logHistory = [];
20
+ }
21
+
22
+ init() {
23
+ if (this.isInitialized) {
24
+ return;
25
+ }
26
+
27
+ this.screen = blessed.screen({
28
+ smartCSR: true,
29
+ fullUnicode: true,
30
+ title: 'SDC Build WP',
31
+ input: process.stdin,
32
+ output: process.stdout
33
+ });
34
+
35
+ this.headerBox = blessed.box({
36
+ top: 0,
37
+ left: 0,
38
+ width: '100%',
39
+ height: 5,
40
+ content: '',
41
+ tags: true,
42
+ style: {
43
+ fg: 'white',
44
+ bg: 'black',
45
+ border: {
46
+ fg: 'blue'
47
+ }
48
+ },
49
+ border: {
50
+ type: 'line',
51
+ bottom: true
52
+ },
53
+ shrink: false,
54
+ scrollable: false
55
+ });
56
+
57
+ this.logBox = blessed.log({
58
+ top: 5,
59
+ left: 0,
60
+ width: '100%',
61
+ height: '100%-5',
62
+ tags: true,
63
+ scrollable: true,
64
+ alwaysScroll: true,
65
+ scrollbar: {
66
+ ch: ' ',
67
+ track: {
68
+ bg: 'blue'
69
+ },
70
+ style: {
71
+ inverse: true
72
+ }
73
+ },
74
+ mouse: true,
75
+ keys: true,
76
+ vi: true,
77
+ style: {
78
+ fg: 'white',
79
+ bg: 'black'
80
+ }
81
+ });
82
+
83
+ this.screen.append(this.logBox);
84
+ this.screen.append(this.headerBox);
85
+
86
+ this.screen.on('resize', () => {
87
+ this.updateHeader();
88
+ this.screen.render();
89
+ });
90
+
91
+ this.screen.key(['escape', 'q', 'C-c'], () => {
92
+ return false;
93
+ });
94
+
95
+ this.screen.key(['enter', 'return'], () => {
96
+ this.logBox.log('');
97
+ this.screen.render();
98
+ });
99
+
100
+ this.screen.key(['down'], () => {
101
+ this.logBox.scroll(1);
102
+ this.screen.render();
103
+ });
104
+
105
+ this.screen.key(['up'], () => {
106
+ this.logBox.scroll(-1);
107
+ this.screen.render();
108
+ });
109
+
110
+ this.screen.key(['pagedown'], () => {
111
+ this.logBox.scroll(this.logBox.height);
112
+ this.screen.render();
113
+ });
114
+
115
+ this.screen.key(['pageup'], () => {
116
+ this.logBox.scroll(-this.logBox.height);
117
+ this.screen.render();
118
+ });
119
+
120
+ this.updateHeader();
121
+ this.screen.render();
122
+
123
+ this.isInitialized = true;
124
+ }
125
+
126
+ updateHeader() {
127
+ if (!this.isInitialized) {
128
+ return;
129
+ }
130
+
131
+ const lines = [];
132
+
133
+ let titleLine = ' ' + chalk.bold.blue('SDC Build WP');
134
+ if (this.watchMode) {
135
+ titleLine += chalk.gray(' (watch mode)');
136
+ }
137
+ if (this.isPaused) {
138
+ titleLine += chalk.bold.yellow(' [PAUSED]');
139
+ }
140
+ if (this.components.length > 0) {
141
+ titleLine += chalk.gray(' [') + chalk.cyan(this.components.join(', ')) + chalk.gray(']');
142
+ }
143
+ lines.push(titleLine);
144
+
145
+ if (this.urls.local || this.urls.external) {
146
+ let urlLine = ' ';
147
+ if (this.urls.local) {
148
+ urlLine += `Local: ${chalk.green(this.urls.local)}`;
149
+ }
150
+ if (this.urls.external) {
151
+ if (urlLine.length > 1) urlLine += ' ';
152
+ urlLine += `External: ${chalk.green(this.urls.external)}`;
153
+ }
154
+ lines.push(urlLine);
155
+ } else {
156
+ lines.push(' ');
157
+ }
158
+
159
+ if (this.commands) {
160
+ lines.push(' ' + this.commands);
161
+ } else {
162
+ lines.push(' ');
163
+ }
164
+
165
+ lines.push(' ');
166
+
167
+ this.headerBox.setContent(lines.join('\n'));
168
+ }
169
+
170
+ setURLs(local, external) {
171
+ this.urls.local = local;
172
+ this.urls.external = external;
173
+ this.updateHeader();
174
+ this.render();
175
+ }
176
+
177
+ setCommands(commands) {
178
+ this.commands = commands;
179
+ this.updateHeader();
180
+ this.render();
181
+ }
182
+
183
+ setPaused(isPaused) {
184
+ this.isPaused = isPaused;
185
+ this.updateHeader();
186
+ this.render();
187
+ }
188
+
189
+ setComponents(components, watchMode = false) {
190
+ this.components = components;
191
+ this.watchMode = watchMode;
192
+ this.updateHeader();
193
+ this.render();
194
+ }
195
+
196
+ log(message) {
197
+ this._logHistory.push(message);
198
+ if (!this.isInitialized) {
199
+ log(null, message);
200
+ return;
201
+ }
202
+ this.logBox.log(message);
203
+ this.render();
204
+ }
205
+
206
+ getLogHistory() {
207
+ return this._logHistory.join('\n');
208
+ }
209
+
210
+ render() {
211
+ if (this.isInitialized && this.screen) {
212
+ this.screen.render();
213
+ }
214
+ }
215
+
216
+ async showMenu(options, prompt = 'Choose an option:') {
217
+ return new Promise((resolve) => {
218
+ const menu = blessed.list({
219
+ parent: this.screen,
220
+ top: 'center',
221
+ left: 'center',
222
+ width: '50%',
223
+ height: options.length + 4,
224
+ label: ` ${prompt} `,
225
+ items: options,
226
+ keys: true,
227
+ mouse: true,
228
+ border: 'line',
229
+ style: {
230
+ fg: 'white',
231
+ bg: 'black',
232
+ border: { fg: 'blue' },
233
+ selected: { bg: 'blue', fg: 'white' }
234
+ }
235
+ });
236
+ menu.focus();
237
+ const stopLogScroll = (ch, key) => {
238
+ if (['up', 'down', 'pagedown', 'pageup'].includes(key.name)) {
239
+ return false;
240
+ }
241
+ };
242
+ this.logBox.ignoreKeys = true;
243
+ menu.on('detach', () => {
244
+ this.logBox.ignoreKeys = false;
245
+ });
246
+ menu.on('keypress', stopLogScroll);
247
+ this.screen.render();
248
+ menu.on('select', (item, idx) => {
249
+ menu.destroy();
250
+ this.screen.render();
251
+ resolve({ value: item.getText(), index: idx });
252
+ });
253
+ menu.on('cancel', () => {
254
+ menu.destroy();
255
+ this.screen.render();
256
+ resolve(null);
257
+ });
258
+ menu.key(['escape', 'q'], () => {
259
+ menu.emit('cancel');
260
+ });
261
+ });
262
+ }
263
+
264
+ async showInput(prompt = 'Enter value:') {
265
+ return new Promise((resolve) => {
266
+ const box = blessed.box({
267
+ parent: this.screen,
268
+ top: 'center',
269
+ left: 'center',
270
+ width: '50%',
271
+ height: 5,
272
+ label: ` ${prompt} `,
273
+ border: 'line',
274
+ style: {
275
+ fg: 'white',
276
+ bg: 'black',
277
+ border: { fg: 'blue' }
278
+ }
279
+ });
280
+ const input = blessed.textbox({
281
+ parent: box,
282
+ top: 2,
283
+ left: 2,
284
+ width: '90%',
285
+ height: 1,
286
+ inputOnFocus: true,
287
+ style: {
288
+ fg: 'white',
289
+ bg: 'black',
290
+ focus: { bg: 'blue' }
291
+ }
292
+ });
293
+ input.focus();
294
+ this.screen.render();
295
+ input.on('submit', (value) => {
296
+ box.destroy();
297
+ this.screen.render();
298
+ resolve(value);
299
+ });
300
+ input.on('cancel', () => {
301
+ box.destroy();
302
+ this.screen.render();
303
+ resolve(null);
304
+ });
305
+ input.key(['escape', 'q'], () => {
306
+ input.emit('cancel');
307
+ });
308
+ });
309
+ }
310
+
311
+ getState() {
312
+ return {
313
+ urls: { ...this.urls },
314
+ commands: this.commands,
315
+ components: [...this.components],
316
+ watchMode: this.watchMode,
317
+ isPaused: this.isPaused
318
+ };
319
+ }
320
+
321
+ setState(state) {
322
+ if (state) {
323
+ this.urls = { ...state.urls };
324
+ this.commands = state.commands;
325
+ this.components = [...state.components];
326
+ this.watchMode = state.watchMode;
327
+ this.isPaused = state.isPaused;
328
+ this.updateHeader();
329
+ this.render();
330
+ }
331
+ }
332
+
333
+ destroy() {
334
+ if (this.isInitialized && this.screen) {
335
+ this.screen.destroy();
336
+ this.isInitialized = false;
337
+ this.screen = null;
338
+ this.headerBox = null;
339
+ this.logBox = null;
340
+ }
341
+ }
342
+ }
343
+
344
+ const tui = new TUI();
345
+ export default tui;
package/lib/utils.js CHANGED
@@ -4,6 +4,7 @@ import { readdir } from 'node:fs/promises';
4
4
  import { fileURLToPath } from 'url';
5
5
  import project from './project.js';
6
6
  import log from './logging.js';
7
+ import tui from './tui.js';
7
8
 
8
9
  export async function getThisPackageVersion() {
9
10
  return JSON.parse(await fs.readFile(path.join(path.dirname(fileURLToPath(import.meta.url)), '../package.json'))).version
@@ -11,6 +12,7 @@ export async function getThisPackageVersion() {
11
12
 
12
13
  export function clearScreen() {
13
14
  if (!process.stdout.isTTY) { return; }
15
+ if (tui.isInitialized) { return; }
14
16
  process.stdout.write('\x1B[2J\x1B[0f');
15
17
  }
16
18
 
@@ -19,7 +21,7 @@ export async function stopActiveComponents() {
19
21
  try {
20
22
  await project.configWatcher.close();
21
23
  } catch (error) {
22
- console.error(error);
24
+ log(null, error);
23
25
  log('error', 'Failed to stop config file watcher');
24
26
  }
25
27
  }
@@ -27,7 +29,7 @@ export async function stopActiveComponents() {
27
29
  try {
28
30
  project.components.server.server.exit();
29
31
  } catch (error) {
30
- console.error(error);
32
+ this.log(null, error);
31
33
  log('error', 'Failed to stop server');
32
34
  }
33
35
  }
@@ -39,7 +41,8 @@ export async function stopActiveComponents() {
39
41
  try {
40
42
  await component.watcher.close();
41
43
  } catch (error) {
42
- console.warn(`Failed to stop watcher for ${component.constructor.name}:`, error.message);
44
+ log(null, error);
45
+ log('error', `Failed to stop watcher for ${component.constructor.name}`);
43
46
  }
44
47
  }
45
48
  }
@@ -217,7 +220,7 @@ export async function ensureDir(dir) {
217
220
  await fs.mkdir(dir, { recursive: true });
218
221
  return true;
219
222
  } catch (error) {
220
- console.error(error);
223
+ log(null, error);
221
224
  log('error', `Failed to create directory ${dir}`);
222
225
  return false;
223
226
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdc-build-wp",
3
- "version": "5.2.0",
3
+ "version": "5.3.1",
4
4
  "description": "Custom WordPress build process.",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -22,13 +22,13 @@
22
22
  "sdc-build-wp": "./index.js"
23
23
  },
24
24
  "dependencies": {
25
- "@inquirer/prompts": "^7.9.0",
26
25
  "@stylistic/eslint-plugin": "^5.4.0",
27
26
  "@stylistic/stylelint-plugin": "^4.0.0",
28
27
  "@typescript-eslint/eslint-plugin": "^8.46.1",
29
28
  "@typescript-eslint/parser": "^8.46.1",
30
29
  "@wordpress/scripts": "^30.25.0",
31
30
  "autoprefixer": "^10.4.21",
31
+ "blessed": "^0.1.81",
32
32
  "browser-sync": "^3.0.4",
33
33
  "chalk": "^5.6.2",
34
34
  "chokidar": "^4.0.3",