diff --git a/.cspell.json b/.cspell.json
index 156c2be7a33..02d12f393c6 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -64,6 +64,7 @@
"nwjs",
"Oikawa",
"pathinfo",
+ "plopfile",
"pnpm",
"postcss",
"prebuild",
@@ -101,7 +102,8 @@
"Yuuji",
"Zangetsu",
"Zenitsu",
- "quickstart"
+ "quickstart",
+ "pinia"
],
"dictionaries": ["npm", "software-terms"],
"ignorePaths": [
diff --git a/.eslintrc.js b/.eslintrc.js
index 4c008e454c6..1f312dc4c4f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -57,5 +57,11 @@ module.exports = {
"@typescript-eslint/no-require-imports": "off",
},
},
+ {
+ files: ["**/packages/create-webpack-app/**/*.js"],
+ parserOptions: {
+ sourceType: "module",
+ },
+ },
],
};
diff --git a/packages/create-webpack-app/README.md b/packages/create-webpack-app/README.md
new file mode 100644
index 00000000000..a23c5eee3f9
--- /dev/null
+++ b/packages/create-webpack-app/README.md
@@ -0,0 +1,48 @@
+
+
+# create-webpack-app CLI
+
+## About
+
+- `create-webpack-app` is a cli tool that enables developers to scaffold a new webpack project quickly. It provides developers with a flexible set of commands to increase speed when setting up a custom webpack project. webpack CLI addresses these needs by providing tools to improve the setup of custom webpack configuration.
+- It also supports several front-end frameworks and libraries like React, Angular, Vue, Svelte, etc.
+- Webpack Loader and Plugin scaffolding is also supported.
+
+## Supported arguments and commands
+
+### Usage
+
+```bash
+npx create-webpack-app [command] [options]
+```
+
+### CLI options
+
+**To generate default template**
+
+```bash
+npx create-webpack-app
+```
+
+**To generate with default answers**
+
+```bash
+npx create-webpack-app -f, --force
+```
+
+**To generate in a specified path**
+
+```bash
+npx create-webpack-app [generation-path]
+```
+
+**To generate a project according to a template**
+
+```bash
+npx create-webpack-app --template
+
+```
diff --git a/packages/create-webpack-app/bin/cli.js b/packages/create-webpack-app/bin/cli.js
new file mode 100755
index 00000000000..aaefc07b7d0
--- /dev/null
+++ b/packages/create-webpack-app/bin/cli.js
@@ -0,0 +1,4 @@
+#!/usr/bin/env node
+
+//eslint-disable-next-line
+import * as cli from "../lib/index.js";
diff --git a/packages/create-webpack-app/package.json b/packages/create-webpack-app/package.json
new file mode 100644
index 00000000000..11c658c11b0
--- /dev/null
+++ b/packages/create-webpack-app/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "create-webpack-app",
+ "version": "1.0.0",
+ "description": "CLI for scaffolding webpack projects using default config, framework templates, loader or plugins templates",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/webpack-cli/create-webpack-app.git"
+ },
+ "homepage": "https://github.com/webpack/webpack-cli/tree/master/packages/create-webpack-app",
+ "bugs": "https://github.com/webpack/webpack-cli/issues",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "bin": {
+ "create-webpack-app": "./bin/cli.js"
+ },
+ "type": "module",
+ "main": "./lib/index.js",
+ "scripts": {
+ "build": "tsc --build",
+ "watch": "tsc --watch"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "keywords": [
+ "webpack",
+ "cli",
+ "scaffolding",
+ "module",
+ "bundler",
+ "web",
+ "frameworks"
+ ],
+ "files": [
+ "bin",
+ "lib",
+ "!**/*__tests__"
+ ],
+ "peerDependencies": {
+ "webpack-cli": "^5.x.x"
+ },
+ "dependencies": {
+ "@inquirer/prompts": "^5.1.2",
+ "colorette": "^2.0.20",
+ "commander": "^12.1.0",
+ "cross-spawn": "^7.0.3",
+ "ejs": "^3.1.10",
+ "node-plop": "^0.32.0"
+ },
+ "devDependencies": {
+ "@types/cross-spawn": "^6.0.6",
+ "@types/ejs": "^3.1.5"
+ }
+}
diff --git a/packages/create-webpack-app/src/generators/init/default.ts b/packages/create-webpack-app/src/generators/init/default.ts
new file mode 100644
index 00000000000..b617059bd86
--- /dev/null
+++ b/packages/create-webpack-app/src/generators/init/default.ts
@@ -0,0 +1,205 @@
+import { Answers, ActionType, FileRecord } from "../../types";
+import { dirname, join, resolve } from "path";
+import { NodePlopAPI, DynamicActionsFunction } from "node-plop";
+import { fileURLToPath } from "url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+export default async function (plop: NodePlopAPI) {
+ // dependencies to be installed
+ const devDependencies: Array = ["webpack", "webpack-cli"];
+
+ await plop.load("../../utils/pkgInstallAction.js", {}, true);
+ await plop.load("../../utils/fileGenerator.js", {}, true);
+
+ plop.setDefaultInclude({ generators: true, actionTypes: true });
+ plop.setPlopfilePath(resolve(__dirname, "../../plopfile.js"));
+ // Define a custom action for installing packages
+
+ // Define a base generator for the project structure
+ plop.setGenerator("init-default", {
+ description: "Create a basic webpack project",
+ prompts: [
+ {
+ type: "list",
+ name: "langType",
+ message: "Which of the following JS solutions do you want to use?",
+ choices: ["none", "ES6", "Typescript"],
+ default: "none",
+ },
+ {
+ type: "confirm",
+ name: "devServer",
+ message: "Would you like to use Webpack Dev server?",
+ default: true,
+ },
+ {
+ type: "confirm",
+ name: "htmlWebpackPlugin",
+ message: "Do you want to simplify the creation of HTML files for your bundle?",
+ default: true,
+ },
+ {
+ type: "confirm",
+ name: "workboxWebpackPlugin",
+ message: "Do you want to add PWA support?",
+ default: true,
+ },
+ {
+ type: "list",
+ name: "cssType",
+ message: "Which of the following CSS solution do you want to use?",
+ choices: ["none", "CSS only", "SASS", "LESS", "Stylus"],
+ default: "none",
+ filter: (input, answers) => {
+ if (input === "none") {
+ answers.isCSS = false;
+ answers.isPostCSS = false;
+ answers.extractPlugin = "No";
+ } else if (input === "CSS only") {
+ answers.isCSS = true;
+ }
+ return input;
+ },
+ },
+ {
+ type: "confirm",
+ name: "isCSS",
+ message: (answers) =>
+ `Will you be using CSS styles along with ${answers.cssType} in your project?`,
+ when: (answers) => answers.cssType !== "CSS only",
+ default: true,
+ },
+ {
+ type: "confirm",
+ name: "isPostCSS",
+ message: "Do you want to use PostCSS in your project?",
+ default: (answers: Answers) => answers.cssType == "CSS only",
+ },
+ {
+ type: "list",
+ name: "extractPlugin",
+ message: "Do you want to extract CSS into separate files?",
+ choices: ["No", "Only for Production", "Yes"],
+ default: "No",
+ },
+ {
+ type: "list",
+ name: "packageManager",
+ message: "Which package manager do you want to use?",
+ choices: ["npm", "yarn", "pnpm"],
+ default: "npm",
+ validate(input) {
+ if (!input.trim()) {
+ return "Package manager cannot be empty";
+ }
+ return true;
+ },
+ },
+ ],
+ actions: function (answers: Answers) {
+ const actions: ActionType[] = [];
+
+ switch (answers.langType) {
+ case "ES6":
+ devDependencies.push("babel-loader", "@babel/core", "@babel/preset-env");
+ break;
+ case "Typescript":
+ devDependencies.push("typescript", "ts-loader");
+ break;
+ }
+
+ if (answers.devServer) {
+ devDependencies.push("webpack-dev-server");
+ }
+
+ if (answers.htmlWebpackPlugin) {
+ devDependencies.push("html-webpack-plugin", "html-loader");
+ }
+
+ if (answers.workboxWebpackPlugin) {
+ devDependencies.push("workbox-webpack-plugin");
+ }
+
+ if (answers.isPostCSS) {
+ devDependencies.push("postcss-loader", "postcss", "autoprefixer");
+ }
+
+ if (answers.extractPlugin !== "No") {
+ devDependencies.push("mini-css-extract-plugin");
+ }
+
+ if (answers.cssType !== "none") {
+ devDependencies.push("style-loader", "css-loader");
+ switch (answers.cssType) {
+ case "SASS":
+ devDependencies.push("sass-loader", "sass");
+ break;
+ case "LESS":
+ devDependencies.push("less-loader", "less");
+ break;
+ case "Stylus":
+ devDependencies.push("stylus-loader", "stylus");
+ break;
+ }
+ }
+ if (answers.extractPlugin !== "No") {
+ devDependencies.push("mini-css-extract-plugin");
+ }
+
+ const files: Array = [
+ { filePath: "./index.html", fileType: "text" },
+ { filePath: "webpack.config.js", fileType: "text" },
+ { filePath: "package.json", fileType: "text" },
+ { filePath: "README.md", fileType: "text" },
+ ];
+
+ switch (answers.langType) {
+ case "Typescript":
+ answers.entryPoint = "./src/index.ts";
+ files.push(
+ { filePath: "tsconfig.json", fileType: "text" },
+ { filePath: answers.entryPoint as string, fileType: "text" },
+ );
+ break;
+ case "ES6":
+ answers.entryPoint = "./src/index.js";
+ files.push(
+ { filePath: "babel.config.json", fileType: "text" },
+ { filePath: answers.entryPoint as string, fileType: "text" },
+ );
+ break;
+ default:
+ answers.entryPoint = "./src/index.js";
+ files.push({ filePath: answers.entryPoint as string, fileType: "text" });
+ break;
+ }
+
+ if (answers.isPostCSS) {
+ files.push({ filePath: "postcss.config.js", fileType: "text" });
+ }
+
+ for (const file of files) {
+ actions.push({
+ type: "fileGenerator",
+ path: join(answers.projectPath, file.filePath),
+ templateFile: join(
+ plop.getPlopfilePath(),
+ "../templates/init/default",
+ `${file.filePath}.tpl`,
+ ),
+ fileType: file.fileType,
+ data: answers,
+ });
+ }
+
+ actions.push({
+ type: "pkgInstall",
+ path: answers.projectPath,
+ packages: devDependencies,
+ });
+
+ return actions;
+ } as DynamicActionsFunction,
+ });
+}
diff --git a/packages/create-webpack-app/src/generators/init/react.ts b/packages/create-webpack-app/src/generators/init/react.ts
new file mode 100644
index 00000000000..32b3a0a540e
--- /dev/null
+++ b/packages/create-webpack-app/src/generators/init/react.ts
@@ -0,0 +1,224 @@
+import { Answers, ActionType, FileRecord } from "../../types";
+import { dirname, resolve, join } from "path";
+import { NodePlopAPI, DynamicActionsFunction } from "node-plop";
+import { fileURLToPath } from "url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+export default async function (plop: NodePlopAPI) {
+ // dependencies to be installed
+ const devDependencies: Array = [
+ "webpack",
+ "webpack-cli",
+ "react@18",
+ "react-dom@18",
+ "webpack-dev-server",
+ "html-webpack-plugin",
+ "react-router-dom",
+ "@types/react-router-dom",
+ ];
+
+ await plop.load("../../utils/pkgInstallAction.js", {}, true);
+ await plop.load("../../utils/fileGenerator.js", {}, true);
+
+ plop.setDefaultInclude({ generators: true, actionTypes: true });
+ plop.setPlopfilePath(resolve(__dirname, "../../plopfile.js"));
+ // Define a custom action for installing packages
+
+ // Define a base generator for the project structure
+ plop.setGenerator("init-react", {
+ description: "Create a basic React-webpack project",
+ prompts: [
+ {
+ type: "list",
+ name: "langType",
+ message: "Which of the following JS solutions do you want to use?",
+ choices: ["ES6", "Typescript"],
+ default: "ES6",
+ },
+ {
+ type: "confirm",
+ name: "useReactState",
+ message: "Do you want to use React State in your project?",
+ default: true,
+ },
+ {
+ type: "confirm",
+ name: "workboxWebpackPlugin",
+ message: "Do you want to add PWA support?",
+ default: true,
+ },
+ {
+ type: "list",
+ name: "cssType",
+ message: "Which of the following CSS solution do you want to use?",
+ choices: ["none", "CSS only", "SASS", "LESS", "Stylus"],
+ default: "CSS only",
+ filter: (input, answers) => {
+ if (input === "none") {
+ answers.isCSS = false;
+ answers.isPostCSS = false;
+ answers.extractPlugin = "No";
+ } else if (input === "CSS only") {
+ answers.isCSS = true;
+ }
+ return input;
+ },
+ },
+ {
+ type: "confirm",
+ name: "isCSS",
+ message: (answers) =>
+ `Will you be using CSS styles along with ${answers.cssType} in your project?`,
+ when: (answers) => answers.cssType !== "CSS only",
+ default: true,
+ },
+ {
+ type: "confirm",
+ name: "isPostCSS",
+ message: "Do you want to use PostCSS in your project?",
+ default: (answers: Answers) => answers.cssType == "CSS only",
+ },
+ {
+ type: "list",
+ name: "extractPlugin",
+ message: "Do you want to extract CSS into separate files?",
+ choices: ["No", "Only for Production", "Yes"],
+ default: "No",
+ },
+ {
+ type: "list",
+ name: "packageManager",
+ message: "Which package manager do you want to use?",
+ choices: ["npm", "yarn", "pnpm"],
+ default: "npm",
+ validate(input) {
+ if (!input.trim()) {
+ return "Package manager cannot be empty";
+ }
+ return true;
+ },
+ },
+ ],
+ actions: function (answers: Answers) {
+ // setting some default values based on the answers
+ const actions: ActionType[] = [];
+ answers.htmlWebpackPlugin = true;
+ answers.devServer = true;
+ switch (answers.langType) {
+ case "ES6":
+ devDependencies.push(
+ "babel-loader",
+ "@babel/core",
+ "@babel/preset-env",
+ "@babel/preset-react",
+ );
+ break;
+ case "Typescript":
+ devDependencies.push("typescript", "ts-loader", "@types/react", "@types/react-dom");
+ break;
+ }
+ if (answers.isPostCSS) {
+ devDependencies.push("postcss-loader", "postcss", "autoprefixer");
+ }
+ if (answers.cssType === "none") {
+ answers.isCSS = false;
+ answers.isPostCSS = false;
+ answers.extractPlugin = "No";
+ } else {
+ devDependencies.push("style-loader", "css-loader");
+ switch (answers.cssType) {
+ case "CSS only":
+ answers.isCSS = true;
+ break;
+ case "SASS":
+ devDependencies.push("sass-loader", "sass");
+ break;
+ case "LESS":
+ devDependencies.push("less-loader", "less");
+ break;
+ case "Stylus":
+ devDependencies.push("stylus-loader", "stylus");
+ break;
+ }
+ }
+ if (answers.extractPlugin !== "No") {
+ devDependencies.push("mini-css-extract-plugin");
+ }
+ if (answers.workboxWebpackPlugin) {
+ devDependencies.push("workbox-webpack-plugin");
+ }
+
+ const files: Array = [
+ { filePath: "./index.html", fileType: "text" },
+ { filePath: "webpack.config.js", fileType: "text" },
+ { filePath: "package.json", fileType: "text" },
+ { filePath: "README.md", fileType: "text" },
+ { filePath: "./src/assets/webpack.png", fileType: "binary" },
+ ];
+
+ switch (answers.langType) {
+ case "Typescript":
+ answers.entry = "./src/index.tsx";
+ files.push(
+ { filePath: "tsconfig.json", fileType: "text" },
+ { filePath: "index.d.ts", fileType: "text" },
+ { filePath: "./src/App.tsx", fileType: "text" },
+ { filePath: "./src/components/About.tsx", fileType: "text" },
+ { filePath: "./src/components/Home.tsx", fileType: "text" },
+ { filePath: "./src/components/Navbar.tsx", fileType: "text" },
+ { filePath: "./src/router/index.tsx", fileType: "text" },
+ { filePath: answers.entry as string, fileType: "text" },
+ );
+ break;
+ case "ES6":
+ answers.entry = "./src/index.jsx";
+ files.push(
+ { filePath: "./src/App.jsx", fileType: "text" },
+ { filePath: "./src/components/About.jsx", fileType: "text" },
+ { filePath: "./src/components/Home.jsx", fileType: "text" },
+ { filePath: "./src/components/Navbar.jsx", fileType: "text" },
+ { filePath: "./src/router/index.jsx", fileType: "text" },
+ { filePath: answers.entry as string, fileType: "text" },
+ );
+ break;
+ }
+
+ switch (answers.cssType) {
+ case "CSS only":
+ files.push({ filePath: "./src/styles/global.css", fileType: "text" });
+ break;
+ case "SASS":
+ files.push({ filePath: "./src/styles/global.scss", fileType: "text" });
+ break;
+ case "LESS":
+ files.push({ filePath: "./src/styles/global.less", fileType: "text" });
+ break;
+ case "Stylus":
+ files.push({ filePath: "./src/styles/global.styl", fileType: "text" });
+ break;
+ }
+
+ for (const file of files) {
+ actions.push({
+ type: "fileGenerator",
+ path: join(answers.projectPath, file.filePath),
+ templateFile: join(
+ plop.getPlopfilePath(),
+ "../templates/init/react",
+ `${file.filePath}.tpl`,
+ ),
+ fileType: file.fileType,
+ data: answers,
+ });
+ }
+
+ actions.push({
+ type: "pkgInstall",
+ path: answers.projectPath,
+ packages: devDependencies,
+ });
+ return actions;
+ } as DynamicActionsFunction,
+ });
+}
diff --git a/packages/create-webpack-app/src/generators/init/svelte.ts b/packages/create-webpack-app/src/generators/init/svelte.ts
new file mode 100644
index 00000000000..98dc3aee228
--- /dev/null
+++ b/packages/create-webpack-app/src/generators/init/svelte.ts
@@ -0,0 +1,212 @@
+import { Answers, ActionType, FileRecord } from "../../types";
+import { dirname, join, resolve } from "path";
+import { NodePlopAPI, DynamicActionsFunction } from "node-plop";
+import { fileURLToPath } from "url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+export default async function (plop: NodePlopAPI) {
+ // dependencies to be installed
+ const devDependencies: Array = [
+ "webpack",
+ "webpack-cli",
+ "svelte",
+ "svelte-loader",
+ "webpack-dev-server",
+ "html-webpack-plugin",
+ ];
+
+ await plop.load("../../utils/pkgInstallAction.js", {}, true);
+ await plop.load("../../utils/fileGenerator.js", {}, true);
+
+ plop.setDefaultInclude({ generators: true, actionTypes: true });
+ plop.setPlopfilePath(resolve(__dirname, "../../plopfile.js"));
+
+ // Define a base generator for the Svelte project structure
+ plop.setGenerator("init-svelte", {
+ description: "Create a basic Svelte-webpack project",
+ prompts: [
+ {
+ type: "list",
+ name: "langType",
+ message: "Which of the following JS solutions do you want to use?",
+ choices: ["ES6", "Typescript"],
+ default: "ES6",
+ },
+ {
+ type: "confirm",
+ name: "workboxWebpackPlugin",
+ message: "Do you want to add PWA support?",
+ default: true,
+ },
+ {
+ type: "list",
+ name: "cssType",
+ message: "Which of the following CSS solution do you want to use?",
+ choices: ["none", "CSS only", "SASS", "LESS", "Stylus"],
+ default: "CSS only",
+ filter: (input, answers) => {
+ if (input === "none") {
+ answers.isCSS = false;
+ answers.isPostCSS = false;
+ answers.extractPlugin = "No";
+ } else if (input === "CSS only") {
+ answers.isCSS = true;
+ }
+ return input;
+ },
+ },
+ {
+ type: "confirm",
+ name: "isCSS",
+ message: (answers) =>
+ `Will you be using CSS styles along with ${answers.cssType} in your project?`,
+ when: (answers) => answers.cssType !== "CSS only",
+ default: true,
+ },
+ {
+ type: "confirm",
+ name: "isPostCSS",
+ message: "Do you want to use PostCSS in your project?",
+ default: (answers: Answers) => answers.cssType == "CSS only",
+ },
+ {
+ type: "list",
+ name: "extractPlugin",
+ message: "Do you want to extract CSS into separate files?",
+ choices: ["No", "Only for Production", "Yes"],
+ default: "No",
+ },
+ {
+ type: "list",
+ name: "packageManager",
+ message: "Which package manager do you want to use?",
+ choices: ["npm", "yarn", "pnpm"],
+ default: "npm",
+ validate(input) {
+ if (!input.trim()) {
+ return "Package manager cannot be empty";
+ }
+ return true;
+ },
+ },
+ ],
+ actions: function (answers: Answers) {
+ // setting some default values based on the answers
+ const actions: ActionType[] = [];
+ answers.htmlWebpackPlugin = true;
+ answers.devServer = true;
+
+ switch (answers.langType) {
+ case "ES6":
+ devDependencies.push("babel-loader", "@babel/core", "@babel/preset-env");
+ break;
+ case "Typescript":
+ devDependencies.push("typescript", "ts-loader", "@tsconfig/svelte");
+ break;
+ }
+
+ if (answers.isPostCSS) {
+ devDependencies.push("postcss-loader", "postcss", "autoprefixer");
+ }
+
+ if (answers.workboxWebpackPlugin) {
+ devDependencies.push("workbox-webpack-plugin");
+ }
+
+ if (answers.cssType === "none") {
+ answers.isCSS = false;
+ answers.isPostCSS = false;
+ answers.extractPlugin = "No";
+ } else {
+ devDependencies.push("style-loader", "css-loader");
+ switch (answers.cssType) {
+ case "CSS only":
+ answers.isCSS = true;
+ break;
+ case "SASS":
+ devDependencies.push("sass-loader", "sass");
+ break;
+ case "LESS":
+ devDependencies.push("less-loader", "less");
+ break;
+ case "Stylus":
+ devDependencies.push("stylus-loader", "stylus");
+ break;
+ }
+ }
+
+ if (answers.extractPlugin !== "No") {
+ devDependencies.push("mini-css-extract-plugin");
+ }
+
+ const files: Array = [
+ { filePath: "./index.html", fileType: "text" },
+ { filePath: "./src/assets/webpack.png", fileType: "binary" },
+ { filePath: "webpack.config.js", fileType: "text" },
+ { filePath: "package.json", fileType: "text" },
+ { filePath: "README.md", fileType: "text" },
+ { filePath: "./src/components/HelloWorld.svelte", fileType: "text" },
+ { filePath: "./src/App.svelte", fileType: "text" },
+ ];
+
+ switch (answers.langType) {
+ case "Typescript":
+ answers.entry = "./src/main.ts";
+ files.push(
+ { filePath: "tsconfig.json", fileType: "text" },
+ { filePath: "./src/index.d.ts", fileType: "text" },
+ { filePath: answers.entry as string, fileType: "text" },
+ );
+ break;
+ case "ES6":
+ answers.entry = "./src/main.js";
+ files.push({ filePath: answers.entry as string, fileType: "text" });
+ break;
+ }
+
+ if (answers.langType === "Typescript") {
+ files.push({ filePath: "./src/store/index.ts", fileType: "text" });
+ } else {
+ files.push({ filePath: "./src/store/index.js", fileType: "text" });
+ }
+
+ switch (answers.cssType) {
+ case "CSS only":
+ files.push({ filePath: "./src/styles/global.css", fileType: "text" });
+ break;
+ case "SASS":
+ files.push({ filePath: "./src/styles/global.scss", fileType: "text" });
+ break;
+ case "LESS":
+ files.push({ filePath: "./src/styles/global.less", fileType: "text" });
+ break;
+ case "Stylus":
+ files.push({ filePath: "./src/styles/global.styl", fileType: "text" });
+ break;
+ }
+
+ for (const file of files) {
+ actions.push({
+ type: "fileGenerator",
+ path: join(answers.projectPath, file.filePath),
+ templateFile: join(
+ plop.getPlopfilePath(),
+ "../templates/init/svelte",
+ `${file.filePath}.tpl`,
+ ),
+ fileType: file.fileType,
+ data: answers,
+ });
+ }
+
+ actions.push({
+ type: "pkgInstall",
+ path: answers.projectPath,
+ packages: devDependencies,
+ });
+
+ return actions;
+ } as DynamicActionsFunction,
+ });
+}
diff --git a/packages/create-webpack-app/src/generators/init/vue.ts b/packages/create-webpack-app/src/generators/init/vue.ts
new file mode 100644
index 00000000000..025daa80648
--- /dev/null
+++ b/packages/create-webpack-app/src/generators/init/vue.ts
@@ -0,0 +1,233 @@
+import { Answers, ActionType, FileRecord } from "../../types";
+import { dirname, join, resolve } from "path";
+import { NodePlopAPI, DynamicActionsFunction } from "node-plop";
+import { fileURLToPath } from "url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+export default async function (plop: NodePlopAPI) {
+ // dependencies to be installed
+ const devDependencies: Array = [
+ "webpack",
+ "webpack-cli",
+ "vue@3",
+ "webpack-dev-server",
+ "html-webpack-plugin",
+ "vue-loader@next",
+ "@vue/compiler-sfc",
+ "vue-router@4",
+ ];
+
+ await plop.load("../../utils/pkgInstallAction.js", {}, true);
+ await plop.load("../../utils/fileGenerator.js", {}, true);
+
+ plop.setDefaultInclude({ generators: true, actionTypes: true });
+ plop.setPlopfilePath(resolve(__dirname, "../../plopfile.js"));
+
+ // Define a base generator for the Vue 3 project structure
+ plop.setGenerator("init-vue", {
+ description: "Create a basic Vue-webpack project",
+ prompts: [
+ {
+ type: "list",
+ name: "langType",
+ message: "Which of the following JS solutions do you want to use?",
+ choices: ["ES6", "Typescript"],
+ default: "ES6",
+ },
+ {
+ type: "confirm",
+ name: "useVueStore",
+ message: "Do you want to use Pinia for state management?",
+ default: true,
+ },
+ {
+ type: "confirm",
+ name: "workboxWebpackPlugin",
+ message: "Do you want to add PWA support?",
+ default: true,
+ },
+ {
+ type: "list",
+ name: "cssType",
+ message: "Which of the following CSS solution do you want to use?",
+ choices: ["none", "CSS only", "SASS", "LESS", "Stylus"],
+ default: "CSS only",
+ filter: (input, answers) => {
+ if (input === "none") {
+ answers.isCSS = false;
+ answers.isPostCSS = false;
+ answers.extractPlugin = "No";
+ } else if (input === "CSS only") {
+ answers.isCSS = true;
+ }
+ return input;
+ },
+ },
+ {
+ type: "confirm",
+ name: "isCSS",
+ message: (answers) =>
+ `Will you be using CSS styles along with ${answers.cssType} in your project?`,
+ when: (answers) => answers.cssType !== "CSS only",
+ default: true,
+ },
+ {
+ type: "confirm",
+ name: "isPostCSS",
+ message: "Do you want to use PostCSS in your project?",
+ default: (answers: Answers) => answers.cssType == "CSS only",
+ },
+ {
+ type: "list",
+ name: "extractPlugin",
+ message: "Do you want to extract CSS into separate files?",
+ choices: ["No", "Only for Production", "Yes"],
+ default: "No",
+ },
+ {
+ type: "list",
+ name: "packageManager",
+ message: "Which package manager do you want to use?",
+ choices: ["npm", "yarn", "pnpm"],
+ default: "npm",
+ validate(input) {
+ if (!input.trim()) {
+ return "Package manager cannot be empty";
+ }
+ return true;
+ },
+ },
+ ],
+ actions: function (answers: Answers) {
+ // setting some default values based on the answers
+ const actions: ActionType[] = [];
+ answers.htmlWebpackPlugin = true;
+ answers.devServer = true;
+
+ switch (answers.langType) {
+ case "ES6":
+ devDependencies.push("babel-loader", "@babel/core", "@babel/preset-env");
+ break;
+ case "Typescript":
+ devDependencies.push("typescript", "ts-loader");
+ break;
+ }
+
+ if (answers.useVueStore) {
+ devDependencies.push("pinia");
+ }
+
+ if (answers.isPostCSS) {
+ devDependencies.push("postcss-loader", "postcss", "autoprefixer");
+ }
+
+ if (answers.workboxWebpackPlugin) {
+ devDependencies.push("workbox-webpack-plugin");
+ }
+
+ if (answers.cssType === "none") {
+ answers.isCSS = false;
+ answers.isPostCSS = false;
+ answers.extractPlugin = "No";
+ } else {
+ devDependencies.push("vue-style-loader", "style-loader", "css-loader");
+ switch (answers.cssType) {
+ case "CSS only":
+ answers.isCSS = true;
+ break;
+ case "SASS":
+ devDependencies.push("sass-loader", "sass");
+ break;
+ case "LESS":
+ devDependencies.push("less-loader", "less");
+ break;
+ case "Stylus":
+ devDependencies.push("stylus-loader", "stylus");
+ break;
+ }
+ }
+
+ if (answers.extractPlugin !== "No") {
+ devDependencies.push("mini-css-extract-plugin");
+ }
+
+ const files: Array = [
+ { filePath: "./index.html", fileType: "text" },
+ { filePath: "./src/assets/webpack.png", fileType: "binary" },
+ { filePath: "webpack.config.js", fileType: "text" },
+ { filePath: "package.json", fileType: "text" },
+ { filePath: "README.md", fileType: "text" },
+ { filePath: "./src/App.vue", fileType: "text" },
+ { filePath: "./src/components/Home.vue", fileType: "text" },
+ { filePath: "./src/components/About.vue", fileType: "text" },
+ { filePath: "./src/components/Layout.vue", fileType: "text" },
+ { filePath: "./src/components/Navbar.vue", fileType: "text" },
+ ];
+
+ switch (answers.langType) {
+ case "Typescript":
+ answers.entry = "./src/main.ts";
+ files.push(
+ { filePath: "tsconfig.json", fileType: "text" },
+ { filePath: answers.entry as string, fileType: "text" },
+ );
+ break;
+ case "ES6":
+ answers.entry = "./src/main.js";
+ files.push({ filePath: answers.entry as string, fileType: "text" });
+ break;
+ }
+
+ if (answers.langType === "Typescript") {
+ files.push({ filePath: "./src/router/index.ts", fileType: "text" });
+ } else {
+ files.push({ filePath: "./src/router/index.js", fileType: "text" });
+ }
+
+ if (answers.useVueStore) {
+ if (answers.langType === "Typescript") {
+ files.push({ filePath: "./src/store/index.ts", fileType: "text" });
+ } else {
+ files.push({ filePath: "./src/store/index.js", fileType: "text" });
+ }
+ }
+
+ switch (answers.cssType) {
+ case "CSS only":
+ files.push({ filePath: "./src/styles/global.css", fileType: "text" });
+ break;
+ case "SASS":
+ files.push({ filePath: "./src/styles/global.scss", fileType: "text" });
+ break;
+ case "LESS":
+ files.push({ filePath: "./src/styles/global.less", fileType: "text" });
+ break;
+ case "Stylus":
+ files.push({ filePath: "./src/styles/global.styl", fileType: "text" });
+ break;
+ }
+
+ for (const file of files) {
+ actions.push({
+ type: "fileGenerator",
+ path: join(answers.projectPath, file.filePath),
+ templateFile: join(
+ plop.getPlopfilePath(),
+ "../templates/init/vue",
+ `${file.filePath}.tpl`,
+ ),
+ fileType: file.fileType,
+ data: answers,
+ });
+ }
+
+ actions.push({
+ type: "pkgInstall",
+ path: answers.projectPath,
+ packages: devDependencies,
+ });
+
+ return actions;
+ } as DynamicActionsFunction,
+ });
+}
diff --git a/packages/create-webpack-app/src/generators/loader/default.ts b/packages/create-webpack-app/src/generators/loader/default.ts
new file mode 100644
index 00000000000..be50e990938
--- /dev/null
+++ b/packages/create-webpack-app/src/generators/loader/default.ts
@@ -0,0 +1,99 @@
+import { Answers, ActionType, FileRecord } from "../../types";
+import { dirname, join, resolve } from "path";
+import { NodePlopAPI, DynamicActionsFunction } from "node-plop";
+import { fileURLToPath } from "url";
+import { logger } from "../../utils/logger.js";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+export default async function (plop: NodePlopAPI) {
+ // dependencies to be installed
+ const devDependencies: Array = ["webpack-defaults"];
+
+ await plop.load("../../utils/pkgInstallAction.js", {}, true);
+ await plop.load("../../utils/fileGenerator.js", {}, true);
+
+ // custom helper function
+ plop.setHelper("makeLoaderName", (name: string) => {
+ name = plop.getHelper("kebabCase")(name);
+
+ if (!/loader$/.test(name)) {
+ name += "-loader";
+ }
+ return name;
+ });
+
+ plop.setDefaultInclude({ generators: true, actionTypes: true });
+ plop.setPlopfilePath(resolve(__dirname, "../../plopfile.js"));
+
+ // Define a base generator for the project structure
+ plop.setGenerator("loader-default", {
+ description: "Create a basic webpack loader.",
+ prompts: [
+ {
+ type: "input",
+ name: "name",
+ message: "Loader name?",
+ default: "my-loader",
+ validate: (str: string): boolean => str.length > 0,
+ },
+ {
+ type: "list",
+ name: "packageManager",
+ message: "Pick a package manager:",
+ choices: ["npm", "yarn", "pnpm"],
+ default: "npm",
+ validate(input) {
+ if (!input.trim()) {
+ return "Package manager cannot be empty";
+ }
+ return true;
+ },
+ },
+ ],
+ actions: function (answers: Answers) {
+ const actions: ActionType[] = [];
+ answers.projectPath = join(answers.projectPath, answers.name);
+
+ logger.error(`
+ Your project must be inside a folder named ${answers.name}
+ I will create this folder for you.
+ `);
+
+ const files: Array = [
+ { filePath: "./package.json", fileType: "text" },
+ { filePath: "./examples/simple/src/index.js", fileType: "text" },
+ { filePath: "./examples/simple/src/lazy-module.js", fileType: "text" },
+ { filePath: "./examples/simple/src/static-esm-module.js", fileType: "text" },
+ { filePath: "./examples/simple/webpack.config.js", fileType: "text" },
+ { filePath: "./src/cjs.js", fileType: "text" },
+ { filePath: "./test/fixtures/simple-file.js", fileType: "text" },
+ { filePath: "./test/functional.test.js", fileType: "text" },
+ { filePath: "./test/test-utils.js", fileType: "text" },
+ { filePath: "./test/unit.test.js", fileType: "text" },
+ { filePath: "./src/index.js", fileType: "text" },
+ ];
+
+ for (const file of files) {
+ actions.push({
+ type: "fileGenerator",
+ path: join(answers.projectPath, file.filePath),
+ templateFile: join(
+ plop.getPlopfilePath(),
+ "../templates/loader/default",
+ `${file.filePath}.tpl`,
+ ),
+ fileType: file.fileType,
+ data: answers,
+ });
+ }
+
+ actions.push({
+ type: "pkgInstall",
+ path: answers.projectPath,
+ packages: devDependencies,
+ });
+ return actions;
+ } as DynamicActionsFunction,
+ });
+}
diff --git a/packages/create-webpack-app/src/generators/plugin/default.ts b/packages/create-webpack-app/src/generators/plugin/default.ts
new file mode 100644
index 00000000000..cd8f5330e9b
--- /dev/null
+++ b/packages/create-webpack-app/src/generators/plugin/default.ts
@@ -0,0 +1,91 @@
+import { Answers, ActionType, FileRecord } from "../../types";
+import { dirname, join, resolve } from "path";
+import { NodePlopAPI, DynamicActionsFunction } from "node-plop";
+import { fileURLToPath } from "url";
+import { logger } from "../../utils/logger.js";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+export default async function (plop: NodePlopAPI) {
+ // dependencies to be installed
+ const devDependencies: Array = ["webpack-defaults"];
+
+ await plop.load("../../utils/pkgInstallAction.js", {}, true);
+ await plop.load("../../utils/fileGenerator.js", {}, true);
+
+ plop.setDefaultInclude({ generators: true, actionTypes: true });
+ plop.setPlopfilePath(resolve(__dirname, "../../plopfile.js"));
+
+ // Define a base generator for the project structure
+ plop.setGenerator("plugin-default", {
+ description: "Create a basic webpack plugin.",
+ prompts: [
+ {
+ type: "input",
+ name: "name",
+ message: "Plugin name?",
+ default: "my-webpack-plugin",
+ filter: (input) => plop.getHelper("kebabCase")(input),
+ validate: (str: string): boolean => str.length > 0,
+ },
+ {
+ type: "list",
+ name: "packageManager",
+ message: "Pick a package manager:",
+ choices: ["npm", "yarn", "pnpm"],
+ default: "npm",
+ validate(input) {
+ if (!input.trim()) {
+ return "Package manager cannot be empty";
+ }
+ return true;
+ },
+ },
+ ],
+ actions: function (answers: Answers) {
+ const actions: ActionType[] = [];
+ answers.projectPath = join(answers.projectPath, answers.name);
+
+ logger.error(`
+ Your project must be inside a folder named ${answers.name}
+ I will create this folder for you.
+ `);
+
+ answers.pluginIdentifier = plop.getHelper("pascalCase")(answers.name);
+
+ const files: Array = [
+ { filePath: "./package.json", fileType: "text" },
+ { filePath: "./examples/simple/src/index.js", fileType: "text" },
+ { filePath: "./examples/simple/src/lazy-module.js", fileType: "text" },
+ { filePath: "./examples/simple/src/static-esm-module.js", fileType: "text" },
+ { filePath: "./examples/simple/webpack.config.js", fileType: "text" },
+ { filePath: "./src/cjs.js", fileType: "text" },
+ { filePath: "./test/fixtures/simple-file.js", fileType: "text" },
+ { filePath: "./test/functional.test.js", fileType: "text" },
+ { filePath: "./test/test-utils.js", fileType: "text" },
+ { filePath: "./src/index.js", fileType: "text" },
+ ];
+
+ for (const file of files) {
+ actions.push({
+ type: "fileGenerator",
+ path: join(answers.projectPath, file.filePath),
+ templateFile: join(
+ plop.getPlopfilePath(),
+ "../templates/plugin/default",
+ `${file.filePath}.tpl`,
+ ),
+ fileType: file.fileType,
+ data: answers,
+ });
+ }
+
+ actions.push({
+ type: "pkgInstall",
+ path: answers.projectPath,
+ packages: devDependencies,
+ });
+ return actions;
+ } as DynamicActionsFunction,
+ });
+}
diff --git a/packages/create-webpack-app/src/index.ts b/packages/create-webpack-app/src/index.ts
new file mode 100644
index 00000000000..1f67cc6c65d
--- /dev/null
+++ b/packages/create-webpack-app/src/index.ts
@@ -0,0 +1,211 @@
+import { Command } from "commander";
+import { resolve, dirname } from "path";
+import { select } from "@inquirer/prompts";
+import nodePlop, { PlopGenerator } from "node-plop";
+import { fileURLToPath } from "url";
+
+import { onSuccessHandler, onFailureHandler, logger } from "./utils/logger.js";
+import { Answers, InitOptions, LoaderOptions, PluginOptions } from "./types";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+const program = new Command();
+
+const plop = await nodePlop(resolve(__dirname, "./plopfile.js"));
+
+const baseAnswers: Answers = {
+ projectPath: process.cwd(),
+ langType: "none",
+ devServer: true,
+ htmlWebpackPlugin: true,
+ workboxWebpackPlugin: true,
+ cssType: "none",
+ isCSS: false,
+ isPostCSS: false,
+ extractPlugin: "No",
+ packageManager: "npm",
+};
+const initValues: Record = {
+ default: {
+ ...baseAnswers,
+ },
+ react: {
+ ...baseAnswers,
+ langType: "ES6",
+ useReactState: true,
+ cssType: "CSS only",
+ },
+ vue: {
+ ...baseAnswers,
+ langType: "ES6",
+ useVueStore: true,
+ cssType: "CSS only",
+ },
+ svelte: {
+ ...baseAnswers,
+ langType: "ES6",
+ cssType: "CSS only",
+ },
+};
+
+const initGenerators: Record = {
+ default: plop.getGenerator("init-default"),
+ react: plop.getGenerator("init-react"),
+ vue: plop.getGenerator("init-vue"),
+ svelte: plop.getGenerator("init-svelte"),
+};
+const loaderGenerators: Record = {
+ default: plop.getGenerator("loader-default"),
+};
+
+const pluginGenerators: Record = {
+ default: plop.getGenerator("plugin-default"),
+};
+
+program
+ .version("1.0.0", "-v, --version")
+ .usage("[command] [options]")
+ .helpOption("-h, --help", "Display help for command")
+ .description("A CLI tool to generate a Webpack project");
+
+program
+ .command("init", { isDefault: true })
+ .aliases(["i", "n", "c", "create", "new"])
+ .description("Initialize a new Webpack project")
+ .argument("[projectPath]", "Path to create the project")
+ .option("-f, --force", "Skip the prompt and use the default values", false)
+ .option("-t --template ", "Template to be used for scaffolding", "default")
+ .action(async function (projectPath, opts: InitOptions) {
+ const { force } = opts;
+ let templateOption = opts.template as string;
+ let generator = initGenerators[templateOption];
+
+ if (generator === undefined) {
+ logger.warn(`${templateOption} is not a valid template, please select one from below`);
+ const template = await select({
+ message: "Select a valid template from below",
+ choices: Object.keys(initGenerators).map((key) => ({
+ name: key,
+ value: key.toLowerCase(),
+ })),
+ });
+ templateOption = template;
+ generator = initGenerators[templateOption];
+ }
+
+ projectPath = projectPath
+ ? resolve(process.cwd(), projectPath)
+ : initValues[templateOption].projectPath;
+
+ try {
+ if (force) {
+ logger.warn("Skipping the prompt and using the default values");
+
+ logger.info("Initializing a new Webpack project");
+ await generator.runActions(
+ {
+ ...initValues[templateOption],
+ projectPath,
+ },
+ {
+ onSuccess: onSuccessHandler,
+ onFailure: onFailureHandler,
+ },
+ );
+ } else {
+ const answers = { projectPath, ...(await generator.runPrompts()) };
+
+ logger.info("Initializing a new Webpack project");
+ await generator.runActions(answers, {
+ onSuccess: onSuccessHandler,
+ onFailure: onFailureHandler,
+ });
+ }
+ logger.success("Project has been initialised with webpack!");
+ } catch (error) {
+ logger.error(`Failed to initialize the project with webpack!\n ${error}`);
+ process.exit(2);
+ }
+ });
+program
+ .command("loader")
+ .aliases(["l", "ld"])
+ .description("Initialize a new loader template.")
+ .argument("[projectPath]", "Path to create the project")
+ .option("-t --template ", "Template to be used for scaffolding", "default")
+ .action(async function (projectPath, opts: LoaderOptions) {
+ let templateOption = opts.template as string;
+ let generator = loaderGenerators[templateOption];
+
+ if (generator === undefined) {
+ logger.warn(`${templateOption} is not a valid template, please select one from below`);
+ const template = await select({
+ message: "Select a valid template from below",
+ choices: Object.keys(loaderGenerators).map((key) => ({
+ name: key,
+ value: key.toLowerCase(),
+ })),
+ });
+ templateOption = template;
+ generator = loaderGenerators[template];
+ }
+
+ projectPath = projectPath
+ ? resolve(process.cwd(), projectPath)
+ : initValues[templateOption].projectPath;
+
+ try {
+ const answers = { projectPath, ...(await generator.runPrompts()) };
+
+ await generator.runActions(answers, {
+ onSuccess: onSuccessHandler,
+ onFailure: onFailureHandler,
+ });
+ logger.success("Loader template has been successfully scaffolded.");
+ } catch (error) {
+ logger.error(`Failed to initialize the loader template!\n ${error}`);
+ process.exit(2);
+ }
+ });
+program
+ .command("plugin")
+ .aliases(["p", "pl"])
+ .description("Initialize a new plugin template.")
+ .argument("[projectPath]", "Path to create the project")
+ .option("-t --template ", "Template to be used for scaffolding", "default")
+ .action(async function (projectPath, opts: PluginOptions) {
+ let templateOption = opts.template as string;
+ let generator = pluginGenerators[templateOption];
+
+ if (generator === undefined) {
+ logger.warn(`${templateOption} is not a valid template, please select one from below`);
+ const template = await select({
+ message: "Select a valid template from below",
+ choices: Object.keys(pluginGenerators).map((key) => ({
+ name: key,
+ value: key.toLowerCase(),
+ })),
+ });
+ templateOption = template;
+ generator = pluginGenerators[template];
+ }
+
+ projectPath = projectPath
+ ? resolve(process.cwd(), projectPath)
+ : initValues[templateOption].projectPath;
+
+ try {
+ const answers = { projectPath, ...(await generator.runPrompts()) };
+
+ await generator.runActions(answers, {
+ onSuccess: onSuccessHandler,
+ onFailure: onFailureHandler,
+ });
+ logger.success("Plugin template has been successfully scaffolded.");
+ } catch (error) {
+ logger.error(`Failed to initialize the plugin template!\n ${error}`);
+ process.exit(2);
+ }
+ });
+
+program.parse();
diff --git a/packages/create-webpack-app/src/plopfile.ts b/packages/create-webpack-app/src/plopfile.ts
new file mode 100644
index 00000000000..22c7b6e8930
--- /dev/null
+++ b/packages/create-webpack-app/src/plopfile.ts
@@ -0,0 +1,15 @@
+import { NodePlopAPI } from "node-plop";
+
+export default async function (plop: NodePlopAPI) {
+ //init generators
+ await plop.load("./generators/init/default.js", {}, true);
+ await plop.load("./generators/init/react.js", {}, true);
+ await plop.load("./generators/init/vue.js", {}, true);
+ await plop.load("./generators/init/svelte.js", {}, true);
+
+ //loader generators
+ await plop.load("./generators/loader/default.js", {}, true);
+
+ //plugin generators
+ await plop.load("./generators/plugin/default.js", {}, true);
+}
diff --git a/packages/create-webpack-app/src/types.ts b/packages/create-webpack-app/src/types.ts
new file mode 100644
index 00000000000..46c0ee18cf3
--- /dev/null
+++ b/packages/create-webpack-app/src/types.ts
@@ -0,0 +1,26 @@
+// eslint-disable-next-line
+export type Answers = Record;
+
+export interface PlopActionHooksFailures {
+ type: string;
+ path: string;
+ error: string;
+ message: string;
+}
+
+export interface PlopActionHooksChanges {
+ type: string;
+ path: string;
+}
+import { ActionType as ActionTypeBase, CustomActionConfig } from "node-plop";
+// extended ACtionType to include custom action config as previously it was not recognizing
+export type ActionType = ActionTypeBase | CustomActionConfig;
+
+export type InitOptions = { template: string; force?: boolean };
+export type LoaderOptions = { template: string };
+export type PluginOptions = { template: string };
+
+export type FileRecord = {
+ filePath: string;
+ fileType: string;
+};
diff --git a/packages/create-webpack-app/src/utils/fileGenerator.ts b/packages/create-webpack-app/src/utils/fileGenerator.ts
new file mode 100644
index 00000000000..fe375e67d61
--- /dev/null
+++ b/packages/create-webpack-app/src/utils/fileGenerator.ts
@@ -0,0 +1,269 @@
+import * as fs from "fs/promises";
+import * as ejs from "ejs";
+import { expand } from "@inquirer/prompts";
+import { spawn, sync } from "cross-spawn";
+import * as path from "path";
+import { fileURLToPath } from "url";
+import { logger } from "./logger.js";
+import { NodePlopAPI } from "node-plop";
+import { Answers } from "../types";
+
+export interface AddConfig {
+ type: string; // Type of action
+ path: string;
+ fileType: "text" | "binary";
+ template?: string;
+ templateFile?: string;
+ skipIfExists?: boolean;
+ transform?: (content: string, data: Answers | undefined) => string | Promise; // transforms rendered string before writing to file
+ skip?: (data: Answers | undefined) => string | Promise; // skips the action and logs the reason returned by the function
+ force?: boolean; // Force overwrite
+ data?: Answers; // Data for EJS template rendering
+ abortOnFail?: boolean; // Abort on failure
+}
+
+export type Content = string | Buffer;
+export interface Result {
+ status: "create" | "skip" | "overwrite" | "error" | "identical";
+ content: Content;
+}
+
+export interface GlobalConfig {
+ overwriteAll: boolean;
+}
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const globalConfig: GlobalConfig = { overwriteAll: false };
+
+async function doesFileExists(filePath: string): Promise {
+ try {
+ await fs.access(filePath);
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+function checkIfCodeInstalled(): boolean {
+ try {
+ const result = sync("code", ["--version"], { stdio: "ignore" });
+ return result.status === 0;
+ } catch {
+ return false;
+ }
+}
+
+function getDiff(filePath: string, tempFilePath: string): Promise {
+ return new Promise((resolve, reject) => {
+ const platform = process.platform;
+ let editor = "";
+
+ // Determine the editor based on platform and availability of VS Code
+ if (platform === "win32") {
+ if (checkIfCodeInstalled()) {
+ editor = "code";
+ } else {
+ return reject(
+ new Error("Visual Studio Code is not installed. Please install VS Code to continue."),
+ );
+ }
+ } else if (platform === "darwin" || platform === "linux") {
+ editor = checkIfCodeInstalled() ? "code" : "vim";
+ } else {
+ return reject(new Error(`Unsupported platform: ${platform}`));
+ }
+
+ // Construct the appropriate diff command
+ let diffCommand = "";
+
+ if (editor === "code") {
+ diffCommand = `${editor} --diff ${filePath} ${tempFilePath}`;
+ } else if (editor === "vim") {
+ diffCommand = `${editor} -d ${filePath} ${tempFilePath}`;
+ }
+
+ // Execute the diff command
+ const diffProcess = spawn(diffCommand, { shell: true, stdio: "inherit" });
+
+ diffProcess.on("exit", (code) => {
+ if (code !== 0) {
+ reject(new Error("Error opening diff in editor"));
+ } else {
+ resolve();
+ }
+ });
+ });
+}
+
+async function renderTemplate(
+ template: string | undefined,
+ templateFile: string | undefined,
+ data: Answers | undefined,
+): Promise {
+ if (template) {
+ return ejs.render(template, data || {}, { async: true });
+ }
+
+ if (templateFile) {
+ const templateContent = await fs.readFile(templateFile, "utf8");
+ return ejs.render(templateContent, data || {}, { async: true });
+ }
+
+ throw new Error("Template or templateFile is required");
+}
+
+async function checkAndPrepareContent(config: AddConfig, isTemplate: boolean): Promise {
+ const fileExists = await doesFileExists(config.path);
+ let existingFileContent: Content = "";
+ let newContent: Content = "";
+
+ // Handle template or binary content
+ if (isTemplate) {
+ // Template rendering for non-binary files
+ newContent = await renderTemplate(config.template, config.templateFile, config.data);
+
+ if (config.transform && typeof config.transform === "function") {
+ newContent = await config.transform(newContent as string, config.data);
+ }
+ } else {
+ // Read binary content for binary files
+ newContent = await fs.readFile(config.templateFile as string);
+ }
+
+ // Check if overwriteAll is set globally
+ if (globalConfig.overwriteAll) {
+ return { status: "overwrite", content: newContent };
+ }
+
+ // Check if skip condition exists
+ if (config.skip && typeof config.skip === "function") {
+ const skipReason = await config.skip(config.data);
+ logger.info(` - ${skipReason}`);
+ return { status: "skip", content: existingFileContent || newContent };
+ }
+
+ // Read existing content if the file exists
+ if (fileExists) {
+ existingFileContent = await fs.readFile(config.path);
+
+ // If skipIfExists is set, skip writing the file
+ if (config.skipIfExists) {
+ return { status: "skip", content: existingFileContent };
+ }
+
+ // If force is set, overwrite the file
+ if (config.force) {
+ return { status: "overwrite", content: newContent };
+ }
+
+ // If the contents are identical (text or binary), return identical status
+ if (existingFileContent == newContent) {
+ return { status: "identical", content: existingFileContent };
+ }
+
+ // Prompt for conflict resolution
+ const tempFilePath = path.join("/tmp", `temp_${path.basename(config.path)}`);
+ await fs.writeFile(tempFilePath, newContent || "");
+
+ let userChoice: Result | undefined;
+ while (!userChoice) {
+ const action = await expand({
+ message: `File conflict at ${path.basename(config.path)}?`,
+ choices: [
+ { key: "y", name: "overwrite", value: "overwrite" },
+ { key: "n", name: "do not overwrite", value: "skip" },
+ { key: "a", name: "overwrite this and all others", value: "overwrite_all" },
+ {
+ key: "d",
+ name: `Show the difference${!isTemplate ? " (size/modified date)" : ""}`,
+ value: "diff",
+ },
+ { key: "x", name: "abort", value: "abort" },
+ ],
+ expanded: false,
+ });
+
+ switch (action) {
+ case "overwrite":
+ userChoice = { status: "overwrite", content: newContent };
+ break;
+ case "skip":
+ userChoice = { status: "skip", content: existingFileContent };
+ break;
+ case "overwrite_all":
+ globalConfig.overwriteAll = true;
+ return { status: "overwrite", content: newContent };
+ case "diff":
+ if (!isTemplate && Buffer.isBuffer(existingFileContent)) {
+ const existingStats = await fs.stat(config.path);
+ const newStats = await fs.stat(tempFilePath);
+ const headers = `| ${"File".padEnd(15)} | ${"Size (bytes)".padEnd(
+ 15,
+ )} | ${"Last Modified".padEnd(25)} |`;
+ const separator = "-".repeat(headers.length);
+
+ const existingRow = `| ${"Existing File".padEnd(15)} | ${existingStats.size
+ .toString()
+ .padEnd(15)} | ${existingStats.mtime.toISOString().padEnd(25)} |`;
+ const newRow = `| ${"New File".padEnd(15)} | ${newStats.size
+ .toString()
+ .padEnd(15)} | ${newStats.mtime.toISOString().padEnd(25)} |`;
+
+ logger.info(separator);
+ logger.info(headers);
+ logger.info(separator);
+ logger.info(existingRow);
+ logger.info(newRow);
+ logger.info(separator);
+ } else {
+ await getDiff(config.path, tempFilePath);
+ }
+ break;
+ case "abort":
+ logger.error("Aborting process...");
+ process.exit(1);
+ }
+ }
+
+ await fs.unlink(tempFilePath).catch(() => {
+ logger.warn(`Failed to delete temporary file: ${tempFilePath}`);
+ });
+
+ return userChoice;
+ } else {
+ // If the file doesn't exist, create it
+ return { status: "create", content: newContent };
+ }
+}
+export default async function (plop: NodePlopAPI) {
+ plop.setPlopfilePath(path.resolve(__dirname, "../plopfile.js"));
+ plop.setDefaultInclude({ actions: true });
+
+ plop.setActionType("fileGenerator", async (answers, config) => {
+ const isTemplate = config.fileType === "text";
+ const result = await checkAndPrepareContent(
+ { ...config, data: answers } as AddConfig,
+ isTemplate,
+ );
+ let returnString = "";
+ switch (result.status) {
+ case "create":
+ case "overwrite":
+ // Write the content to the file (handle text or binary)
+
+ await fs.mkdir(path.dirname(config.path), { recursive: true });
+ await fs.writeFile(config.path, result.content);
+ returnString = `${result.status}|${config.path}`;
+ break;
+
+ case "skip":
+ returnString = `${result.status}|${config.path}`;
+ break;
+
+ case "identical":
+ returnString = `${result.status}|${config.path}`;
+ break;
+ }
+ return returnString;
+ });
+}
diff --git a/packages/create-webpack-app/src/utils/logger.ts b/packages/create-webpack-app/src/utils/logger.ts
new file mode 100644
index 00000000000..162bbc011e1
--- /dev/null
+++ b/packages/create-webpack-app/src/utils/logger.ts
@@ -0,0 +1,41 @@
+import { WebpackCLILogger } from "webpack-cli";
+import { green, yellow, Color, red, cyan, blue, blueBright, greenBright } from "colorette";
+import { PlopActionHooksChanges, PlopActionHooksFailures } from "../types";
+import { relative, normalize } from "path";
+
+const prefix: string = blueBright("create-webpack");
+const getLogger = (): WebpackCLILogger => {
+ return {
+ error: (val) => console.error(`[${prefix}] ⛔${red(val)}`),
+ warn: (val) => console.warn(`[${prefix}] ⚠️${yellow(val)}`),
+ info: (val) => console.info(`[${prefix}] ℹ️ ${cyan(val)}`),
+ success: (val) => console.log(`[${prefix}] ✅ ${green(val)}`),
+ log: (val) => console.log(`[${prefix}] 📃${val}`),
+ raw: (val) => console.log(val),
+ };
+};
+const logger = getLogger();
+const typeDisplay: Record = {
+ function: yellow("-> "),
+ add: green("create "),
+ addMany: green("create "),
+ modify: `${blue("modify")}${green("+")}${red("- ")}`,
+ overwrite: red("overwrite "),
+ append: green("append_+ "),
+ skip: yellow("skip "),
+ identical: greenBright("identical "),
+ create: green("create "),
+};
+function onSuccessHandler(change: PlopActionHooksChanges): void {
+ change.path.split("\n").forEach((line) => {
+ const [operationType = "", renderPath = ""] = line.split("|") as [string, string];
+ console.log(
+ `\t${typeDisplay[operationType]} ${normalize(relative(process.cwd(), renderPath))}`,
+ );
+ });
+}
+function onFailureHandler(failure: PlopActionHooksFailures): void {
+ throw new Error(failure.error);
+}
+
+export { logger, onSuccessHandler, onFailureHandler };
diff --git a/packages/create-webpack-app/src/utils/pkgInstallAction.ts b/packages/create-webpack-app/src/utils/pkgInstallAction.ts
new file mode 100644
index 00000000000..633da7604c0
--- /dev/null
+++ b/packages/create-webpack-app/src/utils/pkgInstallAction.ts
@@ -0,0 +1,57 @@
+import { NodePlopAPI } from "node-plop";
+import { dirname, resolve } from "path";
+import { spawn } from "cross-spawn";
+import { ChildProcess, SpawnOptionsWithStdioTuple, StdioNull, StdioPipe } from "child_process";
+import { fileURLToPath } from "url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+export default async function (plop: NodePlopAPI) {
+ plop.setPlopfilePath(resolve(__dirname, "../plopfile.js"));
+ plop.setDefaultInclude({ actions: true });
+ plop.setActionType("pkgInstall", (answers, config) => {
+ const options: SpawnOptionsWithStdioTuple<
+ StdioNull,
+ StdioNull | StdioPipe,
+ StdioPipe | StdioNull
+ > = {
+ cwd: config.path,
+ stdio: [
+ "inherit", // Use parent's stdio configuration
+ process.stdout.isTTY ? "inherit" : "pipe", // Pipe child process' stdout to parent's stdout
+ process.stderr.isTTY ? "inherit" : "pipe", // Pipe child process' stderr to parent's stderr
+ ],
+ };
+
+ // promise to complete subprocess of installing packages and return a message
+ const returnPromise: Promise = new Promise((resolve, reject) => {
+ const returnMessage = `Project Dependencies installed successfully`;
+ const packageManager = answers.packageManager;
+ const packages = config.packages.length == 1 ? [config.packages[0]] : config.packages;
+ const installOptions: Record> = {
+ npm: ["install", "--save-dev"],
+ yarn: ["add", "-D"],
+ pnpm: ["install", "--save-dev"],
+ };
+ const npmInstallPackages: ChildProcess = spawn(
+ `${packageManager}`,
+ [...installOptions[packageManager], ...packages],
+ options,
+ );
+ npmInstallPackages.stdout?.on("data", (data) => {
+ console.log(data.toString());
+ });
+ npmInstallPackages.stderr?.on("data", (data) => {
+ console.warn(data.toString());
+ });
+ npmInstallPackages.on("exit", (code) => {
+ if (code === 0) {
+ resolve(returnMessage);
+ } else {
+ reject(`Error occurred while installing packages\n Exit code: ${code}`);
+ }
+ });
+ });
+ return returnPromise;
+ });
+}
diff --git a/packages/create-webpack-app/templates/init/default/README.md.tpl b/packages/create-webpack-app/templates/init/default/README.md.tpl
new file mode 100644
index 00000000000..77c91681f17
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/default/README.md.tpl
@@ -0,0 +1,15 @@
+# 🚀 Welcome to your new awesome project!
+
+This project has been created using **create-webpack-app**, you can now run
+
+```bash
+npm run build
+```
+
+or
+
+```bash
+yarn build
+```
+
+to bundle your application
diff --git a/packages/create-webpack-app/templates/init/default/babel.config.json.tpl b/packages/create-webpack-app/templates/init/default/babel.config.json.tpl
new file mode 100644
index 00000000000..f2c9da3ae6e
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/default/babel.config.json.tpl
@@ -0,0 +1,11 @@
+{
+ "plugins": ["@babel/syntax-dynamic-import"],
+ "presets": [
+ [
+ "@babel/preset-env",
+ {
+ "modules": false
+ }
+ ]
+ ]
+}
diff --git a/packages/create-webpack-app/templates/init/default/index.html.tpl b/packages/create-webpack-app/templates/init/default/index.html.tpl
new file mode 100644
index 00000000000..d61cf83b04b
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/default/index.html.tpl
@@ -0,0 +1,26 @@
+
+
+
+
+ Webpack App
+
+
+ Hello world!
+ Tip: Check your console
+
+ <% if (workboxWebpackPlugin) { %>
+ <% } %>
+
diff --git a/packages/create-webpack-app/templates/init/default/package.json.tpl b/packages/create-webpack-app/templates/init/default/package.json.tpl
new file mode 100644
index 00000000000..f7de7502bd3
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/default/package.json.tpl
@@ -0,0 +1,15 @@
+{
+ "version": "1.0.0",
+
+ "description": "My webpack project",
+
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ <% if (devServer) { %>
+ "serve": "webpack serve",
+ <% } %>
+ "watch": "webpack --watch"
+ }
+}
diff --git a/packages/create-webpack-app/templates/init/default/postcss.config.js.tpl b/packages/create-webpack-app/templates/init/default/postcss.config.js.tpl
new file mode 100644
index 00000000000..3fa4289052e
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/default/postcss.config.js.tpl
@@ -0,0 +1,5 @@
+module.exports = {
+ // Add you postcss configuration here
+ // Learn more about it at https://github.com/webpack-contrib/postcss-loader#config-files
+ plugins: [["autoprefixer"]],
+};
diff --git a/packages/create-webpack-app/templates/init/default/src/index.js.tpl b/packages/create-webpack-app/templates/init/default/src/index.js.tpl
new file mode 100644
index 00000000000..b9d005be893
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/default/src/index.js.tpl
@@ -0,0 +1 @@
+console.log("Hello This is a Javascript project.");
diff --git a/packages/create-webpack-app/templates/init/default/src/index.ts.tpl b/packages/create-webpack-app/templates/init/default/src/index.ts.tpl
new file mode 100644
index 00000000000..d4d2d33d835
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/default/src/index.ts.tpl
@@ -0,0 +1 @@
+console.log("Hello This is a Typescript project.");
diff --git a/packages/create-webpack-app/templates/init/default/tsconfig.json.tpl b/packages/create-webpack-app/templates/init/default/tsconfig.json.tpl
new file mode 100644
index 00000000000..31176e658c2
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/default/tsconfig.json.tpl
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "allowSyntheticDefaultImports": true,
+ "noImplicitAny": true,
+ "module": "es6",
+ "target": "es5",
+ "allowJs": true
+ },
+ "files": ["src/index.ts"]
+}
diff --git a/packages/create-webpack-app/templates/init/default/webpack.config.js.tpl b/packages/create-webpack-app/templates/init/default/webpack.config.js.tpl
new file mode 100644
index 00000000000..178aaf1a991
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/default/webpack.config.js.tpl
@@ -0,0 +1,101 @@
+// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');<% if (htmlWebpackPlugin) { %>
+const HtmlWebpackPlugin = require('html-webpack-plugin');<% } %><% if (extractPlugin !== 'No') { %>
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');<% } %><% if (workboxWebpackPlugin) { %>
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin');<% } %>
+
+const isProduction = process.env.NODE_ENV === 'production';
+<% if (cssType !== 'none') { %>
+<% if (extractPlugin === "Yes") { %>
+const stylesHandler = MiniCssExtractPlugin.loader;
+<% } else if (extractPlugin === "Only for Production") { %>
+const stylesHandler = isProduction ? MiniCssExtractPlugin.loader : 'style-loader';
+<% } else { %>
+const stylesHandler = 'style-loader';
+<% } %>
+<% } %>
+
+const config = {
+ entry: '<%= entryPoint %>',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },<% if (devServer) { %>
+ devServer: {
+ open: true,
+ host: 'localhost',
+ },<% } %>
+ plugins: [<% if (htmlWebpackPlugin) { %>
+ new HtmlWebpackPlugin({
+ template: 'index.html',
+ }),
+<% } %><% if (extractPlugin === "Yes") { %>
+ new MiniCssExtractPlugin(),
+<% } %>
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [<% if (langType == "ES6") { %>
+ {
+ test: /\.(js|jsx)$/i,
+ loader: 'babel-loader',
+ },<% } %><% if (langType == "Typescript") { %>
+ {
+ test: /\.(ts|tsx)$/i,
+ loader: 'ts-loader',
+ exclude: ['/node_modules/'],
+ },<% } %><% if (isCSS && !isPostCSS) { %>
+ {
+ test: /\.css$/i,
+ use: [stylesHandler,'css-loader'],
+ },<% } %><% if (cssType == 'SASS') { %>
+ {
+ test: /\.s[ac]ss$/i,
+ use: [stylesHandler, 'css-loader', <% if (isPostCSS) { %>'postcss-loader', <% } %>'sass-loader'],
+ },<% } %><% if (cssType == 'LESS') { %>
+ {
+ test: /\.less$/i,
+ use: [stylesHandler, 'css-loader', <% if (isPostCSS) { %>'postcss-loader', <% } %>'less-loader'],
+ },<% } %><% if (cssType == 'Stylus') { %>
+ {
+ test: /\.styl$/i,
+ use: [stylesHandler, 'css-loader', <% if (isPostCSS) { %>'postcss-loader', <% } %>'stylus-loader'],
+ },<% } %><% if (isPostCSS && isCSS) { %>
+ {
+ test: /\.css$/i,
+ use: [stylesHandler, 'css-loader', 'postcss-loader'],
+ },<% } %>
+ {
+ test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+ %><% if (htmlWebpackPlugin) { %>
+ {
+ test: /\.html$/i,
+ use: ['html-loader'],
+ },<% } %>
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },<% if (langType == "Typescript") {%>
+ resolve: {
+ extensions: ['.tsx', '.ts', '.jsx', '.js', '...'],
+ },<% } %>
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+ <% if (extractPlugin === "Only for Production") { %>
+ config.plugins.push(new MiniCssExtractPlugin());
+ <% } %>
+ <% if (workboxWebpackPlugin) { %>
+ config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
+ <% } %>
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
diff --git a/packages/create-webpack-app/templates/init/react/README.md.tpl b/packages/create-webpack-app/templates/init/react/README.md.tpl
new file mode 100644
index 00000000000..77c91681f17
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/README.md.tpl
@@ -0,0 +1,15 @@
+# 🚀 Welcome to your new awesome project!
+
+This project has been created using **create-webpack-app**, you can now run
+
+```bash
+npm run build
+```
+
+or
+
+```bash
+yarn build
+```
+
+to bundle your application
diff --git a/packages/create-webpack-app/templates/init/react/index.d.ts.tpl b/packages/create-webpack-app/templates/init/react/index.d.ts.tpl
new file mode 100644
index 00000000000..38ee1835c96
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/index.d.ts.tpl
@@ -0,0 +1,8 @@
+declare module "*.png";
+declare module "*.jpg";
+declare module "*.gif";
+declare module "*.svg";
+declare module "*.ttf";
+declare module "*.woff";
+declare module "*.woff2";
+declare module "*.eot";
diff --git a/packages/create-webpack-app/templates/init/react/index.html.tpl b/packages/create-webpack-app/templates/init/react/index.html.tpl
new file mode 100644
index 00000000000..ef140c0d7f0
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/index.html.tpl
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ React webpack Project
+
+
+
+
+
diff --git a/packages/create-webpack-app/templates/init/react/package.json.tpl b/packages/create-webpack-app/templates/init/react/package.json.tpl
new file mode 100644
index 00000000000..e6a317b1051
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/package.json.tpl
@@ -0,0 +1,11 @@
+{
+ "version": "1.0.0",
+ "description": "My webpack project",
+ "name": "my-webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ "serve": "webpack serve"
+ }
+}
diff --git a/packages/create-webpack-app/templates/init/react/src/App.jsx.tpl b/packages/create-webpack-app/templates/init/react/src/App.jsx.tpl
new file mode 100644
index 00000000000..eed6b305dde
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/App.jsx.tpl
@@ -0,0 +1,10 @@
+import React from "react";
+import AppRouter from "./router";
+
+const App = () => {
+ return (
+
+ );
+}
+
+export default App;
diff --git a/packages/create-webpack-app/templates/init/react/src/App.tsx.tpl b/packages/create-webpack-app/templates/init/react/src/App.tsx.tpl
new file mode 100644
index 00000000000..da97c1e6c52
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/App.tsx.tpl
@@ -0,0 +1,10 @@
+import React from "react";
+import AppRouter from "./router";
+
+const App: React.FC = () => {
+ return (
+
+ );
+}
+
+export default App;
diff --git a/packages/create-webpack-app/templates/init/react/src/assets/webpack.png.tpl b/packages/create-webpack-app/templates/init/react/src/assets/webpack.png.tpl
new file mode 100644
index 00000000000..b2390da4f06
Binary files /dev/null and b/packages/create-webpack-app/templates/init/react/src/assets/webpack.png.tpl differ
diff --git a/packages/create-webpack-app/templates/init/react/src/components/About.jsx.tpl b/packages/create-webpack-app/templates/init/react/src/components/About.jsx.tpl
new file mode 100644
index 00000000000..4ec907a7a12
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/components/About.jsx.tpl
@@ -0,0 +1,18 @@
+import React from "react";
+import webpackLogo from "../assets/webpack.png";
+
+const About = ({ msg }) => {
+ return (
+
+
+

+
This is the About page!
+
+ This is a webpack + {msg} project.
+
+
+
+ );
+}
+
+export default About;
diff --git a/packages/create-webpack-app/templates/init/react/src/components/About.tsx.tpl b/packages/create-webpack-app/templates/init/react/src/components/About.tsx.tpl
new file mode 100644
index 00000000000..26c7b77e316
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/components/About.tsx.tpl
@@ -0,0 +1,22 @@
+import React from "react";
+import webpackLogo from "../assets/webpack.png";
+
+interface Props {
+ msg: string;
+}
+
+const About: React.FC = (props: Props) => {
+return (
+
+
+

+
This is the About page!
+
+ This is a webpack + {props.msg} project.
+
+
+
+ );
+}
+
+export default About;
diff --git a/packages/create-webpack-app/templates/init/react/src/components/Home.jsx.tpl b/packages/create-webpack-app/templates/init/react/src/components/Home.jsx.tpl
new file mode 100644
index 00000000000..52f0475c7a7
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/components/Home.jsx.tpl
@@ -0,0 +1,27 @@
+import React<% if (useReactState) { %>, { useState } <% } %> from "react";
+import webpackLogo from "../assets/webpack.png";
+
+const App = () => {
+<% if (useReactState) { %>
+ const [ count, setCount ] = useState(0);
+ const increment = () => setCount(count => count + 1);
+ const decrement = () => setCount(count => count - 1);
+<% } %>
+
+ return (
+
+
+

+
This is the Home page!
+
Click the buttons below to increment and decrement the count.
+ <% if (useReactState) { %>
+
Count: {count}
+
+
+ <% } %>
+
+
+ );
+}
+
+export default App;
diff --git a/packages/create-webpack-app/templates/init/react/src/components/Home.tsx.tpl b/packages/create-webpack-app/templates/init/react/src/components/Home.tsx.tpl
new file mode 100644
index 00000000000..db537f629d3
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/components/Home.tsx.tpl
@@ -0,0 +1,27 @@
+import React<% if (useReactState) { %>, { useState } <% } %> from "react";
+import webpackLogo from "../assets/webpack.png";
+
+const App:React.FC = () => {
+<% if (useReactState) { %>
+ const [ count, setCount ] = useState(0);
+ const increment = () => setCount(count => count + 1);
+ const decrement = () => setCount(count => count - 1);
+<% } %>
+
+ return (
+
+
+

+
This is the Home page!
+
Click the buttons below to increment and decrement the count.
+ <% if (useReactState) { %>
+
Count: {count}
+
+
+ <% } %>
+
+
+ );
+}
+
+export default App;
diff --git a/packages/create-webpack-app/templates/init/react/src/components/Navbar.jsx.tpl b/packages/create-webpack-app/templates/init/react/src/components/Navbar.jsx.tpl
new file mode 100644
index 00000000000..3eea0b07f7b
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/components/Navbar.jsx.tpl
@@ -0,0 +1,36 @@
+
+import React from 'react';
+import { NavLink } from 'react-router-dom';
+
+const NavBar = () => {
+ return (
+
+ );
+};
+
+export default NavBar;
diff --git a/packages/create-webpack-app/templates/init/react/src/components/Navbar.tsx.tpl b/packages/create-webpack-app/templates/init/react/src/components/Navbar.tsx.tpl
new file mode 100644
index 00000000000..ca057944803
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/components/Navbar.tsx.tpl
@@ -0,0 +1,36 @@
+
+import React from 'react';
+import { NavLink } from 'react-router-dom';
+
+const NavBar: React.FC = () => {
+return (
+
+ );
+};
+
+export default NavBar;
diff --git a/packages/create-webpack-app/templates/init/react/src/index.jsx.tpl b/packages/create-webpack-app/templates/init/react/src/index.jsx.tpl
new file mode 100644
index 00000000000..850922b4db8
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/index.jsx.tpl
@@ -0,0 +1,12 @@
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+import App from './App';
+<% if (cssType == 'CSS only') { %>
+import "./styles/global.css";<% } if (cssType == 'SASS') { %>
+import "./styles/global.scss";<% } if (cssType == 'LESS') { %>
+import "./styles/global.less";<% } if (cssType == 'Stylus') { %>
+import "./styles/global.styl";<% } %>
+
+const container = document.getElementById('root');
+const root = createRoot(container);
+root.render();
diff --git a/packages/create-webpack-app/templates/init/react/src/index.tsx.tpl b/packages/create-webpack-app/templates/init/react/src/index.tsx.tpl
new file mode 100644
index 00000000000..e2437f31bf1
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/index.tsx.tpl
@@ -0,0 +1,12 @@
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+import App from './App';
+<% if (cssType == 'CSS only') { %>
+import "./styles/global.css";<% } if (cssType == 'SASS') { %>
+import "./styles/global.scss";<% } if (cssType == 'LESS') { %>
+import "./styles/global.less";<% } if (cssType == 'Stylus') { %>
+import "./styles/global.styl";<% } %>
+
+const container = document.getElementById('root') as HTMLElement;
+const root = createRoot(container);
+root.render();
diff --git a/packages/create-webpack-app/templates/init/react/src/router/index.jsx.tpl b/packages/create-webpack-app/templates/init/react/src/router/index.jsx.tpl
new file mode 100644
index 00000000000..a3ceb61e85b
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/router/index.jsx.tpl
@@ -0,0 +1,19 @@
+import React from 'react';
+import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
+import Home from '../components/Home';
+import About from '../components/About';
+import Navbar from '../components/Navbar';
+
+function AppRouter() {
+ return (
+
+
+
+ } />
+ } />
+
+
+ );
+}
+
+export default AppRouter;
diff --git a/packages/create-webpack-app/templates/init/react/src/router/index.tsx.tpl b/packages/create-webpack-app/templates/init/react/src/router/index.tsx.tpl
new file mode 100644
index 00000000000..dee627b6c1b
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/router/index.tsx.tpl
@@ -0,0 +1,19 @@
+import React from 'react';
+import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
+import Home from '../components/Home';
+import About from '../components/About';
+import Navbar from '../components/Navbar';
+
+const AppRouter: React.FC = () => {
+ return (
+
+
+
+ } />
+ } />
+
+
+ );
+};
+
+export default AppRouter;
diff --git a/packages/create-webpack-app/templates/init/react/src/styles/global.css.tpl b/packages/create-webpack-app/templates/init/react/src/styles/global.css.tpl
new file mode 100644
index 00000000000..bd3373adaa7
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/styles/global.css.tpl
@@ -0,0 +1,91 @@
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+:root {
+ --color-primary: #1C78C0;
+ --color-secondary: #8ED6FB;
+ --color-dark: #2C3E50;
+ --color-background: #f0f0f0;
+}
+.heading {
+ font-weight: 300;
+ bg-color: #f0f0f0;
+}
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+#app {
+ font-family: Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ text-align: center;
+ color: #2c3e50;
+ margin-top: 60px;
+}
+
+span {
+ color: var(--color-primary);
+}
+
+nav {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem;
+ background-color: var(--color-background);
+}
+
+ul {
+ list-style: none;
+ display: flex;
+ gap: 1rem;
+}
+
+p {
+ font-size: 1.2rem;
+ line-height: 1.5;
+}
+
+li {
+ display: flex;
+ align-items: center;
+}
+
+button {
+ border: none;
+ border-radius: 0.5rem;
+ padding: 1rem;
+ font-size: 1.25rem;
+ cursor: pointer;
+ margin: 0 1rem;
+}
+
+a {
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ text-decoration: none;
+ font-weight: bold;
+}
+
+.btn-primary {
+ background-color: var(--color-primary);
+ color: white;
+}
+.btn-secondary {
+ background-color: var(--color-secondary);
+ color: var(--color-dark);
+}
+.btn-primary:hover {
+ background-color: #0E5A8A;
+}
+.btn-secondary:hover {
+ background-color: #6EB8E0;
+}
diff --git a/packages/create-webpack-app/templates/init/react/src/styles/global.less.tpl b/packages/create-webpack-app/templates/init/react/src/styles/global.less.tpl
new file mode 100644
index 00000000000..bd3373adaa7
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/styles/global.less.tpl
@@ -0,0 +1,91 @@
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+:root {
+ --color-primary: #1C78C0;
+ --color-secondary: #8ED6FB;
+ --color-dark: #2C3E50;
+ --color-background: #f0f0f0;
+}
+.heading {
+ font-weight: 300;
+ bg-color: #f0f0f0;
+}
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+#app {
+ font-family: Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ text-align: center;
+ color: #2c3e50;
+ margin-top: 60px;
+}
+
+span {
+ color: var(--color-primary);
+}
+
+nav {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem;
+ background-color: var(--color-background);
+}
+
+ul {
+ list-style: none;
+ display: flex;
+ gap: 1rem;
+}
+
+p {
+ font-size: 1.2rem;
+ line-height: 1.5;
+}
+
+li {
+ display: flex;
+ align-items: center;
+}
+
+button {
+ border: none;
+ border-radius: 0.5rem;
+ padding: 1rem;
+ font-size: 1.25rem;
+ cursor: pointer;
+ margin: 0 1rem;
+}
+
+a {
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ text-decoration: none;
+ font-weight: bold;
+}
+
+.btn-primary {
+ background-color: var(--color-primary);
+ color: white;
+}
+.btn-secondary {
+ background-color: var(--color-secondary);
+ color: var(--color-dark);
+}
+.btn-primary:hover {
+ background-color: #0E5A8A;
+}
+.btn-secondary:hover {
+ background-color: #6EB8E0;
+}
diff --git a/packages/create-webpack-app/templates/init/react/src/styles/global.scss.tpl b/packages/create-webpack-app/templates/init/react/src/styles/global.scss.tpl
new file mode 100644
index 00000000000..bd3373adaa7
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/styles/global.scss.tpl
@@ -0,0 +1,91 @@
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+:root {
+ --color-primary: #1C78C0;
+ --color-secondary: #8ED6FB;
+ --color-dark: #2C3E50;
+ --color-background: #f0f0f0;
+}
+.heading {
+ font-weight: 300;
+ bg-color: #f0f0f0;
+}
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+#app {
+ font-family: Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ text-align: center;
+ color: #2c3e50;
+ margin-top: 60px;
+}
+
+span {
+ color: var(--color-primary);
+}
+
+nav {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem;
+ background-color: var(--color-background);
+}
+
+ul {
+ list-style: none;
+ display: flex;
+ gap: 1rem;
+}
+
+p {
+ font-size: 1.2rem;
+ line-height: 1.5;
+}
+
+li {
+ display: flex;
+ align-items: center;
+}
+
+button {
+ border: none;
+ border-radius: 0.5rem;
+ padding: 1rem;
+ font-size: 1.25rem;
+ cursor: pointer;
+ margin: 0 1rem;
+}
+
+a {
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ text-decoration: none;
+ font-weight: bold;
+}
+
+.btn-primary {
+ background-color: var(--color-primary);
+ color: white;
+}
+.btn-secondary {
+ background-color: var(--color-secondary);
+ color: var(--color-dark);
+}
+.btn-primary:hover {
+ background-color: #0E5A8A;
+}
+.btn-secondary:hover {
+ background-color: #6EB8E0;
+}
diff --git a/packages/create-webpack-app/templates/init/react/src/styles/global.styl.tpl b/packages/create-webpack-app/templates/init/react/src/styles/global.styl.tpl
new file mode 100644
index 00000000000..bd3373adaa7
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/src/styles/global.styl.tpl
@@ -0,0 +1,91 @@
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+:root {
+ --color-primary: #1C78C0;
+ --color-secondary: #8ED6FB;
+ --color-dark: #2C3E50;
+ --color-background: #f0f0f0;
+}
+.heading {
+ font-weight: 300;
+ bg-color: #f0f0f0;
+}
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+#app {
+ font-family: Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ text-align: center;
+ color: #2c3e50;
+ margin-top: 60px;
+}
+
+span {
+ color: var(--color-primary);
+}
+
+nav {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem;
+ background-color: var(--color-background);
+}
+
+ul {
+ list-style: none;
+ display: flex;
+ gap: 1rem;
+}
+
+p {
+ font-size: 1.2rem;
+ line-height: 1.5;
+}
+
+li {
+ display: flex;
+ align-items: center;
+}
+
+button {
+ border: none;
+ border-radius: 0.5rem;
+ padding: 1rem;
+ font-size: 1.25rem;
+ cursor: pointer;
+ margin: 0 1rem;
+}
+
+a {
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ text-decoration: none;
+ font-weight: bold;
+}
+
+.btn-primary {
+ background-color: var(--color-primary);
+ color: white;
+}
+.btn-secondary {
+ background-color: var(--color-secondary);
+ color: var(--color-dark);
+}
+.btn-primary:hover {
+ background-color: #0E5A8A;
+}
+.btn-secondary:hover {
+ background-color: #6EB8E0;
+}
diff --git a/packages/create-webpack-app/templates/init/react/tsconfig.json.tpl b/packages/create-webpack-app/templates/init/react/tsconfig.json.tpl
new file mode 100644
index 00000000000..37b8169e222
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/tsconfig.json.tpl
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src/**/*", "index.d.ts"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/create-webpack-app/templates/init/react/webpack.config.js.tpl b/packages/create-webpack-app/templates/init/react/webpack.config.js.tpl
new file mode 100644
index 00000000000..7a99442d836
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/react/webpack.config.js.tpl
@@ -0,0 +1,105 @@
+// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');<% if (htmlWebpackPlugin) { %>
+const HtmlWebpackPlugin = require('html-webpack-plugin');<% } %><% if (extractPlugin !== 'No') { %>
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');<% } %><% if (workboxWebpackPlugin) { %>
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin');<% } %>
+
+const isProduction = process.env.NODE_ENV === 'production';
+<% if (cssType !== 'none') { %>
+<% if (extractPlugin === "Yes") { %>
+const stylesHandler = MiniCssExtractPlugin.loader;
+<% } else if (extractPlugin === "Only for Production") { %>
+const stylesHandler = isProduction ? MiniCssExtractPlugin.loader : 'style-loader';
+<% } else { %>
+const stylesHandler = 'style-loader';
+<% } %>
+<% } %>
+
+const config = {
+ entry: '<%= entry %>',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },<% if (devServer) { %>
+ devServer: {
+ open: true,
+ host: 'localhost',
+ },<% } %>
+ plugins: [<% if (htmlWebpackPlugin) { %>
+ new HtmlWebpackPlugin({
+ template: 'index.html',
+ }),
+<% } %><% if (extractPlugin === "Yes") { %>
+ new MiniCssExtractPlugin(),
+<% } %>
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [<% if (langType == "ES6") { %>
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ options: {
+ presets: ["@babel/preset-env", "@babel/preset-react"],
+ },
+ },
+ },<% } %><% if (langType == "Typescript") { %>
+ {
+ test: /\.(ts|tsx)$/i,
+ loader: 'ts-loader',
+ exclude: ['/node_modules/'],
+ },<% } %><% if (isCSS && !isPostCSS) { %>
+ {
+ test: /\.css$/i,
+ use: [stylesHandler,'css-loader'],
+ },<% } %><% if (cssType == 'SASS') { %>
+ {
+ test: /\.s[ac]ss$/i,
+ use: [stylesHandler, 'css-loader', <% if (isPostCSS) { %>'postcss-loader', <% } %>'sass-loader'],
+ },<% } %><% if (cssType == 'LESS') { %>
+ {
+ test: /\.less$/i,
+ use: [stylesHandler, 'css-loader', <% if (isPostCSS) { %>'postcss-loader', <% } %>'less-loader'],
+ },<% } %><% if (cssType == 'Stylus') { %>
+ {
+ test: /\.styl$/i,
+ use: [stylesHandler, 'css-loader', <% if (isPostCSS) { %>'postcss-loader', <% } %>'stylus-loader'],
+ },<% } %><% if (isPostCSS && isCSS) { %>
+ {
+ test: /\.css$/i,
+ use: [stylesHandler, 'css-loader', 'postcss-loader'],
+ },<% } %>
+ {
+ test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src/"),
+ },
+ extensions: ['.jsx', '.js'<% if (langType === 'Typescript') { %>, '.tsx', '.ts'<% } %>],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+ <% if (extractPlugin === "Only for Production") { %>
+ config.plugins.push(new MiniCssExtractPlugin());
+ <% } %>
+ <% if (workboxWebpackPlugin) { %>
+ config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
+ <% } %>
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
diff --git a/packages/create-webpack-app/templates/init/svelte/README.md.tpl b/packages/create-webpack-app/templates/init/svelte/README.md.tpl
new file mode 100644
index 00000000000..77c91681f17
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/README.md.tpl
@@ -0,0 +1,15 @@
+# 🚀 Welcome to your new awesome project!
+
+This project has been created using **create-webpack-app**, you can now run
+
+```bash
+npm run build
+```
+
+or
+
+```bash
+yarn build
+```
+
+to bundle your application
diff --git a/packages/create-webpack-app/templates/init/svelte/index.html.tpl b/packages/create-webpack-app/templates/init/svelte/index.html.tpl
new file mode 100644
index 00000000000..a7468d8153c
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/index.html.tpl
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Svelte Webpack Project
+
+
+
+
+
+
diff --git a/packages/create-webpack-app/templates/init/svelte/package.json.tpl b/packages/create-webpack-app/templates/init/svelte/package.json.tpl
new file mode 100644
index 00000000000..e6a317b1051
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/package.json.tpl
@@ -0,0 +1,11 @@
+{
+ "version": "1.0.0",
+ "description": "My webpack project",
+ "name": "my-webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ "serve": "webpack serve"
+ }
+}
diff --git a/packages/create-webpack-app/templates/init/svelte/src/App.svelte.tpl b/packages/create-webpack-app/templates/init/svelte/src/App.svelte.tpl
new file mode 100644
index 00000000000..b6df249e87a
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/src/App.svelte.tpl
@@ -0,0 +1,49 @@
+
+
+
+
+

