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 +1 -1
- package/lib/build.js +9 -1
- package/lib/components/blocks.js +2 -2
- package/lib/components/fonts.js +1 -1
- package/lib/components/images.js +1 -1
- package/lib/components/php.js +1 -1
- package/lib/components/scripts.js +4 -3
- package/lib/components/server.js +16 -15
- package/lib/components/style.js +3 -3
- package/lib/config-validator.js +104 -0
- package/lib/help.js +35 -21
- package/lib/project.js +137 -35
- package/lib/utils.js +66 -22
- package/package.json +3 -3
package/index.js
CHANGED
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
|
|
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
|
+
}
|
package/lib/components/blocks.js
CHANGED
|
@@ -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
|
}
|
package/lib/components/fonts.js
CHANGED
|
@@ -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.
|
|
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();
|
package/lib/components/images.js
CHANGED
|
@@ -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.
|
|
16
|
+
this.glob(this.project.config.imagesPath ||
|
|
17
17
|
`${this.project.paths.images}/**/*`)
|
|
18
18
|
);
|
|
19
19
|
this.globsDirectories = [
|
package/lib/components/php.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
}
|
package/lib/components/server.js
CHANGED
|
@@ -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.
|
|
41
|
-
proxy: this.project.
|
|
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.
|
|
52
|
-
open: this.project.
|
|
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.
|
|
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.
|
|
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.
|
|
76
|
+
return thisProject.config.browsersync?.location == 'end' ? allScripts + match : match + allScripts;
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
}
|
package/lib/components/style.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
6
|
+
${chalk.bold.blue('SDC Build WP')} - Custom WordPress build process
|
|
6
7
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
}).join('')}
|
|
20
|
-
Examples:
|
|
20
|
+
return ` ${chalk.cyan(key.padEnd(12))} ${component.description}`;
|
|
21
|
+
}).join('\n')}
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
sdc-build-wp
|
|
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
|
-
|
|
27
|
+
${chalk.dim('# Build and watch for changes')}
|
|
28
|
+
sdc-build-wp --watch
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
1
|
+
import yargs from 'yargs';
|
|
2
|
+
import { hideBin } from 'yargs/helpers';
|
|
2
3
|
import { readFile } from 'fs/promises';
|
|
3
|
-
import
|
|
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
|
-
|
|
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:
|
|
48
|
-
errorLog:
|
|
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
|
-
|
|
72
|
+
|
|
69
73
|
project.components = Object.fromEntries(Object.entries(LibComponents).map(([name, Class]) => [name, new Class()]));
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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 '\
|
|
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
|
-
|
|
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
|
|
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.
|
|
29
|
+
console.error(error);
|
|
30
|
+
log('error', 'Failed to stop server');
|
|
23
31
|
}
|
|
24
32
|
}
|
|
25
|
-
Object.values(project.components)
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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.
|
|
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": "
|
|
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
|
}
|