sdc-build-wp 4.1.4 → 4.3.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/README.md CHANGED
@@ -4,6 +4,7 @@
4
4
  npm install sdc-build-wp
5
5
  sdc-build-wp # build
6
6
  sdc-build-wp --watch # build and watch
7
+ sdc-build-wp --watch --builds=style,scripts # comma-seperated list of components to include
7
8
  ```
8
9
 
9
10
  ## Develop
package/index.js CHANGED
@@ -1,276 +1,46 @@
1
1
  #!/usr/bin/env node
2
- import path from 'path';
3
- import project from './lib/project.js';
4
2
  import parseArgs from 'minimist';
5
3
  const argv = parseArgs(process.argv.slice(2));
6
- import chokidar from 'chokidar';
7
- import { glob, readdir } from 'node:fs/promises';
8
- import { existsSync } from 'node:fs';
4
+ import { promises as fs } from 'fs';
9
5
  import { Tail } from 'tail';
10
-
6
+ import project from './lib/project.js';
11
7
  import log from './lib/logging.js';
12
- import { buildSass, buildSassTheme, getImportedFiles as getImportedFilesSass } from './lib/style.js';
13
- import buildJS from './lib/scripts.js';
14
- import { default as buildPHP, shouldPHPLint } from './lib/php.js';
15
- import buildBlock from './lib/blocks.js';
16
- import buildImages from './lib/images.js';
17
- import buildFonts from './lib/fonts.js';
18
- import buildBrowserSync from './lib/browsersync.js';
19
-
20
- let paths = {
21
- theme: {
22
- json: `${project.path}/theme.json`,
23
- scss: `${project.path}/_src/style/partials/_theme.scss`
24
- },
25
- nodeModules: `${project.path}/node_modules`,
26
- composer: {
27
- vendor: `${project.path}/vendor`
28
- },
29
- images: project.package?.sdc?.imagesPath || `${project.path}/_src/images`,
30
- errorLog: process.env.ERROR_LOG_PATH || project.package.sdc?.error_log_path || '../../../../../logs/php/error.log'
8
+ import * as LibComponents from './lib/components/index.js';
9
+
10
+ project.components = {
11
+ style: new LibComponents.style(),
12
+ scripts: new LibComponents.scripts(),
13
+ blocks: new LibComponents.blocks(),
14
+ images: new LibComponents.images(),
15
+ fonts: new LibComponents.fonts(),
16
+ php: new LibComponents.php(),
17
+ server: new LibComponents.server()
31
18
  };
32
19
 
33
- let chokidarOpts = {
34
- ignoreInitial: true,
35
- ignored: [
36
- paths.nodeModules,
37
- paths.composer.vendor,
38
- paths.theme.scss
39
- ]
40
- };
41
-
42
- let globs = {};
43
- let entries = {};
44
- let filesSass = [];
45
- let filesJS = [];
46
-
47
- let builds = argv.builds ? argv.builds.split(',') : [
48
- 'sass',
49
- 'js',
50
- 'blocks',
51
- 'images',
52
- 'fonts',
53
- 'php'
54
- ];
20
+ let builds = argv.builds ? argv.builds.split(',') : Object.keys(project.components).map(key => project.components[key].slug);
55
21
 
56
22
  (async() => {
57
23
 
58
24
  let initialBuildTimerStart = Date.now();
59
25
  log('info', `Starting initial build`);
60
-
61
- if (builds.includes('sass')) {
62
- globs.sass = await Array.fromAsync(
63
- glob(project.package?.sdc?.sassGlobPath ||
64
- `${project.path}{/_src/style,/blocks}/**/*.scss`)
65
- );
66
- }
67
- if (builds.includes('js')) {
68
- globs.js = await Array.fromAsync(
69
- glob(project.package?.sdc?.jsGlobPath ||
70
- `${project.path}/_src/scripts/**/*.js`)
71
- );
72
- }
73
- if (builds.includes('blocks')) {
74
- globs.blocks = await Array.fromAsync(
75
- glob(`${project.path}/blocks/*`)
76
- );
77
- globs.blocksSass = await Array.fromAsync(
78
- glob(`${project.path}/blocks/*/src/*.scss`)
79
- );
80
- // for (var filename of globs.blocksSass) {
81
- // entries[`blocks/${path.basename(path.dirname(filename))}/style`] = [ filename ];
82
- // }
26
+ for (let build of builds) {
27
+ await project.components[build].init();
83
28
  }
84
- if (builds.includes('images')) {
85
- globs.images = await Array.fromAsync(
86
- glob(project.package?.sdc?.imagesPath ||
87
- `${paths.images}/**/*`)
88
- );
89
- globs.imageDirectories = [
90
- paths.images,
91
- ...await getAllSubdirectories(paths.images)
92
- ];
93
- }
94
- if (builds.includes('php')) {
95
- globs.php = await Array.fromAsync(
96
- glob(project.package?.sdc?.jsGlobPath ||
97
- `${project.path}/**/*.php`)
98
- );
99
- globs.blocksPHP = await Array.fromAsync(
100
- glob(`${project.path}/blocks/*/build/*.php`)
101
- );
102
- chokidarOpts.ignored = [
103
- ...chokidarOpts.ignored,
104
- ...globs.blocksPHP
105
- ];
106
- }
107
-
108
- for (const [name, files] of Object.entries(project.package.sdc.entries)) {
109
- entries[name] = [];
110
- files.forEach(function(file) {
111
- entries[name].push(project.path + file);
112
- });
113
- }
114
-
115
- for (const [name, files] of Object.entries(entries)) {
116
- files.forEach(function(file) {
117
- switch (path.parse(file).ext) {
118
- case '.scss':
119
- if (builds.includes('sass')) {
120
- filesSass.push({
121
- 'name': name,
122
- 'file': file
123
- });
124
- }
125
- break;
126
- case '.js':
127
- if (builds.includes('js')) {
128
- filesJS.push({
129
- 'name': name,
130
- 'file': file
131
- });
132
- }
133
- break;
134
- }
135
- });
136
- }
137
-
138
- if (builds.includes('sass')) {
139
- await runSass(null, true);
140
- }
141
- if (builds.includes('js')) {
142
- await runJS();
143
- }
144
- if (builds.includes('blocks')) {
145
- await runBlocks();
146
- }
147
- if (builds.includes('images')) {
148
- await frontrunImages();
149
- }
150
- if (builds.includes('fonts')) {
151
- await buildFonts(project.path + '/_src/fonts');
152
- }
153
- // if (builds.includes('php') && shouldPHPLint) {
154
- // await runPHP(null, 'warn'); // this errors "Fatal error: Allowed memory size"
155
- // }
156
-
157
29
  log('info', `Finished initial build in ${Math.round((Date.now() - initialBuildTimerStart) / 1000)} seconds`);
158
30
 
159
31
  if (argv.watch) {
160
-
161
- buildBrowserSync();
162
-
163
- if (builds.includes('sass')) {
164
- chokidar.watch([
165
- ...[paths.theme.json],
166
- globs.sass
167
- ], {
168
- ...chokidarOpts
169
- }).on('all', (event, path) => {
170
- let hasRanSingle = false;
171
- for (var block of filesSass) {
172
- if (path == block.file || getImportedFilesSass(block.file).includes(path)) {
173
- runSass(block.file, path == paths.theme.json);
174
- hasRanSingle = true;
175
- }
176
- }
177
- if (!hasRanSingle) {
178
- runSass(null, path == paths.theme.json);
179
- }
180
- });
181
- }
182
-
183
- if (builds.includes('js')) {
184
- chokidar.watch(globs.js, {
185
- ...chokidarOpts
186
- }).on('all', (event, path) => {
187
- runJS();
188
- });
32
+ for (let build of builds) {
33
+ await project.components[build].watch();
189
34
  }
190
-
191
- if (builds.includes('blocks')) {
192
- for (let block of globs.blocks) {
193
- chokidar.watch(`${block}/src`, {
194
- ...chokidarOpts
195
- }).on('all', (event, path) => {
196
- runBlocks(block);
197
- });
198
- }
199
- }
200
-
201
- if (builds.includes('images')) {
202
- chokidar.watch(paths.images, chokidarOpts).on('all', (event, path) => {
203
- frontrunImages();
204
- });
205
- }
206
-
207
- if (builds.includes('php') && shouldPHPLint) {
208
- chokidar.watch(globs.php, {
209
- ...chokidarOpts
210
- }).on('all', (event, path) => {
211
- runPHP(path);
212
- });
213
- }
214
-
215
- if (existsSync(paths.errorLog)) {
216
- let errorLogTail = new Tail(paths.errorLog);
35
+ try {
36
+ await fs.access(project.paths.errorLog);
37
+ let errorLogTail = new Tail(project.paths.errorLog);
217
38
  errorLogTail.on('line', function(data) {
218
39
  log('php', data);
219
40
  });
220
- } else {
221
- log('info', `Cannot find error log @ ${paths.errorLog}. Skipping watching php error logs`);
41
+ } catch (error) {
42
+ log('info', `Cannot find error log @ ${project.paths.errorLog}. Skipping watching php error logs`);
222
43
  }
223
44
  }
224
45
 
225
46
  })();
226
-
227
- async function frontrunImages() {
228
- const promisesImages = globs.imageDirectories.map(directory => buildImages(directory));
229
- await Promise.all(promisesImages);
230
- }
231
-
232
- async function runBlocks(singleBlock) {
233
- if (singleBlock) {
234
- await buildBlock(singleBlock);
235
- } else {
236
- const promisesBlocks = globs.blocks.map(block => buildBlock(block));
237
- await Promise.all(promisesBlocks);
238
- }
239
- }
240
-
241
- async function runSass(singleEntry, buildTheme = true) {
242
- if (buildTheme) {
243
- await buildSassTheme();
244
- }
245
- for (var block of filesSass) {
246
- if (!singleEntry || singleEntry == block.file) {
247
- await buildSass(block.file, block.name, globs.sass);
248
- if (singleEntry == block.file) {
249
- break;
250
- }
251
- }
252
- }
253
- }
254
-
255
- async function runJS() {
256
- const promisesJS = filesJS.map(block => buildJS(block.file, block.name, globs.js));
257
- await Promise.all(promisesJS);
258
- }
259
-
260
- async function runPHP(file, method) {
261
- await buildPHP(file, method);
262
- }
263
-
264
- async function getAllSubdirectories(dir) {
265
- let subdirectories = [];
266
- const subdirectoriesEntries = await readdir(dir, { withFileTypes: true });
267
- for (const subdirectoriesEntry of subdirectoriesEntries) {
268
- if (subdirectoriesEntry.isDirectory()) {
269
- const subdirPath = path.join(dir, subdirectoriesEntry.name);
270
- subdirectories.push(subdirPath);
271
- const nestedSubdirs = await getAllSubdirectories(subdirPath);
272
- subdirectories = subdirectories.concat(nestedSubdirs);
273
- }
274
- }
275
- return subdirectories;
276
- }
@@ -0,0 +1,47 @@
1
+ import path from 'path';
2
+ import * as utils from '../utils.js';
3
+ import project from '../project.js';
4
+ import log from '../logging.js';
5
+ import chokidar from 'chokidar';
6
+ import { glob } from 'node:fs/promises';
7
+
8
+ class BaseComponent {
9
+
10
+ constructor() {
11
+ this.timer = null;
12
+ this.path = path;
13
+ this.utils = utils;
14
+ this.project = project;
15
+ this.log = log;
16
+ this.chokidar = chokidar;
17
+ this.glob = glob;
18
+ this.files = [];
19
+ this.globs = [];
20
+ this.slug = 'base';
21
+ }
22
+
23
+ async init() {
24
+ //
25
+ }
26
+
27
+ start() {
28
+ this.timer = Date.now();
29
+ }
30
+
31
+ end(options) {
32
+ options = Object.assign({}, {
33
+ verb: 'Built',
34
+ itemLabel: null,
35
+ timerStart: this.timer,
36
+ timerEnd: Date.now()
37
+ }, options);
38
+ this.log('success', `${options.verb}${options.itemLabel ? ` ${options.itemLabel}` : ''} in ${options.timerEnd - options.timerStart}ms`);
39
+ }
40
+
41
+ async watch() {
42
+ //
43
+ }
44
+
45
+ }
46
+
47
+ export { BaseComponent as default }
@@ -0,0 +1,112 @@
1
+ import BaseComponent from './base.js';
2
+ import { stat } from 'fs/promises';
3
+ import { spawn } from 'child_process';
4
+ import process from 'process';
5
+
6
+ class BlocksComponent extends BaseComponent {
7
+
8
+ constructor() {
9
+ super();
10
+ this.slug = 'blocks';
11
+ }
12
+
13
+ async init() {
14
+ this.globs = await Array.fromAsync(
15
+ this.glob(`${this.project.path}/blocks/*`)
16
+ );
17
+ this.globsSass = await Array.fromAsync(
18
+ this.glob(`${this.project.path}/blocks/*/src/*.scss`)
19
+ );
20
+ // for (var filename of this.globsSass) {
21
+ // this.project.entries[`blocks/${this.path.basename(this.path.dirname(filename))}/style`] = [ filename ];
22
+ // }
23
+ await this.process();
24
+ }
25
+
26
+ async build(entry, options) {
27
+ options = Object.assign({}, {}, options);
28
+ let entryLabel = entry.replace(this.project.path, '');
29
+
30
+ let timerStart = Date.now();
31
+
32
+ this.start();
33
+
34
+ let workingBlockJson = null;
35
+ let potentialBlockJsonLocations = [
36
+ `${entry}/src/block.json`,
37
+ // `${entry}/block.json`
38
+ ];
39
+ for (var location of potentialBlockJsonLocations) {
40
+ try {
41
+ await stat(location);
42
+ workingBlockJson = location
43
+ break;
44
+ } catch (error) {
45
+ //
46
+ }
47
+ }
48
+ if (workingBlockJson === null) {
49
+ this.log('error', `Failed building ${entry} blocks - no block.json found.`);
50
+ return false;
51
+ }
52
+ let cmds = [
53
+ `${this.project.path}/node_modules/@wordpress/scripts/bin/wp-scripts.js`,
54
+ `build`,
55
+ `--source-path=.${entry.replace(this.project.path, '')}/src`,
56
+ `--output-path=.${entry.replace(this.project.path, '')}/build`,
57
+ `--webpack-copy-php`
58
+ ];
59
+ await cmd(cmds, { entryLabel: entryLabel });
60
+
61
+ this.end({
62
+ itemLabel: entryLabel,
63
+ timerStart: timerStart,
64
+ timerEnd: Date.now()
65
+ });
66
+ }
67
+
68
+ async process(entry) {
69
+ if (entry) {
70
+ await this.build(entry);
71
+ } else {
72
+ const promisesBlocks = this.globs.map(block => this.build(block));
73
+ await Promise.all(promisesBlocks);
74
+ }
75
+ }
76
+
77
+ watch() {
78
+ for (let block of this.globs) {
79
+ this.chokidar.watch(`${block}/src`, {
80
+ ...this.project.chokidarOpts
81
+ }).on('all', (event, path) => {
82
+ this.process(block);
83
+ });
84
+ }
85
+ }
86
+
87
+ }
88
+
89
+ function cmd(commands) {
90
+ let p = spawn(commands[0], commands.slice(1), {
91
+ shell: true
92
+ });
93
+ return new Promise((resolveFunc) => {
94
+ p.stdout.on('data', (x) => {
95
+ if (x.toString().includes('Error:')) {
96
+ process.stdout.write(x.toString());
97
+ log('error', `Failed building ${entryLabel} block - See above error.`);
98
+ }
99
+ });
100
+ p.stderr.on('data', (x) => {
101
+ if (x.toString().includes('Error:')) {
102
+ process.stderr.write(x.toString());
103
+ log('error', `Failed building ${entryLabel} block - See above error.`);
104
+ }
105
+ });
106
+ p.on('exit', (code) => {
107
+ resolveFunc(code);
108
+ });
109
+ });
110
+ }
111
+
112
+ export { BlocksComponent as default }
@@ -0,0 +1,41 @@
1
+ import BaseComponent from './base.js';
2
+ import { readdir } from 'fs/promises';
3
+ import fs from 'fs-extra';
4
+
5
+ class FontsComponent extends BaseComponent {
6
+
7
+ constructor() {
8
+ super();
9
+ this.slug = 'fonts';
10
+ }
11
+
12
+ async init() {
13
+ await this.process();
14
+ }
15
+
16
+ async build(entry) {
17
+ let entryLabel = `/dist/fonts`;
18
+
19
+ this.start();
20
+
21
+ try {
22
+ const fontsDir = await readdir(entry);
23
+ if (fontsDir.length == 0) { throw new Error('No files present'); }
24
+ await fs.copy(entry, `${this.project.path}${entryLabel}`);
25
+ } catch(error) {
26
+ this.log('info', `${error} at ${entry.replace(this.project.path, '')}/. Skipping font copy`);
27
+ return false;
28
+ }
29
+
30
+ this.end({
31
+ itemLabel: entryLabel
32
+ });
33
+ }
34
+
35
+ async process() {
36
+ await this.build(`${this.project.path}/_src/fonts`);
37
+ }
38
+
39
+ }
40
+
41
+ export { FontsComponent as default }
@@ -0,0 +1,60 @@
1
+ import BaseComponent from './base.js';
2
+ import imagemin from 'imagemin';
3
+ import imageminJpegtran from 'imagemin-jpegtran';
4
+ import imageminPngquant from 'imagemin-pngquant';
5
+ import imageminSvgo from 'imagemin-svgo';
6
+
7
+ class ImagesComponent extends BaseComponent {
8
+
9
+ constructor() {
10
+ super();
11
+ this.slug = 'images';
12
+ }
13
+
14
+ async init() {
15
+ this.globs = await Array.fromAsync(
16
+ this.glob(this.project.package?.sdc?.imagesPath ||
17
+ `${this.project.paths.images}/**/*`)
18
+ );
19
+ this.globsDirectories = [
20
+ this.project.paths.images,
21
+ ...await this.utils.getAllSubdirectories(this.project.paths.images)
22
+ ];
23
+ await this.process();
24
+ }
25
+
26
+ async build(entry, options) {
27
+ let timerStart = Date.now();
28
+ let dest = entry.replace('_src/images', 'dist/images');
29
+ const files = await imagemin([entry + '/*'], {
30
+ destination: dest,
31
+ plugins: [
32
+ imageminJpegtran(),
33
+ imageminPngquant(),
34
+ imageminSvgo()
35
+ ]
36
+ });
37
+
38
+ this.end({
39
+ itemLabel: `${dest.replace(this.project.path, '')} (${files.length} image${files.length == 1 ? '' : 's'})`,
40
+ timerStart: timerStart,
41
+ timerEnd: Date.now()
42
+ });
43
+ }
44
+
45
+ async process() {
46
+ const promisesImages = this.globsDirectories.map(directory => this.build(directory));
47
+ await Promise.all(promisesImages);
48
+ }
49
+
50
+ watch() {
51
+ this.chokidar.watch(this.project.paths.images, {
52
+ ...this.project.chokidarOpts
53
+ }).on('all', (event, path) => {
54
+ this.process();
55
+ });
56
+ }
57
+
58
+ }
59
+
60
+ export { ImagesComponent as default }
@@ -0,0 +1,7 @@
1
+ export { default as style } from './style.js';
2
+ export { default as scripts } from './scripts.js';
3
+ export { default as blocks } from './blocks.js';
4
+ export { default as images } from './images.js';
5
+ export { default as fonts } from './fonts.js';
6
+ export { default as php } from './php.js';
7
+ export { default as server } from './server.js';
@@ -0,0 +1,107 @@
1
+ import BaseComponent from './base.js';
2
+ import { fileURLToPath } from 'url';
3
+ import { exec } from 'child_process';
4
+ import { promisify } from 'util';
5
+
6
+ class PHPComponent extends BaseComponent {
7
+
8
+ constructor() {
9
+ super();
10
+ this.slug = 'php';
11
+ this.execPromise = promisify(exec);
12
+ }
13
+
14
+ async init() {
15
+ this.globs = await Array.fromAsync(
16
+ this.glob(this.project.package?.sdc?.phpGlobPath ||
17
+ `${this.project.path}/**/*.php`)
18
+ );
19
+ this.globsBlocks = await Array.fromAsync(
20
+ this.glob(`${this.project.path}/blocks/*/build/*.php`)
21
+ );
22
+ this.project.chokidarOpts.ignored = [
23
+ ...this.project.chokidarOpts.ignored,
24
+ ...this.globsBlocks
25
+ ];
26
+ // await this.process(null, { lintType: 'warn' }); // this errors "Fatal error: Allowed memory size"
27
+ }
28
+
29
+ async build(entry, options) {
30
+ options = Object.assign({}, {
31
+ lintType: 'fix'
32
+ }, options);
33
+ let entryLabel = `all PHP files`;
34
+
35
+ this.start();
36
+ let workingLintBin = 'phpcbf';
37
+ if (options.lintType == 'warn') {
38
+ workingLintBin = 'phpcs';
39
+ }
40
+ let phpFiles = '.';
41
+ let additionalFlags = '';
42
+ if (entry) {
43
+ phpFiles = entry;
44
+ entryLabel = entry.replace(this.project.path, '');
45
+ } else {
46
+ additionalFlags += ' -d memory_limit=2G'; // FIXME: this doesn't solve error issue "Fatal error: Allowed memory size"
47
+ }
48
+ try {
49
+ const cmds = [
50
+ `vendor/bin/${workingLintBin}`,
51
+ `--parallel=5`,
52
+ `--error-severity=1`,
53
+ `--warning-severity=1`,
54
+ `--colors`,
55
+ `--basepath=${this.project.path}`,
56
+ phpFiles,
57
+ additionalFlags
58
+ ];
59
+
60
+ const { stdout, stderr } = await this.execPromise(cmds.join(' '), {
61
+ cwd: this.path.resolve(this.path.dirname(fileURLToPath(import.meta.url)), '../../')
62
+ }); // returns an error if any violations are found, so we can't rely on the try/catch as usual
63
+ } catch (error) {
64
+ if (
65
+ error.stderr?.length ||
66
+ (
67
+ error.stdout?.length &&
68
+ (
69
+ error.stdout.startsWith('ERROR:') ||
70
+ error.stdout.includes('FAILED TO FIX')
71
+ )
72
+ )
73
+ ) {
74
+ console.error(error.stderr?.length ? error.stderr : error.stdout);
75
+ this.log('error', `Failed linting ${entryLabel.replace(this.project.path, '')} - See above error.`);
76
+ return false;
77
+ } else {
78
+ if (error.stdout?.length) {
79
+ console.log(error.stdout);
80
+ }
81
+ }
82
+ }
83
+ if (this.project.components.server?.server) {
84
+ this.project.components.server?.server.reload();
85
+ }
86
+
87
+ this.end({
88
+ itemLabel: entryLabel,
89
+ verb: `Linted (${options.lintType})`
90
+ });
91
+ }
92
+
93
+ async process(entry, options) {
94
+ await this.build(entry, options);
95
+ }
96
+
97
+ watch() {
98
+ this.chokidar.watch(this.globs, {
99
+ ...this.project.chokidarOpts
100
+ }).on('all', (event, path) => {
101
+ this.process(path);
102
+ });
103
+ }
104
+
105
+ }
106
+
107
+ export { PHPComponent as default }