+
+
Count: {$count}
+
+
+
+
+
+<% if (isCSS) { %>
+
+<% } %>
diff --git a/packages/create-webpack-app/templates/init/svelte/src/assets/webpack.png.tpl b/packages/create-webpack-app/templates/init/svelte/src/assets/webpack.png.tpl
new file mode 100644
index 00000000000..b2390da4f06
Binary files /dev/null and b/packages/create-webpack-app/templates/init/svelte/src/assets/webpack.png.tpl differ
diff --git a/packages/create-webpack-app/templates/init/svelte/src/components/HelloWorld.svelte.tpl b/packages/create-webpack-app/templates/init/svelte/src/components/HelloWorld.svelte.tpl
new file mode 100644
index 00000000000..51aaeea8939
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/src/components/HelloWorld.svelte.tpl
@@ -0,0 +1,32 @@
+
+
+
+
+ {reactiveMsg}
+
+
+ This is a Webpack + Svelte project.
+
+
diff --git a/packages/create-webpack-app/templates/init/svelte/src/index.d.ts.tpl b/packages/create-webpack-app/templates/init/svelte/src/index.d.ts.tpl
new file mode 100644
index 00000000000..d8c267b25a1
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/src/index.d.ts.tpl
@@ -0,0 +1,3 @@
+declare module '*.svelte' {
+ export { SvelteComponentDev as default } from 'svelte/internal';
+}
diff --git a/packages/create-webpack-app/templates/init/svelte/src/main.js.tpl b/packages/create-webpack-app/templates/init/svelte/src/main.js.tpl
new file mode 100644
index 00000000000..58e2b6bc94e
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/src/main.js.tpl
@@ -0,0 +1,22 @@
+import App from './App.svelte';
+
+<% if (cssType == 'CSS only') { %>
+import "./styles/global.css";<% } if (cssType == 'SASS') { %>
+import "./styles/global.scss";<% } if (cssType == 'LESS') { %>
+import "./styles/global.less";<% } if (cssType == 'Stylus') { %>
+import "./styles/global.styl";<% } %>
+
+
+const app = new App({
+ target: document.getElementById('app'),
+});
+
+export default app;
+
+<% if (workboxWebpackPlugin) { %>
+if ('serviceWorker' in navigator) {
+ window.addEventListener('load', () => {
+ navigator.serviceWorker.register('/service-worker.js');
+ });
+}
+<% } %>
diff --git a/packages/create-webpack-app/templates/init/svelte/src/main.ts.tpl b/packages/create-webpack-app/templates/init/svelte/src/main.ts.tpl
new file mode 100644
index 00000000000..58e2b6bc94e
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/src/main.ts.tpl
@@ -0,0 +1,22 @@
+import App from './App.svelte';
+
+<% if (cssType == 'CSS only') { %>
+import "./styles/global.css";<% } if (cssType == 'SASS') { %>
+import "./styles/global.scss";<% } if (cssType == 'LESS') { %>
+import "./styles/global.less";<% } if (cssType == 'Stylus') { %>
+import "./styles/global.styl";<% } %>
+
+
+const app = new App({
+ target: document.getElementById('app'),
+});
+
+export default app;
+
+<% if (workboxWebpackPlugin) { %>
+if ('serviceWorker' in navigator) {
+ window.addEventListener('load', () => {
+ navigator.serviceWorker.register('/service-worker.js');
+ });
+}
+<% } %>
diff --git a/packages/create-webpack-app/templates/init/svelte/src/store/index.js.tpl b/packages/create-webpack-app/templates/init/svelte/src/store/index.js.tpl
new file mode 100644
index 00000000000..06fe960d136
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/src/store/index.js.tpl
@@ -0,0 +1,41 @@
+import { writable, derived } from 'svelte/store';
+
+
+const initialState= {
+ count: 0,
+ user: null,
+};
+
+// Create writable stores with the initial state
+const count = writable(initialState.count);
+const user = writable(initialState.user);
+
+// Define actions to modify the state
+function increment(){
+ count.update(n => n + 1);
+}
+
+function decrement(){
+ count.update(n => n - 1);
+}
+
+function setUser(newUser) {
+ user.set(newUser);
+}
+
+// Define a derived store for the `isLoggedIn` computed property
+const isLoggedIn = derived(user, $user => $user !== null);
+
+export const useMainStore = () => ({
+ // State
+ count,
+ user,
+
+ // Actions
+ increment,
+ decrement,
+ setUser,
+
+ // Getters
+ isLoggedIn,
+});
diff --git a/packages/create-webpack-app/templates/init/svelte/src/store/index.ts.tpl b/packages/create-webpack-app/templates/init/svelte/src/store/index.ts.tpl
new file mode 100644
index 00000000000..addd841703a
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/src/store/index.ts.tpl
@@ -0,0 +1,48 @@
+import { writable, derived, type Writable } from 'svelte/store';
+
+type nameType = string | null;
+
+interface MainState {
+ // define your state interface
+ count: number;
+ user: nameType;
+}
+
+const initialState: MainState = {
+ count: 0,
+ user: null,
+};
+
+// Create writable stores with the initial state
+const count: Writable = writable(initialState.count);
+const user: Writable = writable(initialState.user);
+
+// Define actions to modify the state
+function increment(): void {
+ count.update(n => n + 1);
+}
+
+function decrement(): void {
+ count.update(n => n - 1);
+}
+
+function setUser(newUser: nameType): void {
+ user.set(newUser);
+}
+
+// Define a derived store for the `isLoggedIn` computed property
+const isLoggedIn = derived(user, $user => $user !== null);
+
+export const useMainStore = () => ({
+ // State
+ count,
+ user,
+
+ // Actions
+ increment,
+ decrement,
+ setUser,
+
+ // Getters
+ isLoggedIn,
+});
diff --git a/packages/create-webpack-app/templates/init/svelte/src/styles/global.css.tpl b/packages/create-webpack-app/templates/init/svelte/src/styles/global.css.tpl
new file mode 100644
index 00000000000..3983999e983
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/src/styles/global.css.tpl
@@ -0,0 +1,27 @@
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+
+.heading {
+ font-weight: 300;
+ bg-color: #f0f0f0;
+}
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+#app {
+ font-family: Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ text-align: center;
+ color: #2c3e50;
+ margin-top: 60px;
+}
diff --git a/packages/create-webpack-app/templates/init/svelte/src/styles/global.less.tpl b/packages/create-webpack-app/templates/init/svelte/src/styles/global.less.tpl
new file mode 100644
index 00000000000..16a4f524e27
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/src/styles/global.less.tpl
@@ -0,0 +1,26 @@
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+
+.heading {
+ font-weight: 300;
+}
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+#app {
+ font-family: Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ text-align: center;
+ color: #2c3e50;
+ margin-top: 60px;
+}
diff --git a/packages/create-webpack-app/templates/init/svelte/src/styles/global.scss.tpl b/packages/create-webpack-app/templates/init/svelte/src/styles/global.scss.tpl
new file mode 100644
index 00000000000..16a4f524e27
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/src/styles/global.scss.tpl
@@ -0,0 +1,26 @@
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+
+.heading {
+ font-weight: 300;
+}
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+#app {
+ font-family: Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ text-align: center;
+ color: #2c3e50;
+ margin-top: 60px;
+}
diff --git a/packages/create-webpack-app/templates/init/svelte/src/styles/global.styl.tpl b/packages/create-webpack-app/templates/init/svelte/src/styles/global.styl.tpl
new file mode 100644
index 00000000000..16a4f524e27
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/src/styles/global.styl.tpl
@@ -0,0 +1,26 @@
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+
+.heading {
+ font-weight: 300;
+}
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+#app {
+ font-family: Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ text-align: center;
+ color: #2c3e50;
+ margin-top: 60px;
+}
diff --git a/packages/create-webpack-app/templates/init/svelte/tsconfig.json.tpl b/packages/create-webpack-app/templates/init/svelte/tsconfig.json.tpl
new file mode 100644
index 00000000000..40932dd52a0
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/tsconfig.json.tpl
@@ -0,0 +1,21 @@
+{
+ "extends": "@tsconfig/svelte/tsconfig.json",
+ "compilerOptions": {
+ "target": "es5",
+ "module": "es6",
+ "strict": true,
+ "moduleResolution": "node",
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "sourceMap": true,
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.js",
+ "src/**/*.svelte"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/packages/create-webpack-app/templates/init/svelte/webpack.config.js.tpl b/packages/create-webpack-app/templates/init/svelte/webpack.config.js.tpl
new file mode 100644
index 00000000000..14c608c3670
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/svelte/webpack.config.js.tpl
@@ -0,0 +1,119 @@
+// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');<% if (htmlWebpackPlugin) { %>
+const HtmlWebpackPlugin = require('html-webpack-plugin');<% } %><% if (extractPlugin !== 'No') { %>
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');<% } %><% if (workboxWebpackPlugin) { %>
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin');<% } %>
+const isProduction = process.env.NODE_ENV === 'production';
+
+<% if (cssType !== 'none') { %>
+<% if (extractPlugin === "Yes") { %>
+const stylesHandler = MiniCssExtractPlugin.loader;
+<% } else if (extractPlugin === "Only for Production") { %>
+const stylesHandler = isProduction ? MiniCssExtractPlugin.loader : 'style-loader';
+<% } else { %>
+const stylesHandler = 'style-loader';
+<% } %>
+<% } %>
+
+const config = {
+ entry: '<%= entry %>',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },<% if (devServer) { %>
+ devServer: {
+ open: true,
+ host: 'localhost',
+ },<% } %>
+ plugins: [<% if (htmlWebpackPlugin) { %>
+ new HtmlWebpackPlugin({
+ template: './index.html',
+ }),
+<% } %><% if (extractPlugin === "Yes") { %>
+ new MiniCssExtractPlugin(),
+<% } %>
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\.svelte$/,
+ use: {
+ loader: 'svelte-loader',
+ options: {
+ emitCss: true,
+ hotReload: true
+ }
+ }
+ },<% if (langType == "ES6") { %>
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ options: {
+ presets: ["@babel/preset-env"],
+ },
+ },
+ },<% } %><% if (langType == "Typescript") { %>
+ {
+ test: /\.ts$/,
+ loader: 'ts-loader',
+ exclude: /node_modules/,
+ options: {
+ appendTsSuffixTo: [/\.svelte$/],
+ },
+ },<% } %><% if (isCSS && !isPostCSS) { %>
+ {
+ test: /\.css$/i,
+ use: [stylesHandler,'css-loader'],
+ },<% } %><% if (cssType == 'SASS') { %>
+ {
+ test: /\.s[ac]ss$/i,
+ use: [stylesHandler, 'css-loader', <% if (isPostCSS) { %>'postcss-loader', <% } %>'sass-loader'],
+ },<% } %><% if (cssType == 'LESS') { %>
+ {
+ test: /\.less$/i,
+ use: [stylesHandler, 'css-loader', <% if (isPostCSS) { %>'postcss-loader', <% } %>'less-loader'],
+ },<% } %><% if (cssType == 'Stylus') { %>
+ {
+ test: /\.styl$/i,
+ use: [stylesHandler, 'css-loader', <% if (isPostCSS) { %>'postcss-loader', <% } %>'stylus-loader'],
+ },<% } %><% if (isPostCSS && isCSS) { %>
+ {
+ test: /\.css$/i,
+ use: [stylesHandler, 'css-loader', 'postcss-loader'],
+ },<% } %>
+ {
+ test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src/"),
+ },
+ extensions: ['.mjs', '.js', '.svelte'<% if (langType == "Typescript") {%>, '.ts'<% } %>],
+ mainFields: ['svelte', 'browser', 'module', 'main'],
+ conditionNames: ['svelte', 'module', 'browser', 'main', 'default']
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+ <% if (extractPlugin === "Only for Production") { %>
+ config.plugins.push(new MiniCssExtractPlugin());
+ <% } %>
+ <% if (workboxWebpackPlugin) { %>
+ config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
+ <% } %>
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
diff --git a/packages/create-webpack-app/templates/init/vue/README.md.tpl b/packages/create-webpack-app/templates/init/vue/README.md.tpl
new file mode 100644
index 00000000000..77c91681f17
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/README.md.tpl
@@ -0,0 +1,15 @@
+# 🚀 Welcome to your new awesome project!
+
+This project has been created using **create-webpack-app**, you can now run
+
+```bash
+npm run build
+```
+
+or
+
+```bash
+yarn build
+```
+
+to bundle your application
diff --git a/packages/create-webpack-app/templates/init/vue/index.html.tpl b/packages/create-webpack-app/templates/init/vue/index.html.tpl
new file mode 100644
index 00000000000..40294bb4c8d
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/index.html.tpl
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Vue webpack Project
+
+
+
+
+
+
diff --git a/packages/create-webpack-app/templates/init/vue/package.json.tpl b/packages/create-webpack-app/templates/init/vue/package.json.tpl
new file mode 100644
index 00000000000..e6a317b1051
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/package.json.tpl
@@ -0,0 +1,11 @@
+{
+ "version": "1.0.0",
+ "description": "My webpack project",
+ "name": "my-webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ "serve": "webpack serve"
+ }
+}
diff --git a/packages/create-webpack-app/templates/init/vue/src/App.vue.tpl b/packages/create-webpack-app/templates/init/vue/src/App.vue.tpl
new file mode 100644
index 00000000000..5e1bb628ef0
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/App.vue.tpl
@@ -0,0 +1,3 @@
+
+
+
diff --git a/packages/create-webpack-app/templates/init/vue/src/assets/webpack.png.tpl b/packages/create-webpack-app/templates/init/vue/src/assets/webpack.png.tpl
new file mode 100644
index 00000000000..b2390da4f06
Binary files /dev/null and b/packages/create-webpack-app/templates/init/vue/src/assets/webpack.png.tpl differ
diff --git a/packages/create-webpack-app/templates/init/vue/src/components/About.vue.tpl b/packages/create-webpack-app/templates/init/vue/src/components/About.vue.tpl
new file mode 100644
index 00000000000..15b1b8dec1f
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/components/About.vue.tpl
@@ -0,0 +1,34 @@
+
+
+
+
![Webpack logo]()
+
+ This is the About page!
+
+
+ This is a webpack + {{ msg }} project.
+
+
+
+
+
+
diff --git a/packages/create-webpack-app/templates/init/vue/src/components/Home.vue.tpl b/packages/create-webpack-app/templates/init/vue/src/components/Home.vue.tpl
new file mode 100644
index 00000000000..128ecb73d3a
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/components/Home.vue.tpl
@@ -0,0 +1,19 @@
+
+
+

