diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..85e612f4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: ci + +on: + push: + branches: + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - node-version: 14 + node-lts: true + - node-version: 16 + node-lts: true + - node-version: 18 + node-lts: true + - node-version: 19 + node-lts: false + continue-on-error: ${{ ! matrix.node-lts }} + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: yarn + - run: yarn install + - run: yarn build + - run: yarn test diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml new file mode 100644 index 00000000..dfe5e8d8 --- /dev/null +++ b/.github/workflows/format-check.yml @@ -0,0 +1,19 @@ +name: format-check + +on: + push: + branches: + - main + +jobs: + release: + name: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + cache: yarn + - run: yarn install + - run: yarn format-check diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 12cfa339..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: test - -on: - push: - branches: - - main - pull_request: - -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - include: - - node: 14 - lts: true - - node: 16 - lts: true - - node: 18 - lts: false - - node: 19 - lts: false - continue-on-error: ${{ ! matrix.lts }} - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node }} - cache: yarn - - run: yarn install - - run: yarn build - # - run: yarn test diff --git a/.npmrc b/.npmrc index 13e9bbd7..e2c81f15 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,2 @@ workspaces = true workspaces-update = false - diff --git a/.prettierignore b/.prettierignore index 09a3e843..93962cb4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,5 +2,5 @@ .yarn # artifacts -lib/ +dist/ types/ diff --git a/README.md b/README.md index 9185ce10..507aebb6 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ yarn moker add --template cra client - - [Getting started](#getting-started) +- [Getting started](#getting-started) - [Prerequisites](#prerequisites) - [Create monorepo](#create-monorepo) - [Use plugins](#use-plugins) @@ -49,7 +49,7 @@ yarn moker add --template cra client - [`lint-staged` _monorepo_](#lint-staged-_monorepo_) - [`prettier` _monorepo_](#prettier-_monorepo_) - [`jest` _workspace_](#jest-_workspace_) - - [`semantic-release` _workspace_](#semantic-release-_workspace_) + - [`semantic-release` _monorepo_](#semantic-release-_monorepo_) - [`typescript` _workspace_](#typescript-_workspace_) - [Available templates](#available-templates) - [`common` _monorepo_](#common-_monorepo_) @@ -65,7 +65,7 @@ yarn moker add --template cra client -## Getting started +# Getting started ## Prerequisites @@ -161,10 +161,21 @@ monorepo level. ## `github-actions` _monorepo_ -**🚧 This plugin is a work in progress** +This plugin creates a simple `ci.yml` +[GitHub Actions](https://github.com/features/actions) workflow. -This plugin sets up [GitHub Actions](https://github.com/features/actions) at the -monorepo level. +If you have the `prettier` plugin installed, this will also setup a `lint.yml` +workflow. + +If you have the `semantic-release` plugin installed, this will also setup a +`release.yml` workflow. This workflow needs these secrets to be added to your +repository: + +- `GH_TOKEN`: a GitHub token with read/write access to your repository +- `NPM_TOKEN`: an NPM token with publish access to your packages + +> 🤓 The workflows will use the `main` branch by default, but it is trivial to +> change this. ## `husky` _monorepo_ @@ -201,14 +212,41 @@ This plugin sets up [Prettier](https://prettier.io). This plugin sets up [Jest](https://jestjs.io) and adds a `test` and `watch:test` script to both the workspace and the monorepo. -## `semantic-release` _workspace_ +## `semantic-release` _monorepo_ This plugin sets up -[semantic-release](https://semantic-release.gitbook.io/semantic-release/). +[semantic-release](https://semantic-release.gitbook.io/semantic-release/). It +uses a workaround so that it can be used in a monorepo, which is to set up a +`.npmrc` file containing: + +```ini +workspaces = true +workspaces-update = false +``` + +This causes both `npm version` and `npm publish` to be run for each monorepo in +the `semantic-release` context. + +Please note that the root repository is not published. Furthermore, make sure +that the root `package.json` doesn't contain: + +```json +"private": true +``` + +Otherwise, the `semantic-release` process will skip the `publish` step. + +> 🤓 The release configuration will use the `main` branch by default, but it is +> trivial to change this. > ⚠️ The semantic-release plugin in our monorepo configuration is currently -> broken due to -> [this issue with their npm plugin](https://github.com/semantic-release/npm/pull/529) +> broken due to an issue with their npm plugin (see [semantic-release/npm#529]) +> [this issue with their npm plugin]. Take a look at [patch-semantic-commit.js] +> in this repository for a workaround. + +[semantic-release/npm#529]: https://github.com/semantic-release/npm/pull/529 +[patch-semantic-commit.js]: + https://github.com/hongaar/moker/blob/main/scripts/patch-semantic-commit.js ## `typescript` _workspace_ @@ -257,7 +295,6 @@ Contributions are very welcome! - [ ] github-actions plugin - [ ] devcontainer plugin -- [ ] semantic-release plugin - [ ] leasot (todos) plugin - [ ] doctoc plugin - [ ] Add LICENSE file to monorepo @@ -266,6 +303,7 @@ Contributions are very welcome! - [ ] Adapt for non-monorepo use-cases (?) - [ ] Blog post / tutorial - [ ] Docs for writing custom plugins / templates +- [x] semantic-release plugin - [x] Port templates - [x] Support for BYO plugins/templates - [x] Remove plugins diff --git a/package.json b/package.json index 26e7aab4..c83b5eef 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "plugins": [ "husky", "lint-staged", - "prettier" + "prettier", + "semantic-release", + "github-actions" ] }, "packageManager": "yarn@3.2.4", @@ -25,19 +27,21 @@ "watch:test": "yarn workspaces foreach --parallel --interlaced run watch:test", "postinstall": "husky install && node scripts/postinstall.js", "format": "prettier --write --ignore-unknown .", + "format-check": "prettier --check --ignore-unknown .", "doctoc": "doctoc README.md", - "leasot": "leasot --exit-nicely --reporter markdown --ignore \"**/node_modules\" \"**/*.ts\" > TODO.md" + "leasot": "leasot --exit-nicely --reporter markdown --ignore \"**/node_modules\" \"**/*.ts\" > TODO.md", + "release": "semantic-release" }, "devDependencies": { - "@semantic-release/changelog": "^6.0.1", - "@semantic-release/git": "^10.0.1", + "@semantic-release/changelog": "6.0.1", + "@semantic-release/git": "10.0.1", "@types/prettier": "^2", "doctoc": "^2.2.1", "husky": "8.0.2", "leasot": "^13.2.0", "lint-staged": "13.0.3", "prettier": "2.7.1", - "semantic-release": "^19.0.5" + "semantic-release": "19.0.5" }, "lint-staged": { "*": "prettier --write --ignore-unknown" diff --git a/packages/core/src/file.ts b/packages/core/src/file.ts index 13f56b93..1dd4b2cd 100644 --- a/packages/core/src/file.ts +++ b/packages/core/src/file.ts @@ -15,6 +15,10 @@ export async function writeFile({ return fs.promises.writeFile(path, `${contents.trim()}${os.EOL}`, "utf8"); } +export async function copyFile({ from, to }: { from: string; to: string }) { + return fs.promises.copyFile(from, to); +} + export async function removeFile({ path }: { path: string }) { return fs.promises.rm(path); } diff --git a/packages/core/src/monorepo.ts b/packages/core/src/monorepo.ts index d1044caa..98d812ec 100644 --- a/packages/core/src/monorepo.ts +++ b/packages/core/src/monorepo.ts @@ -57,7 +57,8 @@ export async function createMonorepo({ plugins: [], }, scripts: { - publish: "yarn publish", + build: "echo 'not implemented'", + test: "echo 'not implemented'", }, }, }); diff --git a/packages/core/src/workspace.ts b/packages/core/src/workspace.ts index 0ac2c0b1..69d38469 100644 --- a/packages/core/src/workspace.ts +++ b/packages/core/src/workspace.ts @@ -48,6 +48,9 @@ export async function addWorkspace({ }, } : {}), + moker: { + plugins: [], + }, }, }); diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 141c1dd0..f9db5be2 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -13,7 +13,8 @@ "types": "types/index.d.ts", "files": [ "dist", - "types" + "types", + "static" ], "scripts": { "prepublishOnly": "yarn build", diff --git a/packages/plugins/src/githubActions/githubActions.ts b/packages/plugins/src/githubActions/githubActions.ts index 590b1c56..9187005f 100644 --- a/packages/plugins/src/githubActions/githubActions.ts +++ b/packages/plugins/src/githubActions/githubActions.ts @@ -1,15 +1,63 @@ -import { PluginType } from "@mokr/core"; +import { + copyFile, + createDirectory, + hasPlugin, + PluginArgs, + PluginType, + removeDirectory, +} from "@mokr/core"; +import { join } from "node:path"; +import { URL } from "node:url"; -async function install() { - // jest -> test workflow - // semanticRelease -> release workflow - // prettier -> lint workflow - // typescript -> build workflow +const WORKFLOWS_DIRECTORY = ".github/workflows"; + +async function install({ directory }: PluginArgs) { + const workflowDirectory = join(directory, WORKFLOWS_DIRECTORY); + + await createDirectory({ directory: workflowDirectory }); + + // Workspaces + + // Since there's currently no way to detect plugins in workspaces, we always + // install the build and test workflows. + + // We could build plugin detection in workspaces, but then we would need to + // load all plugins at the monorepo level when we modify plugins at the + // workspace level + + await copyFile({ + from: new URL("../../static/ci.yml", import.meta.url).pathname, + to: join(workflowDirectory, "ci.yml"), + }); } -async function remove() {} +async function remove({ directory }: PluginArgs) { + const workflowDirectory = join(directory, WORKFLOWS_DIRECTORY); -async function load() {} + await removeDirectory({ directory: workflowDirectory }); +} + +async function load({ directory }: PluginArgs) { + const workflowDirectory = join(directory, WORKFLOWS_DIRECTORY); + + await createDirectory({ directory: workflowDirectory }); + + // Monorepo plugins + + if (await hasPlugin({ directory, name: "semanticRelease" })) { + await copyFile({ + from: new URL("../../static/release.yml", import.meta.url).pathname, + to: join(workflowDirectory, "release.yml"), + }); + } + + if (await hasPlugin({ directory, name: "prettier" })) { + await copyFile({ + from: new URL("../../static/format-check.yml", import.meta.url).pathname, + to: join(workflowDirectory, "format-check.yml"), + }); + } +} export const githubActions = { type: PluginType.Monorepo, diff --git a/packages/plugins/src/index.ts b/packages/plugins/src/index.ts index dd7451d8..4f09f1be 100644 --- a/packages/plugins/src/index.ts +++ b/packages/plugins/src/index.ts @@ -1,3 +1,4 @@ +export * from "./githubActions/githubActions.js"; export * from "./husky/husky.js"; export * from "./jest/jest.js"; export * from "./lintStaged/lintStaged.js"; diff --git a/packages/plugins/src/prettier/prettier.ts b/packages/plugins/src/prettier/prettier.ts index a1a43418..c13aae40 100644 --- a/packages/plugins/src/prettier/prettier.ts +++ b/packages/plugins/src/prettier/prettier.ts @@ -17,7 +17,7 @@ const PRETTIER_IGNORE = [ ".yarn", "", "# artifacts", - "lib/", + "dist/", "types/", "", ]; @@ -41,6 +41,7 @@ async function install({ directory }: PluginArgs) { data: { scripts: { format: "prettier --write --ignore-unknown .", + "format-check": "prettier --check --ignore-unknown .", }, }, }); diff --git a/packages/plugins/src/semanticRelease/semanticRelease.ts b/packages/plugins/src/semanticRelease/semanticRelease.ts index a007cecd..2dffd0f6 100644 --- a/packages/plugins/src/semanticRelease/semanticRelease.ts +++ b/packages/plugins/src/semanticRelease/semanticRelease.ts @@ -7,6 +7,7 @@ import { removeFile, writeFile, writeJson, + writePackage, } from "@mokr/core"; import { join } from "node:path"; @@ -57,6 +58,15 @@ async function install({ directory }: PluginArgs) { contents: NPMRC, }); + await writePackage({ + directory, + data: { + scripts: { + release: "semantic-release", + }, + }, + }); + logInfo( `semantic-release in our monorepo configuration is currently broken due to https://github.com/semantic-release/npm/pull/529` ); diff --git a/packages/plugins/static/ci.yml b/packages/plugins/static/ci.yml new file mode 100644 index 00000000..85e612f4 --- /dev/null +++ b/packages/plugins/static/ci.yml @@ -0,0 +1,32 @@ +name: ci + +on: + push: + branches: + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - node-version: 14 + node-lts: true + - node-version: 16 + node-lts: true + - node-version: 18 + node-lts: true + - node-version: 19 + node-lts: false + continue-on-error: ${{ ! matrix.node-lts }} + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: yarn + - run: yarn install + - run: yarn build + - run: yarn test diff --git a/packages/plugins/static/format-check.yml b/packages/plugins/static/format-check.yml new file mode 100644 index 00000000..dfe5e8d8 --- /dev/null +++ b/packages/plugins/static/format-check.yml @@ -0,0 +1,19 @@ +name: format-check + +on: + push: + branches: + - main + +jobs: + release: + name: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + cache: yarn + - run: yarn install + - run: yarn format-check diff --git a/packages/plugins/static/release.yml b/packages/plugins/static/release.yml new file mode 100644 index 00000000..3e8bac39 --- /dev/null +++ b/packages/plugins/static/release.yml @@ -0,0 +1,23 @@ +name: release + +on: + push: + branches: + - main + +jobs: + release: + name: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: lts/* + cache: yarn + - run: yarn install + - run: yarn build + - run: yarn release + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/yarn.lock b/yarn.lock index 6dfd7119..b1f7fed4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -81,15 +81,15 @@ __metadata: version: 0.0.0-use.local resolution: "@mokr/root@workspace:." dependencies: - "@semantic-release/changelog": ^6.0.1 - "@semantic-release/git": ^10.0.1 + "@semantic-release/changelog": 6.0.1 + "@semantic-release/git": 10.0.1 "@types/prettier": ^2 doctoc: ^2.2.1 husky: 8.0.2 leasot: ^13.2.0 lint-staged: 13.0.3 prettier: 2.7.1 - semantic-release: ^19.0.5 + semantic-release: 19.0.5 languageName: unknown linkType: soft @@ -469,7 +469,7 @@ __metadata: languageName: node linkType: hard -"@semantic-release/changelog@npm:^6.0.1": +"@semantic-release/changelog@npm:6.0.1": version: 6.0.1 resolution: "@semantic-release/changelog@npm:6.0.1" dependencies: @@ -507,7 +507,7 @@ __metadata: languageName: node linkType: hard -"@semantic-release/git@npm:^10.0.1": +"@semantic-release/git@npm:10.0.1": version: 10.0.1 resolution: "@semantic-release/git@npm:10.0.1" dependencies: @@ -4788,7 +4788,7 @@ __metadata: languageName: node linkType: hard -"semantic-release@npm:^19.0.5": +"semantic-release@npm:19.0.5": version: 19.0.5 resolution: "semantic-release@npm:19.0.5" dependencies: