@ideasonpurpose/build-tools-wordpress 1.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.
@@ -0,0 +1,23 @@
1
+ name: Publish to NPM
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ publish:
9
+ name: Build and publish to npm
10
+ # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
11
+ runs-on: ubuntu-22.04
12
+ steps:
13
+ # https://github.com/marketplace/actions/checkout
14
+ - uses: actions/checkout@v3
15
+ # https://github.com/actions/setup-node
16
+ - uses: actions/setup-node@v3
17
+ with:
18
+ node-version: 18
19
+ registry-url: https://registry.npmjs.org/
20
+ # - run: npm ci
21
+ - run: npm publish
22
+ env:
23
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
@@ -0,0 +1,32 @@
1
+ name: Create Release from Version Tags
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build Release
10
+ # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
11
+ runs-on: ubuntu-22.04
12
+
13
+ steps:
14
+ # https://github.com/marketplace/actions/checkout
15
+ - uses: actions/checkout@v3
16
+
17
+ - name: Set up REPO and TAG environment vars
18
+ run: |
19
+ echo "REPO=${GITHUB_REPOSITORY#*/}" >> $GITHUB_ENV
20
+ echo "TAG=${GITHUB_SHA:0:6}" >> $GITHUB_ENV
21
+
22
+ - name: This run was triggered by a version tag, reset the $TAG variable to the tag name
23
+ if: startsWith(github.ref, 'refs/tags/v')
24
+ run: |
25
+ echo "TAG=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
26
+
27
+ - name: Create GitHub release
28
+ if: ${{ contains(github.ref, 'refs/tags/') }}
29
+ env:
30
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31
+ run: |
32
+ gh release create v${TAG}
@@ -0,0 +1,22 @@
1
+ {
2
+ "workbench.colorCustomizations": {
3
+ "activityBar.activeBackground": "#ffc1dc",
4
+ "activityBar.background": "#ffc1dc",
5
+ "activityBar.foreground": "#15202b",
6
+ "activityBar.inactiveForeground": "#15202b99",
7
+ "activityBarBadge.background": "#459f00",
8
+ "activityBarBadge.foreground": "#e7e7e7",
9
+ "commandCenter.border": "#15202b99",
10
+ "sash.hoverBorder": "#ffc1dc",
11
+ "statusBar.background": "#ff8ebf",
12
+ "statusBar.foreground": "#15202b",
13
+ "statusBarItem.hoverBackground": "#ff5ba2",
14
+ "statusBarItem.remoteBackground": "#ff8ebf",
15
+ "statusBarItem.remoteForeground": "#15202b",
16
+ "titleBar.activeBackground": "#ff8ebf",
17
+ "titleBar.activeForeground": "#15202b",
18
+ "titleBar.inactiveBackground": "#ff8ebf99",
19
+ "titleBar.inactiveForeground": "#15202b99"
20
+ },
21
+ "peacock.color": "#ff8ebf"
22
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Ideas On Purpose
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # @ideasonpurpose/build-tools-wordpress
2
+
3
+ #### Version 1.1.0
4
+
5
+ Build scripts and dependencies for IOP's WordPress development environments.
6
+
7
+ ## About This Project
8
+
9
+ These tools were migrated from our [Docker-based WordPress build tools](https://github.com/ideasonpurpose/docker-build) to speed up development and began the process of moving our build tools away from webpack. Gathering dependencies also simplifies the package.json files in host projects, making those slightly more manageable.
10
+
11
+ This may end up being the container for all tools required for building a project. That would allow us to clean out most of the cruft from our project directories and only have a single dependency which packages everything we need.
12
+
13
+ The included webpack.config.js file works best when paired with a PHP environment like our [Docker WordPress environments](https://github.com/ideasonpurpose/docker-wordpress-dev). It's capable of proxying to other servers, but that's sort of crazy.
14
+
15
+ ### Additional Notes
16
+
17
+ This project expects an entirely ES Module based environment and specifies all dependencies using standard ESM import syntax. Projects importing this file should set `"type": "module"` in their package.json files.
18
+
19
+ ##  
20
+
21
+ #### Brought to you by IOP
22
+
23
+ <a href="https://www.ideasonpurpose.com"><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/iop-logo-mint-on-black-88px.png" height="44" align="top" alt="IOP Logo"></a><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/spacer.png" align="middle" width="4" height="54"> This project is actively developed and used in production at <a href="https://www.ideasonpurpose.com">Ideas On Purpose</a>.
24
+
25
+
26
+ #### Brought to you by IOP
27
+
28
+ <a href="https://www.ideasonpurpose.com"><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/iop-logo-white-on-black-88px.png" height="44" align="top" alt="IOP Logo"></a><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/spacer.png" align="middle" width="4" height="54"> This project is actively developed and used in production at <a href="https://www.ideasonpurpose.com">Ideas On Purpose</a>.
29
+
30
+
31
+ #### Brought to you by IOP
32
+
33
+ <a href="https://www.ideasonpurpose.com"><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/iop-logo-clay-on-black-88px.png" height="44" align="top" alt="IOP Logo"></a><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/spacer.png" align="middle" width="4" height="54"> This project is actively developed and used in production at <a href="https://www.ideasonpurpose.com">Ideas On Purpose</a>.
34
+
35
+
36
+ #### Brought to you by IOP
37
+
38
+ <a href="https://www.ideasonpurpose.com"><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/iop-logo-white-on-mint-88px.png" height="44" align="top" alt="IOP Logo"></a><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/spacer.png" align="middle" width="4" height="54"> This project is actively developed and used in production at <a href="https://www.ideasonpurpose.com">Ideas On Purpose</a>.
39
+
40
+
41
+
42
+ #### Brought to you by IOP
43
+
44
+ <a href="https://www.ideasonpurpose.com"><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/iop-logo-black-on-mint-88px.png" height="44" align="top" alt="IOP Logo"></a><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/spacer.png" align="middle" width="4" height="54"> This project is actively developed and used in production at <a href="https://www.ideasonpurpose.com">Ideas On Purpose</a>.
45
+
46
+
47
+
48
+ #### Brought to you by IOP
49
+
50
+ <a href="https://www.ideasonpurpose.com"><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/iop-logo-white-on-darkmint-88px.png" height="44" align="top" alt="IOP Logo"></a><img src="https://raw.githubusercontent.com/ideasonpurpose/ideasonpurpose/master/spacer.png" align="middle" width="4" height="54"> This project is actively developed and used in production at <a href="https://www.ideasonpurpose.com">Ideas On Purpose</a>.
51
+
52
+
53
+ > *logo test...*
54
+ >😂
@@ -0,0 +1,22 @@
1
+ #! /usr/bin/env node
2
+ import { exec } from "node:child_process";
3
+
4
+ import chalk from "chalk";
5
+
6
+ exec("docker compose port wordpress 80", (error, stdout, stderr) => {
7
+ if (error) {
8
+ console.error(`exec error: ${error}`);
9
+ return;
10
+ }
11
+ if (stdout) {
12
+ const [addr, port] = stdout.split(":");
13
+
14
+ console.log("\n",
15
+ " 🚀 ",
16
+ chalk.bold( "Local WP site:"),
17
+ chalk.magenta(`http://localhost:${chalk.bold(port)}`),
18
+ );
19
+ }
20
+ // console.log(`stdout: ${stdout}`);
21
+ // console.error(`stderr: ${stderr}`);
22
+ });
package/index.js ADDED
@@ -0,0 +1,16 @@
1
+ import AfterDoneReporterPlugin from "./lib/AfterDoneReporterPlugin.js";
2
+ import buildConfig from "./lib/buildConfig.js";
3
+ import DependencyManifestPlugin from "./lib/DependencyManifestPlugin.js";
4
+ import devserverProxy from "./lib/devserver-proxy.js";
5
+ import WatchRunReporterPlugin from "./lib/WatchRunReporterPlugin.js";
6
+
7
+
8
+
9
+ // export { };
10
+ export {
11
+ AfterDoneReporterPlugin,
12
+ buildConfig,
13
+ DependencyManifestPlugin,
14
+ devserverProxy,
15
+ WatchRunReporterPlugin,
16
+ };
@@ -0,0 +1,50 @@
1
+ import chalk from "chalk";
2
+ import humanizeDuration from "humanize-duration";
3
+
4
+ /**
5
+ * Simple plugin for printing some text after compilation stats are displayed
6
+ *
7
+ * Messages should be configured in an argument object:
8
+ *
9
+ * new AfterDoneReporterPlugin({message: "Your Message Here"})
10
+ */
11
+ export default class AfterDoneReporterPlugin {
12
+ constructor(options = {}) {
13
+ const defaults = {
14
+ echo: true,
15
+ prefix: `🟢${chalk.gray("(iop)")}:`,
16
+ message: "",
17
+ };
18
+ this.config = { ...defaults, ...options };
19
+ this.name = "IOP Reporter Plugin";
20
+ this.messages = [];
21
+ }
22
+
23
+ apply(compiler) {
24
+ compiler.hooks.done.tapPromise(this.name, async (stats) => {
25
+ if (this.config.echo) {
26
+ const { host, port } = compiler.options.devServer;
27
+ const { startTime, endTime, modules, assetsInfo } = stats.compilation;
28
+ const hostname = host === "0.0.0.0" ? "localhost" : host;
29
+
30
+ const time = chalk.yellow.bold(
31
+ humanizeDuration(endTime - startTime, { units: ["h", "m", "s"] })
32
+ );
33
+ const mCount = chalk.yellow(modules.size);
34
+ const aCount = chalk.yellow.bold(assetsInfo.size);
35
+
36
+ const messages = [
37
+ `Compiled ${mCount} input modules into ${aCount} files in ${time}`,
38
+ "Dev site " + chalk.blue(`http://${hostname}:${chalk.bold(port)}`),
39
+ this.config.message,
40
+ ]
41
+ .filter((m) => m.length)
42
+ .join("\n" + this.config.prefix);
43
+
44
+ setTimeout(() =>
45
+ console.log("\n" + this.config.prefix + " " + messages)
46
+ );
47
+ }
48
+ });
49
+ }
50
+ }
@@ -0,0 +1,77 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+
4
+ class DependencyManifestPlugin {
5
+ constructor(options) {
6
+ const defaults = {
7
+ writeManifestFile: false,
8
+ manifestFile: "./dependency-manifest.json",
9
+ };
10
+ this.config = { ...defaults, ...options };
11
+ this.name = "Dependency Manifest Plugin";
12
+ this.manifest = {};
13
+ }
14
+
15
+ apply(compiler) {
16
+ compiler.hooks.emit.tapAsync(this.name, (compilation, callback) => {
17
+ // const logger = compilation.getLogger("dependency-manifest-plugin");
18
+
19
+ // logger.info(compilation.namedChunkGroups.keys());
20
+
21
+ Array.from(compilation.namedChunkGroups.entries()).forEach(
22
+ ([key, group]) => {
23
+ this.manifest[key] = group.chunks.reduce(
24
+ (entry, chunk) => {
25
+ Array.from(chunk.files)
26
+ /**
27
+ * hot-update files will stomp on the main file, filter them out
28
+ */
29
+ .filter((file) => !file.includes("hot-update"))
30
+ .forEach((file) => {
31
+ const { chunkGraph } = compilation;
32
+ const ext = path.extname(file);
33
+
34
+ const filepath = path.resolve(
35
+ compiler.options.output.publicPath,
36
+ file
37
+ );
38
+
39
+ let silo, fileKey;
40
+ /**
41
+ * If the chunk has one or more entryModules, it's a file
42
+ * If there are zero entryModules, it's a generated dependency
43
+ */
44
+ if (chunkGraph.getNumberOfEntryModules(chunk) > 0) {
45
+ silo = "files";
46
+ fileKey = key + ext;
47
+ } else {
48
+ silo = "dependencies";
49
+ fileKey = chunk.id + ext;
50
+ }
51
+
52
+ entry[silo][fileKey] = filepath;
53
+ });
54
+ return entry;
55
+ },
56
+ { files: {}, dependencies: {} }
57
+ );
58
+ }
59
+ );
60
+
61
+ callback();
62
+ });
63
+
64
+ compiler.hooks.afterEmit.tapPromise(this.name, async () => {
65
+ if (this.config.writeManifestFile) {
66
+ return await fs.outputJSON(
67
+ path.resolve(compiler.options.output.path, this.config.manifestFile),
68
+ this.manifest,
69
+ { spaces: 2 }
70
+ );
71
+ }
72
+ console.log("in DependencyManifestPlugin afterEmit");
73
+ });
74
+ }
75
+ }
76
+
77
+ export default DependencyManifestPlugin;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Container to provide plugin objects to the imageMin loader
3
+ */
4
+
5
+ /**
6
+ * We use the same svgo config for dev and prod
7
+ */
8
+ const svgoConfig = {
9
+ js2svg: { pretty: true },
10
+ floatPrecision: 3,
11
+ plugins: [
12
+ {
13
+ name: "preset-default",
14
+ params: {
15
+ overrides: {
16
+ cleanupIDs: false,
17
+ removeViewBox: false,
18
+ },
19
+ },
20
+ },
21
+ "sortAttrs"
22
+ ],
23
+ };
24
+
25
+ /**
26
+ * Optimized for size/quality
27
+ */
28
+ const imageminProdPlugins = [
29
+ ["gifsicle", { optimizationLevel: 3 }],
30
+ [
31
+ "pngquant",
32
+ {
33
+ strip: true,
34
+ dithering: 0.3,
35
+ quality: [0.5, 0.8],
36
+ verbose: true,
37
+ },
38
+ ],
39
+ ["mozjpeg", { quality: 80, progressive: true }],
40
+ ["svgo", svgoConfig],
41
+ ];
42
+
43
+ /**
44
+ * Optimized for speed
45
+ */
46
+ const imageminDevPlugins = [
47
+ ["gifsicle", { optimizationLevel: 1 }],
48
+ ["optipng", { optimizationLevel: 0 }],
49
+ ["jpegtran", { progressive: true }],
50
+ ["svgo", svgoConfig],
51
+ ];
52
+
53
+ // const plugins = (isProd) => (isProd ? plugins.prod : plugins.dev);
54
+ // plugins.dev = imageminDevPlugins;
55
+ // plugins.prod = imageminProdPlugins;
56
+ // const plugins = (isProd) => (isProd ? imageminDevPlugins : imageminDevPlugins);
57
+ const plugins = (isProd) => (isProd ? imageminProdPlugins : imageminDevPlugins);
58
+
59
+ export default plugins;
60
+ // module.exports = plugins;
@@ -0,0 +1,54 @@
1
+ import chalk from "chalk";
2
+ /**
3
+ * Simple plugin for restoring the starting compile message which vanished in
4
+ * DevServer v4
5
+ *
6
+ * Messages should be configured in an argument object:
7
+ *
8
+ * new WatchRunReporterPlugin({message: "Your Message Here"})
9
+ */
10
+ class WatchRunReporterPlugin {
11
+ constructor(options = {}) {
12
+ const defaults = {
13
+ echo: true,
14
+ prefix: `🟢${chalk.gray("(iop)")}:`,
15
+ message: chalk.bold("Compiling..."),
16
+ };
17
+ this.config = { ...defaults, ...options };
18
+ this.name = "IOP Reporter Plugin";
19
+ }
20
+
21
+ apply(compiler) {
22
+ compiler.hooks.watchRun.tapPromise(this.name, async () => {
23
+ if (this.config.echo) {
24
+ /**
25
+ * compiler.modifiedFiles is a Set containing the changed file or files
26
+ * along ith the base path containing those files (or the entryPoint?)
27
+ *
28
+ * Sorting the set (as an Array) puts the base path first (it's the shortest)
29
+ * then we remove it from the paths to clean up.
30
+ *
31
+ * Report a single changed path or the count of changed paths
32
+ */
33
+ let msg = "";
34
+ if (compiler.modifiedFiles) {
35
+ let modFiles = Array.from(compiler.modifiedFiles).sort();
36
+ const basePath = modFiles[0];
37
+ const basePathPattern = new RegExp(`${basePath}/?`, "gi");
38
+ modFiles = modFiles
39
+ .map((p) => p.replace(basePathPattern, ""))
40
+ .filter((p) => p);
41
+
42
+ if (modFiles.length > 1) {
43
+ msg = `Modified ${modFiles.length - 1} files `;
44
+ } else {
45
+ msg = `Modified ${chalk.bold.yellow(modFiles[0] || basePath)} `;
46
+ }
47
+ }
48
+ console.log(this.config.prefix, msg + this.config.message);
49
+ }
50
+ });
51
+ }
52
+ }
53
+
54
+ export default WatchRunReporterPlugin;
@@ -0,0 +1,211 @@
1
+ import fs from "fs-extra";
2
+ import { posix as path } from "path";
3
+ import chalk from "chalk";
4
+
5
+ // import defaultConfig from "../default.config.js";
6
+
7
+ const defaultConfig = {
8
+ src: "./src",
9
+ dist: "./dist",
10
+ entry: ["./js/index.js"],
11
+ publicPath: "/dist/",
12
+ contentBase: "/dist/", // TODO: Should this be false?
13
+ manifestFile: "./dependency-manifest.json",
14
+ sass: "sass-embedded",
15
+ port: 8080,
16
+ transpileDependencies: ["ansi-regex", "normalize-url"],
17
+ proxy: 'wordpress',
18
+ };
19
+
20
+
21
+
22
+
23
+
24
+ /**
25
+ * This file encapsulates all the config file massaging and allows
26
+ * for asynchronous values.
27
+ *
28
+ * Asynchronous values:
29
+ * -
30
+ */
31
+
32
+ export default (configFile = { config: {} }) => {
33
+ /**
34
+ * This is a temporary workaround for a problem with using `process.env.NAME` in
35
+ * base ideasonpurpose.config.js files. `NAME` is often set in the environment
36
+ * and will override the value in package.json, resulting in orphan folders and
37
+ * misnamed archives.
38
+ *
39
+ * TODO: This warning can be removed once all process.env.NAME assignments are removed
40
+ * removed from project configuration files.
41
+ */
42
+ if (configFile.filepath) {
43
+ const rawConfigFile = fs.readFileSync(configFile.filepath);
44
+ const packageJson = fs.readJsonSync(
45
+ path.resolve(configFile.filepath, "../package.json")
46
+ );
47
+ // console.log({ p: process.env.NAME });
48
+ if (
49
+ packageJson.name !== process.env.NAME &&
50
+ rawConfigFile.includes("process.env.NAME") &&
51
+ configFile.config.src.includes(process.env.NAME)
52
+ ) {
53
+ nameEnvVarWarning();
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Merge configFile onto defaults
59
+ */
60
+ const config = { ...defaultConfig, ...configFile.config };
61
+
62
+ /**
63
+ * Merge transpileDependency arrays
64
+ */
65
+ if (configFile.config?.transpileDependencies) {
66
+ const configDeps =
67
+ typeof configFile.config.transpileDependencies === "string"
68
+ ? [configFile.config.transpileDependencies]
69
+ : configFile.config.transpileDependencies;
70
+ config.transpileDependencies = [
71
+ ...defaultConfig.transpileDependencies,
72
+ ...configDeps,
73
+ ];
74
+ }
75
+
76
+ /**
77
+ * Normalize paths relative to the configFile
78
+ *
79
+ * When used with ideasonpurpose/docker-wordpress-dev the environment
80
+ * is set up like this:
81
+ *
82
+ * - Tools live in /usr/src/tools
83
+ * - Site is linked to /src/src/site
84
+ *
85
+ * placeholder.file is a sloppy workaround for needing a path ending in a file.
86
+ * This lets us move up a level in the same way we could if cosmiConfig returned
87
+ * the path to a config.js or package.json file.
88
+ */
89
+ configFile.filepath = configFile.filepath || "../site/placeholder.file";
90
+ config.src = path.resolve(configFile.filepath, "..", config.src);
91
+ config.dist = path.resolve(configFile.filepath, "..", config.dist);
92
+
93
+ /**
94
+ * Check for `config.src` and create it if missing
95
+ * TODO: unresolved, create the file? Fail? Throw error?
96
+ */
97
+ if (!fs.existsSync(config.src)) {
98
+ noSrcExists(config.src);
99
+ // TODO: Make this optional? User prompt? Throw error?
100
+ // TODO: This is a massive side-effect which makes testing very difficult
101
+ // fs.ensureDirSync(config.src);
102
+ // throw new Error();
103
+ }
104
+
105
+ /**
106
+ * Generate an entry object from config.entry.
107
+ * Output names will be based on the source file's basename.
108
+ *
109
+ * Config.entry should preferably be an array, but strings or objects will
110
+ * also work. Strings will be treated as a single-item array. Arrays will be
111
+ * parsed into an object, objects (or whatever) will pass through unmodified.
112
+ *
113
+ * A setting like this:
114
+ * [ "./js/index.js" ]
115
+ *
116
+ * Yields an entry object like this:
117
+ * { index: "./js/index.js"}
118
+ *
119
+ * A string will be wrapped in an array:
120
+ * "./js/main.js" => [ "./js/main.js" ]
121
+ *
122
+ * Objects pass straight through
123
+ * { app: "./js/main.js" }
124
+ *
125
+ * Overlapping basenames will be joined into a single entrypoint
126
+ * ["./index.js", "./app.js", "./index.scss"] => { app: ["./app.js"], index: ["./index.js", "./index.scss"]}
127
+ */
128
+ if (typeof config.entry === "string") {
129
+ config.entry = [config.entry];
130
+ }
131
+ if (Array.isArray(config.entry)) {
132
+ config.entry = config.entry.reduce((obj, src) => {
133
+ obj[path.parse(src).name] = []
134
+ .concat(obj[path.parse(src).name], src)
135
+ .filter((i) => i);
136
+ return obj;
137
+ }, {});
138
+ }
139
+
140
+ /**
141
+ * Normalize the sass implementation, default to 'sass-embedded'
142
+ *
143
+ * Returns a known module name (string) which will be dynamically imported into the config
144
+ */
145
+ const sassInput = config.sass.toString().toLowerCase();
146
+ const nodeSass = ["sass", "node-sass"];
147
+ config.sass = nodeSass.includes(sassInput) ? "sass" : "sass-embedded";
148
+
149
+ /**
150
+ * Print a deprecation warning for node-sass
151
+ * @link https://sass-lang.com/blog/libsass-is-deprecated/
152
+ */
153
+ if (sassInput === "node-sass") {
154
+ console.log(
155
+ "⚠️ ",
156
+ chalk.bold.yellow("NOTICE"),
157
+ chalk.yellow("NOTICE node-sass is no longer supported. The js-compiled")
158
+ );
159
+ console.log(chalk.yellow(" dart-sass package will be used instead."));
160
+ console.log(
161
+ chalk.cyan(" https://sass-lang.com/blog/libsass-is-deprecated/")
162
+ );
163
+ console.log(chalk.cyan(" https://www.npmjs.com/package/sass"));
164
+ }
165
+
166
+ /**
167
+ * Remap proxy: true to default value
168
+ */
169
+ if (config.proxy === true) {
170
+ config.proxy = defaultConfig.proxy;
171
+ }
172
+
173
+ return config;
174
+ };
175
+
176
+ /**
177
+ * Warnings are broken out below
178
+ */
179
+ const noSrcExists = (src) => {
180
+ console.log(
181
+ "🛑 ",
182
+ chalk.bold.red(
183
+ `ERROR: The src directory ${chalk.white(src)} does not exist.`
184
+ )
185
+ );
186
+ };
187
+
188
+ const nameEnvVarWarning = () => {
189
+ console.log(
190
+ "⚠️ ",
191
+ chalk.yellow.bold(
192
+ "WARNING: $NAME is set in the environment and the project's config file."
193
+ )
194
+ );
195
+ console.log(
196
+ chalk.yellow(
197
+ " The value of $NAME will likely conflict with the name property from"
198
+ )
199
+ );
200
+ console.log(
201
+ chalk.yellow(
202
+ " package.json. Please update the project's config.js file. To silence "
203
+ )
204
+ );
205
+ console.log(
206
+ chalk.yellow(
207
+ " this message, set $NAME in the environment to match the project's"
208
+ )
209
+ );
210
+ console.log(chalk.yellow(" package.json `name` property."));
211
+ };
@@ -0,0 +1,166 @@
1
+ // const { dns } = require("dns").promises;
2
+
3
+ // DO NOT destructure this or we won't be able to mock it
4
+ // import {promises as dnsPromises} from "dns";
5
+ import dns from "dns";
6
+ import chalk from "chalk";
7
+ import { isIP } from "net";
8
+
9
+ /**
10
+ * If config.proxy is explicitly not false, return a {proxy} object
11
+ * otherwise return an empty object.
12
+ */
13
+ export default async (config) => {
14
+ let target;
15
+
16
+ console.log({config});
17
+
18
+ // if (
19
+ // config.proxy ===
20
+ // "http://devserver-proxy-token--d939bef2a41c4aa154ddb8db903ce19fff338b61"
21
+ // ) {
22
+ // /**
23
+ // * Deal with the legacy service hostname, re-map to 'wordpress' which will
24
+ // * be resolved to an IP address.
25
+ // *
26
+ // * TODO: Remove this warning after 2023-02 (extended another year, not hurting anything)
27
+ // */
28
+
29
+ // console.log(
30
+ // "⚠️ ",
31
+ // `The ${chalk.magenta(
32
+ // "devserver-proxy-token"
33
+ // )} value is no longer necessary`,
34
+ // "and can be safely removed from docker-compose"
35
+ // );
36
+
37
+ // /**
38
+ // * 'wordpress' is the internal hostname used by docker
39
+ // */
40
+ // target = await dns.promises.resolve("wordpress").catch(() => false);
41
+ // if (target) {
42
+ // target = "http://" + target.pop();
43
+ // }
44
+ // } else if (!!isIP(config.proxy)) {
45
+ if (!!isIP(config.proxy)) {
46
+ /**
47
+ * Bare IP addresses
48
+ * net.isIP() returns 4, 6 or 0(IPv4, IPv6 or not-an-IP address)
49
+ */
50
+ target = `http://${config.proxy}`;
51
+ } else if (
52
+ typeof config.proxy == "string" &&
53
+ !config.proxy.match(/^https?:\/\//i)
54
+ ) {
55
+ /**
56
+ * Handle plain strings that don't start with http:// or https://
57
+ * Attempt to resolve these to IP addresses
58
+ */
59
+ target = await dns.promises.resolve(config.proxy).catch(() => false);
60
+
61
+ if (target) {
62
+ target = "http://" + target.pop();
63
+ }
64
+ } else {
65
+ /**
66
+ * Pass anything else straight through
67
+ */
68
+ target = config.proxy;
69
+ }
70
+
71
+ /**
72
+ * Note: This is a sticky bit of code. Everything above gets bottlenecked here, and
73
+ * if aa previous value of target can't be converted to a URL, the whole proxy will
74
+ * return an empty object.
75
+ */
76
+ try {
77
+ config.proxyUrl = new URL(target);
78
+ } catch (err) {
79
+ // Invalid URL, unable to configure proxy.
80
+ return {};
81
+ }
82
+
83
+ // console.log({ target , proxyUrl: config.proxyUrl});
84
+
85
+ const proxy = {
86
+ "**": {
87
+ target,
88
+ secure: false,
89
+ autoRewrite: true,
90
+ selfHandleResponse: true, // necessary to avoid res.end being called automatically
91
+ changeOrigin: true, // needed for virtual hosted sites
92
+ cookieDomainRewrite: "", // was `${config.host}:8080` ??
93
+ headers: { "Accept-Encoding": "identity" },
94
+
95
+ onError: (err, req, res) => {
96
+ if (err.code === "ECONNRESET") {
97
+ console.log(chalk.yellow("Caught ECONNRESET error, ignoring..."));
98
+ } else {
99
+ console.log("PROXY ERROR: ", req.url, err, err.stack);
100
+ res.writeHead(500, { "Content-Type": "text-plain" });
101
+ res.end("Webpack DevServer Proxy Error: " + err);
102
+ }
103
+ },
104
+
105
+ onProxyRes: function (proxyRes, req, res) {
106
+ /**
107
+ * Update urls in files with these content-types
108
+ */
109
+ const contentTypes = [
110
+ "application/javascript",
111
+ "application/json",
112
+ "multipart/form-data",
113
+ "text/css",
114
+ "text/html",
115
+ "text/plain",
116
+ ];
117
+
118
+ let originalBody = [];
119
+
120
+ proxyRes.on("data", (data) => originalBody.push(data));
121
+
122
+ proxyRes.on("end", () => {
123
+ res.statusCode = proxyRes.statusCode;
124
+ if (proxyRes.statusMessage) {
125
+ res.statusMessage = proxyRes.statusMessage;
126
+ }
127
+
128
+ Object.keys(proxyRes.headers).forEach((key) => {
129
+ let header = proxyRes.headers[key];
130
+ if (typeof header == "string") {
131
+ header = header.replace(
132
+ new RegExp(config.proxyUrl.host, "gi"),
133
+ req.headers.host
134
+ );
135
+ }
136
+ res.setHeader(String(key).trim(), header);
137
+ });
138
+
139
+ originalBody = Buffer.concat(originalBody);
140
+ let newBody;
141
+ const type = (proxyRes.headers["content-type"] || "").split(";")[0];
142
+ const wpRegexp = new RegExp(
143
+ "^/wp-(?:admin|includes|content/plugins).*(?:css|js|map)$"
144
+ );
145
+ const originRegExp = new RegExp(
146
+ `(http:\\\\/\\\\/|http://)${config.proxyUrl.host}`,
147
+ "gi"
148
+ );
149
+
150
+ if (contentTypes.includes(type) && !wpRegexp.test(req.path)) {
151
+ newBody = originalBody
152
+ .toString("utf8")
153
+ .replace(originRegExp, `$1${req.headers.host}`);
154
+ res.setHeader("Content-Length", Buffer.byteLength(newBody));
155
+ } else {
156
+ newBody = originalBody;
157
+ }
158
+
159
+ res.end(newBody);
160
+ });
161
+ },
162
+ },
163
+ };
164
+
165
+ return { proxy };
166
+ };
@@ -0,0 +1,16 @@
1
+ import prettyHrtime from "pretty-hrtime";
2
+
3
+ export const prettierHrtime = (hrtime) => {
4
+ let timeString;
5
+ const seconds = hrtime[1] > 5e6 ? " seconds" : " second";
6
+ if (hrtime[0] > 60) {
7
+ timeString = prettyHrtime(hrtime, { verbose: true })
8
+ .replace(/\d+ (milli|micro|nano)seconds/gi, "")
9
+ .trim();
10
+ } else if (hrtime[1] > 5e6) {
11
+ timeString = prettyHrtime(hrtime).replace(/ s$/, seconds);
12
+ } else {
13
+ timeString = prettyHrtime(hrtime);
14
+ }
15
+ return timeString;
16
+ };
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@ideasonpurpose/build-tools-wordpress",
3
+ "version": "1.1.0",
4
+ "description": "Build scripts and dependencies for IOP's WordPress development environments.",
5
+ "homepage": "https://github.com/ideasonpurpose/build-tools-wordpress#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/ideasonpurpose/build-tools-wordpress/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/ideasonpurpose/build-tools-wordpress.git"
12
+ },
13
+ "license": "MIT",
14
+ "author": "Ideas On Purpose (https://www.ideasonpurpose.com/)",
15
+ "contributors": [
16
+ "Joe Maller <joe@ideasonpurpose.com>",
17
+ "Codrin Pavel <codrin@ideasonpurpose.com>"
18
+ ],
19
+ "type": "module",
20
+ "main": "index.js",
21
+ "bin": {
22
+ "iop-build-port-reporter": "port-reporter.js"
23
+ },
24
+ "scripts": {
25
+ "version": "version-everything && auto-changelog && git add -u"
26
+ },
27
+ "dependencies": {
28
+ "@rollup/plugin-commonjs": "^25.0.7",
29
+ "@rollup/plugin-json": "^6.1.0",
30
+ "@rollup/plugin-node-resolve": "^15.2.3",
31
+ "@svgr/webpack": "^8.1.0",
32
+ "@wordpress/dependency-extraction-webpack-plugin": "^5.0.0",
33
+ "ansi-html": "^0.0.9",
34
+ "archiver": "^6.0.1",
35
+ "auto-changelog": "^2.4.0",
36
+ "autoprefixer": "^10.4.17",
37
+ "babel-loader": "^9.1.3",
38
+ "body-parser": "^1.20.2",
39
+ "caniuse-lite": "^1.0.30001579",
40
+ "chalk": "^5.3.0",
41
+ "cli-truncate": "^4.0.0",
42
+ "copy-webpack-plugin": "^12.0.2",
43
+ "cosmiconfig": "^9.0.0",
44
+ "css-loader": "^6.9.1",
45
+ "cssnano": "^6.0.1",
46
+ "del": "^7.1.0",
47
+ "dotenv": "^16.3.2",
48
+ "esbuild-loader": "^4.0.3",
49
+ "filesize": "^10.0.12",
50
+ "fs-extra": "^11.1.1",
51
+ "globby": "^14.0.0",
52
+ "http-proxy": "^1.18.1",
53
+ "humanize-duration": "^3.31.0",
54
+ "image-minimizer-webpack-plugin": "^4.0.0",
55
+ "is-text-path": "^2.0.0",
56
+ "lodash": "^4.17.21",
57
+ "mini-css-extract-plugin": "^2.7.6",
58
+ "node-sass": "^9.0.0",
59
+ "postcss": "^8.4.29",
60
+ "postcss-loader": "^8.0.0",
61
+ "pretty-hrtime": "^1.0.3",
62
+ "replacestream": "^4.0.3",
63
+ "sass": "^1.70.0",
64
+ "sass-embedded": "^1.70.0",
65
+ "sass-loader": "^14.0.0",
66
+ "sharp": "^0.33.2",
67
+ "source-map-explorer": "^2.5.3",
68
+ "string-length": "^6.0.0",
69
+ "style-loader": "^3.3.3",
70
+ "svgo": "^3.0.2",
71
+ "svgo-loader": "^4.0.0",
72
+ "version-everything": "^0.11.0",
73
+ "webpack": "^5.88.2",
74
+ "webpack-bundle-analyzer": "^4.9.1",
75
+ "webpack-cli": "^5.1.4",
76
+ "webpack-dev-middleware": "^7.0.0",
77
+ "webpack-dev-server": "^4.15.1"
78
+ },
79
+ "version-everything": {
80
+ "files": [
81
+ "README.md"
82
+ ]
83
+ },
84
+ "directories": {
85
+ "lib": "lib"
86
+ },
87
+ "devDependencies": {}
88
+ }