diff --git a/package-lock.json b/package-lock.json index 2603ab6d9..414bdb86a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -230,6 +230,10 @@ "resolved": "plugins/dotnet-provisioning-terraform-aws-database-rds", "link": true }, + "node_modules/@amplication/plugin-dotnet-provisioning-terraform-aws-deployment-ecs": { + "resolved": "plugins/dotnet-provisioning-terraform-aws-deployment-ecs", + "link": true + }, "node_modules/@amplication/plugin-formatter-prettier": { "resolved": "plugins/formatter-prettier", "link": true @@ -17122,6 +17126,7 @@ } }, "plugins/dotnet-provisioning-terraform-aws-database-rds": { + "name": "@amplication/plugin-dotnet-provisioning-terraform-aws-database-rds", "version": "0.0.1", "license": "Apache-2.0", "devDependencies": { @@ -17161,6 +17166,46 @@ "@amplication/csharp-ast": "*" } }, + "plugins/dotnet-provisioning-terraform-aws-deployment-ecs": { + "version": "1.0.5", + "license": "Apache-2.0", + "devDependencies": { + "@amplication/code-gen-types": "2.0.34", + "@amplication/code-gen-utils": "^0.0.9", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "@types/lodash": "^4.14.200", + "@typescript-eslint/eslint-plugin": "^6.9.1", + "@typescript-eslint/parser": "^6.9.1", + "copy-webpack-plugin": "^12.0.2", + "eslint": "^8.52.0", + "jest-mock-extended": "^3.0.5", + "lodash": "^4.17.21", + "prettier": "^2.6.2", + "rimraf": "^5.0.5", + "ts-loader": "^9.5.0", + "typescript": "^5.2.2", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4", + "webpack-node-externals": "^3.0.0" + } + }, + "plugins/dotnet-provisioning-terraform-aws-deployment-ecs/node_modules/@amplication/code-gen-types": { + "version": "2.0.34", + "resolved": "https://registry.npmjs.org/@amplication/code-gen-types/-/code-gen-types-2.0.34.tgz", + "integrity": "sha512-RLjFuooQoCRomZ6KBHw+Kp25OsvpJ1eAwLi+jO/6S/9QkAGZ+jHWhUUoSiyI8pmWYvi7T2VI8b6d5K8s1Nzh9w==", + "dev": true, + "dependencies": { + "ast-types": "^0.14.2", + "json-schema": "^0.4.0", + "prisma-schema-dsl-types": "^1.1.2", + "tslib": "^2.6.2", + "type-fest": "^3.11.0" + }, + "peerDependencies": { + "@amplication/csharp-ast": "*" + } + }, "plugins/formatter-prettier": { "name": "@amplication/plugin-formatter-prettier", "version": "1.0.7", diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.amplicationrc.json b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.amplicationrc.json new file mode 100644 index 000000000..c98640fd2 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.amplicationrc.json @@ -0,0 +1,25 @@ +{ + "settings": { + "cluster": { + "name": "", + "capacity_provider": { + "type": "fargate", + "fargate": { + "fargate_weight": 100, + "fargate_base": 0, + "fargate_spot_weight": 0 + } + } + }, + "service": { + "name": "", + "container_definitions": { + "image": "0000000000.dkr.ecr.eu-west-1.amazonaws.com/service-name", + "port": 3000 + } + } + }, + "systemSettings": { + "requireAuthenticationEntity": "false" + } +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.eslintrc.json b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.eslintrc.json new file mode 100644 index 000000000..052a5874e --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"] +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.npmignore b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.npmignore new file mode 100644 index 000000000..99ba36b26 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.npmignore @@ -0,0 +1,2 @@ +.prettierignore +.gitignore \ No newline at end of file diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.prettierignore b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.prettierignore new file mode 100644 index 000000000..53c37a166 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/.prettierignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/README.md b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/README.md new file mode 100644 index 000000000..20c4c1ca8 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/README.md @@ -0,0 +1,27 @@ +# @amplication/plugin-provisioning-terraform-aws-deployment-ecs + +[![NPM Downloads](https://img.shields.io/npm/dt/@amplication/plugin-provisioning-terraform-aws-deployment-ecs)](https://www.npmjs.com/package/@amplication/plugin-provisioning-terraform-aws-deployment-ecs) + +Adds terraform code for provisioning Amazon Web Services Elastic Container Service (ECS) as an addition to the 'core' terraform code base. + +## Purpose + +Adds terraform code for provisioning Amazon Web Services Elastic Container Service (ECS) as an addition to the 'core' terraform code base. + +## Configuration + +If a configuration is required, add it here. + +## Scripts + +### `build` + +Running `npm run build` will bundle your plugin with Webpack for production. + +### `dev` + +Running `npm run dev` will watch your plugin's source code and automatically bundle it with every change. + +## Usage + +Explain the usage of this plugin and its effect on the final build. diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/package.json b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/package.json new file mode 100644 index 000000000..768b4b30d --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/package.json @@ -0,0 +1,36 @@ +{ + "name": "@amplication/plugin-dotnet-provisioning-terraform-aws-deployment-ecs", + "version": "1.0.5", + "description": "Adds terraform code for provisioning Amazon Web Services core network infrastructure", + "main": "dist/index.js", + "nx": {}, + "scripts": { + "prepublishOnly": "npm run build", + "dev": "webpack --watch", + "build": "webpack", + "prebuild": "rimraf dist" + }, + "author": "Haim Bell", + "license": "Apache-2.0", + "dependencies": {}, + "devDependencies": { + "@amplication/code-gen-types": "2.0.34", + "@amplication/code-gen-utils": "^0.0.9", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "@types/lodash": "^4.14.200", + "@typescript-eslint/eslint-plugin": "^6.9.1", + "@typescript-eslint/parser": "^6.9.1", + "copy-webpack-plugin": "^12.0.2", + "eslint": "^8.52.0", + "jest-mock-extended": "^3.0.5", + "lodash": "^4.17.21", + "prettier": "^2.6.2", + "rimraf": "^5.0.5", + "ts-loader": "^9.5.0", + "typescript": "^5.2.2", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4", + "webpack-node-externals": "^3.0.0" + } +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/project.json b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/project.json new file mode 100644 index 000000000..1407e4eb5 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/project.json @@ -0,0 +1,6 @@ +{ + "targets": { + "lint": {}, + "npm:publish": {} + } +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/constants.ts b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/constants.ts new file mode 100644 index 000000000..43c57e9f4 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/constants.ts @@ -0,0 +1,16 @@ +// generic constants +export const moduleNameEcsClusterKey = "${{ ECS_CLUSTER_MODULE_NAME }}"; +export const moduleNameEcsServiceKey = "${{ ECS_SERVICE_MODULE_NAME }}"; +export const moduleNameEcsAlbKey = "${{ ECS_ALB_MODULE_NAME }}"; +export const moduleNameEcsSgKey = "${{ ECS_SG_MODULE_NAME }}"; + +// settings cluster constants +export const clusterHyphenNameKey = "${{ CLUSTER_NAME }}"; +export const clusterUnderscoreNameKey = "${{ CLUSTER_NAME_UNDERSCORE }}"; +export const clusterCapacityProviderKey = "${{ CLUSTER_CAPACITY_PROVIDER }}"; + +// settings service constants +export const serviceHyphenNameKey = "${{ SERVICE_NAME }}"; +export const serviceUnderscoreNameKey = "${{ SERVICE_NAME_UNDERSCORE }}"; +export const serviceContainerImage = "${{ SERVICE_CONTAINER_IMAGE }}"; +export const serviceContainerPort = "${{ SERVICE_CONTAINER_PORT }}"; diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/index.ts b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/index.ts new file mode 100644 index 000000000..f2f435508 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/index.ts @@ -0,0 +1,148 @@ +import { + dotnetPluginEventsTypes, + dotnetPluginEventsParams as dotnet, + dotnetTypes, + FileMap, + IFile, +} from "@amplication/code-gen-types"; +import { CodeBlock } from "@amplication/csharp-ast"; +import { + clusterHyphenNameKey, + clusterUnderscoreNameKey, + moduleNameEcsClusterKey, + moduleNameEcsServiceKey, + moduleNameEcsAlbKey, + moduleNameEcsSgKey, + clusterCapacityProviderKey, + serviceContainerImage, + serviceContainerPort, + serviceHyphenNameKey, + serviceUnderscoreNameKey, +} from "./constants"; +import { resolve } from "path"; +import { getPluginSettings, getTerraformDirectory } from "./utils"; +import { kebabCase, snakeCase } from "lodash"; + +class TerraformAwsDeploymentEcsPlugin implements dotnetTypes.AmplicationPlugin { + register(): dotnetPluginEventsTypes.DotnetEvents { + return { + LoadStaticFiles: { + after: this.afterLoadStaticFiles, + }, + }; + } + async afterLoadStaticFiles( + context: dotnetTypes.DsgContext, + eventParams: dotnet.LoadStaticFilesParams, + files: FileMap + ): Promise> { + context.logger.info(`Generating Terraform AWS Deployment ECS...`); + + // get the name for the service, to be used as a fallback for the + // repository name + const serviceName = kebabCase(context.resourceInfo?.name); + if (!serviceName) { + throw new Error( + "TerraformAwsRepositoryEcrPlugin: Service name is undefined" + ); + } + + // instantiate a variable consisting of the path on the + // 'provisioning-terraform-aws-core' made up of the settings + // 'root_directory' & 'directory_name', this function will throw + // an error if the aforementioned plugin wasnt installed. + const terraformDirectory = getTerraformDirectory( + context.pluginInstallations, + context.serverDirectories.baseDirectory + ); + + // fetch the plugin specific settings and merge them with the defaults + const settings = getPluginSettings(context.pluginInstallations); + + const templateFileName = "ecs-template.tf"; + const fileNamePrefix = "ecs-"; + const fileNameSuffix = ".tf"; + const ecsServiceName: string = settings.service.name + ? settings.service.name + : serviceName; + const ecsClusterName: string = settings.cluster.name + ? settings.cluster.name + : serviceName; + + const staticPath = resolve(__dirname, "./static"); + const staticFiles = await context.utils.importStaticFiles( + staticPath, + terraformDirectory + ); + + // switch statement for determining capacity provider within the + // cluster configuration, default to fargate + let capacityProvider: string; + switch (settings.cluster.capacity_provider.type) { + default: { + capacityProvider = `fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = ${settings.cluster.capacity_provider.fargate?.fargate_weight} + base = ${settings.cluster.capacity_provider.fargate?.fargate_base} + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = ${settings.cluster.capacity_provider.fargate?.fargate_spot_weight} + } + } + }`; + } + } + + const hyphenServiceName: string = kebabCase(ecsServiceName); + const underscoreServiceName: string = snakeCase(ecsServiceName); + const hyphenClusterName: string = kebabCase(ecsClusterName); + const underscoreClusterName: string = snakeCase(ecsClusterName); + for (const item of staticFiles.getAll()) { + const newPath = item.path.replace( + templateFileName, + fileNamePrefix + kebabCase(serviceName) + fileNameSuffix + ); + + const newCode = item.code + .replaceAll(clusterHyphenNameKey, hyphenClusterName) + .replaceAll(clusterUnderscoreNameKey, underscoreClusterName) + .replaceAll(serviceHyphenNameKey, hyphenServiceName) + .replaceAll(serviceUnderscoreNameKey, underscoreServiceName) + .replaceAll( + moduleNameEcsClusterKey, + "ecs_cluster_" + underscoreClusterName + ) + .replaceAll( + moduleNameEcsServiceKey, + "ecs_service_" + underscoreServiceName + ) + .replaceAll(moduleNameEcsAlbKey, "ecs_alb_" + underscoreServiceName) + .replaceAll(moduleNameEcsSgKey, "ecs_alb_sg_" + underscoreServiceName) + .replaceAll(clusterCapacityProviderKey, capacityProvider) + .replaceAll( + serviceContainerImage, + settings.service.container_definitions.image + ) + .replaceAll( + serviceContainerPort, + String(settings.service.container_definitions.port) + ); + const file: IFile = { + path: newPath, + code: new CodeBlock({ + code: newCode, + }), + }; + files.set(file); + } + + context.logger.info(`Generated Terraform AWS Deployment ECS...`); + + return files; + } +} + +export default TerraformAwsDeploymentEcsPlugin; diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/static/ecs-template.tf b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/static/ecs-template.tf new file mode 100644 index 000000000..0945b889d --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/static/ecs-template.tf @@ -0,0 +1,195 @@ +module "${{ ECS_CLUSTER_MODULE_NAME }}" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + version = "5.2.2" + + cluster_name = "${{ CLUSTER_NAME }}" + + ${{ CLUSTER_CAPACITY_PROVIDER }} +} + +module "${{ ECS_SERVICE_MODULE_NAME }}" { + source = "terraform-aws-modules/ecs/aws//modules/service" + version = "5.2.2" + + name = "${{ SERVICE_NAME }}" + cluster_arn = module.${{ ECS_CLUSTER_MODULE_NAME }}.arn + + cpu = 1024 + memory = 4096 + + container_definitions = { + ("${{ SERVICE_NAME }}") = { + essential = true + cpu = 512 + memory = 1024 + image = "${{ SERVICE_CONTAINER_IMAGE }}" + + port_mappings = [ + { + name = "${{ SERVICE_NAME }}" + containerPort = ${{ SERVICE_CONTAINER_PORT }} + hostPort = ${{ SERVICE_CONTAINER_PORT }} + protocol = "tcp" + } + ] + + readonly_root_filesystem = false + + enable_cloudwatch_logging = false + + log_configuration = { + logDriver = "awslogs" + options = { + awslogs-create-group = "true" + awslogs-group = "/ecs/${{ SERVICE_NAME }}" + awslogs-region = local.region + awslogs-stream-prefix = "ecs" + } + } + + memory_reservation = 100 + } + } + + load_balancer = { + service = { + target_group_arn = element(module.${{ ECS_ALB_MODULE_NAME }}.target_group_arns, 0) + container_name = "${{ SERVICE_NAME }}" + container_port = ${{ SERVICE_CONTAINER_PORT }} + } + } + + subnet_ids = module.vpc.private_subnets + + security_group_rules = { + alb_ingress_3000 = { + type = "ingress" + from_port = ${{ SERVICE_CONTAINER_PORT }} + to_port = ${{ SERVICE_CONTAINER_PORT }} + protocol = "tcp" + source_security_group_id = module.${{ ECS_SG_MODULE_NAME }}.security_group_id + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } +} + +resource "aws_service_discovery_http_namespace" "${{ CLUSTER_NAME_UNDERSCORE }}" { + name = "${{ CLUSTER_NAME }}" +} + +module "${{ ECS_SG_MODULE_NAME }}" { + source = "terraform-aws-modules/security-group/aws" + version = "5.1.0" + + name = "${{ SERVICE_NAME }}" + vpc_id = module.vpc.vpc_id + + ingress_rules = ["http-80-tcp"] + ingress_cidr_blocks = ["0.0.0.0/0"] + + egress_rules = ["all-all"] + egress_cidr_blocks = module.vpc.private_subnets_cidr_blocks +} + +module "${{ ECS_ALB_MODULE_NAME }}" { + source = "terraform-aws-modules/alb/aws" + version = "8.7.0" + + name = "${{ SERVICE_NAME }}" + + load_balancer_type = "application" + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + security_groups = [module.${{ ECS_SG_MODULE_NAME }}.security_group_id] + + http_tcp_listeners = [ + { + port = 80 + protocol = "HTTP" + target_group_index = 0 + }, + ] + + target_groups = [ + { + name = "${{ SERVICE_NAME }}" + backend_protocol = "HTTP" + backend_port = ${{ SERVICE_CONTAINER_PORT }} + target_type = "ip" + + health_check = { + enabled = true + interval = 30 + path = "/api/_health/live" + port = "traffic-port" + healthy_threshold = 3 + unhealthy_threshold = 3 + timeout = 6 + protocol = "HTTP" + matcher = "200-299" + } + }, + ] +} + +output "cluster_arn" { + description = "ARN that identifies the cluster" + value = module.${{ ECS_CLUSTER_MODULE_NAME }}.arn +} + +output "cluster_id" { + description = "ID that identifies the cluster" + value = module.${{ ECS_CLUSTER_MODULE_NAME }}.id +} + +output "cluster_name" { + description = "Name that identifies the cluster" + value = module.${{ ECS_CLUSTER_MODULE_NAME }}.name +} + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = module.${{ ECS_CLUSTER_MODULE_NAME }}.cluster_capacity_providers +} + +output "service_id" { + description = "ARN that identifies the service" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.id +} + +output "service_name" { + description = "Name of the service" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.name +} + +output "service_iam_role_name" { + description = "Service IAM role name" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.iam_role_name +} + +output "service_iam_role_arn" { + description = "Service IAM role ARN" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.iam_role_arn +} + +output "service_iam_role_unique_id" { + description = "Stable and unique string identifying the service IAM role" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.iam_role_unique_id +} + +output "service_container_definitions" { + description = "Container definitions" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.container_definitions +} + +output "service_task_definition_arn" { + description = "Full ARN of the Task Definition (including both `family` and `revision`)" + value = module.${{ ECS_SERVICE_MODULE_NAME }}.task_definition_arn +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/tests/.keep b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/tests/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/types.ts b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/types.ts new file mode 100644 index 000000000..fde72d54b --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/types.ts @@ -0,0 +1,20 @@ +export interface Settings { + cluster: { + name: string; + capacity_provider: { + type: string; + fargate?: { + fargate_weight: number; + fargate_base: number; + fargate_spot_weight: number; + }; + }; + }; + service: { + name: string; + container_definitions: { + image: string; + port: number; + }; + }; +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/utils.ts b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/utils.ts new file mode 100644 index 000000000..1c0f20f02 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/src/utils.ts @@ -0,0 +1,45 @@ +import { PluginInstallation } from "@amplication/code-gen-types"; +import { name as PackageName } from "../package.json"; +import { Settings } from "./types"; +import defaultSettings from "../.amplicationrc.json"; + +export const getPluginSettings = ( + pluginInstallations: PluginInstallation[] +): Settings => { + const plugin = pluginInstallations.find( + (plugin) => plugin.npm === PackageName + ); + + const userSettings = plugin?.settings ?? {}; + + const settings: Settings = { + ...defaultSettings.settings, + ...userSettings, + }; + + return settings; +}; + +export const getTerraformDirectory = ( + pluginInstallations: PluginInstallation[], + serverBaseDirectory: string +): string => { + const plugin = pluginInstallations.find( + (plugin) => + plugin.npm === "@amplication/plugin-dotnet-provisioning-terraform-aws-core" + ); + + if (!plugin) { + throw new Error( + "TerraformAwsDeploymentEcsPlugin: is dependent on 'Terraform - AWS Core' plugin" + ); + } + + const { root_level, directory_name } = plugin.settings; + + if (root_level) { + return `./${directory_name}`; + } else { + return `./${serverBaseDirectory}/${directory_name}`; + } +}; diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/tsconfig.json b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/tsconfig.json new file mode 100644 index 000000000..6646f44f5 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/index.ts"], + "exclude": ["node_modules"] +} diff --git a/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/webpack.config.js b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/webpack.config.js new file mode 100644 index 000000000..07eadf115 --- /dev/null +++ b/plugins/dotnet-provisioning-terraform-aws-deployment-ecs/webpack.config.js @@ -0,0 +1,43 @@ +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); + +/** @type {import("webpack").Configuration} */ +module.exports = { + mode: "production", + target: "node", + entry: "./src/index.ts", + externals: ["@amplication/code-gen-utils", "@amplication/code-gen-types"], + plugins: [ + new webpack.SourceMapDevToolPlugin({ + filename: "[name].js.map", + }), + new CopyWebpackPlugin({ + patterns: [ + { from: "src/static", to: "static", noErrorOnMissing: true }, + { from: "src/templates", to: "templates", noErrorOnMissing: true }, + ], + }), + ], + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: [".ts", ".js", ".json"], + }, + optimization: { + minimize: false, + }, + output: { + filename: "index.js", + path: path.resolve(__dirname, "dist"), + libraryTarget: "commonjs2", + clean: true, + }, +};