sdc-build-wp 5.0.2 → 5.1.0

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
@@ -26,7 +26,8 @@ export async function build(watch = false) {
26
26
 
27
27
  promisesBuilds.push(
28
28
  project.components[build].init().catch(error => {
29
- log('error', `Failed to initialize ${build} component: ${error.message}`);
29
+ console.error(error);
30
+ log('error', `Failed to initialize ${build} component`);
30
31
  return { failed: true, component: build, error };
31
32
  })
32
33
  );
@@ -48,7 +49,7 @@ export async function build(watch = false) {
48
49
  project.builds.splice(project.builds.indexOf('server'), 1);
49
50
  project.builds.push('server');
50
51
  log('info', `Started watching [${project.builds.join(', ')}]`);
51
- log('info', `[r] to restart, [c] to clear cache, [p] to pause/resume, [q] to quit`);
52
+ log('info', `[r]estart, [c]lear cache, [p]ause/resume, [n]ew component, [q]uit`);
52
53
 
53
54
  for (let build of project.builds) {
54
55
  try {
@@ -108,7 +108,7 @@ export default class BlocksComponent extends BaseComponent {
108
108
  }
109
109
  }
110
110
  if (workingBlockJson === null) {
111
- this.log('error', `Failed building ${entry} blocks - no block.json found.`);
111
+ this.log('error', `Failed building ${entryLabel} - no block.json found.`);
112
112
  return false;
113
113
  }
114
114
 
@@ -177,6 +177,19 @@ export default class BlocksComponent extends BaseComponent {
177
177
  }
178
178
  }
179
179
 
180
+ addBlock(blockPath) {
181
+ if (!this.globs.includes(blockPath)) {
182
+ this.globs.push(blockPath);
183
+ if (this.watcher) {
184
+ this.watcher.add([`${blockPath}/src`, `${blockPath}/src/**/*`]);
185
+ }
186
+ this.build(blockPath).catch(err => {
187
+ console.error(err);
188
+ this.log('error', `Failed initial build for new block ${blockPath}`);
189
+ });
190
+ }
191
+ }
192
+
180
193
  watch() {
181
194
  const watchPaths = this.globs.map(block => `${block}/src`);
182
195
  const buildQueue = new Set();
@@ -277,7 +290,8 @@ export default class BlocksComponent extends BaseComponent {
277
290
  if (path.endsWith('.js')) {
278
291
  if (!this.project.components.scripts.isBuilding) {
279
292
  this.project.components.scripts.lint(path).catch(lintError => {
280
- this.log('warn', `Linting failed for ${path}: ${lintError.message}`);
293
+ console.error(lintError);
294
+ this.log('warn', `Linting failed for ${path}`);
281
295
  });
282
296
  }
283
297
  }
@@ -99,6 +99,9 @@ export default class ScriptsComponent extends BaseComponent {
99
99
  if (!this.project.isRunning) { return; }
100
100
  try {
101
101
  await this.process();
102
+ if (this.project.components.blocks && (path.endsWith('.js') || path.endsWith('.jsx') || path.endsWith('.ts') || path.endsWith('.tsx'))) {
103
+ await this.checkAndRebuildAffectedBlocks(path);
104
+ }
102
105
  } catch (error) {
103
106
  console.error(error);
104
107
  this.log('error', `Failed to process scripts`);
@@ -106,6 +109,35 @@ export default class ScriptsComponent extends BaseComponent {
106
109
  });
107
110
  }
108
111
 
112
+ async checkAndRebuildAffectedBlocks(changedPath) {
113
+ if (!this.project.components.blocks || !this.project.components.blocks.globs) {
114
+ return;
115
+ }
116
+ const affectedBlocks = new Set();
117
+ for (const blockPath of this.project.components.blocks.globs) {
118
+ try {
119
+ const dependencies = await this.project.components.blocks.getBlockDependencies(blockPath);
120
+ if (dependencies.includes(changedPath)) {
121
+ affectedBlocks.add(blockPath);
122
+ }
123
+ } catch (error) {
124
+ console.error(error);
125
+ }
126
+ }
127
+
128
+ if (!affectedBlocks.size) { return; }
129
+ for (const blockPath of affectedBlocks) {
130
+ try {
131
+ if (this.project.components.server?.server) {
132
+ this.project.components.server.server.notify('Building block...', 5000);
133
+ }
134
+ await this.project.components.blocks.process(blockPath);
135
+ } catch (error) {
136
+ //
137
+ }
138
+ }
139
+ }
140
+
109
141
  async lint(entry, options) {
110
142
  try {
111
143
  const eslint = new ESLint({
package/lib/project.js CHANGED
@@ -3,6 +3,7 @@ import { hideBin } from 'yargs/helpers';
3
3
  import { readFile } from 'fs/promises';
4
4
  import path from 'path';
5
5
  import { promises as fs } from 'fs';
6
+ import { fileURLToPath } from 'url';
6
7
  import chokidar from 'chokidar';
7
8
  import { restartBuild } from './build.js';
8
9
  import * as utils from './utils.js';
@@ -10,6 +11,7 @@ import log from './logging.js';
10
11
  import * as LibComponents from './components/index.js';
11
12
  import help from './help.js';
12
13
  import { validateConfig, mergeWithDefaults } from './config-validator.js';
14
+ import { input, select } from '@inquirer/prompts';
13
15
 
14
16
  let project = {
15
17
  config: {},
@@ -114,7 +116,7 @@ export async function init() {
114
116
  project.builds = project.argv.builds ? (Array.isArray(project.argv.builds) ? project.argv.builds : project.argv.builds.split(',')) : Object.keys(project.components).filter(component => component !== 'cache');
115
117
 
116
118
  if (!project.argv['no-cache'] && !project.builds.includes('cache')) {
117
- project.builds.unshift('cache');
119
+ project.builds = ['cache', ...project.builds];
118
120
  }
119
121
 
120
122
  if (Object.keys(project.entries).length === 0) {
@@ -174,11 +176,19 @@ export async function init() {
174
176
  export function keypressListen() {
175
177
  if (!process.stdin.isTTY) { return; }
176
178
 
177
- process.stdin.setRawMode(true);
178
- process.stdin.resume();
179
- process.stdin.setEncoding('utf8');
179
+ let isPrompting = false;
180
180
 
181
- process.stdin.on('data', async (key) => {
181
+ const installRaw = () => {
182
+ if (!process.stdin.isTTY) return;
183
+ try { process.stdin.setRawMode(true); } catch {}
184
+ process.stdin.resume();
185
+ process.stdin.setEncoding('utf8');
186
+ };
187
+
188
+ installRaw();
189
+
190
+ const handler = async (key) => {
191
+ if (isPrompting) { return; }
182
192
  switch (key) {
183
193
  case '\r': // [Enter]/[Return]
184
194
  console.log('\r');
@@ -203,8 +213,149 @@ export function keypressListen() {
203
213
  log('info', 'Restarted build process');
204
214
  await restartBuild();
205
215
  break;
216
+ case 'n': // New menu
217
+ isPrompting = true;
218
+ process.stdin.removeListener('data', handler);
219
+ try {
220
+ try { process.stdin.setRawMode(false); } catch {}
221
+ await handleCreateNew();
222
+ } finally {
223
+ isPrompting = false;
224
+ installRaw();
225
+ process.stdin.on('data', handler);
226
+ }
227
+ break;
206
228
  }
207
- });
229
+ };
230
+
231
+ process.stdin.on('data', handler);
232
+ }
233
+
234
+ async function handleCreateNew() {
235
+ if (!project.isRunning) {
236
+ log('warn', 'Build process paused. Press [p] to resume if needed. Continuing creation.');
237
+ }
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; }
253
+ if (typeKey === 'b') {
254
+ let name;
255
+ try {
256
+ name = await input({ message: 'Block name:' });
257
+ } catch (error) {
258
+ return;
259
+ }
260
+ if (!name) { log('warn', 'No name provided.'); return; }
261
+ const slug = utils.slugify(name);
262
+ const blockDir = `${project.path}/blocks/${slug}`;
263
+ const srcDir = `${blockDir}/src`;
264
+ try {
265
+ await fs.access(blockDir);
266
+ log('warn', `Block ${slug} already exists.`);
267
+ return;
268
+ } catch (error) {
269
+ //
270
+ }
271
+ const created = await utils.ensureDir(srcDir);
272
+ if (!created) { return; }
273
+ const blockJsonPath = `${srcDir}/block.json`;
274
+ const indexJsPath = `${srcDir}/index.js`;
275
+ try {
276
+ const libDir = path.dirname(fileURLToPath(import.meta.url));
277
+ const templateBlockJsonRaw = await fs.readFile(path.resolve(libDir, 'templates/block/block.json'), 'utf8');
278
+ let templateBlock = JSON.parse(templateBlockJsonRaw);
279
+ templateBlock.name = `custom/${slug}`;
280
+ templateBlock.title = name;
281
+ await fs.writeFile(blockJsonPath, JSON.stringify(templateBlock, null, '\t'));
282
+ const templateIndex = await fs.readFile(path.resolve(libDir, 'templates/block/index.js'), 'utf8');
283
+ await fs.writeFile(indexJsPath, templateIndex);
284
+ log('success', `Created block at blocks/${slug}`);
285
+ if (project.components.blocks) {
286
+ project.components.blocks.addBlock(blockDir);
287
+ }
288
+ } catch (error) {
289
+ console.error(error);
290
+ log('error', `Failed to scaffold block`);
291
+ }
292
+ } else if (typeKey === 'p') {
293
+ let name;
294
+ try { name = await input({ message: 'Pattern name:' }); } catch (error) { return; }
295
+ if (!name) { log('warn', 'No name provided.'); return; }
296
+ const slug = utils.slugify(name);
297
+ const patternsDir = `${project.path}/patterns`;
298
+ await utils.ensureDir(patternsDir);
299
+ const filePath = `${patternsDir}/${slug}.php`;
300
+ try {
301
+ await fs.access(filePath);
302
+ log('warn', `Pattern ${slug}.php already exists.`);
303
+ return;
304
+ } catch (error) {
305
+ //
306
+ }
307
+ try {
308
+ const libDir = path.dirname(fileURLToPath(import.meta.url));
309
+ const templatePath = path.resolve(libDir, 'templates/pattern/pattern.php');
310
+ let template = await fs.readFile(templatePath, 'utf8');
311
+ template = template.replace(/Title:\s.*$/m, `Title: ${name}`);
312
+ template = template.replace(/Slug:\s.*$/m, `Slug: custom/${slug}`);
313
+ await fs.writeFile(filePath, template);
314
+ log('success', `Created pattern at patterns/${slug}.php`);
315
+ if (project.components.php?.watcher) {
316
+ project.components.php.watcher.add(filePath);
317
+ } else if (project.components.php?.globs && !project.components.php.globs.includes(filePath)) {
318
+ project.components.php.globs.push(filePath);
319
+ }
320
+ } catch (error) {
321
+ console.error(error);
322
+ log('error', `Failed to create pattern`);
323
+ }
324
+ } else if (typeKey === 's') {
325
+ let name;
326
+ try { name = await input({ message: 'Style variation name:' }); } catch (error) { return; }
327
+ if (!name) { log('warn', 'No name provided.'); return; }
328
+ const slug = utils.slugify(name);
329
+ const stylesDir = `${project.path}/styles`;
330
+ await utils.ensureDir(stylesDir);
331
+ const filePath = `${stylesDir}/${slug}.json`;
332
+ try {
333
+ await fs.access(filePath);
334
+ log('warn', `Style variation ${slug}.json already exists.`);
335
+ return;
336
+ } catch (error) {
337
+
338
+ }
339
+ try {
340
+ const libDir = path.dirname(fileURLToPath(import.meta.url));
341
+ const templatePath = path.resolve(libDir, 'templates/style/style.json');
342
+ let templateRaw = await fs.readFile(templatePath, 'utf8');
343
+ let templateObj;
344
+ try {
345
+ templateObj = JSON.parse(templateRaw);
346
+ } catch (error) {
347
+ console.error(error);
348
+ throw new Error('Invalid style variation template JSON');
349
+ }
350
+ templateObj.title = name;
351
+ templateObj.slug = slug;
352
+ await fs.writeFile(filePath, JSON.stringify(templateObj, null, '\t'));
353
+ log('success', `Created style variation at styles/${slug}.json`);
354
+ } catch (error) {
355
+ console.error(error);
356
+ log('error', `Failed to create style variation`);
357
+ }
358
+ }
208
359
  }
209
360
 
210
361
  export async function convertPackageToConfig() {
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://schemas.wp.org/trunk/block.json",
3
+ "apiVersion": 3,
4
+ "version": "1.0.0",
5
+ "name": "custom/name-of-block",
6
+ "title": "Name of Block",
7
+ "category": "custom",
8
+ "icon": "admin-page",
9
+ "editorScript": "file:./index.js"
10
+ }
@@ -0,0 +1,19 @@
1
+ import { registerBlockType } from '@wordpress/blocks';
2
+ import { useBlockProps } from '@wordpress/block-editor';
3
+
4
+ import metadata from './block.json';
5
+
6
+ // import './style.scss';
7
+ // import './editor.scss';
8
+
9
+ registerBlockType(metadata.name, {
10
+ edit: ({ attributes, setAttributes }) => {
11
+ const blockProps = useBlockProps();
12
+ return (
13
+ <div { ...blockProps }><em>Placeholder for {metadata.title}</em></div>
14
+ );
15
+ },
16
+ save: ({ attributes }) => {
17
+ return null;
18
+ },
19
+ });
@@ -0,0 +1,8 @@
1
+ <?php
2
+ /**
3
+ * Title: New Pattern
4
+ * Slug: custom/new-pattern
5
+ */
6
+ ?>
7
+
8
+ New Pattern
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://schemas.wp.org/trunk/theme.json",
3
+ "version": 3,
4
+ "title": "New Style",
5
+ "slug": "new-style",
6
+ "blockTypes": [ "core/group" ],
7
+ "styles": {
8
+ "css": "& {}"
9
+ }
10
+ }
package/lib/utils.js CHANGED
@@ -3,6 +3,7 @@ import { promises as fs } from 'fs';
3
3
  import { readdir } from 'node:fs/promises';
4
4
  import { fileURLToPath } from 'url';
5
5
  import project from './project.js';
6
+ import log from './logging.js';
6
7
 
7
8
  export async function getThisPackageVersion() {
8
9
  return JSON.parse(await fs.readFile(path.join(path.dirname(fileURLToPath(import.meta.url)), '../package.json'))).version
@@ -197,3 +198,23 @@ export function addEntriesByFiletypes(filetypes = []) {
197
198
  }
198
199
  return finalFiles;
199
200
  }
201
+
202
+ export function slugify(str) {
203
+ return str
204
+ .toLowerCase()
205
+ .replace(/[^a-z0-9\s-]/g, '')
206
+ .trim()
207
+ .replace(/\s+/g, '-')
208
+ .replace(/-+/g, '-');
209
+ }
210
+
211
+ export async function ensureDir(dir) {
212
+ try {
213
+ await fs.mkdir(dir, { recursive: true });
214
+ return true;
215
+ } catch (error) {
216
+ console.error(error);
217
+ log('error', `Failed to create directory ${dir}`);
218
+ return false;
219
+ }
220
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdc-build-wp",
3
- "version": "5.0.2",
3
+ "version": "5.1.0",
4
4
  "description": "Custom WordPress build process.",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -22,24 +22,25 @@
22
22
  "sdc-build-wp": "./index.js"
23
23
  },
24
24
  "dependencies": {
25
- "@stylistic/eslint-plugin": "^5.2.2",
25
+ "@inquirer/prompts": "^7.8.1",
26
+ "@stylistic/eslint-plugin": "^5.2.3",
26
27
  "@stylistic/stylelint-plugin": "^4.0.0",
27
- "@typescript-eslint/eslint-plugin": "^8.38.0",
28
- "@typescript-eslint/parser": "^8.38.0",
29
- "@wordpress/scripts": "^30.20.0",
28
+ "@typescript-eslint/eslint-plugin": "^8.39.0",
29
+ "@typescript-eslint/parser": "^8.39.0",
30
+ "@wordpress/scripts": "^30.21.0",
30
31
  "autoprefixer": "^10.4.21",
31
32
  "browser-sync": "^3.0.4",
32
- "chalk": "^5.4.1",
33
+ "chalk": "^5.5.0",
33
34
  "chokidar": "^4.0.3",
34
35
  "esbuild": "^0.25.8",
35
- "eslint": "^9.32.0",
36
- "fs-extra": "^11.3.0",
36
+ "eslint": "^9.33.0",
37
+ "fs-extra": "^11.3.1",
37
38
  "postcss": "^8.5.6",
38
39
  "postcss-scss": "^4.0.9",
39
40
  "postcss-sort-media-queries": "^5.2.0",
40
- "sass": "^1.89.2",
41
+ "sass": "^1.90.0",
41
42
  "sharp": "^0.34.3",
42
- "stylelint": "^16.23.0",
43
+ "stylelint": "^16.23.1",
43
44
  "svgo": "^4.0.0",
44
45
  "tail": "^2.2.6",
45
46
  "yargs": "^18.0.0"