From 3dc4fcbee44f8fbfa252e1e5bba73a22b97c6ff5 Mon Sep 17 00:00:00 2001 From: Haim Bell Date: Mon, 16 Sep 2024 14:26:51 +0300 Subject: [PATCH] feat(dotnet-ci-github): add plugin --- package-lock.json | 318 ++++++++++++++++++ .../.amplicationrc copy.json | 5 + .../.amplicationrc.json | 5 + .../.eslintrc copy.json | 4 + .../dotnet-ci-github-actions/.eslintrc.json | 4 + plugins/dotnet-ci-github-actions/.npmignore | 2 + .../dotnet-ci-github-actions/.npmignore copy | 2 + .../dotnet-ci-github-actions/.prettierignore | 1 + .../.prettierignore copy | 1 + plugins/dotnet-ci-github-actions/README.md | 60 ++++ plugins/dotnet-ci-github-actions/package.json | 33 ++ plugins/dotnet-ci-github-actions/project.json | 6 + .../dotnet-ci-github-actions/src/constants.ts | 5 + plugins/dotnet-ci-github-actions/src/index.ts | 164 +++++++++ .../src/static/default/workflow.yaml | 36 ++ .../src/static/github/workflow.yaml | 71 ++++ plugins/dotnet-ci-github-actions/src/types.ts | 15 + plugins/dotnet-ci-github-actions/src/utils.ts | 21 ++ .../dotnet-ci-github-actions/tsconfig.json | 5 + .../webpack.config.js | 39 +++ 20 files changed, 797 insertions(+) create mode 100644 plugins/dotnet-ci-github-actions/.amplicationrc copy.json create mode 100644 plugins/dotnet-ci-github-actions/.amplicationrc.json create mode 100644 plugins/dotnet-ci-github-actions/.eslintrc copy.json create mode 100644 plugins/dotnet-ci-github-actions/.eslintrc.json create mode 100644 plugins/dotnet-ci-github-actions/.npmignore create mode 100644 plugins/dotnet-ci-github-actions/.npmignore copy create mode 100644 plugins/dotnet-ci-github-actions/.prettierignore create mode 100644 plugins/dotnet-ci-github-actions/.prettierignore copy create mode 100644 plugins/dotnet-ci-github-actions/README.md create mode 100644 plugins/dotnet-ci-github-actions/package.json create mode 100644 plugins/dotnet-ci-github-actions/project.json create mode 100644 plugins/dotnet-ci-github-actions/src/constants.ts create mode 100644 plugins/dotnet-ci-github-actions/src/index.ts create mode 100644 plugins/dotnet-ci-github-actions/src/static/default/workflow.yaml create mode 100644 plugins/dotnet-ci-github-actions/src/static/github/workflow.yaml create mode 100644 plugins/dotnet-ci-github-actions/src/types.ts create mode 100644 plugins/dotnet-ci-github-actions/src/utils.ts create mode 100644 plugins/dotnet-ci-github-actions/tsconfig.json create mode 100644 plugins/dotnet-ci-github-actions/webpack.config.js diff --git a/package-lock.json b/package-lock.json index fe0ba7df..8d7bf403 100644 --- a/package-lock.json +++ b/package-lock.json @@ -215,6 +215,10 @@ "resolved": "plugins/dotnet-broker-kafka", "link": true }, + "node_modules/@amplication/plugin-dotnet-ci-github-actions": { + "resolved": "plugins/dotnet-ci-github-actions", + "link": true + }, "node_modules/@amplication/plugin-dotnet-db-postgres": { "resolved": "plugins/dotnet-db-postgres", "link": true @@ -16175,6 +16179,320 @@ "node": ">=4.2.0" } }, + "plugins/dotnet-ci-github-actions": { + "version": "1.0.0", + "license": "Apache-2.0", + "devDependencies": { + "@amplication/code-gen-types": "2.0.34", + "@amplication/code-gen-utils": "^0.0.9", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/lodash": "^4.14.182", + "@types/normalize-path": "^3.0.0", + "@typescript-eslint/eslint-plugin": "^5.33.0", + "@typescript-eslint/parser": "^5.33.0", + "copy-webpack-plugin": "^12.0.2", + "eslint": "^8.21.0", + "jest-mock-extended": "^3.0.1", + "lodash": "^4.17.21", + "prettier": "^2.6.2", + "rimraf": "^4.4.1", + "ts-loader": "^9.4.2", + "typescript": "^4.9.3", + "webpack": "^5.76.0", + "webpack-cli": "^5.0.1" + } + }, + "plugins/dotnet-ci-github-actions/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/dotnet-ci-github-actions/node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "plugins/dotnet-ci-github-actions/node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "plugins/dotnet-ci-github-actions/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "plugins/dotnet-ci-github-actions/node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "plugins/dotnet-ci-github-actions/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "plugins/dotnet-ci-github-actions/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "plugins/dotnet-ci-github-actions/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "plugins/dotnet-ci-github-actions/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "plugins/dotnet-ci-github-actions/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "plugins/dotnet-ci-github-actions/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "plugins/dotnet-ci-github-actions/node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "plugins/dotnet-ci-github-actions/node_modules/minimatch": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "plugins/dotnet-ci-github-actions/node_modules/rimraf": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", + "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", + "dev": true, + "dependencies": { + "glob": "^9.2.0" + }, + "bin": { + "rimraf": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "plugins/dotnet-ci-github-actions/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "plugins/dotnet-db-postgres": { "name": "@amplication/plugin-dotnet-db-postgres", "version": "0.1.0", diff --git a/plugins/dotnet-ci-github-actions/.amplicationrc copy.json b/plugins/dotnet-ci-github-actions/.amplicationrc copy.json new file mode 100644 index 00000000..7c08629d --- /dev/null +++ b/plugins/dotnet-ci-github-actions/.amplicationrc copy.json @@ -0,0 +1,5 @@ +{ + "settings": { + "registry": "" + } +} diff --git a/plugins/dotnet-ci-github-actions/.amplicationrc.json b/plugins/dotnet-ci-github-actions/.amplicationrc.json new file mode 100644 index 00000000..7c08629d --- /dev/null +++ b/plugins/dotnet-ci-github-actions/.amplicationrc.json @@ -0,0 +1,5 @@ +{ + "settings": { + "registry": "" + } +} diff --git a/plugins/dotnet-ci-github-actions/.eslintrc copy.json b/plugins/dotnet-ci-github-actions/.eslintrc copy.json new file mode 100644 index 00000000..052a5874 --- /dev/null +++ b/plugins/dotnet-ci-github-actions/.eslintrc copy.json @@ -0,0 +1,4 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"] +} diff --git a/plugins/dotnet-ci-github-actions/.eslintrc.json b/plugins/dotnet-ci-github-actions/.eslintrc.json new file mode 100644 index 00000000..052a5874 --- /dev/null +++ b/plugins/dotnet-ci-github-actions/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"] +} diff --git a/plugins/dotnet-ci-github-actions/.npmignore b/plugins/dotnet-ci-github-actions/.npmignore new file mode 100644 index 00000000..99ba36b2 --- /dev/null +++ b/plugins/dotnet-ci-github-actions/.npmignore @@ -0,0 +1,2 @@ +.prettierignore +.gitignore \ No newline at end of file diff --git a/plugins/dotnet-ci-github-actions/.npmignore copy b/plugins/dotnet-ci-github-actions/.npmignore copy new file mode 100644 index 00000000..99ba36b2 --- /dev/null +++ b/plugins/dotnet-ci-github-actions/.npmignore copy @@ -0,0 +1,2 @@ +.prettierignore +.gitignore \ No newline at end of file diff --git a/plugins/dotnet-ci-github-actions/.prettierignore b/plugins/dotnet-ci-github-actions/.prettierignore new file mode 100644 index 00000000..53c37a16 --- /dev/null +++ b/plugins/dotnet-ci-github-actions/.prettierignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/plugins/dotnet-ci-github-actions/.prettierignore copy b/plugins/dotnet-ci-github-actions/.prettierignore copy new file mode 100644 index 00000000..53c37a16 --- /dev/null +++ b/plugins/dotnet-ci-github-actions/.prettierignore copy @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/plugins/dotnet-ci-github-actions/README.md b/plugins/dotnet-ci-github-actions/README.md new file mode 100644 index 00000000..76a412f9 --- /dev/null +++ b/plugins/dotnet-ci-github-actions/README.md @@ -0,0 +1,60 @@ +# @amplication/plugin-ci-github-actions + +[![NPM Downloads](https://img.shields.io/npm/dt/@amplication/plugin-ci-github-actions)](https://www.npmjs.com/package/@amplication/plugin-ci-github-actions) + +Adds a github actions workflow to for the service used for building and testing the service. + +## Purpose + +Adds a github actions workflow to for the service used for building and testing the service. With the possibility to also include containerization of the service by building the service as a container image and publishing the image to a registry. + +## Configuration - default + +The setting `registry` determines the workflow base that is used. When it is left empty, the default workflow is used - this workflow only includes steps for building and testing the service. + +``` +{ + "registry" : "" +} +``` + +## Configuration - github + +The setting `registry` determines the workflow base that is used. When this value is set to 'github' the workflow includes steps for publishing a container image, next to building and testing the service. + +The `configuration` part lets the user further customize the way the image for the service is pushed to the container registry. At the moment the Github Packages registry is the only registry supported. The configuration options are: + +- `registry_path` to push the image to - this equats to either the organization's packages - e.g., ghcr.io/amplication/hello-world - or the user profile - e.g., ghcr.io/levivannoort/hello-world. + +> Note: if registry_path isn't filled the plugind default to use the contact ${{ github.actor }} which works for most use-cases but in-case of working within an organization/team this should be statically set to the github organization - otherwise it will try to push to the user that triggered the workflow. + +- `authentication_method` there are two methods of authentication, the first method is using the github token - i.e., '${{ secrets.GITHUB_TOKEN }}' this does require editing the settings of the repository to also be able to push an image to Github Packages. The default github token is used when anything besides "pat" is provided for this setting, this is the preferred method of authentication. The second option is to use a personal acces token - i.e. "authentication_method" : "pat". This requires the user to generate a Personal Access Token with permissions scoped to `write:packages`. This subsequently needs to be saved as a secret within the repository's secrets, do so under the key name 'GITHUB_PACKAGES_PAT'. + +> Note: Using the ${{ secrets.GITHUB_TOKEN }} requires additional permissions within the settings of the repository. Navigate to the 'repository', go to the 'settings', under 'Code and automation' go to 'Actions' > 'General', within the heading 'Workflow permissions' select 'Read and write permissions' and press 'Save'. + +Default github token example: + +``` +{ + "registry" : "github", + "configuration" : { + "registry_path" : "levivannoort" + } +} +``` + +Personal access token example: + +``` +{ + "registry" : "github", + "configuration" : { + "registry_path" : "levivannoort" + "authentication_method" : "pat" + } +} +``` + +## Usage + +Adds a github actions workflow under the `.github/workflows/` directory at the root of the repository. This will subsequently run the workflow on each push to any branch of the repository. diff --git a/plugins/dotnet-ci-github-actions/package.json b/plugins/dotnet-ci-github-actions/package.json new file mode 100644 index 00000000..0d50ef6f --- /dev/null +++ b/plugins/dotnet-ci-github-actions/package.json @@ -0,0 +1,33 @@ +{ + "name": "@amplication/plugin-dotnet-ci-github-actions", + "version": "1.0.0", + "description": "Adds a github actions workflow to for the service used for building and testing the service.", + "main": "dist/index.js", + "nx": {}, + "scripts": { + "dev": "webpack --watch", + "build": "webpack" + }, + "author": "Haim Bell", + "license": "Apache-2.0", + "devDependencies": { + "@amplication/code-gen-types": "2.0.34", + "@amplication/code-gen-utils": "^0.0.9", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/lodash": "^4.14.182", + "@types/normalize-path": "^3.0.0", + "@typescript-eslint/eslint-plugin": "^5.33.0", + "@typescript-eslint/parser": "^5.33.0", + "copy-webpack-plugin": "^12.0.2", + "eslint": "^8.21.0", + "jest-mock-extended": "^3.0.1", + "lodash": "^4.17.21", + "prettier": "^2.6.2", + "rimraf": "^4.4.1", + "ts-loader": "^9.4.2", + "typescript": "^4.9.3", + "webpack": "^5.76.0", + "webpack-cli": "^5.0.1" + } +} diff --git a/plugins/dotnet-ci-github-actions/project.json b/plugins/dotnet-ci-github-actions/project.json new file mode 100644 index 00000000..1407e4eb --- /dev/null +++ b/plugins/dotnet-ci-github-actions/project.json @@ -0,0 +1,6 @@ +{ + "targets": { + "lint": {}, + "npm:publish": {} + } +} diff --git a/plugins/dotnet-ci-github-actions/src/constants.ts b/plugins/dotnet-ci-github-actions/src/constants.ts new file mode 100644 index 00000000..052be99c --- /dev/null +++ b/plugins/dotnet-ci-github-actions/src/constants.ts @@ -0,0 +1,5 @@ +export const serviceNameKey = "${{ SERVICE_NAME }}"; +export const imageKey = "${{ IMAGE }}"; +export const serviceWorkingDirectoryKey = "${{ SERVICE_DIRECTORY }}"; +export const authenticationUsernameKey = "${{ AUTHENTICATION_USERNAME }}"; +export const authenticationPasswordKey = "${{ AUTHENTICATION_PASSWORD }}"; diff --git a/plugins/dotnet-ci-github-actions/src/index.ts b/plugins/dotnet-ci-github-actions/src/index.ts new file mode 100644 index 00000000..95cad76b --- /dev/null +++ b/plugins/dotnet-ci-github-actions/src/index.ts @@ -0,0 +1,164 @@ +import { + dotnetPluginEventsTypes, + dotnetPluginEventsParams as dotnet, + dotnetTypes, + FileMap, + IFile, +} from "@amplication/code-gen-types"; +import { CodeBlock } from "@amplication/csharp-ast"; +import { + authenticationPasswordKey, + authenticationUsernameKey, + imageKey, + serviceWorkingDirectoryKey, + serviceNameKey, +} from "./constants"; +import { getPluginSettings } from "./utils"; +import { resolve } from "path"; +import { kebabCase } from "lodash"; +import { GitHubAuthenticationMethods, RegistryProviders } from "./types"; + +class GithubActionsPlugin 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 GitHub Actions workflow...`); + + // determine the name of the service which will be used as the name for the workflow + // workflow names must be lower case letters and numbers. words may be separated with dashes (-): + const serviceName = kebabCase(context.resourceInfo?.name); + + if (!serviceName) { + throw new Error("Service name is undefined"); + } + + // getPluginSettings: fetch user settings + merge with default settings + const settings = getPluginSettings(context.pluginInstallations); + + /** + * option 1 '} else {' (settings.registry == "") + * when the registry setting is left empty - or doesnt equat to + * on of the RegistryProviders.<...>, the steps for setting up + * the metadata, logging in to the container repostiory and build + * and publishing the image arent included + * + * option 2 'specific provider' (settings.registry == registryProvider.Github || registryProvider.<...>): + * when the registry setting is provided with a supported provider, the steps + * for setting up the metadata, logging in to the container + * repostiory and build an publishing the image are included + */ + + const templateFileName = "workflow.yaml"; + const workflowFileNamePrefix = "ci-"; + const workflowFileNameSuffix = ".yaml"; + const outputDirectory = "./.github/workflows/"; + + const successfulPluginCodeGeneration = + "Generated GitHub Actions workflow..."; + const staticFilesRevised = new FileMap(context.logger); + + if (settings.registry == RegistryProviders.GitHub) { + const githubStaticFiles = "./static/github/"; + + const staticPath = resolve(__dirname, githubStaticFiles); + const staticFiles = await context.utils.importStaticFiles( + staticPath, + outputDirectory + ); + + // set the registry to the github packages registry url and + // define the image name so that the image can be pushed to the + // github packages of the users profile or organization if specfied + // in the registry_path instead. + const registryUrl = "ghcr.io"; + const image = settings.configuration?.registry_path + ? `${registryUrl}/${settings.configuration?.registry_path}/${serviceName}` + : `${registryUrl}/\${{ github.actor }}/${serviceName}`; + + // split the registry path so that the first part can be used to determine + // to what registry the push the container images to - i.e., personal profile + // or organization. + const authenticationUsername = settings.configuration?.registry_path + ? settings.configuration?.registry_path?.split("/")[0] + : "${{ github.actor }}"; + + // if the authenticaiton method is set to pat (personal access token), specify + // the key for a secret within the secrets of the repository otherwise the + // default github token is used for authentication to the github packages container + // registry. + const authenticationPassword = + settings.configuration?.authentication_method == + GitHubAuthenticationMethods.PersonalAccessToken + ? "${{ secrets.GITHUB_PACKAGES_PAT }}" + : "${{ secrets.GITHUB_TOKEN }}"; + + for (const item of staticFiles.getAll()) { + const newPath = item.path.replace( + templateFileName, + workflowFileNamePrefix + serviceName + workflowFileNameSuffix + ); + const newCode = item.code + .replaceAll(serviceNameKey, serviceName) + .replaceAll(imageKey, image) + .replaceAll(authenticationUsernameKey, authenticationUsername) + .replaceAll(authenticationPasswordKey, authenticationPassword) + .replaceAll( + serviceWorkingDirectoryKey, + context.serverDirectories.baseDirectory + ); + + const file: IFile = { + path: newPath, + code: new CodeBlock({ + code: newCode, + }), + }; + staticFilesRevised.set(file); + } + } else { + const defaultStaticFiles = "./static/default/"; + + const staticPath = resolve(__dirname, defaultStaticFiles); + const staticFiles = await context.utils.importStaticFiles( + staticPath, + outputDirectory + ); + for (const item of staticFiles.getAll()) { + const newPath = item.path.replace( + templateFileName, + workflowFileNamePrefix + serviceName + workflowFileNameSuffix + ); + const newCode = item.code + .replaceAll(serviceNameKey, serviceName) + .replaceAll( + serviceWorkingDirectoryKey, + context.serverDirectories.baseDirectory + ); + + const file: IFile = { + path: newPath, + code: new CodeBlock({ + code: newCode, + }), + }; + + staticFilesRevised.set(file); + } + } + context.logger.info(successfulPluginCodeGeneration); + await files.merge(staticFilesRevised); + return files; + } +} + +export default GithubActionsPlugin; diff --git a/plugins/dotnet-ci-github-actions/src/static/default/workflow.yaml b/plugins/dotnet-ci-github-actions/src/static/default/workflow.yaml new file mode 100644 index 00000000..5100e818 --- /dev/null +++ b/plugins/dotnet-ci-github-actions/src/static/default/workflow.yaml @@ -0,0 +1,36 @@ +name: continuous integration - ${{ SERVICE_NAME }} + +concurrency: + group: ${{ SERVICE_NAME }} + cancel-in-progress: true + +on: + workflow_dispatch: + push: + branches: ["*"] + paths: ["${{ SERVICE_DIRECTORY }}"] + +jobs: + continuous-integration: + name: continuous integration + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + + - name: setup node.js + uses: actions/setup-node@v3 + with: + node-version: 18.x + + - name: install dependencies + run: npm install + working-directory: ${{ SERVICE_DIRECTORY }} + + - name: build + run: npm run build + working-directory: ${{ SERVICE_DIRECTORY }} + + - name: test + run: npm test + working-directory: ${{ SERVICE_DIRECTORY }} diff --git a/plugins/dotnet-ci-github-actions/src/static/github/workflow.yaml b/plugins/dotnet-ci-github-actions/src/static/github/workflow.yaml new file mode 100644 index 00000000..5e1d86a3 --- /dev/null +++ b/plugins/dotnet-ci-github-actions/src/static/github/workflow.yaml @@ -0,0 +1,71 @@ +name: continuous integration - ${{ SERVICE_NAME }} + +concurrency: + group: ${{ SERVICE_NAME }} + cancel-in-progress: true + +on: + workflow_dispatch: + push: + branches: ["*"] + paths: ["${{ SERVICE_DIRECTORY }}"] + +env: + REGISTRY: ghcr.io + +jobs: + continuous-integration: + name: continuous integration + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: checkout + uses: actions/checkout@v3 + + - name: setup node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: install dependencies + run: npm install + working-directory: ${{ SERVICE_DIRECTORY }} + + - name: build + run: npm run build + working-directory: ${{ SERVICE_DIRECTORY }} + + - name: test + run: npm test + working-directory: ${{ SERVICE_DIRECTORY }} + + - name: docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ IMAGE }} + flavor: latest=true + tags: | + type=sha,prefix=sha- + type=ref,event=branch + type=ref,event=pr,prefix=pr- + type=ref,event=tag,prefix=tag- + type=raw,value=${{ github.run_id }},prefix=gh- + + - name: login to image repostiory + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ AUTHENTICATION_USERNAME }} + password: ${{ AUTHENTICATION_PASSWORD }} + + - name: build and push + uses: docker/build-push-action@v3 + with: + context: ${{ SERVICE_DIRECTORY }} + file: ${{ SERVICE_DIRECTORY }}/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/plugins/dotnet-ci-github-actions/src/types.ts b/plugins/dotnet-ci-github-actions/src/types.ts new file mode 100644 index 00000000..3d994896 --- /dev/null +++ b/plugins/dotnet-ci-github-actions/src/types.ts @@ -0,0 +1,15 @@ +export enum RegistryProviders { + GitHub = "github", +} + +export enum GitHubAuthenticationMethods { + PersonalAccessToken = "pat", +} + +export interface Settings { + registry?: RegistryProviders; + configuration?: { + registry_path: string; + authentication_method?: string; + }; +} //TODO: create a json schema for this settings interface diff --git a/plugins/dotnet-ci-github-actions/src/utils.ts b/plugins/dotnet-ci-github-actions/src/utils.ts new file mode 100644 index 00000000..a94bfe69 --- /dev/null +++ b/plugins/dotnet-ci-github-actions/src/utils.ts @@ -0,0 +1,21 @@ +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; +}; diff --git a/plugins/dotnet-ci-github-actions/tsconfig.json b/plugins/dotnet-ci-github-actions/tsconfig.json new file mode 100644 index 00000000..6646f44f --- /dev/null +++ b/plugins/dotnet-ci-github-actions/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/index.ts"], + "exclude": ["node_modules"] +} diff --git a/plugins/dotnet-ci-github-actions/webpack.config.js b/plugins/dotnet-ci-github-actions/webpack.config.js new file mode 100644 index 00000000..79f84dee --- /dev/null +++ b/plugins/dotnet-ci-github-actions/webpack.config.js @@ -0,0 +1,39 @@ +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", + plugins: [ + new webpack.SourceMapDevToolPlugin({ + filename: "[name].js.map", + }), + new CopyWebpackPlugin({ + patterns: [{ from: "src/static", to: "static" }], + }), + ], + 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, + }, +};