+
This is the Home page!
+
Click the buttons below to increment and decrement the count.
+ <% if (useVueStore) { %>
+
Count: {{ mainStore.count }}
+
+
+ <% } %>
+
+
+
+
diff --git a/packages/create-webpack-app/templates/init/vue/src/components/Layout.vue.tpl b/packages/create-webpack-app/templates/init/vue/src/components/Layout.vue.tpl
new file mode 100644
index 00000000000..394f81f27f8
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/components/Layout.vue.tpl
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/packages/create-webpack-app/templates/init/vue/src/components/Navbar.vue.tpl b/packages/create-webpack-app/templates/init/vue/src/components/Navbar.vue.tpl
new file mode 100644
index 00000000000..8cc83206a53
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/components/Navbar.vue.tpl
@@ -0,0 +1,34 @@
+
+
+
+
+
diff --git a/packages/create-webpack-app/templates/init/vue/src/main.js.tpl b/packages/create-webpack-app/templates/init/vue/src/main.js.tpl
new file mode 100644
index 00000000000..bc26c6d6d6e
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/main.js.tpl
@@ -0,0 +1,32 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import router from './router'
+<% if (useVueStore) { %>
+ import { createPinia } from 'pinia'
+ const store = createPinia()
+<% } %>
+<% if (cssType == 'CSS only') { %>
+import "./styles/global.css";<% } if (cssType == 'SASS') { %>
+import "./styles/global.scss";<% } if (cssType == 'LESS') { %>
+import "./styles/global.less";<% } if (cssType == 'Stylus') { %>
+import "./styles/global.styl";<% } %>
+
+
+const app = createApp(App)
+app.use(router)
+<% if (useVueStore) { %>app.use(store)<% } %>
+app.mount('#root')
+
+<% if (workboxWebpackPlugin) { %>
+if ('serviceWorker' in navigator) {
+ window.addEventListener('load', () => {
+ navigator.serviceWorker.register('service-worker.js')
+ .then((registration) => {
+ console.log('Service Worker registered: ', registration);
+ })
+ .catch((registrationError) => {
+ console.error('Service Worker registration failed: ', registrationError);
+ });
+ });
+}
+<% } %>
diff --git a/packages/create-webpack-app/templates/init/vue/src/main.ts.tpl b/packages/create-webpack-app/templates/init/vue/src/main.ts.tpl
new file mode 100644
index 00000000000..bc26c6d6d6e
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/main.ts.tpl
@@ -0,0 +1,32 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import router from './router'
+<% if (useVueStore) { %>
+ import { createPinia } from 'pinia'
+ const store = createPinia()
+<% } %>
+<% if (cssType == 'CSS only') { %>
+import "./styles/global.css";<% } if (cssType == 'SASS') { %>
+import "./styles/global.scss";<% } if (cssType == 'LESS') { %>
+import "./styles/global.less";<% } if (cssType == 'Stylus') { %>
+import "./styles/global.styl";<% } %>
+
+
+const app = createApp(App)
+app.use(router)
+<% if (useVueStore) { %>app.use(store)<% } %>
+app.mount('#root')
+
+<% if (workboxWebpackPlugin) { %>
+if ('serviceWorker' in navigator) {
+ window.addEventListener('load', () => {
+ navigator.serviceWorker.register('service-worker.js')
+ .then((registration) => {
+ console.log('Service Worker registered: ', registration);
+ })
+ .catch((registrationError) => {
+ console.error('Service Worker registration failed: ', registrationError);
+ });
+ });
+}
+<% } %>
diff --git a/packages/create-webpack-app/templates/init/vue/src/router/index.js.tpl b/packages/create-webpack-app/templates/init/vue/src/router/index.js.tpl
new file mode 100644
index 00000000000..d2f4e9b87dc
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/router/index.js.tpl
@@ -0,0 +1,31 @@
+import { createRouter, createWebHistory } from 'vue-router'
+import Home from '../components/Home.vue';
+import Layout from '../components/Layout.vue';
+import About from '../components/About.vue';
+
+const routes = [
+ {
+ path: '/',
+ component: Layout,
+ children: [
+ {
+ path: '',
+ name: 'Home',
+ component: Home
+ },
+ {
+ path: 'about',
+ name: 'About',
+ component: About,
+ props: { msg: 'Vue' }
+ }
+ ]
+ }
+];
+
+const router = createRouter({
+ history: createWebHistory(),
+ routes
+});
+
+export default router;
diff --git a/packages/create-webpack-app/templates/init/vue/src/router/index.ts.tpl b/packages/create-webpack-app/templates/init/vue/src/router/index.ts.tpl
new file mode 100644
index 00000000000..7061cd5a81f
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/router/index.ts.tpl
@@ -0,0 +1,31 @@
+import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
+import Layout from '../components/Layout.vue';
+import Home from '../components/Home.vue';
+import About from '../components/About.vue';
+
+const routes: Array = [
+ {
+ path: '/',
+ component: Layout,
+ children: [
+ {
+ path: '',
+ name: 'Home',
+ component: Home
+ },
+ {
+ path: 'about',
+ name: 'About',
+ component: About,
+ props: { msg: 'Vue' }
+ }
+ ]
+ }
+];
+
+const router = createRouter({
+ history: createWebHistory(),
+ routes
+});
+
+export default router;
diff --git a/packages/create-webpack-app/templates/init/vue/src/store/index.js.tpl b/packages/create-webpack-app/templates/init/vue/src/store/index.js.tpl
new file mode 100644
index 00000000000..eff0cdc1b80
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/store/index.js.tpl
@@ -0,0 +1,28 @@
+import { defineStore } from 'pinia'
+
+// Define your store
+export const useMainStore = defineStore('main', {
+ state: () => {
+ return {
+ // Define your state here
+ count: 0,
+ user: null
+ }
+ },
+ actions: {
+ // Define your actions here
+ increment() {
+ this.count++
+ },
+ decrement() {
+ this.count--
+ },
+ setUser(user) {
+ this.user = user
+ }
+ },
+ getters: {
+ // Define your getters here (optional)
+ isLoggedIn: (state) => !!state.user,
+ }
+})
diff --git a/packages/create-webpack-app/templates/init/vue/src/store/index.ts.tpl b/packages/create-webpack-app/templates/init/vue/src/store/index.ts.tpl
new file mode 100644
index 00000000000..51ae66ef34d
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/store/index.ts.tpl
@@ -0,0 +1,36 @@
+import { defineStore } from 'pinia'
+
+type nameType = string | null;
+
+interface State{
+ // Define your state interface
+ count: number,
+ user: nameType
+}
+
+// Define your store
+export const useMainStore = defineStore('main', {
+ state: (): State => {
+ return {
+ // Define your state here
+ count: 0,
+ user: null
+ }
+ },
+ actions: {
+ // Define your actions here
+ increment() {
+ this.count++
+ },
+ decrement() {
+ this.count--
+ },
+ setUser(user: nameType) {
+ this.user = user
+ }
+ },
+ getters: {
+ // Define your getters here (optional)
+ isLoggedIn: (state: State): boolean => !!state.user,
+ }
+})
diff --git a/packages/create-webpack-app/templates/init/vue/src/styles/global.css.tpl b/packages/create-webpack-app/templates/init/vue/src/styles/global.css.tpl
new file mode 100644
index 00000000000..bd3373adaa7
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/styles/global.css.tpl
@@ -0,0 +1,91 @@
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+:root {
+ --color-primary: #1C78C0;
+ --color-secondary: #8ED6FB;
+ --color-dark: #2C3E50;
+ --color-background: #f0f0f0;
+}
+.heading {
+ font-weight: 300;
+ bg-color: #f0f0f0;
+}
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+#app {
+ font-family: Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ text-align: center;
+ color: #2c3e50;
+ margin-top: 60px;
+}
+
+span {
+ color: var(--color-primary);
+}
+
+nav {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem;
+ background-color: var(--color-background);
+}
+
+ul {
+ list-style: none;
+ display: flex;
+ gap: 1rem;
+}
+
+p {
+ font-size: 1.2rem;
+ line-height: 1.5;
+}
+
+li {
+ display: flex;
+ align-items: center;
+}
+
+button {
+ border: none;
+ border-radius: 0.5rem;
+ padding: 1rem;
+ font-size: 1.25rem;
+ cursor: pointer;
+ margin: 0 1rem;
+}
+
+a {
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ text-decoration: none;
+ font-weight: bold;
+}
+
+.btn-primary {
+ background-color: var(--color-primary);
+ color: white;
+}
+.btn-secondary {
+ background-color: var(--color-secondary);
+ color: var(--color-dark);
+}
+.btn-primary:hover {
+ background-color: #0E5A8A;
+}
+.btn-secondary:hover {
+ background-color: #6EB8E0;
+}
diff --git a/packages/create-webpack-app/templates/init/vue/src/styles/global.less.tpl b/packages/create-webpack-app/templates/init/vue/src/styles/global.less.tpl
new file mode 100644
index 00000000000..bd3373adaa7
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/styles/global.less.tpl
@@ -0,0 +1,91 @@
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+:root {
+ --color-primary: #1C78C0;
+ --color-secondary: #8ED6FB;
+ --color-dark: #2C3E50;
+ --color-background: #f0f0f0;
+}
+.heading {
+ font-weight: 300;
+ bg-color: #f0f0f0;
+}
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+#app {
+ font-family: Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ text-align: center;
+ color: #2c3e50;
+ margin-top: 60px;
+}
+
+span {
+ color: var(--color-primary);
+}
+
+nav {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem;
+ background-color: var(--color-background);
+}
+
+ul {
+ list-style: none;
+ display: flex;
+ gap: 1rem;
+}
+
+p {
+ font-size: 1.2rem;
+ line-height: 1.5;
+}
+
+li {
+ display: flex;
+ align-items: center;
+}
+
+button {
+ border: none;
+ border-radius: 0.5rem;
+ padding: 1rem;
+ font-size: 1.25rem;
+ cursor: pointer;
+ margin: 0 1rem;
+}
+
+a {
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ text-decoration: none;
+ font-weight: bold;
+}
+
+.btn-primary {
+ background-color: var(--color-primary);
+ color: white;
+}
+.btn-secondary {
+ background-color: var(--color-secondary);
+ color: var(--color-dark);
+}
+.btn-primary:hover {
+ background-color: #0E5A8A;
+}
+.btn-secondary:hover {
+ background-color: #6EB8E0;
+}
diff --git a/packages/create-webpack-app/templates/init/vue/src/styles/global.scss.tpl b/packages/create-webpack-app/templates/init/vue/src/styles/global.scss.tpl
new file mode 100644
index 00000000000..bd3373adaa7
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/styles/global.scss.tpl
@@ -0,0 +1,91 @@
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+:root {
+ --color-primary: #1C78C0;
+ --color-secondary: #8ED6FB;
+ --color-dark: #2C3E50;
+ --color-background: #f0f0f0;
+}
+.heading {
+ font-weight: 300;
+ bg-color: #f0f0f0;
+}
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+#app {
+ font-family: Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ text-align: center;
+ color: #2c3e50;
+ margin-top: 60px;
+}
+
+span {
+ color: var(--color-primary);
+}
+
+nav {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem;
+ background-color: var(--color-background);
+}
+
+ul {
+ list-style: none;
+ display: flex;
+ gap: 1rem;
+}
+
+p {
+ font-size: 1.2rem;
+ line-height: 1.5;
+}
+
+li {
+ display: flex;
+ align-items: center;
+}
+
+button {
+ border: none;
+ border-radius: 0.5rem;
+ padding: 1rem;
+ font-size: 1.25rem;
+ cursor: pointer;
+ margin: 0 1rem;
+}
+
+a {
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ text-decoration: none;
+ font-weight: bold;
+}
+
+.btn-primary {
+ background-color: var(--color-primary);
+ color: white;
+}
+.btn-secondary {
+ background-color: var(--color-secondary);
+ color: var(--color-dark);
+}
+.btn-primary:hover {
+ background-color: #0E5A8A;
+}
+.btn-secondary:hover {
+ background-color: #6EB8E0;
+}
diff --git a/packages/create-webpack-app/templates/init/vue/src/styles/global.styl.tpl b/packages/create-webpack-app/templates/init/vue/src/styles/global.styl.tpl
new file mode 100644
index 00000000000..bd3373adaa7
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/src/styles/global.styl.tpl
@@ -0,0 +1,91 @@
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+:root {
+ --color-primary: #1C78C0;
+ --color-secondary: #8ED6FB;
+ --color-dark: #2C3E50;
+ --color-background: #f0f0f0;
+}
+.heading {
+ font-weight: 300;
+ bg-color: #f0f0f0;
+}
+
+.container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+#app {
+ font-family: Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ text-align: center;
+ color: #2c3e50;
+ margin-top: 60px;
+}
+
+span {
+ color: var(--color-primary);
+}
+
+nav {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem;
+ background-color: var(--color-background);
+}
+
+ul {
+ list-style: none;
+ display: flex;
+ gap: 1rem;
+}
+
+p {
+ font-size: 1.2rem;
+ line-height: 1.5;
+}
+
+li {
+ display: flex;
+ align-items: center;
+}
+
+button {
+ border: none;
+ border-radius: 0.5rem;
+ padding: 1rem;
+ font-size: 1.25rem;
+ cursor: pointer;
+ margin: 0 1rem;
+}
+
+a {
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ text-decoration: none;
+ font-weight: bold;
+}
+
+.btn-primary {
+ background-color: var(--color-primary);
+ color: white;
+}
+.btn-secondary {
+ background-color: var(--color-secondary);
+ color: var(--color-dark);
+}
+.btn-primary:hover {
+ background-color: #0E5A8A;
+}
+.btn-secondary:hover {
+ background-color: #6EB8E0;
+}
diff --git a/packages/create-webpack-app/templates/init/vue/tsconfig.json.tpl b/packages/create-webpack-app/templates/init/vue/tsconfig.json.tpl
new file mode 100644
index 00000000000..5a61545c28b
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/tsconfig.json.tpl
@@ -0,0 +1,35 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "es6",
+ "strict": true,
+ "jsx": "preserve",
+ "moduleResolution": "node",
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "sourceMap": true,
+ "baseUrl": ".",
+ "types": [
+ "webpack-env",
+ "vue-router"
+ ],
+ "paths": {
+ "@/*": [
+ "src/*"
+ ]
+ },
+ "lib": [
+ "esnext",
+ "dom",
+ ]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "src/**/*.vue"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/packages/create-webpack-app/templates/init/vue/webpack.config.js.tpl b/packages/create-webpack-app/templates/init/vue/webpack.config.js.tpl
new file mode 100644
index 00000000000..1c8913c9df4
--- /dev/null
+++ b/packages/create-webpack-app/templates/init/vue/webpack.config.js.tpl
@@ -0,0 +1,115 @@
+// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+const { VueLoaderPlugin } = require('vue-loader');<% if (htmlWebpackPlugin) { %>
+const HtmlWebpackPlugin = require('html-webpack-plugin');<% } %><% if (extractPlugin !== 'No') { %>
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');<% } %><% if (workboxWebpackPlugin) { %>
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin');<% } %>
+
+const isProduction = process.env.NODE_ENV === 'production';
+<% if (cssType !== "none") { %>
+<% if (extractPlugin === "Yes") { %>
+const stylesHandler = MiniCssExtractPlugin.loader;
+<% } else if (extractPlugin === "Only for Production") { %>
+const stylesHandler = isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader';
+<% } else { %>
+const stylesHandler = 'vue-style-loader';
+<% } %>
+<% } %>
+
+const config = {
+ entry: '<%= entry %>',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },<% if (devServer) { %>
+ devServer: {
+ open: true,
+ host: 'localhost',
+ },<% } %>
+ plugins: [
+ new VueLoaderPlugin(),<% if (htmlWebpackPlugin) { %>
+ new HtmlWebpackPlugin({
+ template: 'index.html',
+ }),
+<% } %><% if (extractPlugin === "Yes") { %>
+ new MiniCssExtractPlugin(),
+<% } %>
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\.vue$/,
+ loader: 'vue-loader'
+ },<% if (langType == "ES6") { %>
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ options: {
+ presets: ["@babel/preset-env"],
+ },
+ },
+ },<% } %><% if (langType == "Typescript") { %>
+ {
+ test: /\.(ts|tsx)$/i,
+ loader: 'ts-loader',
+ options: {
+ appendTsSuffixTo: [/\.vue$/],
+ transpileOnly: true,
+ },
+ exclude: ['/node_modules/'],
+ },<% } %><% if (isCSS && !isPostCSS) { %>
+ {
+ test: /\.css$/i,
+ use: [stylesHandler,'css-loader'],
+ },<% } %><% if (cssType == 'SASS') { %>
+ {
+ test: /\.s[ac]ss$/i,
+ use: [stylesHandler, 'css-loader', <% if (isPostCSS) { %>'postcss-loader', <% } %>'sass-loader'],
+ },<% } %><% if (cssType == 'LESS') { %>
+ {
+ test: /\.less$/i,
+ use: [stylesHandler, 'css-loader', <% if (isPostCSS) { %>'postcss-loader', <% } %>'less-loader'],
+ },<% } %><% if (cssType == 'Stylus') { %>
+ {
+ test: /\.styl$/i,
+ use: [stylesHandler, 'css-loader', <% if (isPostCSS) { %>'postcss-loader', <% } %>'stylus-loader'],
+ },<% } %><% if (isPostCSS && isCSS) { %>
+ {
+ test: /\.css$/i,
+ use: [stylesHandler, 'css-loader', 'postcss-loader'],
+ },<% } %>
+ {
+ test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src/"),
+ 'vue': '@vue/runtime-dom'
+ },<% if (langType == "Typescript") {%>
+ extensions: ['.tsx', '.ts', '.js', '.vue', '.json'],<% } %>
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+ <% if (extractPlugin === "Only for Production") { %>
+ config.plugins.push(new MiniCssExtractPlugin());
+ <% } %>
+ <% if (workboxWebpackPlugin) { %>
+ config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
+ <% } %>
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
diff --git a/packages/create-webpack-app/templates/loader/default/examples/simple/src/index.js.tpl b/packages/create-webpack-app/templates/loader/default/examples/simple/src/index.js.tpl
new file mode 100644
index 00000000000..2c49d366408
--- /dev/null
+++ b/packages/create-webpack-app/templates/loader/default/examples/simple/src/index.js.tpl
@@ -0,0 +1,11 @@
+import esmModule from './static-esm-module';
+
+const getLazyModule = () => System.import('./lazy-module');
+
+setTimeout(() => {
+ getLazyModule.then((modDefault) => {
+ console.log(modDefault);
+ });
+}, 300);
+
+console.log(esmModule);
diff --git a/packages/create-webpack-app/templates/loader/default/examples/simple/src/lazy-module.js.tpl b/packages/create-webpack-app/templates/loader/default/examples/simple/src/lazy-module.js.tpl
new file mode 100644
index 00000000000..f3dac067326
--- /dev/null
+++ b/packages/create-webpack-app/templates/loader/default/examples/simple/src/lazy-module.js.tpl
@@ -0,0 +1 @@
+export default 'lazy';
diff --git a/packages/create-webpack-app/templates/loader/default/examples/simple/src/static-esm-module.js.tpl b/packages/create-webpack-app/templates/loader/default/examples/simple/src/static-esm-module.js.tpl
new file mode 100644
index 00000000000..d02ba545bd3
--- /dev/null
+++ b/packages/create-webpack-app/templates/loader/default/examples/simple/src/static-esm-module.js.tpl
@@ -0,0 +1 @@
+export default 'foo';
diff --git a/packages/create-webpack-app/templates/loader/default/examples/simple/webpack.config.js.tpl b/packages/create-webpack-app/templates/loader/default/examples/simple/webpack.config.js.tpl
new file mode 100644
index 00000000000..75b6744b7a2
--- /dev/null
+++ b/packages/create-webpack-app/templates/loader/default/examples/simple/webpack.config.js.tpl
@@ -0,0 +1,27 @@
+const path = require('path');
+
+module.exports = {
+ entry: './src/index.js',
+ output: {
+ path: path.join(__dirname, 'example_dist'),
+ filename: '[name].chunk.js',
+ },
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ use: [
+ {
+ loader: 'example-loader',
+ options: {},
+ },
+ ],
+ },
+ ],
+ },
+ resolveLoader: {
+ alias: {
+ 'example-loader': require.resolve('../../src/'),
+ },
+ },
+};
diff --git a/packages/create-webpack-app/templates/loader/default/package.json.tpl b/packages/create-webpack-app/templates/loader/default/package.json.tpl
new file mode 100644
index 00000000000..2485d1b9692
--- /dev/null
+++ b/packages/create-webpack-app/templates/loader/default/package.json.tpl
@@ -0,0 +1,5 @@
+{
+ "version": "1.0.0",
+ "description": "webpack loader",
+ "name": "<%= name %>"
+}
diff --git a/packages/create-webpack-app/templates/loader/default/src/cjs.js.tpl b/packages/create-webpack-app/templates/loader/default/src/cjs.js.tpl
new file mode 100644
index 00000000000..82657ce3a74
--- /dev/null
+++ b/packages/create-webpack-app/templates/loader/default/src/cjs.js.tpl
@@ -0,0 +1 @@
+module.exports = require('./index').default;
diff --git a/packages/create-webpack-app/templates/loader/default/src/index.js.tpl b/packages/create-webpack-app/templates/loader/default/src/index.js.tpl
new file mode 100644
index 00000000000..fa1972c5704
--- /dev/null
+++ b/packages/create-webpack-app/templates/loader/default/src/index.js.tpl
@@ -0,0 +1,25 @@
+/**
+ * See the webpack docs for more information about loaders:
+ * https://webpack.js.org/contribute/writing-a-loader
+ */
+
+module.exports = function loader(source) {
+ const { loaders, resource, request, version, webpack } = this;
+ console.log('<%= name %>');
+ const newSource = `
+ /**
+ * <%= name %>
+ *
+ * Resource Location: ${resource}
+ * Loaders chained to module: ${JSON.stringify(loaders)}
+ * Loader API Version: ${version}
+ * Is this in "webpack mode": ${webpack}
+ * This is the users request for the module: ${request}
+ */
+ /**
+ * Original Source From Loader
+ */
+ ${source}`;
+
+ return newSource;
+}
diff --git a/packages/create-webpack-app/templates/loader/default/test/fixtures/simple-file.js.tpl b/packages/create-webpack-app/templates/loader/default/test/fixtures/simple-file.js.tpl
new file mode 100644
index 00000000000..908ed137dff
--- /dev/null
+++ b/packages/create-webpack-app/templates/loader/default/test/fixtures/simple-file.js.tpl
@@ -0,0 +1,3 @@
+import foo from "./foo"; // eslint-disable-line
+
+console.log(foo);
diff --git a/packages/create-webpack-app/templates/loader/default/test/functional.test.js.tpl b/packages/create-webpack-app/templates/loader/default/test/functional.test.js.tpl
new file mode 100644
index 00000000000..b69f8d7f17a
--- /dev/null
+++ b/packages/create-webpack-app/templates/loader/default/test/functional.test.js.tpl
@@ -0,0 +1,21 @@
+import {
+ runWebpackExampleInMemory,
+} from '../test/test-utils';
+
+test('should run with no errors or warnings', async () => {
+ const buildStats = await runWebpackExampleInMemory('simple');
+ const { errors, warnings } = buildStats;
+
+ expect([...errors, ...warnings].length).toBe(0);
+});
+
+test('should append transformations to JavaScript module', async () => {
+ const buildStats = await runWebpackExampleInMemory('simple');
+ const { modules } = buildStats;
+
+ const moduleToTest = modules[0].source()._source._value;
+ const loadedString = '* Original Source From Loader';
+
+ expect(moduleToTest).toEqual(expect.stringContaining(loadedString));
+ expect(moduleToTest).toMatchSnapshot();
+});
diff --git a/packages/create-webpack-app/templates/loader/default/test/test-utils.js.tpl b/packages/create-webpack-app/templates/loader/default/test/test-utils.js.tpl
new file mode 100644
index 00000000000..d4ffb201d74
--- /dev/null
+++ b/packages/create-webpack-app/templates/loader/default/test/test-utils.js.tpl
@@ -0,0 +1,85 @@
+import path from 'path';
+import webpack from 'webpack';
+import MemoryFs from 'memory-fs';
+
+const fs = new MemoryFs();
+const unitTestFixtures = path.resolve(__dirname, 'fixtures');
+
+/**
+ *
+ *
+ * @param {string} fixtureName
+ * @param {string} [withQueryString='']
+ * @returns {string} Absolute path of a file with query that is to be run by a loader.
+ */
+function getFixtureResource(fixtureName, withQueryString = '') {
+ return `${getFixture(fixtureName)}?${withQueryString}`;
+}
+
+/**
+ *
+ *
+ * @param {string} fixtureName
+ * @returns {string} Absolute path of a file with query that is to be run by a loader.
+ */
+function getFixture(fixtureName) {
+ return path.resolve(unitTestFixtures, `${fixtureName}.js`);
+}
+
+/**
+ *
+ *
+ * @param {Object} withOptions - Loader Options
+ * @returns {loader: string, options: Object}
+ */
+function getLoader(withOptions) {
+ return [{ loader: path.resolve(__dirname, '../dist/index.js'), options: withOptions }];
+}
+
+/**
+ *
+ *
+ * @param {string} exampleName
+ * @returns {Object|Array} - Returns an object or array of objects representing the webpack configuration options
+ */
+function getExampleConfig(exampleName) {
+ return require(`../examples/${exampleName}/webpack.config.js`);
+}
+
+/**
+ *
+ *
+ * @param {string} exampleName - name of example inside of examples folder
+ * @returns
+ */
+async function runWebpackExampleInMemory(exampleName) {
+ const webpackConfig = getExampleConfig(exampleName);
+ const compiler = webpack(webpackConfig);
+
+ compiler.outputFileSystem = fs;
+
+ const result = await new Promise((resolve, reject) => {
+ compiler.run((err, stats) => {
+ if(err) console.error(error)
+
+ const { compilation } = stats
+ const { errors, warnings, assets, entrypoints, chunks, modules } = compilation;
+ const statsJson = stats.toJson();
+
+ resolve({
+ assets,
+ entrypoints,
+ errors,
+ warnings,
+ stats,
+ chunks,
+ modules,
+ statsJson,
+ });
+ })
+ })
+
+ return result
+}
+
+export { getExampleConfig, runWebpackExampleInMemory, fs, getFixtureResource, getLoader, getFixture };
diff --git a/packages/create-webpack-app/templates/loader/default/test/unit.test.js.tpl b/packages/create-webpack-app/templates/loader/default/test/unit.test.js.tpl
new file mode 100644
index 00000000000..c8817fe2520
--- /dev/null
+++ b/packages/create-webpack-app/templates/loader/default/test/unit.test.js.tpl
@@ -0,0 +1,30 @@
+import fs from 'fs';
+import { runLoaders } from 'loader-runner';
+import { getFixtureResource, getFixture, getLoader } from './test-utils';
+
+const loaders = getLoader();
+
+describe('Example Loader Tests: Fixture: simple-file', () => {
+ const fixtureName = 'simple-file';
+ const resource = getFixture(fixtureName);
+
+ test('loaded file should be different', async (done) => {
+ const originalSource = fs.readFileSync(resource);
+ runLoaders({ resource: getFixtureResource(fixtureName), loaders }, (_, result) => {
+ expect(result).not.toEqual(originalSource);
+ done();
+ })
+ });
+
+ test('loader prepends correct information', async (done) => {
+ runLoaders({ resource: getFixtureResource(fixtureName), loaders }, (_, result) => {
+ const resultMatcher = expect.arrayContaining([
+ expect.stringContaining(' * Original Source From Loader'),
+ ]);
+
+ expect(result).toEqual(resultMatcher);
+ expect(result).toMatchSnapshot();
+ done();
+ })
+ });
+});
diff --git a/packages/create-webpack-app/templates/plugin/default/examples/simple/src/index.js.tpl b/packages/create-webpack-app/templates/plugin/default/examples/simple/src/index.js.tpl
new file mode 100644
index 00000000000..2c49d366408
--- /dev/null
+++ b/packages/create-webpack-app/templates/plugin/default/examples/simple/src/index.js.tpl
@@ -0,0 +1,11 @@
+import esmModule from './static-esm-module';
+
+const getLazyModule = () => System.import('./lazy-module');
+
+setTimeout(() => {
+ getLazyModule.then((modDefault) => {
+ console.log(modDefault);
+ });
+}, 300);
+
+console.log(esmModule);
diff --git a/packages/create-webpack-app/templates/plugin/default/examples/simple/src/lazy-module.js.tpl b/packages/create-webpack-app/templates/plugin/default/examples/simple/src/lazy-module.js.tpl
new file mode 100644
index 00000000000..f3dac067326
--- /dev/null
+++ b/packages/create-webpack-app/templates/plugin/default/examples/simple/src/lazy-module.js.tpl
@@ -0,0 +1 @@
+export default 'lazy';
diff --git a/packages/create-webpack-app/templates/plugin/default/examples/simple/src/static-esm-module.js.tpl b/packages/create-webpack-app/templates/plugin/default/examples/simple/src/static-esm-module.js.tpl
new file mode 100644
index 00000000000..d02ba545bd3
--- /dev/null
+++ b/packages/create-webpack-app/templates/plugin/default/examples/simple/src/static-esm-module.js.tpl
@@ -0,0 +1 @@
+export default 'foo';
diff --git a/packages/create-webpack-app/templates/plugin/default/examples/simple/webpack.config.js.tpl b/packages/create-webpack-app/templates/plugin/default/examples/simple/webpack.config.js.tpl
new file mode 100644
index 00000000000..c3ce97c64b6
--- /dev/null
+++ b/packages/create-webpack-app/templates/plugin/default/examples/simple/webpack.config.js.tpl
@@ -0,0 +1,13 @@
+const path = require('path');
+const <%= pluginIdentifier %> = require('../../src/index.js');
+
+module.exports = {
+ entry: './src/index.js',
+ output: {
+ path: path.join(__dirname, 'example_dist'),
+ filename: '[name].chunk.js',
+ },
+ plugins: [
+ new <%= pluginIdentifier %>()
+ ]
+};
diff --git a/packages/create-webpack-app/templates/plugin/default/package.json.tpl b/packages/create-webpack-app/templates/plugin/default/package.json.tpl
new file mode 100644
index 00000000000..507e63215dd
--- /dev/null
+++ b/packages/create-webpack-app/templates/plugin/default/package.json.tpl
@@ -0,0 +1,5 @@
+{
+ "version": "1.0.0",
+ "description": "webpack plugin",
+ "name": "<%= name %>"
+}
diff --git a/packages/create-webpack-app/templates/plugin/default/src/cjs.js.tpl b/packages/create-webpack-app/templates/plugin/default/src/cjs.js.tpl
new file mode 100644
index 00000000000..82657ce3a74
--- /dev/null
+++ b/packages/create-webpack-app/templates/plugin/default/src/cjs.js.tpl
@@ -0,0 +1 @@
+module.exports = require('./index').default;
diff --git a/packages/create-webpack-app/templates/plugin/default/src/index.js.tpl b/packages/create-webpack-app/templates/plugin/default/src/index.js.tpl
new file mode 100644
index 00000000000..e4bf0acdeac
--- /dev/null
+++ b/packages/create-webpack-app/templates/plugin/default/src/index.js.tpl
@@ -0,0 +1,16 @@
+/**
+ * See the webpack docs for more information about plugins:
+ * https://webpack.js.org/contribute/writing-a-plugin/#basic-plugin-architecture
+ */
+
+class <%= pluginIdentifier %> {
+ apply(compiler) {
+ compiler.hooks.done.tap('<%= name %>', (
+ stats /* stats is passed as an argument when done hook is tapped. */
+ ) => {
+ console.log('Hello World!');
+ });
+ }
+}
+
+module.exports = <%= pluginIdentifier %>;
diff --git a/packages/create-webpack-app/templates/plugin/default/test/fixtures/simple-file.js.tpl b/packages/create-webpack-app/templates/plugin/default/test/fixtures/simple-file.js.tpl
new file mode 100644
index 00000000000..908ed137dff
--- /dev/null
+++ b/packages/create-webpack-app/templates/plugin/default/test/fixtures/simple-file.js.tpl
@@ -0,0 +1,3 @@
+import foo from "./foo"; // eslint-disable-line
+
+console.log(foo);
diff --git a/packages/create-webpack-app/templates/plugin/default/test/functional.test.js.tpl b/packages/create-webpack-app/templates/plugin/default/test/functional.test.js.tpl
new file mode 100644
index 00000000000..07f2099cec0
--- /dev/null
+++ b/packages/create-webpack-app/templates/plugin/default/test/functional.test.js.tpl
@@ -0,0 +1,10 @@
+import {
+ runWebpackExampleInMemory,
+} from '../test/test-utils';
+
+test('should run with no errors or warnings', async () => {
+ const buildStats = await runWebpackExampleInMemory('simple');
+ const { errors, warnings } = buildStats;
+
+ expect([...errors, ...warnings].length).toBe(0);
+});
diff --git a/packages/create-webpack-app/templates/plugin/default/test/test-utils.js.tpl b/packages/create-webpack-app/templates/plugin/default/test/test-utils.js.tpl
new file mode 100644
index 00000000000..d4ffb201d74
--- /dev/null
+++ b/packages/create-webpack-app/templates/plugin/default/test/test-utils.js.tpl
@@ -0,0 +1,85 @@
+import path from 'path';
+import webpack from 'webpack';
+import MemoryFs from 'memory-fs';
+
+const fs = new MemoryFs();
+const unitTestFixtures = path.resolve(__dirname, 'fixtures');
+
+/**
+ *
+ *
+ * @param {string} fixtureName
+ * @param {string} [withQueryString='']
+ * @returns {string} Absolute path of a file with query that is to be run by a loader.
+ */
+function getFixtureResource(fixtureName, withQueryString = '') {
+ return `${getFixture(fixtureName)}?${withQueryString}`;
+}
+
+/**
+ *
+ *
+ * @param {string} fixtureName
+ * @returns {string} Absolute path of a file with query that is to be run by a loader.
+ */
+function getFixture(fixtureName) {
+ return path.resolve(unitTestFixtures, `${fixtureName}.js`);
+}
+
+/**
+ *
+ *
+ * @param {Object} withOptions - Loader Options
+ * @returns {loader: string, options: Object}
+ */
+function getLoader(withOptions) {
+ return [{ loader: path.resolve(__dirname, '../dist/index.js'), options: withOptions }];
+}
+
+/**
+ *
+ *
+ * @param {string} exampleName
+ * @returns {Object|Array} - Returns an object or array of objects representing the webpack configuration options
+ */
+function getExampleConfig(exampleName) {
+ return require(`../examples/${exampleName}/webpack.config.js`);
+}
+
+/**
+ *
+ *
+ * @param {string} exampleName - name of example inside of examples folder
+ * @returns
+ */
+async function runWebpackExampleInMemory(exampleName) {
+ const webpackConfig = getExampleConfig(exampleName);
+ const compiler = webpack(webpackConfig);
+
+ compiler.outputFileSystem = fs;
+
+ const result = await new Promise((resolve, reject) => {
+ compiler.run((err, stats) => {
+ if(err) console.error(error)
+
+ const { compilation } = stats
+ const { errors, warnings, assets, entrypoints, chunks, modules } = compilation;
+ const statsJson = stats.toJson();
+
+ resolve({
+ assets,
+ entrypoints,
+ errors,
+ warnings,
+ stats,
+ chunks,
+ modules,
+ statsJson,
+ });
+ })
+ })
+
+ return result
+}
+
+export { getExampleConfig, runWebpackExampleInMemory, fs, getFixtureResource, getLoader, getFixture };
diff --git a/packages/create-webpack-app/tsconfig.json b/packages/create-webpack-app/tsconfig.json
new file mode 100644
index 00000000000..a41c8c48bc9
--- /dev/null
+++ b/packages/create-webpack-app/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../../tsconfig.json",
+ "exclude": ["src/utils/__tests__"],
+ "compilerOptions": {
+ "outDir": "lib",
+ "rootDir": "src",
+ "module": "esnext"
+ },
+ "include": ["src"],
+ "references": [
+ {
+ "path": "../webpack-cli"
+ }
+ ]
+}
diff --git a/test/create-webpack-app/init/__snapshots__/init.test.js.snap.webpack5 b/test/create-webpack-app/init/__snapshots__/init.test.js.snap.webpack5
new file mode 100644
index 00000000000..d4374a8be83
--- /dev/null
+++ b/test/create-webpack-app/init/__snapshots__/init.test.js.snap.webpack5
@@ -0,0 +1,1893 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`create-webpack-app cli recognizes '-f' as an alias for '--force' 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "html-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli recognizes '-t' as an alias for '--template' 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "html-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should ask question when wrong template is supplied 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "html-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should configure WDS as opted 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should configure WDS as opted 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const config = {
+ entry: './src/index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ devServer: {
+ open: true,
+ host: 'localhost',
+ },
+ plugins: [
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should configure assets modules by default 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "html-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should configure assets modules by default 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const config = {
+ entry: './src/index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ devServer: {
+ open: true,
+ host: 'localhost',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: 'index.html',
+ }),
+
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+ {
+ test: /\\.html$/i,
+ use: ['html-loader'],
+ },
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should configure html-webpack-plugin as opted 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "html-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should configure html-webpack-plugin as opted 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const config = {
+ entry: './src/index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: 'index.html',
+ }),
+
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+ {
+ test: /\\.html$/i,
+ use: ['html-loader'],
+ },
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should configure workbox-webpack-plugin as opted 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "html-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should configure workbox-webpack-plugin as opted 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const config = {
+ entry: './src/index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: 'index.html',
+ }),
+
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+ {
+ test: /\\.html$/i,
+ use: ['html-loader'],
+ },
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should generate ES6 project correctly 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "@babel/core": "x.x.x",
+ "@babel/preset-env": "x.x.x",
+ "babel-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should generate ES6 project correctly 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const config = {
+ entry: './src/index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ plugins: [
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.(js|jsx)$/i,
+ loader: 'babel-loader',
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should generate default project when nothing is passed 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "html-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should generate folders if non existing generation path is given 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "html-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should generate project when generationPath is supplied 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "html-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should generate react template with --force 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "@babel/core": "x.x.x",
+ "@babel/preset-env": "x.x.x",
+ "@babel/preset-react": "x.x.x",
+ "@types/react-router-dom": "x.x.x",
+ "babel-loader": "x.x.x",
+ "css-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "react": "x.x.x",
+ "react-dom": "x.x.x",
+ "react-router-dom": "x.x.x",
+ "style-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "my-webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should generate react template with --force 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const stylesHandler = 'style-loader';
+
+
+
+const config = {
+ entry: './src/index.jsx',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ devServer: {
+ open: true,
+ host: 'localhost',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: 'index.html',
+ }),
+
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.(js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ options: {
+ presets: ["@babel/preset-env", "@babel/preset-react"],
+ },
+ },
+ },
+ {
+ test: /\\.css$/i,
+ use: [stylesHandler,'css-loader'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src/"),
+ },
+ extensions: ['.jsx', '.js'],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should generate react template with state and routing support with prompt answers 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "@babel/core": "x.x.x",
+ "@babel/preset-env": "x.x.x",
+ "@babel/preset-react": "x.x.x",
+ "@types/react-router-dom": "x.x.x",
+ "autoprefixer": "x.x.x",
+ "babel-loader": "x.x.x",
+ "css-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "postcss": "x.x.x",
+ "postcss-loader": "x.x.x",
+ "react": "x.x.x",
+ "react-dom": "x.x.x",
+ "react-router-dom": "x.x.x",
+ "style-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "my-webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should generate react template with state and routing support with prompt answers 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const stylesHandler = 'style-loader';
+
+
+
+const config = {
+ entry: './src/index.jsx',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ devServer: {
+ open: true,
+ host: 'localhost',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: 'index.html',
+ }),
+
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.(js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ options: {
+ presets: ["@babel/preset-env", "@babel/preset-react"],
+ },
+ },
+ },
+ {
+ test: /\\.css$/i,
+ use: [stylesHandler, 'css-loader', 'postcss-loader'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src/"),
+ },
+ extensions: ['.jsx', '.js'],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should generate svelte template with --force 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "@babel/core": "x.x.x",
+ "@babel/preset-env": "x.x.x",
+ "babel-loader": "x.x.x",
+ "css-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "style-loader": "x.x.x",
+ "svelte": "x.x.x",
+ "svelte-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "my-webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should generate svelte template with --force 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+
+const stylesHandler = 'style-loader';
+
+
+
+const config = {
+ entry: './src/main.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ devServer: {
+ open: true,
+ host: 'localhost',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: './index.html',
+ }),
+
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.svelte$/,
+ use: {
+ loader: 'svelte-loader',
+ options: {
+ emitCss: true,
+ hotReload: true
+ }
+ }
+ },
+ {
+ test: /\\.js$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ options: {
+ presets: ["@babel/preset-env"],
+ },
+ },
+ },
+ {
+ test: /\\.css$/i,
+ use: [stylesHandler,'css-loader'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src/"),
+ },
+ extensions: ['.mjs', '.js', '.svelte'],
+ mainFields: ['svelte', 'browser', 'module', 'main'],
+ conditionNames: ['svelte', 'module', 'browser', 'main', 'default']
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should generate svelte template with prompt answers 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "@babel/core": "x.x.x",
+ "@babel/preset-env": "x.x.x",
+ "autoprefixer": "x.x.x",
+ "babel-loader": "x.x.x",
+ "css-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "postcss": "x.x.x",
+ "postcss-loader": "x.x.x",
+ "style-loader": "x.x.x",
+ "svelte": "x.x.x",
+ "svelte-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "my-webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should generate svelte template with prompt answers 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+
+const stylesHandler = 'style-loader';
+
+
+
+const config = {
+ entry: './src/main.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ devServer: {
+ open: true,
+ host: 'localhost',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: './index.html',
+ }),
+
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.svelte$/,
+ use: {
+ loader: 'svelte-loader',
+ options: {
+ emitCss: true,
+ hotReload: true
+ }
+ }
+ },
+ {
+ test: /\\.js$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ options: {
+ presets: ["@babel/preset-env"],
+ },
+ },
+ },
+ {
+ test: /\\.css$/i,
+ use: [stylesHandler, 'css-loader', 'postcss-loader'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src/"),
+ },
+ extensions: ['.mjs', '.js', '.svelte'],
+ mainFields: ['svelte', 'browser', 'module', 'main'],
+ conditionNames: ['svelte', 'module', 'browser', 'main', 'default']
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should generate typescript project correctly 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "ts-loader": "x.x.x",
+ "typescript": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should generate typescript project correctly 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const config = {
+ entry: './src/index.ts',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ plugins: [
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.(ts|tsx)$/i,
+ loader: 'ts-loader',
+ exclude: ['/node_modules/'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+ resolve: {
+ extensions: ['.tsx', '.ts', '.jsx', '.js', '...'],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should generate vue template with --force 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "@babel/core": "x.x.x",
+ "@babel/preset-env": "x.x.x",
+ "@vue/compiler-sfc": "x.x.x",
+ "babel-loader": "x.x.x",
+ "css-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "pinia": "x.x.x",
+ "style-loader": "x.x.x",
+ "vue": "x.x.x",
+ "vue-loader": "x.x.x",
+ "vue-router": "x.x.x",
+ "vue-style-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "my-webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should generate vue template with --force 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+const { VueLoaderPlugin } = require('vue-loader');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const stylesHandler = 'vue-style-loader';
+
+
+
+const config = {
+ entry: './src/main.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ devServer: {
+ open: true,
+ host: 'localhost',
+ },
+ plugins: [
+ new VueLoaderPlugin(),
+ new HtmlWebpackPlugin({
+ template: 'index.html',
+ }),
+
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.vue$/,
+ loader: 'vue-loader'
+ },
+ {
+ test: /\\.js$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ options: {
+ presets: ["@babel/preset-env"],
+ },
+ },
+ },
+ {
+ test: /\\.css$/i,
+ use: [stylesHandler,'css-loader'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src/"),
+ 'vue': '@vue/runtime-dom'
+ },
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should generate vue template with store and router support on prompt answers 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "@babel/core": "x.x.x",
+ "@babel/preset-env": "x.x.x",
+ "@vue/compiler-sfc": "x.x.x",
+ "autoprefixer": "x.x.x",
+ "babel-loader": "x.x.x",
+ "css-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "pinia": "x.x.x",
+ "postcss": "x.x.x",
+ "postcss-loader": "x.x.x",
+ "style-loader": "x.x.x",
+ "vue": "x.x.x",
+ "vue-loader": "x.x.x",
+ "vue-router": "x.x.x",
+ "vue-style-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "my-webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should generate vue template with store and router support on prompt answers 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+const { VueLoaderPlugin } = require('vue-loader');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const stylesHandler = 'vue-style-loader';
+
+
+
+const config = {
+ entry: './src/main.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ devServer: {
+ open: true,
+ host: 'localhost',
+ },
+ plugins: [
+ new VueLoaderPlugin(),
+ new HtmlWebpackPlugin({
+ template: 'index.html',
+ }),
+
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.vue$/,
+ loader: 'vue-loader'
+ },
+ {
+ test: /\\.js$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ options: {
+ presets: ["@babel/preset-env"],
+ },
+ },
+ },
+ {
+ test: /\\.css$/i,
+ use: [stylesHandler, 'css-loader', 'postcss-loader'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src/"),
+ 'vue': '@vue/runtime-dom'
+ },
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should use less in project when selected 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "css-loader": "x.x.x",
+ "less": "x.x.x",
+ "less-loader": "x.x.x",
+ "style-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should use less in project when selected 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const stylesHandler = 'style-loader';
+
+
+
+const config = {
+ entry: './src/index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ plugins: [
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.less$/i,
+ use: [stylesHandler, 'css-loader', 'less-loader'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should use mini-css-extract-plugin when selected 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "css-loader": "x.x.x",
+ "sass": "x.x.x",
+ "sass-loader": "x.x.x",
+ "style-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should use mini-css-extract-plugin when selected 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const stylesHandler = 'style-loader';
+
+
+
+const config = {
+ entry: './src/index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ plugins: [
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.s[ac]ss$/i,
+ use: [stylesHandler, 'css-loader', 'sass-loader'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should use postcss in project when selected 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "autoprefixer": "x.x.x",
+ "css-loader": "x.x.x",
+ "postcss": "x.x.x",
+ "postcss-loader": "x.x.x",
+ "style-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should use postcss in project when selected 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const stylesHandler = 'style-loader';
+
+
+
+const config = {
+ entry: './src/index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ plugins: [
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.css$/i,
+ use: [stylesHandler, 'css-loader', 'postcss-loader'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should use sass and css with postcss in project when selected 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "autoprefixer": "x.x.x",
+ "css-loader": "x.x.x",
+ "postcss": "x.x.x",
+ "postcss-loader": "x.x.x",
+ "sass": "x.x.x",
+ "sass-loader": "x.x.x",
+ "style-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should use sass and css with postcss in project when selected 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const stylesHandler = 'style-loader';
+
+
+
+const config = {
+ entry: './src/index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ plugins: [
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.s[ac]ss$/i,
+ use: [stylesHandler, 'css-loader', 'postcss-loader', 'sass-loader'],
+ },
+ {
+ test: /\\.css$/i,
+ use: [stylesHandler, 'css-loader', 'postcss-loader'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should use sass in project when selected 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "css-loader": "x.x.x",
+ "sass": "x.x.x",
+ "sass-loader": "x.x.x",
+ "style-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should use sass in project when selected 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const stylesHandler = 'style-loader';
+
+
+
+const config = {
+ entry: './src/index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ plugins: [
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.s[ac]ss$/i,
+ use: [stylesHandler, 'css-loader', 'sass-loader'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should use sass with postcss in project when selected 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "autoprefixer": "x.x.x",
+ "css-loader": "x.x.x",
+ "postcss": "x.x.x",
+ "postcss-loader": "x.x.x",
+ "sass": "x.x.x",
+ "sass-loader": "x.x.x",
+ "style-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should use sass with postcss in project when selected 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const stylesHandler = 'style-loader';
+
+
+
+const config = {
+ entry: './src/index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ plugins: [
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.s[ac]ss$/i,
+ use: [stylesHandler, 'css-loader', 'postcss-loader', 'sass-loader'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should use stylus in project when selected 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "css-loader": "x.x.x",
+ "style-loader": "x.x.x",
+ "stylus": "x.x.x",
+ "stylus-loader": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should use stylus in project when selected 2`] = `
+"// Generated using webpack-cli https://github.com/webpack/webpack-cli
+
+const path = require('path');
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+
+const stylesHandler = 'style-loader';
+
+
+
+const config = {
+ entry: './src/index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ },
+ plugins: [
+ // Add your plugins here
+ // Learn more about plugins from https://webpack.js.org/configuration/plugins/
+ ],
+ module: {
+ rules: [
+ {
+ test: /\\.styl$/i,
+ use: [stylesHandler, 'css-loader', 'stylus-loader'],
+ },
+ {
+ test: /\\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
+ type: 'asset',
+ },
+
+
+ // Add your rules for custom modules here
+ // Learn more about loaders from https://webpack.js.org/loaders/
+ ],
+ },
+};
+
+module.exports = () => {
+ if (isProduction) {
+ config.mode = 'production';
+
+
+ } else {
+ config.mode = 'development';
+ }
+ return config;
+};
+"
+`;
+
+exports[`create-webpack-app cli should work with 'c' alias 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "html-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should work with 'create' alias 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "html-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should work with 'n' alias 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "html-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli should work with 'new' alias 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "html-loader": "x.x.x",
+ "html-webpack-plugin": "x.x.x",
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ "webpack-dev-server": "x.x.x",
+ "workbox-webpack-plugin": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "serve": "webpack serve",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
+
+exports[`create-webpack-app cli uses yarn as the package manager when opted 1`] = `
+{
+ "description": "My webpack project",
+ "devDependencies": {
+ "webpack": "x.x.x",
+ "webpack-cli": "x.x.x",
+ },
+ "name": "webpack-project",
+ "scripts": {
+ "build": "webpack --mode=production --node-env=production",
+ "build:dev": "webpack --mode=development",
+ "watch": "webpack --watch",
+ },
+ "version": "1.0.0",
+}
+`;
diff --git a/test/create-webpack-app/init/init.test.js b/test/create-webpack-app/init/init.test.js
new file mode 100644
index 00000000000..0dc866e569c
--- /dev/null
+++ b/test/create-webpack-app/init/init.test.js
@@ -0,0 +1,780 @@
+const os = require("os");
+const path = require("path");
+const { mkdirSync, existsSync, readFileSync } = require("fs");
+const { join, resolve } = require("path");
+const {
+ createPathDependentUtils,
+ uniqueDirectoryForTest,
+ isWindows,
+ nodeVersion,
+} = require("../test.utils.js");
+const { run, runPromptWithAnswers } = createPathDependentUtils("create-webpack-app");
+
+jest.setTimeout(480000);
+
+const ENTER = "\x0D";
+const DOWN = "\x1B\x5B\x42";
+
+const defaultTemplateFiles = [
+ "package.json",
+ "package-lock.json",
+ "src",
+ "src/index.js",
+ "webpack.config.js",
+ "README.md",
+];
+
+const reactTemplateFiles = [
+ ...defaultTemplateFiles.slice(0, 3),
+ "src/index.jsx",
+ ...defaultTemplateFiles.slice(4),
+];
+
+const vueTemplateFiles = [
+ ...defaultTemplateFiles.slice(0, 3),
+ "src/main.js",
+ ...defaultTemplateFiles.slice(4),
+];
+
+const svelteTemplateFiles = [
+ ...defaultTemplateFiles.slice(0, 3),
+ "src/main.js",
+ ...defaultTemplateFiles.slice(4),
+ "src/store/index.js",
+];
+
+// helper function to resolve the path from the test directory to actual assets
+// Helper to read from package.json in a given path
+const readFromPkgJSON = (path) => {
+ const pkgJSONPath = join(path, "package.json");
+
+ if (!existsSync(pkgJSONPath)) {
+ return {};
+ }
+
+ const pkgJSON = JSON.parse(readFileSync(pkgJSONPath, "utf8"));
+ const { devDependencies: devDeps } = pkgJSON;
+
+ // Update devDeps versions to be x.x.x to prevent frequent snapshot updates
+ Object.keys(devDeps).forEach((dep) => (devDeps[dep] = "x.x.x"));
+
+ return { ...pkgJSON, devDependencies: devDeps };
+};
+
+// Helper to read from webpack.config.js in a given path
+const readFromWebpackConfig = (path) => readFileSync(join(path, "webpack.config.js"), "utf8");
+
+(nodeVersion >= 18 ? describe : describe.skip)("create-webpack-app cli", () => {
+ it("should generate default project when nothing is passed", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await run(assetsPath, ["init", "--force"]);
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(assetsPath, file)).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+ });
+
+ it("should generate project when generationPath is supplied", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await run(__dirname, ["init", assetsPath, "--force"]);
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ // expect(stderr).toContain("webpack.config.js");
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should generate folders if non existing generation path is given", async () => {
+ const assetsPath = path.resolve(os.tmpdir(), Date.now().toString());
+ const { stdout } = await run(__dirname, ["init", assetsPath, "--force"]);
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(join(assetsPath, file))).toBeTruthy();
+ });
+
+ //Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+ });
+ // //
+ it("should configure assets modules by default", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await run(assetsPath, ["init", "--force"]);
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should ask question when wrong template is supplied", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout, stderr } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "--force", "--template=apple"],
+ [`${ENTER}`],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stderr).toContain("apple is not a valid template, please select one from below");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should generate typescript project correctly", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "."],
+ [`${DOWN}${DOWN}${ENTER}`, `n${ENTER}`, `n${ENTER}`, `n${ENTER}`, ENTER, ENTER],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+ expect(stdout).toContain("tsconfig.json");
+
+ // Test files
+ const files = [
+ ...defaultTemplateFiles.filter((file) => file !== "src/index.js"),
+ "src/index.ts",
+ "tsconfig.json",
+ ];
+
+ files.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ // //
+ it("should generate ES6 project correctly", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "."],
+ [`${DOWN}${ENTER}`, `n${ENTER}`, `n${ENTER}`, `n${ENTER}`, ENTER, ENTER],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+ expect(stdout).toContain("babel.config.json");
+
+ // Test files
+ const files = [...defaultTemplateFiles, "babel.config.json"];
+
+ files.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should use sass in project when selected", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "."],
+ [
+ `${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `${DOWN}${DOWN}${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ ENTER,
+ ],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should use sass with postcss in project when selected", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "."],
+ [
+ `${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `${DOWN}${DOWN}${ENTER}`,
+ `n${ENTER}`,
+ `y${ENTER}`,
+ `n${ENTER}`,
+ ENTER,
+ ],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ const files = [...defaultTemplateFiles, "postcss.config.js"];
+
+ files.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should use mini-css-extract-plugin when selected", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "."],
+ [
+ `${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `${DOWN}${DOWN}${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `y${ENTER}`,
+ ENTER,
+ ],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should use sass and css with postcss in project when selected", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "."],
+ [
+ `${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `${DOWN}${DOWN}${ENTER}`,
+ `y${ENTER}`,
+ `y${ENTER}`,
+ `n${ENTER}`,
+ ENTER,
+ ],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ const files = [...defaultTemplateFiles, "postcss.config.js"];
+
+ files.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should use less in project when selected", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "."],
+ [
+ `${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `${DOWN}${DOWN}${DOWN}${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ ENTER,
+ ],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ // //
+ it("should use stylus in project when selected", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "."],
+ [
+ `${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `${DOWN}${DOWN}${DOWN}${DOWN}${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ ENTER,
+ ],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ // //
+ it("should configure WDS as opted", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "."],
+ [ENTER, ENTER, `n${ENTER}`, `n${ENTER}`, ENTER, ENTER],
+ );
+
+ expect(stdout).toContain("Would you like to use Webpack Dev server?");
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ // //
+ it("should use postcss in project when selected", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "."],
+ [
+ `${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `n${ENTER}`,
+ `${DOWN}${ENTER}`,
+ ENTER,
+ `n${ENTER}`,
+ ENTER,
+ ],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ const files = [...defaultTemplateFiles, "postcss.config.js"];
+
+ files.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should configure html-webpack-plugin as opted", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "."],
+ [ENTER, `n${ENTER}`, ENTER, `n${ENTER}`, ENTER, ENTER],
+ );
+
+ expect(stdout).toContain("Do you want to simplify the creation of HTML files for your bundle?");
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should configure workbox-webpack-plugin as opted", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "."],
+ [ENTER, `n${ENTER}`, ENTER, ENTER, ENTER, ENTER],
+ );
+
+ expect(stdout).toContain("Do you want to add PWA support?");
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test file
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should throw if the current path is not writable", async () => {
+ if (isWindows) {
+ return;
+ }
+
+ const assetsPath = await uniqueDirectoryForTest();
+ const projectPath = join(assetsPath, "non-writable-path");
+
+ mkdirSync(projectPath, 0o500);
+ const { exitCode, stderr } = await run(projectPath, ["init", "my-app", "--force"], {
+ reject: false,
+ });
+ expect(stderr).toContain("Failed to initialize the project with webpack!");
+ expect(exitCode).toBe(2);
+ });
+ //
+ it("should work with 'new' alias", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await run(assetsPath, ["new", "--force"]);
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should work with 'create' alias", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await run(assetsPath, ["create", "--force"]);
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should work with 'c' alias", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await run(assetsPath, ["c", "--force"]);
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should work with 'n' alias", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await run(assetsPath, ["n", "--force"]);
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+ });
+
+ it("recognizes '-t' as an alias for '--template'", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await run(assetsPath, ["init", "-t", "default", "--force"]);
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+ });
+
+ it("recognizes '-f' as an alias for '--force'", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await run(assetsPath, ["init", "-f"]);
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("uses yarn as the package manager when opted", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", "."],
+ [ENTER, `n${ENTER}`, `n${ENTER}`, `n${ENTER}`, ENTER, `${DOWN}${ENTER}`],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ const files = [
+ ...defaultTemplateFiles.filter((file) => file !== "package-lock.json"),
+ "yarn.lock",
+ ];
+
+ files.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should generate react template with state and routing support with prompt answers", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", ".", "--template=react"],
+ [ENTER, `y${ENTER}`, `y${ENTER}`, `${ENTER}`, `y${ENTER}`, ENTER, ENTER],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ reactTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ //
+ it("should generate react template with --force", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await run(assetsPath, ["init", "--template=react", "--force"]);
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ reactTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+
+ it("should generate vue template with --force", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await run(assetsPath, ["init", "--template=vue", "--force"]);
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ vueTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+
+ it("should generate vue template with store and router support on prompt answers", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", ".", "--template=vue"],
+ [ENTER, `y${ENTER}`, `y${ENTER}`, `${ENTER}`, `y${ENTER}`, ENTER, ENTER],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ const files = [...vueTemplateFiles, "src/store/index.js"];
+
+ // Test files
+ files.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ it("should generate svelte template with prompt answers", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["init", ".", "--template=svelte"],
+ [ENTER, `y${ENTER}`, `${ENTER}`, `y${ENTER}`, ENTER, ENTER],
+ );
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ svelteTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+ it("should generate svelte template with --force", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout } = await run(assetsPath, ["init", "--template=svelte", "--force"]);
+
+ expect(stdout).toContain("Project has been initialised with webpack!");
+ expect(stdout).toContain("webpack.config.js");
+
+ // Test files
+ svelteTemplateFiles.forEach((file) => {
+ expect(existsSync(resolve(assetsPath, file))).toBeTruthy();
+ });
+
+ // Check if the generated package.json file content matches the snapshot
+ expect(readFromPkgJSON(assetsPath)).toMatchSnapshot();
+
+ // Check if the generated webpack configuration matches the snapshot
+ expect(readFromWebpackConfig(assetsPath)).toMatchSnapshot();
+ });
+});
diff --git a/test/create-webpack-app/loader/error-test/loader-error.test.js b/test/create-webpack-app/loader/error-test/loader-error.test.js
new file mode 100644
index 00000000000..d6b4a3ad7e0
--- /dev/null
+++ b/test/create-webpack-app/loader/error-test/loader-error.test.js
@@ -0,0 +1,15 @@
+"use strict";
+
+const { createPathDependentUtils } = require("../../test.utils");
+const { run } = createPathDependentUtils("webpack-cli");
+
+describe("loader error regression test for #1581", () => {
+ it(`should not ignore loader's error produce a failing build`, async () => {
+ // Ignoring assertion on stderr because ts-loader is producing depreciation warnings
+ // with webpack@v5.0.0-beta.24 -> https://github.com/TypeStrong/ts-loader/issues/1169
+ const { stdout, exitCode } = await run(__dirname, []);
+ expect(exitCode).not.toEqual(0);
+ expect(stdout).toContain("[1 error]");
+ expect(stdout).toContain(`Cannot assign to 'foobar' because it is a constant`);
+ });
+});
diff --git a/test/create-webpack-app/loader/error-test/src/index.ts b/test/create-webpack-app/loader/error-test/src/index.ts
new file mode 100644
index 00000000000..f7c6b335f8e
--- /dev/null
+++ b/test/create-webpack-app/loader/error-test/src/index.ts
@@ -0,0 +1,4 @@
+const foobar = "foobar";
+// eslint-disable-next-line no-const-assign
+foobar = "barbaz"; // Error!
+console.log(foobar);
diff --git a/test/create-webpack-app/loader/error-test/tsconfig.json b/test/create-webpack-app/loader/error-test/tsconfig.json
new file mode 100644
index 00000000000..27682f8e29b
--- /dev/null
+++ b/test/create-webpack-app/loader/error-test/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compileOnSave": false,
+ "buildOnSave": false,
+ "compilerOptions": {
+ "target": "es6",
+ "moduleResolution": "node",
+ "noEmitOnError": false,
+ "outDir": "./dist"
+ },
+ "exclude": ["node_modules"]
+}
diff --git a/test/create-webpack-app/loader/error-test/webpack.config.js b/test/create-webpack-app/loader/error-test/webpack.config.js
new file mode 100644
index 00000000000..056147de1ac
--- /dev/null
+++ b/test/create-webpack-app/loader/error-test/webpack.config.js
@@ -0,0 +1,25 @@
+const path = require("path");
+
+module.exports = {
+ mode: "development",
+
+ entry: {
+ bundle: "./src/index.ts",
+ },
+
+ output: {
+ path: path.resolve(__dirname, "dist"),
+ filename: "[name].js",
+ },
+
+ module: {
+ rules: [
+ {
+ test: /.(ts|tsx)?$/,
+ loader: "ts-loader",
+ include: [path.resolve(__dirname, "src")],
+ exclude: [/node_modules/],
+ },
+ ],
+ },
+};
diff --git a/test/create-webpack-app/loader/loader.test.js b/test/create-webpack-app/loader/loader.test.js
new file mode 100644
index 00000000000..590c46bfcb8
--- /dev/null
+++ b/test/create-webpack-app/loader/loader.test.js
@@ -0,0 +1,248 @@
+"use strict";
+
+const { existsSync } = require("fs");
+const { join, resolve } = require("path");
+
+const {
+ uniqueDirectoryForTest,
+ normalizeStdout,
+ normalizeStderr,
+ createPathDependentUtils,
+} = require("../test.utils");
+const { runPromptWithAnswers } = createPathDependentUtils("create-webpack-app");
+const { run } = createPathDependentUtils("webpack-cli");
+const firstPrompt = "? Loader name? (my-loader)";
+const ENTER = "\x0D";
+const DOWN = "\x1B\x5B\x42";
+
+const dataForTests = (rootAssetsPath) => ({
+ loaderName: "test-loader",
+ loaderPath: join(rootAssetsPath, "test-loader"),
+ defaultLoaderPath: join(rootAssetsPath, "my-loader"),
+ genPath: join(rootAssetsPath, "test-assets"),
+ customLoaderPath: join(rootAssetsPath, "test-assets", "loaderName"),
+ defaultTemplateFiles: [
+ "package.json",
+ "package-lock.json",
+ "examples",
+ "src",
+ "test",
+ "src/index.js",
+ "examples/simple/webpack.config.js",
+ ],
+});
+
+describe.skip("loader command", () => {
+ it("should ask the loader name when invoked", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout, stderr } = await runPromptWithAnswers(assetsPath, ["loader", "."]);
+
+ expect(stdout).toBeTruthy();
+ expect(normalizeStderr(stderr)).toBeFalsy();
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+ });
+ it("should scaffold loader with default name if no loader name provided", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { defaultLoaderPath, defaultTemplateFiles } = dataForTests(assetsPath);
+ let { stdout } = await runPromptWithAnswers(assetsPath, ["loader", "."], [ENTER, ENTER]);
+
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+
+ // Skip test in case installation fails
+ if (!existsSync(resolve(defaultLoaderPath, "./package-lock.json"))) {
+ return;
+ }
+
+ // Check if the output directory exists with the appropriate loader name
+ expect(existsSync(defaultLoaderPath)).toBeTruthy();
+
+ // All test files are scaffolded
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(defaultLoaderPath, file)).toBeTruthy();
+ });
+
+ // Check if the the generated loader works successfully
+ const path = resolve(defaultLoaderPath, "./examples/simple/");
+
+ ({ stdout } = await run(path, []));
+
+ expect(stdout).toContain("my-loader");
+ });
+
+ it("should scaffold loader template with a given name", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { loaderName, loaderPath, defaultTemplateFiles } = dataForTests(assetsPath);
+ let { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["loader", "."],
+ [`${loaderName}${ENTER}`, ENTER],
+ );
+
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+
+ // Skip test in case installation fails
+ if (!existsSync(resolve(loaderPath, "./package-lock.json"))) {
+ return;
+ }
+
+ // Check if the output directory exists with the appropriate loader name
+ expect(existsSync(loaderPath)).toBeTruthy();
+
+ // All test files are scaffolded
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(loaderPath, file)).toBeTruthy();
+ });
+
+ // Check if the the generated loader works successfully
+ const path = resolve(loaderPath, "./examples/simple/");
+
+ ({ stdout } = await run(path, []));
+
+ expect(stdout).toContain("test-loader");
+ });
+
+ it("should scaffold loader template in the specified path", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { loaderName, customLoaderPath, defaultTemplateFiles } = dataForTests(assetsPath);
+ let { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["loader", "test-assets"],
+ [`${loaderName}${ENTER}`, ENTER],
+ );
+
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+
+ // Skip test in case installation fails
+ if (!existsSync(resolve(customLoaderPath, "./package-lock.json"))) {
+ return;
+ }
+
+ // Check if the output directory exists with the appropriate loader name
+ expect(existsSync(customLoaderPath)).toBeTruthy();
+
+ // All test files are scaffolded
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(customLoaderPath, file)).toBeTruthy();
+ });
+
+ // Check if the the generated loader works successfully
+ const path = resolve(customLoaderPath, "./examples/simple/");
+
+ ({ stdout } = await run(path, []));
+
+ expect(stdout).toContain("test-loader");
+ });
+
+ it("should scaffold loader template in the current directory", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { loaderName, customLoaderPath, defaultTemplateFiles } = dataForTests(assetsPath);
+
+ let { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["loader", "./"],
+ [`${loaderName}${ENTER}`, ENTER],
+ );
+
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+
+ // Skip test in case installation fails
+ if (!existsSync(resolve(customLoaderPath, "./package-lock.json"))) {
+ return;
+ }
+
+ // Check if the output directory exists with the appropriate loader name
+ expect(existsSync(customLoaderPath)).toBeTruthy();
+
+ // All test files are scaffolded
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(customLoaderPath, file)).toBeTruthy();
+ });
+
+ // Check if the the generated loader works successfully
+ const path = resolve(customLoaderPath, "./examples/simple/");
+
+ ({ stdout } = await run(path, []));
+
+ expect(stdout).toContain("test-loader");
+ });
+
+ it("should prompt on supplying an invalid template", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stderr } = await runPromptWithAnswers(assetsPath, [
+ "loader",
+ ".",
+ "--template=unknown",
+ ]);
+
+ expect(stderr).toContain("unknown is not a valid template");
+ });
+
+ it("recognizes '-t' as an alias for '--template'", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { defaultLoaderPath, defaultTemplateFiles } = dataForTests(assetsPath);
+ let { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["loader", ".", "-t", "default"],
+ [`${ENTER}`, ENTER],
+ );
+
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+
+ // Skip test in case installation fails
+ if (!existsSync(resolve(defaultLoaderPath, "./package-lock.json"))) {
+ return;
+ }
+
+ // Check if the output directory exists with the appropriate loader name
+ expect(existsSync(defaultLoaderPath)).toBeTruthy();
+
+ // All test files are scaffolded
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(defaultLoaderPath, file)).toBeTruthy();
+ });
+
+ // Check if the the generated loader works successfully
+ const path = resolve(assetsPath, "./my-loader/examples/simple/");
+
+ ({ stdout } = await run(path, []));
+
+ expect(stdout).toContain("my-loader");
+ });
+
+ it("uses yarn as the package manager when opted", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { defaultLoaderPath, defaultTemplateFiles } = dataForTests(assetsPath);
+ let { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["loader", ".", "-t", "default"],
+ [`${ENTER}`, `${DOWN}${ENTER}`],
+ );
+
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+
+ // Skip test in case installation fails
+ if (!existsSync(resolve(defaultLoaderPath, "./yarn.lock"))) {
+ return;
+ }
+
+ // Check if the output directory exists with the appropriate loader name
+ expect(existsSync(defaultLoaderPath)).toBeTruthy();
+
+ // All test files are scaffolded
+ const files = [
+ ...defaultTemplateFiles.filter((file) => file !== "package-lock.json"),
+ "yarn.lock",
+ ];
+
+ files.forEach((file) => {
+ expect(existsSync(defaultLoaderPath, file)).toBeTruthy();
+ });
+
+ // Check if the the generated loader works successfully
+ const path = resolve(assetsPath, "./my-loader/examples/simple/");
+
+ ({ stdout } = await run(path, []));
+
+ expect(stdout).toContain("my-loader");
+ });
+});
diff --git a/test/create-webpack-app/loader/warning-test/loader-warning.test.js b/test/create-webpack-app/loader/warning-test/loader-warning.test.js
new file mode 100644
index 00000000000..e670d10b40a
--- /dev/null
+++ b/test/create-webpack-app/loader/warning-test/loader-warning.test.js
@@ -0,0 +1,13 @@
+"use strict";
+const { createPathDependentUtils } = require("../../test.utils");
+const { run } = createPathDependentUtils("webpack-cli");
+
+describe("loader warning test", () => {
+ it(`should not ignore loader's warning and exit with a non zero exit code`, async () => {
+ const { stdout, exitCode } = await run(__dirname, [], false);
+
+ expect(stdout).toContain("[1 warning]");
+ expect(stdout).toContain("This is a warning");
+ expect(exitCode).toEqual(0);
+ });
+});
diff --git a/test/create-webpack-app/loader/warning-test/my-loader.js b/test/create-webpack-app/loader/warning-test/my-loader.js
new file mode 100644
index 00000000000..9ad6b8e4ba0
--- /dev/null
+++ b/test/create-webpack-app/loader/warning-test/my-loader.js
@@ -0,0 +1,5 @@
+module.exports = function loader(source) {
+ const { emitWarning } = this;
+ emitWarning("This is a warning");
+ return source;
+};
diff --git a/test/create-webpack-app/loader/warning-test/src/main.js b/test/create-webpack-app/loader/warning-test/src/main.js
new file mode 100644
index 00000000000..d89f4de2697
--- /dev/null
+++ b/test/create-webpack-app/loader/warning-test/src/main.js
@@ -0,0 +1,2 @@
+require("../my-loader");
+console.log("loader warning test");
diff --git a/test/create-webpack-app/loader/warning-test/webpack.config.js b/test/create-webpack-app/loader/warning-test/webpack.config.js
new file mode 100644
index 00000000000..c7fe3695bd5
--- /dev/null
+++ b/test/create-webpack-app/loader/warning-test/webpack.config.js
@@ -0,0 +1,33 @@
+const path = require("path");
+
+module.exports = {
+ mode: "development",
+
+ entry: {
+ bundle: "./src/main.js",
+ },
+
+ output: {
+ path: path.resolve(__dirname, "dist"),
+ filename: "[name].js",
+ },
+
+ module: {
+ rules: [
+ {
+ test: /.(js|jsx)?$/,
+ loader: "my-loader",
+ include: [path.resolve(__dirname, "src")],
+ exclude: [/node_modules/],
+ },
+ ],
+ },
+ resolveLoader: {
+ alias: {
+ "my-loader": require.resolve("./my-loader"),
+ },
+ },
+ performance: {
+ hints: "warning",
+ },
+};
diff --git a/test/create-webpack-app/plugin/plugin.test.js b/test/create-webpack-app/plugin/plugin.test.js
new file mode 100644
index 00000000000..71a8a35580a
--- /dev/null
+++ b/test/create-webpack-app/plugin/plugin.test.js
@@ -0,0 +1,245 @@
+const { existsSync, mkdirSync } = require("fs");
+const { join, resolve } = require("path");
+const { uniqueDirectoryForTest, normalizeStdout, normalizeStderr } = require("../test.utils");
+const { createPathDependentUtils, nodeVersion } = require("../test.utils");
+const webpackCliUtils = createPathDependentUtils("webpack-cli");
+const createWebpackAppUtils = createPathDependentUtils("create-webpack-app");
+const { runPromptWithAnswers } = createWebpackAppUtils;
+const { run } = webpackCliUtils;
+
+const ENTER = "\x0D";
+const DOWN = "\x1B\x5B\x42";
+
+const firstPrompt = "? Plugin name";
+const dataForTests = (rootAssetsPath) => ({
+ pluginName: "test-plugin",
+ pluginPath: join(rootAssetsPath, "test-plugin"),
+ defaultPluginPath: join(rootAssetsPath, "my-webpack-plugin"),
+ genPath: join(rootAssetsPath, "test-assets"),
+ customPluginPath: join(rootAssetsPath, "test-assets", "test-plugin"),
+ defaultTemplateFiles: [
+ "package.json",
+ "examples",
+ "src",
+ "test",
+ "src/index.js",
+ "examples/simple/webpack.config.js",
+ ],
+});
+
+(nodeVersion >= 18 ? describe : describe.skip)("plugin command", () => {
+ it("should ask the plugin name when invoked", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stdout, stderr } = await runPromptWithAnswers(assetsPath, ["plugin", "."]);
+
+ expect(stdout).toBeTruthy();
+ expect(normalizeStderr(stderr)).toBeFalsy();
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+ });
+
+ it("should scaffold plugin with default name if no plugin name provided", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { defaultPluginPath, defaultTemplateFiles } = dataForTests(assetsPath);
+ const { stdout } = await runPromptWithAnswers(assetsPath, ["plugin", "."], [ENTER, ENTER]);
+
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+
+ // Check if the output directory exists with the appropriate plugin name
+ expect(existsSync(defaultPluginPath)).toBeTruthy();
+
+ // Skip test in case installation fails
+ if (!existsSync(resolve(defaultPluginPath, "./package-lock.json"))) {
+ return;
+ }
+
+ // Test regressively files are scaffolded
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(join(defaultPluginPath, file))).toBeTruthy();
+ });
+
+ // Check if the the generated plugin works successfully
+ const { stdout: stdout2 } = await run(defaultPluginPath, [
+ "--config",
+ "./examples/simple/webpack.config.js",
+ ]);
+ expect(normalizeStdout(stdout2)).toContain("Hello World!");
+ });
+
+ it("should scaffold plugin template with a given name", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { pluginName, pluginPath, defaultTemplateFiles } = dataForTests(assetsPath);
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["plugin", "."],
+ [`${pluginName}${ENTER}`, ENTER],
+ );
+
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+
+ // Check if the output directory exists with the appropriate plugin name
+ expect(existsSync(pluginPath)).toBeTruthy();
+
+ // Skip test in case installation fails
+ if (!existsSync(resolve(pluginPath, "./package-lock.json"))) {
+ return;
+ }
+
+ // Test regressively files are scaffolded
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(join(pluginPath, file))).toBeTruthy();
+ });
+
+ // Check if the the generated plugin works successfully
+ const { stdout: stdout2 } = await run(pluginPath, [
+ "--config",
+ "./examples/simple/webpack.config.js",
+ ]);
+ expect(normalizeStdout(stdout2)).toContain("Hello World!");
+ });
+
+ it("should scaffold plugin template in the specified path", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { pluginName, customPluginPath, defaultTemplateFiles } = dataForTests(assetsPath);
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["plugin", "test-assets"],
+ [`${pluginName}${ENTER}`, ENTER],
+ );
+
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+
+ // Check if the output directory exists with the appropriate plugin name
+ expect(existsSync(customPluginPath)).toBeTruthy();
+
+ // Skip test in case installation fails
+ if (!existsSync(resolve(customPluginPath, "./package-lock.json"))) {
+ return;
+ }
+
+ // Test regressively files are scaffolded
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(join(customPluginPath, file))).toBeTruthy();
+ });
+
+ // Check if the the generated plugin works successfully
+ const { stdout: stdout2 } = await run(customPluginPath, [
+ "--config",
+ "./examples/simple/webpack.config.js",
+ ]);
+ expect(normalizeStdout(stdout2)).toContain("Hello World!");
+ });
+
+ it("should scaffold plugin template in the current directory", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { genPath, customPluginPath, pluginName, defaultTemplateFiles } =
+ dataForTests(assetsPath);
+
+ if (!existsSync(genPath)) {
+ mkdirSync(genPath);
+ }
+
+ const { stdout } = await runPromptWithAnswers(
+ genPath,
+ ["plugin", "./"],
+ [`${pluginName}${ENTER}`, ENTER],
+ );
+
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+
+ // Check if the output directory exists with the appropriate plugin name
+ expect(existsSync(customPluginPath)).toBeTruthy();
+
+ // Skip test in case installation fails
+ if (!existsSync(resolve(customPluginPath, "./package-lock.json"))) {
+ return;
+ }
+
+ // Test regressively files are scaffolded
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(join(customPluginPath, file))).toBeTruthy();
+ });
+
+ // Check if the the generated plugin works successfully
+ const { stdout: stdout2 } = await run(customPluginPath, [
+ "--config",
+ "./examples/simple/webpack.config.js",
+ ]);
+ expect(normalizeStdout(stdout2)).toContain("Hello World!");
+ });
+
+ it("should prompt on supplying an invalid template", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { stderr } = await runPromptWithAnswers(assetsPath, ["plugin", "--template=unknown"]);
+
+ expect(stderr).toContain("unknown is not a valid template");
+ });
+
+ it("recognizes '-t' as an alias for '--template'", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { defaultPluginPath, defaultTemplateFiles } = dataForTests(assetsPath);
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["plugin", ".", "-t", "default"],
+ [`${ENTER}`, ENTER],
+ );
+
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+
+ // Check if the output directory exists with the appropriate plugin name
+ expect(existsSync(defaultPluginPath)).toBeTruthy();
+
+ // Skip test in case installation fails
+ if (!existsSync(resolve(defaultPluginPath, "./package-lock.json"))) {
+ return;
+ }
+
+ // Test regressively files are scaffolded
+ defaultTemplateFiles.forEach((file) => {
+ expect(existsSync(join(defaultPluginPath, file))).toBeTruthy();
+ });
+
+ // Check if the the generated plugin works successfully
+ const { stdout: stdout2 } = await run(defaultPluginPath, [
+ "--config",
+ "./examples/simple/webpack.config.js",
+ ]);
+ expect(normalizeStdout(stdout2)).toContain("Hello World!");
+ });
+
+ it("uses yarn as the package manager when opted", async () => {
+ const assetsPath = await uniqueDirectoryForTest();
+ const { defaultPluginPath, defaultTemplateFiles } = dataForTests(assetsPath);
+ const { stdout } = await runPromptWithAnswers(
+ assetsPath,
+ ["plugin", "."],
+ [`${ENTER}`, `${DOWN}${ENTER}`],
+ );
+
+ expect(normalizeStdout(stdout)).toContain(firstPrompt);
+
+ // Check if the output directory exists with the appropriate plugin name
+ expect(existsSync(defaultPluginPath)).toBeTruthy();
+
+ // Skip test in case installation fails
+ if (!existsSync(resolve(defaultPluginPath, "./yarn.lock"))) {
+ return;
+ }
+
+ // Test regressively files are scaffolded
+ const files = [
+ ...defaultTemplateFiles.filter((file) => file !== "package-lock.json"),
+ "yarn.lock",
+ ];
+
+ files.forEach((file) => {
+ expect(existsSync(join(defaultPluginPath, file))).toBeTruthy();
+ });
+
+ // Check if the the generated plugin works successfully
+ const { stdout: stdout2 } = await run(defaultPluginPath, [
+ "--config",
+ "./examples/simple/webpack.config.js",
+ ]);
+ expect(normalizeStdout(stdout2)).toContain("Hello World!");
+ });
+});
diff --git a/test/create-webpack-app/test.utils.js b/test/create-webpack-app/test.utils.js
new file mode 100644
index 00000000000..77d9d0b6821
--- /dev/null
+++ b/test/create-webpack-app/test.utils.js
@@ -0,0 +1,226 @@
+"use strict";
+
+const testUtilsPkg = require("../utils/test-utils.js");
+const { processKill } = testUtilsPkg;
+const path = require("path");
+const execa = require("execa");
+const { node: execaNode } = execa;
+const stripAnsi = require("strip-ansi");
+const concat = require("concat-stream");
+const { Writable } = require("readable-stream");
+
+const ENABLE_LOG_COMPILATION = process.env.ENABLE_PIPE || false;
+const nodeVersion = parseInt(process.versions.node.split(".")[0]);
+
+function createPathDependentUtils(cli) {
+ const CLI_PATH = path.resolve(__dirname, `../../packages/${cli}/bin/cli.js`);
+
+ /**
+ * Webpack CLI test runner.
+ *
+ * @param {string} cwd The path to folder that contains test
+ * @param {Array} args Array of arguments
+ * @param {Object} options Options for tests
+ * @returns {Promise}
+ */
+ const createProcess = (cwd, args, options) => {
+ const { nodeOptions = [] } = options;
+ const processExecutor = nodeOptions.length ? execaNode : execa;
+
+ return processExecutor(CLI_PATH, args, {
+ cwd: path.resolve(cwd),
+ reject: false,
+ stdio: ENABLE_LOG_COMPILATION ? "inherit" : "pipe",
+ maxBuffer: Infinity,
+ env: { WEBPACK_CLI_HELP_WIDTH: 1024 },
+ ...options,
+ });
+ };
+
+ /**
+ * Run the webpack CLI for a test case.
+ *
+ * @param {string} cwd The path to folder that contains test
+ * @param {Array} args Array of arguments
+ * @param {Object} options Options for tests
+ * @returns {Promise}
+ */
+ const run = async (cwd, args = [], options = {}) => {
+ return createProcess(cwd, args, options);
+ };
+
+ /**
+ * Run the webpack CLI for a test case and get process.
+ *
+ * @param {string} cwd The path to folder that contains test
+ * @param {Array} args Array of arguments
+ * @param {Object} options Options for tests
+ * @returns {Promise}
+ */
+ const runAndGetProcess = (cwd, args = [], options = {}) => {
+ return createProcess(cwd, args, options);
+ };
+
+ /**
+ * Run the webpack CLI in watch mode for a test case.
+ *
+ * @param {string} cwd The path to folder that contains test
+ * @param {Array} args Array of arguments
+ * @param {Object} options Options for tests
+ * @returns {Object} The webpack output or Promise when nodeOptions are present
+ */
+ const runWatch = (cwd, args = [], options = {}) => {
+ return new Promise((resolve, reject) => {
+ const process = createProcess(cwd, args, options);
+ const outputKillStr = options.killString || /webpack \d+\.\d+\.\d/;
+ const stdoutKillStr = options.stdoutKillStr;
+ const stderrKillStr = options.stderrKillStr;
+
+ let isStdoutDone = false;
+ let isStderrDone = false;
+
+ process.stdout.pipe(
+ new Writable({
+ write(chunk, encoding, callback) {
+ const output = stripAnsi(chunk.toString("utf8"));
+
+ if (stdoutKillStr && stdoutKillStr.test(output)) {
+ isStdoutDone = true;
+ } else if (!stdoutKillStr && outputKillStr.test(output)) {
+ processKill(process);
+ }
+
+ if (isStdoutDone && isStderrDone) {
+ processKill(process);
+ }
+
+ callback();
+ },
+ }),
+ );
+
+ process.stderr.pipe(
+ new Writable({
+ write(chunk, encoding, callback) {
+ const output = stripAnsi(chunk.toString("utf8"));
+
+ if (stderrKillStr && stderrKillStr.test(output)) {
+ isStderrDone = true;
+ } else if (!stderrKillStr && outputKillStr.test(output)) {
+ processKill(process);
+ }
+
+ if (isStdoutDone && isStderrDone) {
+ processKill(process);
+ }
+
+ callback();
+ },
+ }),
+ );
+
+ process
+ .then((result) => {
+ resolve(result);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
+ };
+ /*
+ * runPromptWithAnswers
+ * @param {string} location location of current working directory
+ * @param {string[]} args CLI args to pass in
+ * @param {string[]} answers answers to be passed to stdout for inquirer question
+ */
+ const runPromptWithAnswers = (location, args, answers) => {
+ const process = runAndGetProcess(location, args);
+
+ process.stdin.setDefaultEncoding("utf-8");
+
+ const delay = 2000;
+ let outputTimeout;
+ let currentAnswer = 0;
+
+ const writeAnswer = (output) => {
+ if (!answers) {
+ process.stdin.write(output);
+ processKill(process);
+
+ return;
+ }
+
+ if (currentAnswer < answers.length) {
+ process.stdin.write(answers[currentAnswer]);
+ currentAnswer++;
+ }
+ };
+
+ process.stdout.pipe(
+ new Writable({
+ write(chunk, encoding, callback) {
+ const output = chunk.toString("utf8");
+
+ if (output) {
+ if (outputTimeout) {
+ clearTimeout(outputTimeout);
+ }
+
+ // we must receive new stdout, then have 2 seconds
+ // without any stdout before writing the next answer
+ outputTimeout = setTimeout(() => {
+ writeAnswer(output);
+ }, delay);
+ }
+
+ callback();
+ },
+ }),
+ );
+
+ return new Promise((resolve) => {
+ const obj = {};
+
+ let stdoutDone = false;
+ let stderrDone = false;
+
+ const complete = () => {
+ if (outputTimeout) {
+ clearTimeout(outputTimeout);
+ }
+
+ if (stdoutDone && stderrDone) {
+ processKill(process);
+ resolve(obj);
+ }
+ };
+
+ process.stdout.pipe(
+ concat((result) => {
+ stdoutDone = true;
+ obj.stdout = result.toString();
+
+ complete();
+ }),
+ );
+
+ process.stderr.pipe(
+ concat((result) => {
+ stderrDone = true;
+ obj.stderr = result.toString();
+
+ complete();
+ }),
+ );
+ });
+ };
+
+ return { runAndGetProcess, run, runWatch, runPromptWithAnswers };
+}
+
+module.exports = {
+ createPathDependentUtils,
+ nodeVersion,
+ ...testUtilsPkg,
+};
diff --git a/tsconfig.json b/tsconfig.json
index 796ecc1c96e..7a8f5bdf817 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -35,6 +35,9 @@
},
{
"path": "packages/webpack-cli"
+ },
+ {
+ "path": "packages/create-webpack-app"
}
]
}
diff --git a/yarn.lock b/yarn.lock
index 3d9b7c0ea5a..972787cd757 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1574,6 +1574,151 @@
resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340"
integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==
+"@inquirer/checkbox@^2.5.0":
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/@inquirer/checkbox/-/checkbox-2.5.0.tgz#41c5c9dd332c0a8fa159be23982ce080d0b199d4"
+ integrity sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==
+ dependencies:
+ "@inquirer/core" "^9.1.0"
+ "@inquirer/figures" "^1.0.5"
+ "@inquirer/type" "^1.5.3"
+ ansi-escapes "^4.3.2"
+ yoctocolors-cjs "^2.1.2"
+
+"@inquirer/confirm@^3.2.0":
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-3.2.0.tgz#6af1284670ea7c7d95e3f1253684cfbd7228ad6a"
+ integrity sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw==
+ dependencies:
+ "@inquirer/core" "^9.1.0"
+ "@inquirer/type" "^1.5.3"
+
+"@inquirer/core@^9.1.0":
+ version "9.2.1"
+ resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-9.2.1.tgz#677c49dee399c9063f31e0c93f0f37bddc67add1"
+ integrity sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==
+ dependencies:
+ "@inquirer/figures" "^1.0.6"
+ "@inquirer/type" "^2.0.0"
+ "@types/mute-stream" "^0.0.4"
+ "@types/node" "^22.5.5"
+ "@types/wrap-ansi" "^3.0.0"
+ ansi-escapes "^4.3.2"
+ cli-width "^4.1.0"
+ mute-stream "^1.0.0"
+ signal-exit "^4.1.0"
+ strip-ansi "^6.0.1"
+ wrap-ansi "^6.2.0"
+ yoctocolors-cjs "^2.1.2"
+
+"@inquirer/editor@^2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@inquirer/editor/-/editor-2.2.0.tgz#a41eb7b151bd9a6bc3c0b69219d02d82547bc387"
+ integrity sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==
+ dependencies:
+ "@inquirer/core" "^9.1.0"
+ "@inquirer/type" "^1.5.3"
+ external-editor "^3.1.0"
+
+"@inquirer/expand@^2.3.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@inquirer/expand/-/expand-2.3.0.tgz#afc44aee303315a85563e9d0275e658f0ee0e701"
+ integrity sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==
+ dependencies:
+ "@inquirer/core" "^9.1.0"
+ "@inquirer/type" "^1.5.3"
+ yoctocolors-cjs "^2.1.2"
+
+"@inquirer/figures@^1.0.3", "@inquirer/figures@^1.0.5", "@inquirer/figures@^1.0.6":
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.6.tgz#1a562f916da39888c56b65b78259d2261bd7d40b"
+ integrity sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==
+
+"@inquirer/input@^2.3.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@inquirer/input/-/input-2.3.0.tgz#9b99022f53780fecc842908f3f319b52a5a16865"
+ integrity sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==
+ dependencies:
+ "@inquirer/core" "^9.1.0"
+ "@inquirer/type" "^1.5.3"
+
+"@inquirer/number@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@inquirer/number/-/number-1.1.0.tgz#4dac004021ea67c89552a261564f103a494cac96"
+ integrity sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==
+ dependencies:
+ "@inquirer/core" "^9.1.0"
+ "@inquirer/type" "^1.5.3"
+
+"@inquirer/password@^2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-2.2.0.tgz#0b6f26336c259c8a9e5f5a3f2e1a761564f764ba"
+ integrity sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==
+ dependencies:
+ "@inquirer/core" "^9.1.0"
+ "@inquirer/type" "^1.5.3"
+ ansi-escapes "^4.3.2"
+
+"@inquirer/prompts@^5.1.2":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@inquirer/prompts/-/prompts-5.5.0.tgz#5805aa15a13180017829aa31d071fd37a43b735d"
+ integrity sha512-BHDeL0catgHdcHbSFFUddNzvx/imzJMft+tWDPwTm3hfu8/tApk1HrooNngB2Mb4qY+KaRWF+iZqoVUPeslEog==
+ dependencies:
+ "@inquirer/checkbox" "^2.5.0"
+ "@inquirer/confirm" "^3.2.0"
+ "@inquirer/editor" "^2.2.0"
+ "@inquirer/expand" "^2.3.0"
+ "@inquirer/input" "^2.3.0"
+ "@inquirer/number" "^1.1.0"
+ "@inquirer/password" "^2.2.0"
+ "@inquirer/rawlist" "^2.3.0"
+ "@inquirer/search" "^1.1.0"
+ "@inquirer/select" "^2.5.0"
+
+"@inquirer/rawlist@^2.3.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@inquirer/rawlist/-/rawlist-2.3.0.tgz#6b2c0da39c1cd855af5608b2d627681cdac7277d"
+ integrity sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==
+ dependencies:
+ "@inquirer/core" "^9.1.0"
+ "@inquirer/type" "^1.5.3"
+ yoctocolors-cjs "^2.1.2"
+
+"@inquirer/search@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@inquirer/search/-/search-1.1.0.tgz#665928cac2326b9501ddafbb8606ce4823b3106b"
+ integrity sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==
+ dependencies:
+ "@inquirer/core" "^9.1.0"
+ "@inquirer/figures" "^1.0.5"
+ "@inquirer/type" "^1.5.3"
+ yoctocolors-cjs "^2.1.2"
+
+"@inquirer/select@^2.5.0":
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-2.5.0.tgz#345c6908ecfaeef3d84ddd2f9feb2f487c558efb"
+ integrity sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==
+ dependencies:
+ "@inquirer/core" "^9.1.0"
+ "@inquirer/figures" "^1.0.5"
+ "@inquirer/type" "^1.5.3"
+ ansi-escapes "^4.3.2"
+ yoctocolors-cjs "^2.1.2"
+
+"@inquirer/type@^1.5.3":
+ version "1.5.5"
+ resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-1.5.5.tgz#303ea04ce7ad2e585b921b662b3be36ef7b4f09b"
+ integrity sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==
+ dependencies:
+ mute-stream "^1.0.0"
+
+"@inquirer/type@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-2.0.0.tgz#08fa513dca2cb6264fe1b0a2fabade051444e3f6"
+ integrity sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==
+ dependencies:
+ mute-stream "^1.0.0"
+
"@isaacs/cliui@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
@@ -2926,6 +3071,13 @@
dependencies:
"@types/node" "*"
+"@types/cross-spawn@^6.0.6":
+ version "6.0.6"
+ resolved "https://registry.yarnpkg.com/@types/cross-spawn/-/cross-spawn-6.0.6.tgz#0163d0b79a6f85409e0decb8dcca17147f81fd22"
+ integrity sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==
+ dependencies:
+ "@types/node" "*"
+
"@types/debug@*":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917"
@@ -2938,7 +3090,7 @@
resolved "https://registry.yarnpkg.com/@types/diff/-/diff-5.2.1.tgz#cceae9c4b2dae5c6b8ab1ce1263601c255d87fb3"
integrity sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==
-"@types/ejs@*":
+"@types/ejs@*", "@types/ejs@^3.1.5":
version "3.1.5"
resolved "https://registry.yarnpkg.com/@types/ejs/-/ejs-3.1.5.tgz#49d738257cc73bafe45c13cb8ff240683b4d5117"
integrity sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==
@@ -3005,6 +3157,14 @@
"@types/through" "*"
rxjs "^7.2.0"
+"@types/inquirer@^9.0.3":
+ version "9.0.7"
+ resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-9.0.7.tgz#61bb8d0e42f038b9a1738b08fba7fa98ad9b4b24"
+ integrity sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==
+ dependencies:
+ "@types/through" "*"
+ rxjs "^7.2.0"
+
"@types/interpret@*":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@types/interpret/-/interpret-1.1.3.tgz#fa7695584530077e0338948188bb59270077ab7a"
@@ -3084,6 +3244,13 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433"
integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==
+"@types/mute-stream@^0.0.4":
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/@types/mute-stream/-/mute-stream-0.0.4.tgz#77208e56a08767af6c5e1237be8888e2f255c478"
+ integrity sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==
+ dependencies:
+ "@types/node" "*"
+
"@types/node-forge@^1.3.0":
version "1.3.11"
resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da"
@@ -3186,6 +3353,11 @@
"@types/expect" "^1.20.4"
"@types/node" "*"
+"@types/wrap-ansi@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd"
+ integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==
+
"@types/ws@^8.5.10":
version "8.5.12"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e"
@@ -3613,7 +3785,7 @@ ansi-colors@^4.1.1:
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==
-ansi-escapes@^4.2.1:
+ansi-escapes@^4.2.1, ansi-escapes@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
@@ -4173,6 +4345,14 @@ callsites@^3.0.0, callsites@^3.1.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+camel-case@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
+ integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
+ dependencies:
+ pascal-case "^3.1.2"
+ tslib "^2.0.3"
+
camelcase-keys@^6.2.2:
version "6.2.2"
resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0"
@@ -4212,6 +4392,15 @@ caniuse-lite@^1.0.30001663:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001666.tgz#112d77e80f1762f62a1b71ba92164e0cb3f3dd13"
integrity sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==
+capital-case@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/capital-case/-/capital-case-1.0.4.tgz#9d130292353c9249f6b00fa5852bee38a717e669"
+ integrity sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==
+ dependencies:
+ no-case "^3.0.4"
+ tslib "^2.0.3"
+ upper-case-first "^2.0.2"
+
chalk-template@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-1.1.0.tgz#ffc55db6dd745e9394b85327c8ac8466edb7a7b1"
@@ -4249,6 +4438,24 @@ chalk@^5.2.0, chalk@^5.3.0, chalk@~5.3.0:
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
+change-case@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/change-case/-/change-case-4.1.2.tgz#fedfc5f136045e2398c0410ee441f95704641e12"
+ integrity sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==
+ dependencies:
+ camel-case "^4.1.2"
+ capital-case "^1.0.4"
+ constant-case "^3.0.4"
+ dot-case "^3.0.4"
+ header-case "^2.0.4"
+ no-case "^3.0.4"
+ param-case "^3.0.4"
+ pascal-case "^3.1.2"
+ path-case "^3.0.4"
+ sentence-case "^3.0.4"
+ snake-case "^3.0.4"
+ tslib "^2.0.3"
+
char-regex@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
@@ -4375,6 +4582,11 @@ cli-width@^3.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
+cli-width@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5"
+ integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==
+
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
@@ -4625,6 +4837,15 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0:
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
+constant-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1"
+ integrity sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==
+ dependencies:
+ no-case "^3.0.4"
+ tslib "^2.0.3"
+ upper-case "^2.0.2"
+
content-disposition@0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
@@ -5192,6 +5413,14 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
+dot-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
+ integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
+ dependencies:
+ no-case "^3.0.4"
+ tslib "^2.0.3"
+
dot-prop@^5.1.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
@@ -5667,7 +5896,7 @@ express@^4.19.2:
utils-merge "1.0.1"
vary "~1.1.2"
-external-editor@^3.0.3:
+external-editor@^3.0.3, external-editor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495"
integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==
@@ -6260,7 +6489,7 @@ globby@11.1.0, globby@^11.0.1, globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
-globby@^13.1.2:
+globby@^13.1.2, globby@^13.2.2:
version "13.2.2"
resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592"
integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==
@@ -6305,7 +6534,7 @@ handle-thing@^2.0.0:
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==
-handlebars@^4.7.7:
+handlebars@^4.7.7, handlebars@^4.7.8:
version "4.7.8"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9"
integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==
@@ -6374,6 +6603,14 @@ hasown@^2.0.0, hasown@^2.0.2:
dependencies:
function-bind "^1.1.2"
+header-case@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/header-case/-/header-case-2.0.4.tgz#5a42e63b55177349cf405beb8d775acabb92c063"
+ integrity sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==
+ dependencies:
+ capital-case "^1.0.4"
+ tslib "^2.0.3"
+
hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
@@ -6709,6 +6946,24 @@ inquirer@^8.0.0, inquirer@^8.2.4:
through "^2.3.6"
wrap-ansi "^6.0.1"
+inquirer@^9.2.10:
+ version "9.3.7"
+ resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-9.3.7.tgz#0b562bf843812208844741c9aec9244c939b83d4"
+ integrity sha512-LJKFHCSeIRq9hanN14IlOtPSTe3lNES7TYDTE2xxdAy1LS5rYphajK1qtwvj3YmQXvvk0U2Vbmcni8P9EIQW9w==
+ dependencies:
+ "@inquirer/figures" "^1.0.3"
+ ansi-escapes "^4.3.2"
+ cli-width "^4.1.0"
+ external-editor "^3.1.0"
+ mute-stream "1.0.0"
+ ora "^5.4.1"
+ run-async "^3.0.0"
+ rxjs "^7.8.1"
+ string-width "^4.2.3"
+ strip-ansi "^6.0.1"
+ wrap-ansi "^6.2.0"
+ yoctocolors-cjs "^2.1.2"
+
interpret@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
@@ -7870,6 +8125,11 @@ lodash.flattendeep@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==
+lodash.get@^4.4.2:
+ version "4.4.2"
+ resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
+ integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
+
lodash.ismatch@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37"
@@ -7944,6 +8204,13 @@ log-update@^6.1.0:
strip-ansi "^7.1.0"
wrap-ansi "^9.0.0"
+lower-case@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
+ integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
+ dependencies:
+ tslib "^2.0.3"
+
lru-cache@^10.0.1, lru-cache@^10.2.0, lru-cache@^10.2.2:
version "10.4.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
@@ -8442,6 +8709,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
+mkdirp@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
+ integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
+
modify-values@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
@@ -8491,7 +8763,7 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
-mute-stream@^1.0.0:
+mute-stream@1.0.0, mute-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e"
integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==
@@ -8516,6 +8788,14 @@ neo-async@^2.6.2:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
+no-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
+ integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
+ dependencies:
+ lower-case "^2.0.2"
+ tslib "^2.0.3"
+
node-addon-api@^7.0.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
@@ -8599,6 +8879,25 @@ node-machine-id@1.1.12:
resolved "https://registry.yarnpkg.com/node-machine-id/-/node-machine-id-1.1.12.tgz#37904eee1e59b320bb9c5d6c0a59f3b469cb6267"
integrity sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==
+node-plop@^0.32.0:
+ version "0.32.0"
+ resolved "https://registry.yarnpkg.com/node-plop/-/node-plop-0.32.0.tgz#ec9952c3a8f7e47733a9e7b96b006d05369a5624"
+ integrity sha512-lKFSRSRuDHhwDKMUobdsvaWCbbDRbV3jMUSMiajQSQux1aNUevAZVxUHc2JERI//W8ABPRbi3ebYuSuIzkNIpQ==
+ dependencies:
+ "@types/inquirer" "^9.0.3"
+ change-case "^4.1.2"
+ del "^7.1.0"
+ globby "^13.2.2"
+ handlebars "^4.7.8"
+ inquirer "^9.2.10"
+ isbinaryfile "^5.0.0"
+ lodash.get "^4.4.2"
+ lower-case "^2.0.2"
+ mkdirp "^3.0.1"
+ resolve "^1.22.4"
+ title-case "^3.0.3"
+ upper-case "^2.0.2"
+
node-preload@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301"
@@ -9335,6 +9634,14 @@ pacote@^18.0.0, pacote@^18.0.6:
ssri "^10.0.0"
tar "^6.1.11"
+param-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
+ integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
+ dependencies:
+ dot-case "^3.0.4"
+ tslib "^2.0.3"
+
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
@@ -9404,6 +9711,22 @@ parseurl@~1.3.2, parseurl@~1.3.3:
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+pascal-case@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
+ integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
+ dependencies:
+ no-case "^3.0.4"
+ tslib "^2.0.3"
+
+path-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/path-case/-/path-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f"
+ integrity sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==
+ dependencies:
+ dot-case "^3.0.4"
+ tslib "^2.0.3"
+
path-exists@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
@@ -10090,7 +10413,7 @@ resolve.exports@^2.0.0:
resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==
-resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0:
+resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.4:
version "1.22.8"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@@ -10159,6 +10482,11 @@ run-async@^2.0.0, run-async@^2.4.0:
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
+run-async@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/run-async/-/run-async-3.0.0.tgz#42a432f6d76c689522058984384df28be379daad"
+ integrity sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==
+
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
@@ -10173,7 +10501,7 @@ rxjs@^6.4.0:
dependencies:
tslib "^1.9.0"
-rxjs@^7.2.0, rxjs@^7.5.5:
+rxjs@^7.2.0, rxjs@^7.5.5, rxjs@^7.8.1:
version "7.8.1"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
@@ -10283,6 +10611,15 @@ send@0.19.0:
range-parser "~1.2.1"
statuses "2.0.1"
+sentence-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-3.0.4.tgz#3645a7b8c117c787fde8702056225bb62a45131f"
+ integrity sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==
+ dependencies:
+ no-case "^3.0.4"
+ tslib "^2.0.3"
+ upper-case-first "^2.0.2"
+
serialize-javascript@^6.0.1:
version "6.0.2"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"
@@ -10466,6 +10803,14 @@ smart-buffer@^4.2.0:
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
+snake-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c"
+ integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==
+ dependencies:
+ dot-case "^3.0.4"
+ tslib "^2.0.3"
+
sockjs@^0.3.24:
version "0.3.24"
resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce"
@@ -10704,16 +11049,7 @@ string-length@^5.0.1:
char-regex "^2.0.0"
strip-ansi "^7.0.1"
-"string-width-cjs@npm:string-width@^4.2.0":
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -10754,14 +11090,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -10995,6 +11324,13 @@ tinyglobby@^0.2.9:
fdir "^6.4.0"
picomatch "^4.0.2"
+title-case@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982"
+ integrity sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==
+ dependencies:
+ tslib "^2.0.3"
+
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -11128,7 +11464,7 @@ tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2.0.0:
+tslib@^2.0.0, tslib@^2.0.3:
version "2.7.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
@@ -11234,9 +11570,9 @@ uglify-js@^3.1.4:
integrity sha512-S8KA6DDI47nQXJSi2ctQ629YzwOVs+bQML6DAtvy0wgNdpi+0ySpQK0g2pxBq2xfF2z3YCscu7NNA8nXT9PlIQ==
undici-types@~6.19.2:
- version "6.19.6"
- resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.6.tgz#e218c3df0987f4c0e0008ca00d6b6472d9b89b36"
- integrity sha512-e/vggGopEfTKSvj4ihnOLTsqhrKRN3LeO6qSN/GxohhuRv8qH9bNQ4B8W7e/vFL+0XTnmHPB4/kegunZGA4Org==
+ version "6.19.8"
+ resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
+ integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
unicode-canonical-property-names-ecmascript@^2.0.0:
version "2.0.0"
@@ -11341,6 +11677,20 @@ update-browserslist-db@^1.1.0:
escalade "^3.1.2"
picocolors "^1.0.1"
+upper-case-first@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-2.0.2.tgz#992c3273f882abd19d1e02894cc147117f844324"
+ integrity sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==
+ dependencies:
+ tslib "^2.0.3"
+
+upper-case@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-2.0.2.tgz#d89810823faab1df1549b7d97a76f8662bae6f7a"
+ integrity sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==
+ dependencies:
+ tslib "^2.0.3"
+
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
@@ -11669,7 +12019,7 @@ wordwrap@^1.0.0:
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -11687,15 +12037,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
@@ -11964,3 +12305,8 @@ yocto-queue@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110"
integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==
+
+yoctocolors-cjs@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242"
+ integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==