├── .gitattributes ├── .github └── workflows │ ├── deploy-docs-beta.yml │ ├── deploy-docs.yml │ ├── lint.yml │ └── publish.yml ├── .gitignore ├── .hooks ├── commit-msg.json ├── partials │ └── .gitkeep └── shared │ └── .gitkeep ├── .mookme.json ├── LICENSE ├── MIGRATION.md ├── README.md ├── assets ├── banner.png └── demo.gif ├── commitlint.config.js ├── package-lock.json ├── package.json └── packages ├── docs ├── .gitignore ├── .hooks │ └── pre-commit.json ├── .markdownlint.json ├── .vscode │ └── settings.json ├── .vuepress │ ├── config.js │ └── public │ │ ├── Black on White.png │ │ ├── Grayscale on Transparent.png │ │ ├── Monochrome on Transparent.png │ │ ├── Original on Transparent.png │ │ ├── White on Black.png │ │ ├── White on Transparent.png │ │ ├── banner.png │ │ ├── demo.gif │ │ ├── favicon.ico │ │ ├── inspect-results.png │ │ ├── logo-squared.png │ │ └── logo.png ├── README.md ├── examples │ └── README.md ├── features │ └── README.md ├── get-started │ └── README.md ├── package-lock.json ├── package.json └── references │ └── README.md └── mookme ├── .eslintrc ├── .gitignore ├── .hooks └── pre-commit.json ├── .npmignore ├── .prettierrc ├── .releaserc ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── index.js ├── package-lock.json ├── package.json ├── src ├── commands │ ├── index.ts │ ├── init.ts │ ├── inspect.ts │ └── run.ts ├── config │ ├── index.ts │ └── types.ts ├── debug.ts ├── events │ ├── bus.ts │ ├── events.ts │ └── index.ts ├── executor │ ├── index.ts │ ├── package-executor.ts │ └── step-executor.ts ├── index.ts ├── loaders │ ├── filter-strategies │ │ ├── base-filter.ts │ │ ├── current-commit-filter.ts │ │ ├── from-to.filter.ts │ │ ├── list-of-files-filter.ts │ │ ├── not-pushed-files-filter.ts │ │ └── previous-commit-filter.ts │ └── hooks-resolver.ts ├── prompts │ └── init.ts ├── runner │ ├── init.ts │ ├── inspect.ts │ └── run.ts ├── types │ ├── hook.types.ts │ ├── status.types.ts │ └── step.types.ts ├── ui │ ├── index.ts │ ├── renderers │ │ ├── fancy-renderer.ts │ │ ├── noclear-renderer.ts │ │ └── renderer.ts │ ├── types.ts │ └── writer.ts └── utils │ ├── git.ts │ ├── logger.ts │ ├── root-dir.ts │ └── run-helpers.ts ├── tests ├── cases │ ├── non-existant-command.sh │ ├── pre-commit-fails.sh │ ├── pre-commit-succeeds.sh │ └── use-local-hook.sh ├── logs │ └── .gitkeep └── test.sh └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto -------------------------------------------------------------------------------- /.github/workflows/deploy-docs-beta.yml: -------------------------------------------------------------------------------- 1 | name: Deploy documentation 2 | on: 3 | push: 4 | branches: 5 | - beta 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout source code 12 | uses: actions/checkout@master 13 | 14 | - name: Cache node modules 15 | uses: actions/cache@v1 16 | with: 17 | path: node_modules 18 | key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }} 19 | restore-keys: | 20 | ${{ runner.OS }}-build- 21 | ${{ runner.OS }}- 22 | - name: Install 23 | run: | 24 | cd packages/docs 25 | npm install 26 | 27 | - name: Build 28 | run: | 29 | cd packages/docs 30 | npm run build 31 | 32 | - name: Deploy 33 | env: 34 | AWS_ACCESS_KEY_ID: ${{ secrets.BETA_AWS_ACCESS_KEY_ID }} 35 | AWS_SECRET_ACCESS_KEY: ${{ secrets.BETA_AWS_SECRET_ACCESS_KEY }} 36 | AWS_CLOUDFRONT_ID: ${{ secrets.BETA_DOCS_CLOUDFRONT_ID }} 37 | AWS_DEFAULT_REGION: us-east-1 38 | AWS_DEFAULT_OUTPUT: json 39 | run: | 40 | aws s3 sync packages/docs/.vuepress/dist s3://mookme-documentation-beta --delete 41 | aws cloudfront create-invalidation --distribution-id $AWS_CLOUDFRONT_ID --paths "/*" -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy documentation 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout source code 12 | uses: actions/checkout@master 13 | 14 | - name: Cache node modules 15 | uses: actions/cache@v1 16 | with: 17 | path: node_modules 18 | key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }} 19 | restore-keys: | 20 | ${{ runner.OS }}-build- 21 | ${{ runner.OS }}- 22 | - name: Install 23 | run: | 24 | cd packages/docs 25 | npm install 26 | 27 | - name: Build 28 | run: | 29 | cd packages/docs 30 | npm run build 31 | 32 | - name: Deploy 33 | env: 34 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 35 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 36 | AWS_CLOUDFRONT_ID: ${{ secrets.DOCS_CLOUDFRONT_ID }} 37 | AWS_DEFAULT_REGION: us-east-1 38 | AWS_DEFAULT_OUTPUT: json 39 | run: | 40 | aws s3 sync packages/docs/.vuepress/dist s3://mookme-documentation --delete 41 | aws cloudfront create-invalidation --distribution-id $AWS_CLOUDFRONT_ID --paths "/*" -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Unit Testing 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | - beta 11 | pull_request: 12 | branches: 13 | - main 14 | - beta 15 | 16 | jobs: 17 | lint-mookme: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Use Node.js 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: '16' 25 | - name: lint 26 | run: | 27 | cd packages/mookme 28 | npm install 29 | npm run eslint 30 | npm run prettier 31 | 32 | lint-docs: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v2 36 | - name: Use Node.js 37 | uses: actions/setup-node@v2 38 | with: 39 | node-version: '16' 40 | - name: lint 41 | run: | 42 | cd packages/docs 43 | npm install 44 | npm run lint 45 | 46 | test-mookme: 47 | runs-on: ubuntu-latest 48 | steps: 49 | - uses: actions/checkout@v2 50 | - name: Use Node.js 51 | uses: actions/setup-node@v2 52 | with: 53 | node-version: '16' 54 | - name: test 55 | run: | 56 | cd packages/mookme 57 | npm install 58 | ./tests/test.sh 59 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Release npm package 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - none 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout source code 13 | uses: actions/checkout@v2 14 | with: 15 | persist-credentials: false 16 | token: ${{ secrets.ACCESS_TOKEN }} 17 | 18 | 19 | - name: Cache node modules 20 | uses: actions/cache@v1 21 | with: 22 | path: node_modules 23 | key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }} 24 | restore-keys: | 25 | ${{ runner.OS }}-build- 26 | ${{ runner.OS }}- 27 | 28 | - name: Install 29 | run: | 30 | npm install 31 | cd packages/mookme 32 | npm install 33 | 34 | - name: Build 35 | run: | 36 | cd packages/mookme 37 | npm run build 38 | 39 | - name: Publish 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 42 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 43 | run: | 44 | cd packages/mookme 45 | npx semantic-release -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.hooks/commit-msg.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [ 3 | { 4 | "name": "commit lint", 5 | "command": "npx commitlint --edit" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.hooks/partials/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/.hooks/partials/.gitkeep -------------------------------------------------------------------------------- /.hooks/shared/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/.hooks/shared/.gitkeep -------------------------------------------------------------------------------- /.mookme.json: -------------------------------------------------------------------------------- 1 | { 2 | "addedBehavior": "exit", 3 | "noClearRenderer": true 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Escape Technologies Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | # Migration Guide: From Mookme to Gookme 2 | 3 | Mookme has been officially discontinued and replaced by Gookme, a Golang-based CLI for managing Git hooks. This guide will help you transition smoothly from Mookme to Gookme. 4 | 5 | ## Why the Change? 6 | 7 | - Simpler Installation: No dependency on package.json or npm. Gookme is a binary executable. 8 | - Better Cross-Platform Support: Gookme works seamlessly on Windows, macOS, and Linux. 9 | - Future Development: Gookme will continue to receive updates and new features, unlike Mookme. 10 | 11 | ## Key Differences 12 | 13 | | Feature | Mookme | Gookme 14 | |---------|--------|--------| 15 | | Installation | Requires Node.js and npm | Prebuilt binary, no dependencies 16 | | Hook Scripts | Written in any language but tied to JS or Python | Language-agnostic and independent 17 | | Platforms | Linux, macOS (Windows support tricky) | Full cross-platform support 18 | 19 | ## Installation of Gookme 20 | 21 | ### 1. Download Gookme 22 | 23 | See [the Gookme documentation](https://lmaxence.github.io/gookme/getting-started/) 24 | 25 | ### 2. Verify Installation 26 | 27 | ```bash 28 | gookme --version 29 | ``` 30 | 31 | You should see the installed version. 32 | 33 | ## Global configuration 34 | 35 | Gookme uses a global configuration file to store settings. You can create this file by running: 36 | 37 | ```bash 38 | touch ~/.gookme.json 39 | ``` 40 | 41 | - The `addedBehavior` parameter has been renamed `onModifiedFiles` 42 | - The `noClearRenderer` parameter has been removed 43 | 44 | ## Updating Hooks 45 | 46 | Gookme handles hooks similarly to Mookme but hook files are lightly different. This will allow you to have concurrent installations of Mookme and Gookme, and progressively migrate your hooks to Gookme. 47 | 48 | ## Step 1: Remove Mookme Hooks 49 | 50 | Run the following to clean up existing hooks: 51 | 52 | ```bash 53 | #!/bin/bash 54 | 55 | # Check if the .git/hooks directory exists 56 | if [ ! -d ".git/hooks" ]; then 57 | echo "Error: .git/hooks directory not found!" 58 | exit 1 59 | fi 60 | 61 | # Iterate over all files in the .git/hooks directory 62 | for file in .git/hooks/*; do 63 | # Check if the file is a regular file (not a directory or special file) 64 | if [ -f "$file" ]; then 65 | # Use sed to remove lines starting with 'npx mookme run' 66 | sed -i '/^npx mookme run/d' "$file" 67 | echo "Processed $file" 68 | fi 69 | done 70 | 71 | echo "All lines starting with 'npx mookme run' have been removed from .git/hooks files." 72 | ``` 73 | 74 | ## Step 2: Install Gookme Hooks 75 | 76 | Run the following to install Gookme-managed hooks: 77 | 78 | ```bash 79 | gookme init -t pre-commit,commit-msg # Add any other hooks you need 80 | ``` 81 | 82 | ## Step 3: Migrate Hook Scripts 83 | 84 | - Using Gookme, hooks are not stored in `.hooks` folders but in `hooks` folders. 85 | 86 | ```bash 87 | mv /path/to/.hooks /path/to/hooks 88 | ``` 89 | 90 | - Gookme hooks format is slightly different. 91 | 92 | However, Gookme will provide a JSON schema for you to check in a code editor if the hook is valid. 93 | 94 | The following is the `pre-commit.hook.json` hook file of Gookme: 95 | 96 | ```json 97 | { 98 | "$schema": "https://raw.githubusercontent.com/LMaxence/gookme/refs/heads/main/assets/schemas/hooks.schema.json", 99 | "steps": [ 100 | { 101 | "name": "Check format", 102 | "command": "./scripts/check-format.sh", 103 | "onlyOn": "*.go" 104 | }, 105 | { 106 | "name": "Code quality", 107 | "command": "golangci-lint run ./...", 108 | "onlyOn": "*.go" 109 | }, 110 | { 111 | "name": "Assets generation", 112 | "command": "make assets" 113 | } 114 | ] 115 | } 116 | ``` 117 | 118 | ## Further Support 119 | 120 | If you encounter issues or have questions: 121 | 122 | - Visit the [Gookme Documentation](https://lmaxence.github.io/gookme). 123 | - Open an issue in the [Gookme GitHub Repository](https://github.com/LMaxence/gookme/issues). 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MookMe 2 | 3 | *A simple and easy-to-use, yet powerful and language agnostic git hook for monorepos.* 4 | 5 | - **[see the documentation](https://mookme.org)** 6 | - **[see the *beta* documentation](https://beta.mookme.org)** 7 | 8 | ## Notice: Mookme is deprecated 9 | 10 | As of December 23rd 2024, Mookme has been officially discontinued in favor of [Gookme](https://github.com/LMaxence/gookme), a Golang CLI with a binary executable offering the same functionality. Mookme will no longer receive updates or support. Please refer to the [migration guide](https://github.com/Escape-Technologies/mookme/raw/refs/heads/beta/MIGRATION.md) for more information. 11 | 12 | The domain name mookme.org and the documentation website hosted on this domain will keep working until March 1st 2024. 13 | 14 | **After this date, the website will cease to exist and the repository as well as the npm package will be archived.** 15 | 16 | demo 17 | 18 | ## What you will find on this repository 19 | 20 | - Description of the repository structure 21 | - The entire source code associated with the Mookme project, including source code of CLI and the documentation page 22 | - A [roadmap of the project](https://github.com/Escape-Technologies/mookme/projects) in a Github project [WIP] 23 | - A list of [opened issues](https://github.com/Escape-Technologies/mookme/issues) and [merge requests](https://github.com/Escape-Technologies/mookme/pulls) 24 | - Development guidelines and contribution guide [WIP] 25 | 26 | ## What you will not find on this page 27 | 28 | - User documentation. See [the project's website](https://mookme.org) 29 | 30 | ## Repository structure 31 | 32 | The Mookme project is architectured around this monorepo. Every packages are stored under the `packages` folder. These include: 33 | 34 | - `/packages/mookme`: The NodeJs project for the CLI of Mookme 35 | - `/packages/docs`: A vuepress website holding the code of the user's documentation 36 | 37 | ## You are new and willing to contribute ? 38 | 39 | There are a [few small issues and improvements that you can look into](https://github.com/Escape-Technologies/mookme/labels/good%20first%20issue), that are designed and explained for first contributors. 40 | 41 | ## Release channels 42 | 43 | Mookme is released under a `main` channel and a `beta` channel. These both correspond to the `beta` and `main` branches of this repository. 44 | 45 | Releases are manual for the `beta` channel, automated on the `main` channel. 46 | -------------------------------------------------------------------------------- /assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/assets/banner.png -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/assets/demo.gif -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@escape.tech/mookme-repo", 3 | "version": "2.4.0-beta.1", 4 | "description": "The monorepo for Mookme", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/Escape-Technologies/mookme" 11 | }, 12 | "keywords": [ 13 | "pre", 14 | "commit", 15 | "pre-commit", 16 | "hook", 17 | "monorepos" 18 | ], 19 | "author": "lecanu.maxence@gmail.com", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/Escape-Technologies/mookme/issues" 23 | }, 24 | "homepage": "https://github.com/Escape-Technologies/mookme", 25 | "devDependencies": { 26 | "@commitlint/cli": "^17.0.0", 27 | "@commitlint/config-conventional": "^17.6.3", 28 | "@escape.tech/mookme": "^2.2.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vuepress/dist 3 | .hooks/*.local.json 4 | -------------------------------------------------------------------------------- /packages/docs/.hooks/pre-commit.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [{ 3 | "name": "markdownlint", 4 | "command": "npm run lint", 5 | "onlyOn": "**/*.md" 6 | }] 7 | } -------------------------------------------------------------------------------- /packages/docs/.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD013": false, 3 | "MD024": { 4 | "allow_different_nesting": true 5 | }, 6 | "MD033": { 7 | "allowed_elements": ["img"] 8 | } 9 | } -------------------------------------------------------------------------------- /packages/docs/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "spellright.language": [ 3 | "en" 4 | ], 5 | "spellright.documentTypes": [ 6 | "markdown", 7 | "latex", 8 | "plaintext" 9 | ] 10 | } -------------------------------------------------------------------------------- /packages/docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "", 3 | description: 4 | "The documentation of Mookme, a simple and easy-to-use, yet powerful and language agnostic git hook for monorepos.", 5 | head: [["link", { rel: "icon", href: "/favicon.ico" }]], 6 | smoothScroll: true, 7 | patterns: ["**/*.md", "**/*.vue"], 8 | themeConfig: { 9 | logo: "/logo.png", 10 | displayAllHeaders: true, 11 | nav: [ 12 | { text: "GitHub", link: "https://github.com/Escape-Technologies/mookme"}, 13 | { text: "Changelog", link: "https://github.com/Escape-Technologies/mookme/blob/beta/packages/mookme/CHANGELOG.md"} 14 | ], 15 | sidebar: [ 16 | "", 17 | "get-started/", 18 | "features/", 19 | "examples/", 20 | "references/" 21 | ], 22 | smoothScroll: true 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /packages/docs/.vuepress/public/Black on White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/packages/docs/.vuepress/public/Black on White.png -------------------------------------------------------------------------------- /packages/docs/.vuepress/public/Grayscale on Transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/packages/docs/.vuepress/public/Grayscale on Transparent.png -------------------------------------------------------------------------------- /packages/docs/.vuepress/public/Monochrome on Transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/packages/docs/.vuepress/public/Monochrome on Transparent.png -------------------------------------------------------------------------------- /packages/docs/.vuepress/public/Original on Transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/packages/docs/.vuepress/public/Original on Transparent.png -------------------------------------------------------------------------------- /packages/docs/.vuepress/public/White on Black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/packages/docs/.vuepress/public/White on Black.png -------------------------------------------------------------------------------- /packages/docs/.vuepress/public/White on Transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/packages/docs/.vuepress/public/White on Transparent.png -------------------------------------------------------------------------------- /packages/docs/.vuepress/public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/packages/docs/.vuepress/public/banner.png -------------------------------------------------------------------------------- /packages/docs/.vuepress/public/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/packages/docs/.vuepress/public/demo.gif -------------------------------------------------------------------------------- /packages/docs/.vuepress/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/packages/docs/.vuepress/public/favicon.ico -------------------------------------------------------------------------------- /packages/docs/.vuepress/public/inspect-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/packages/docs/.vuepress/public/inspect-results.png -------------------------------------------------------------------------------- /packages/docs/.vuepress/public/logo-squared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/packages/docs/.vuepress/public/logo-squared.png -------------------------------------------------------------------------------- /packages/docs/.vuepress/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/packages/docs/.vuepress/public/logo.png -------------------------------------------------------------------------------- /packages/docs/README.md: -------------------------------------------------------------------------------- 1 | # Welcome on Mookme 2 | 3 | *A simple and easy-to-use, yet powerful and language agnostic git hook for monorepos.* 4 | 5 | ::: danger 6 | As of December 23rd 2024, Mookme has been officially discontinued in favor of [Gookme](https://github.com/LMaxence/gookme), a Golang CLI with a binary executable offering the same functionality. Mookme will no longer receive updates or support. Please refer to the [migration guide](https://github.com/Escape-Technologies/mookme/raw/refs/heads/beta/MIGRATION.md) for more information. 7 | 8 | The domain name mookme.org and the documentation website hosted on this domain will keep working until March 1st 2024. 9 | 10 | **After this date, the website will cease to exist and the repository as well as the npm package will be archived.** 11 | ::: 12 | 13 | banner 14 | 15 | ## What is Mookme ? 16 | 17 | Mookme is a Git hook manager. It's sole purpose is to execute some scripts when you want to commit or do other git stuff. It could be a 18 | linter, tests, your favorite commit message checker. 19 | 20 | ::: tip 21 | **Everything that is invoked through a cli can be used with Mookme!** 22 | ::: 23 | 24 | You are welcome to use it and enjoy it's simplicity. 25 | **If you encounter any bug or weird behavior, don't be afraid to open an [issue](https://github.com/Escape-Technologies/mookme/issues) :)** 26 | 27 | ## How does it work ? 28 | 29 | 1. Mookme is invoked by a git hook script 30 | 2. Mookme looks for `.hooks` folders across your repository 31 | 3. For each detected folders, Mookme detects if there are any hooks defined for the git hook currently being executed 32 | 4. For each detected folders, Mookme detects if there are git-staged changes in this folder (only for pre-commit hooks) 33 | 5. If both conditions above are valid, Mookme runs concurrently (or not) the different commands provided in the hook file. 34 | 35 | ## Why not ... ? 36 | 37 | ### `bash` scripts 38 | 39 | **`bash` scripts directly written in my `.git/hooks` folder** 40 | 41 | - Even if it is true that, in the end, `Mookme` will do nothing more than invoking commands the exact same way a bash 42 | script would, the `.git/hooks` folder is a not a versioned one, *hence it will prevent you from sharing configuration*. 43 | - `Mookme` provides you with a way to version these hooks, and to share repository configuration among the rest of your team. 44 | - The hook setup is a one liner for the new developers landing in your team. It won't download anything, just write a 45 | small line in your `.git/hooks` files 46 | 47 | ### `pre-commit` *(our tool before developing Mookme)* 48 | 49 | We had several issues with `pre-commit`, that led us to develop our own tool : 50 | 51 | - pre-commit is not designed for monorepos, hence most of the hook are some sort of hacks 52 | - per-package environment were not easy to manage, because `pre-commit` has it's own global environment and we have to 53 | create global dependency to run a particular hook for one package. 54 | 55 | ::: warning 56 | This led us to one of the guideline used by `Mookme` to work: 57 | If we run a hook on a package in your monorepo it means: 58 | 59 | - that you have changes in the folder of this package 60 | - that you developed something on this package 61 | - that the dev environment of this package is okay 62 | - we can invoke your test/lint commands **as they are provided** without worrying about an environment 63 | properly setup 64 | ::: 65 | -------------------------------------------------------------------------------- /packages/docs/examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## `commitlint` 4 | 5 | > This guide will help you in the setup of a hook a hook for linting your commits with [commitlint](https://github.com/conventional-changelog/commitlint) 6 | 7 | ### Prerequisite 8 | 9 | - You have installed `Mookme` 10 | - You have installed and configured [commitlint](https://github.com/conventional-changelog/commitlint) 11 | - You have setup `Mookme` using `npx mookme init` (see [get started](../../README.md) if needed) 12 | 13 | ### Hook 14 | 15 | - In the global hooks folder of your project `/.hooks/commit-msg.json` add the following configuration : 16 | 17 | ```js 18 | { 19 | "steps": [ 20 | // ... 21 | // your other steps 22 | { 23 | "name": "commit lint", 24 | "command": "cat {args} | ./node_modules/@commitlint/cli/cli.js" 25 | } 26 | // ... 27 | // your other steps 28 | ] 29 | } 30 | ``` 31 | 32 | ## `eslint` 33 | 34 | > This guide will help you in the setup of a hook for linting your code with [eslint](https://eslint.org/)* 35 | 36 | ### Prerequisite 37 | 38 | - You have installed `mookme` 39 | - You have installed and configured [eslint](https://eslint.org/) 40 | - You have setup `mookme` using `npx mookme init` (see [get started](../../README.md) if needed) 41 | 42 | ### Hook 43 | 44 | - In the hooks folder of the package you want to lint with `eslint` `//.hooks/commit-msg` add 45 | the following configuration : 46 | 47 | ```js 48 | { 49 | "steps": [ 50 | // ... 51 | // your other steps 52 | { 53 | "name": "eslint", 54 | "command": "./node_modules/eslint/bin/eslint.js ." 55 | } 56 | // ... 57 | // your other steps 58 | ] 59 | } 60 | ``` 61 | 62 | *Alternative: setup a `npm` script and directly invoke `eslint` in the command field :* 63 | 64 | ```js 65 | { 66 | "steps": [ 67 | // ... 68 | // your other steps 69 | { 70 | "name": "eslint", 71 | "command": "npm run lint" // npm run lint -> eslint . in your package.json 72 | } 73 | // ... 74 | // your other steps 75 | ] 76 | } 77 | ``` 78 | 79 | ## `python` 80 | 81 | > This guide will help you in the setup of a python-based hook* 82 | 83 | ### Prerequisite 84 | 85 | - You have installed `Mookme` 86 | - You have setup `Mookme` using `npx mookme init` (see [get started](../../README.md) if needed) 87 | - You have a working virtual environment in your package 88 | 89 | ### Hook 90 | 91 | - In the hooks folder of your package or in the global hooks folder `//.hooks/pre-commit.json` 92 | or `//.hooks/pre-commit.json`, add the following configuration : 93 | 94 | ```js 95 | { 96 | "type": "python", 97 | "venvActivate": "./////" // ./.venv/bin/activate for instance, this will be sourced 98 | "steps": [ 99 | // ... 100 | // your other steps 101 | { 102 | "name": "some python stuff", 103 | "command": "mypy ." 104 | } 105 | // ... 106 | // your other steps 107 | ] 108 | } 109 | ``` 110 | -------------------------------------------------------------------------------- /packages/docs/features/README.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 | ## Reusable steps 4 | 5 | `Mookme` provides you with step-sharing features, allowing you to declare shared step example, and to use them in your steps. 6 | 7 | Given a project directory such as this: 8 | 9 | ```sh 10 | project-root 11 | |--- .mookme.json 12 | |--- .hooks 13 | |--- shared 14 | |--- flake8.json 15 | |--- packages 16 | |--- some-package 17 | |--- .hooks 18 | |--- pre-commit.json 19 | ``` 20 | 21 | ::: tip 22 | The `/.hooks/shared` is automatically generated with a `.gitkeep` file by `mookme init`. 23 | ::: 24 | 25 | You can declare a step in `/.hooks/shared/flake8.json`… 26 | 27 | ```json 28 | { 29 | "name": "Ensure Style (flake8)", 30 | "command": "flake8 $(python-module) --ignore E203,W503 --max-line-length 90", 31 | "onlyOn": "**/*.py" 32 | } 33 | ``` 34 | 35 | … and then re-use it in `some-package/.hooks/pre-commit.json` with the `from` keyword: 36 | 37 | ```json 38 | { 39 | "steps": [ 40 | { 41 | "from": "flake8" 42 | }, 43 | ... // some other steps 44 | ] 45 | } 46 | ``` 47 | 48 | ## Writing and using utils scripts 49 | 50 | It is possible to declare some scripts in the project root `Mookme` configuration, and then use them directly in the commands invoked by the steps. 51 | 52 | Given a project directory such as this: 53 | 54 | ```sh 55 | project-root 56 | |--- .mookme.json 57 | |--- .hooks 58 | |--- partials 59 | |--- pylint-changed-files 60 | |--- packages 61 | |--- some-package 62 | |--- .hooks 63 | |--- pre-commit.json 64 | ``` 65 | 66 | *Here is how the `python-changed-files` script looks like* 67 | 68 | ```bash 69 | #!/usr/bin/env bash 70 | git --no-pager diff --cached --name-only --diff-filter=AM --relative -- "***.py" | tr '\n' '\0' | xargs -0 "$@" 71 | ``` 72 | 73 | ::: tip 74 | The `/.hooks/partials` is automatically generated with a `.gitkeep` file by `npx mookme init`. 75 | ::: 76 | 77 | One can declare a script in flake8 (don't forget to `chmod+x`) and then re-use it in `some-package/.hooks/pre-commit.json` by directly invoking the script's name: 78 | 79 | ```json 80 | { 81 | "steps": [ 82 | { 83 | "name": "Run pylint but only on changed files", 84 | "command": "python-changed-files pylint" 85 | }, 86 | ... // some other steps 87 | ] 88 | } 89 | ``` 90 | 91 | ## Uncommited steps (gitignore) 92 | 93 | Mookme steps are shared by default. The point of the tool is to provide a shared git hooks configuration. However we understand that sometimes, one would like to have a custom mookme configuration. 94 | 95 | You can use {hook-type}.local.json` files that are located and formatted in the very same way that shared hook files are. 96 | 97 | For instance, with the following configuration: 98 | 99 | ```json 100 | // package1/.hooks/pre-commit.json 101 | { 102 | "steps": [ 103 | { 104 | "name": "common hook", 105 | "command": "npm run lint:fix" 106 | } 107 | ] 108 | } 109 | ``` 110 | 111 | ```json 112 | // package1/.hooks/pre-commit.local.json 113 | { 114 | "steps": [ 115 | { 116 | "name": "local hook", 117 | "command": "npm test" 118 | } 119 | ] 120 | } 121 | ``` 122 | 123 | You will run both setps when committing. The difference between these two files is that `package1/.hooks/pre-commit.local.json` is git-ignored by default through the command-line project initialization. 124 | 125 | ## Use a range of commits 126 | 127 | Using the Mookme CLI, it is possible to invoke a set of hooks and steps selected using the files changed between two git references. 128 | 129 | ````bash 130 | npx mookme run -t pre-commit --from HEAD~1 --to f9ff43 131 | npx mookme run -t pre-commit --from HEAD~25 --to d58688dd611ef01079f61ebae36df0ce8c380ddb 132 | ```` 133 | 134 | You can find more details about these options on the [mookme run reference](/references/#options-2) 135 | -------------------------------------------------------------------------------- /packages/docs/get-started/README.md: -------------------------------------------------------------------------------- 1 | # Get started 2 | 3 | ## Installation 4 | 5 | ```bash 6 | npm install @escape.tech/mookme 7 | ``` 8 | 9 | ### Requirements 10 | 11 | `Mookme` requires Node.js to work properly. 12 | 13 | ## Configuration 14 | 15 | Mookme will automatically detect the `.hooks` folder across your repository and trigger the command related to your current VCS state. 16 | 17 | Hence it only requires a very minimal configuration, as most of this is defined by where you place your `.hooks` folders, and what you put in them. 18 | 19 | ### First configuration 20 | 21 | **Case 1 : You are the first to configure `Mookme` on your project** 22 | 23 | ```bash 24 | npx @escape.tech/mookme init 25 | ``` 26 | 27 | This will display a prompter to let you define how you want the hooks to behave when a file is changed during commit hooks, write the corresponding documentation in your `package.json`, and write your `.git/hooks` scripts. 28 | 29 | Every step of this process is clearly shown and nothing will be written without asking you if you're okay with it :) 30 | 31 | ### Already-configured project 32 | 33 | **Case 2 : Someone in your team already configured `Mookme`** 34 | 35 | This will only write your `.git/hooks` scripts. 36 | 37 | ```bash 38 | npx @escape.tech/mookme init --only-hook 39 | ``` 40 | 41 | ## Writing your hooks 42 | 43 | ### Global structure of your project hooks 44 | 45 | `Mookme` is designed for monorepos, hence it assumes your project has a root folder where global hooks can be defined, 46 | and multiple packages where you can define per-package hook. 47 | 48 | ::: tip 49 | Hooks are written in a folder `.hooks` located at the root of your project and at the root of your packages' folders. 50 | 51 | **When using `Mookme` in a monorepo, you will have a project structure following this :** 52 | 53 | ```bash 54 | # where your .git is located 55 | |- .mookme.json 56 | |- .hooks # will always be executed when you commit 57 | | |- pre-commit.json # will be executed with the pre-commit git hook 58 | | |- commit-msg.json # will be executed with the commit-msg git hook 59 | | |- prepare-commit-msg.json 60 | | |- post-commit.json 61 | |- packages 62 | | |- package A 63 | | | |- .hooks # will be executed if you commit changes on package A 64 | | | | |- pre-commit.json 65 | | | | |- post-commit.json 66 | | |- package A 67 | | | |- .hooks # will be executed if you commit changes on package B 68 | | | | |- pre-commit.json 69 | ``` 70 | 71 | ::: 72 | 73 | With `mookme`, your hooks are stored in JSON files called `{hook-type}.json` where the hook type is one of the 74 | available git hooks, eg : 75 | 76 | - `pre-commit` 77 | - `prepare-commit-msg` 78 | - `commit-msg` 79 | - `post-commit` 80 | - `post-merge` 81 | - `post-rewrite` 82 | - `pre-rebase` 83 | - `post-checkout` 84 | - `pre-push` 85 | 86 | ::: warning 87 | The exit behavior is only applied on pre-commit hook types: `pre-commit`, `prepare-commit-msg`, `commit-msg`, `post-commit` 88 | ::: 89 | 90 | ::: warning 91 | If the command executed by a hook fails, it will prevent the git command to be executed. We recommend you to use the pre-receive hooks carefully, with relatively safe commands, otherwise you might prevent your team for doign stuff like `git pull` or `git fetch`. 92 | ::: 93 | 94 | ### How will Mookme decide which hooks to run ? 95 | 96 | 1. Based on the hook type being executed, Mookme will pick a strategy for selecting files concerned by an execution. For instance: 97 | 98 | - When running a `pre-commit` hook, Mookme will select the current staged files in the repository 99 | - When running a `post-commit` hook, Mookme will select the files commited in the last commit 100 | The result of this step is a list of relative paths from the root of the repository. 101 | 102 | 103 | 2. For each folder where a `.hooks` folder is found, Mookme will assess if there are file paths in the previous list matching the relative path to this folder from the root of the repository. For each matched package, the list of matched paths is attached to it, and the same paths, but relative from the package itself (where the `.hooks` folder is located) are attached to the step objects. 104 | 105 | 106 | 3. Other selections (`onlyOn` for instance) are applied on each step of each package, based on the list of paths attached to the package and it's steps. 107 | 108 | ### Example of hook file 109 | 110 | Your hooks are defined in simple json files. 111 | 112 | - For complete reference, see the [JSON hooks reference](/references/#hook-files) 113 | - For specific hook examples, see the [recipes](/examples) 114 | 115 | A hook defines a list of `steps`, which are basically commands to run, with a name for proper display. A few 116 | configuration option are available, but the minimal requirement is `name` and `command`. 117 | 118 | Here is an example that will run your commit message using `commitlint`. 119 | 120 | ```js 121 | # commit-msg.json 122 | { 123 | "steps": [{ 124 | "name": "commit lint", 125 | "command": "cat {args} | ./node_modules/@commitlint/cli/cli.js" 126 | }] 127 | } 128 | ``` 129 | 130 | ::: tip 131 | When writing package-scoped hooks, the current working directory assumed by `Mookme` is the folder where this 132 | package's `.hooks'` folder is located 133 | ::: 134 | 135 | ::: warning 136 | `{args}` are replaced with the hook arguments when the command is executed. See [the Git documentation on hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) 137 | ::: 138 | 139 | **More examples to [get you started](./docs/hooks-examples/index.md)**! 140 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@escape.tech/mookme-docs", 3 | "version": "1.0.0", 4 | "description": "The documentation of mookme", 5 | "scripts": { 6 | "dev": "vuepress dev .", 7 | "build": "vuepress build .", 8 | "lint": "markdownlint -c .markdownlint.json . -i node_modules" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Escape-Technologies/mookme" 13 | }, 14 | "keywords": [ 15 | "pre", 16 | "commit", 17 | "pre-commit", 18 | "hook", 19 | "monorepos" 20 | ], 21 | "author": "lecanu.maxence@gmail.com", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/Escape-Technologies/mookme/issues" 25 | }, 26 | "homepage": "https://github.com/Escape-Technologies/mookme", 27 | "devDependencies": { 28 | "eslint": "^7.26.0", 29 | "eslint-config-prettier": "8.3.0", 30 | "eslint-plugin-import": "^2.22.1", 31 | "eslint-plugin-prettier": "3.4.0", 32 | "markdownlint-cli": "^0.27.1", 33 | "prettier": "2.3.0", 34 | "vuepress": "^1.8.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/docs/references/README.md: -------------------------------------------------------------------------------- 1 | # CLI & references 2 | 3 | ## `mookme init` 4 | 5 | The main initialization command. It : 6 | 7 | - **asks** you to select a behavior to observe when the staged files are updated by your git hooks 8 | - **asks** you to select one or multiple git event to hook 9 | - **creates** the `.hooks` folder in each package where you can write **dedicated hooks!** that will be triggered only 10 | when changes in this package occur 11 | - **creates** a `.hooks` folder at the root of your project where you can write **project-wide hooks** that will be 12 | triggered on every commit 13 | - **writes** `.git/hooks` files 14 | - **writes** into the `.gitignore` files of your root project and specified packages to ignore [local steps](/features/#uncommited-steps-gitignore) 15 | 16 | ### Options 17 | 18 | - `--only-hook` (optional) 19 | 20 | Skip prompters and only write `.git/hooks` files. This is for installation in an already-configured project. 21 | 22 | - `--skip-types-selection` (optional) 23 | 24 | Skip hook types selection and hook every git event. 25 | 26 | ## `mookme run` 27 | 28 | Mainly used for debugging and dry run : 29 | 30 | `mookme run --type pre-commit --args "test arguments"` 31 | 32 | ### Options 33 | 34 | - `-t --type` (required) 35 | 36 | The type of hook to run, has to be one of `pre-commit`, `prepare-commit-msg`, `commit-msg`, `post-commit`. 37 | 38 | - `-a --args` (optional) 39 | 40 | The arguments that would be normally passed by git to the hook 41 | 42 | - `-r --run-all` (optional) 43 | 44 | Skip the selection of hooks to run based on git-staged files, and run hooks of every package for this type 45 | 46 | - `--from` (optional) 47 | 48 | Starting git reference used to evaluate hooks to run. If set, `to` has to be set as well, otherwise this option is ignored. 49 | 50 | - `--to` (optional) 51 | 52 | Ending git reference used to evaluate hooks to run. If set, `from` has to be set as well, otherwise this option is ignored. 53 | 54 | - `--config-root` (optional) 55 | 56 | The path of the folder where the mookme configuration file (`.mookme.json`) is stored 57 | 58 | ## `mookme inspect` 59 | 60 | Manually test wich packages are discovered and assess if your hooks are properly configured. 61 | 62 | `mookme inspect --type pre-commit` 63 | 64 | inspect-results 65 | 66 | ### Options 67 | 68 | - `-t --type` (required) 69 | 70 | The type of hook to inspect, has to be one of `pre-commit`, `prepare-commit-msg`, `commit-msg`, `post-commit`. 71 | 72 | - `-f --files` (optional) 73 | 74 | A list of files paths to inspect. Paths must be relative from the repository root. 75 | 76 | ## Hook files 77 | 78 | ### General description 79 | 80 | See [Writing your hooks](/get-started/#writing-your-hooks) 81 | 82 | ### Available options 83 | 84 | - `steps` 85 | 86 | The list of steps (commands) being executed by this hook. In a step you can define : 87 | 88 | #### Step options 89 | 90 | | Option | Description | Required | 91 | | ------------- | ------------- | ------| 92 | | `name` | The name that will be given to this step | yes | 93 | | `cmd` | The command invoked at this step | yes | 94 | | `onlyOn` | A shell wildcard (or a list of wildcard) conditioning the execution of the step based on modified files | no | 95 | | `serial` | A boolean value describing if the package hook execution should await for the step to end | no | 96 | | `from` | Extend a shared step | no | 97 | 98 | ::: warning 99 | A serial step that fails will not prevent the execution of the following steps 100 | ::: 101 | 102 | ::: warning 103 | The pattern provided in `onlyOn` will be matched agains the relative path of matched files of the execution, from the package folder, not from the repository root. 104 | ::: 105 | 106 | - `type` 107 | 108 | A flag used mainly to tell `mookme` this is a python hook, and might need a virtual environment to be activated. Possible values are `python, js, script` 109 | 110 | - `venvActivate` 111 | 112 | A path to a `/bin/activate` script to execute before the command if the hook type is `python` 113 | 114 | ### Available arguments 115 | 116 | A set of arguments is provided by Mookme, that can be directly used in the hooks command definitions using the following syntax in the step definition: 117 | 118 | ````json 119 | { 120 | "command": "run-something {args}" // Will be replaced with the value of `args` 121 | } 122 | ```` 123 | 124 | - `args` 125 | 126 | The argument being passed by git to the hook file. See [the Git documentation on the hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) for more details about what it contains depending on the hook type being executed. 127 | 128 | - `packageFiles` 129 | 130 | The list of files being matched by the package filtering strategy, and responsible for it being ran in this execution. The files are separated by a blank space (" "). See [the section about how Mookme selects packages to run](/get-started/#how-will-mookme-decide-which-hooks-to-run) for more details on how the files passed in this are selected. 131 | 132 | ::: warning 133 | The paths are relative from the repository root folder 134 | ::: 135 | 136 | - `matchedFiles` 137 | 138 | The list of files being matched by the step filtering strategy, and responsible for it being ran in this execution. The files are separated by a blank space (" "). See [the section about how Mookme selects packages to run](/get-started/#how-will-mookme-decide-which-hooks-to-run) for more details on how the files passed in this are selected. 139 | 140 | ::: warning 141 | The paths are relative from the package folder (eg, the folder where the `.hooks` folder was detected) 142 | ::: 143 | -------------------------------------------------------------------------------- /packages/mookme/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint", "prettier", "eslint-plugin-tsdoc"], 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:prettier/recommended", 8 | "prettier", 9 | "plugin:@typescript-eslint/recommended" 10 | ], 11 | "rules": { 12 | "semi": [2, "always"], 13 | "prettier/prettier": "error", 14 | "@typescript-eslint/indent": ["error", 2], 15 | "@typescript-eslint/no-unused-vars": "error", 16 | "@typescript-eslint/no-explicit-any": "error", 17 | "tsdoc/syntax": "warn" 18 | 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /packages/mookme/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | coverage 5 | tests/logs/*.log 6 | .hooks/*.local.json 7 | -------------------------------------------------------------------------------- /packages/mookme/.hooks/pre-commit.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [{ 3 | "name": "eslint", 4 | "command": "npm run eslint", 5 | "onlyOn": "src/**/*.ts" 6 | }, { 7 | "name": "prettier", 8 | "command": "npm run prettier" 9 | }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/mookme/.npmignore: -------------------------------------------------------------------------------- 1 | .hooks 2 | .tmp 3 | .vscode 4 | .eslintrc 5 | .gitignore 6 | .prettierrc 7 | assets 8 | tsconfig.json 9 | README.md 10 | .releaserc 11 | -------------------------------------------------------------------------------- /packages/mookme/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "typescript", 3 | "printWidth": 120, 4 | "singleQuote": true, 5 | "trailingComma": "all" 6 | } -------------------------------------------------------------------------------- /packages/mookme/.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "main", 4 | {"name": "beta", "channel": "beta", "prerelease": "beta"} 5 | ], 6 | "repositoryUrl": "git@github.com:Escape-Technologies/mookme.git", 7 | "dryRun": false, 8 | "plugins": [ 9 | "@semantic-release/commit-analyzer", 10 | "@semantic-release/changelog", 11 | "@semantic-release/release-notes-generator", 12 | "@semantic-release/npm", 13 | [ 14 | "@semantic-release/git", 15 | { 16 | "assets": [ 17 | "CHANGELOG.md", 18 | "package.json", 19 | "package-lock.json" 20 | ], 21 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 22 | } 23 | ] 24 | ] 25 | } -------------------------------------------------------------------------------- /packages/mookme/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "davidanson.vscode-markdownlint", "esbenp.prettier-vscode"] 3 | } -------------------------------------------------------------------------------- /packages/mookme/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.alwaysShowStatus": true, 3 | "eslint.format.enable": true, 4 | "eslint.lintTask.enable": true, 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": true, 7 | "source.fixAll.markdownlint": true 8 | }, 9 | "spellright.language": [ 10 | "en" 11 | ], 12 | "spellright.documentTypes": [ 13 | "markdown", 14 | "latex", 15 | "plaintext" 16 | ], 17 | "[json]": { 18 | "editor.defaultFormatter": "vscode.json-language-features" 19 | } 20 | } -------------------------------------------------------------------------------- /packages/mookme/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [1.3.0-beta.1](https://github.com/Escape-Technologies/mookme/compare/v1.2.0...v1.3.0-beta.1) (2022-01-28) 2 | 3 | 4 | ### Features 5 | 6 | * **mooke:** rewrite UI module for an event-based architecture ([49e9650](https://github.com/Escape-Technologies/mookme/commit/49e9650c27cd74bb21306d24648d1b3b7d2dd21f)) 7 | * **mookme:** improve event-based rendering by removing the loop ([39d659a](https://github.com/Escape-Technologies/mookme/commit/39d659ac8f81f2232a41fa0757a282db1916f4aa)) 8 | 9 | # [1.2.0](https://github.com/Escape-Technologies/mookme/compare/v1.1.0...v1.2.0) (2022-01-14) 10 | 11 | ### Bug Fixes 12 | 13 | * **ci:** fix release ci ([e318c32](https://github.com/Escape-Technologies/mookme/commit/e318c328181d3f9615abb352655c3b8b1f47d73e)) 14 | * **docs:** add changelog in docs ([a4fbe77](https://github.com/Escape-Technologies/mookme/commit/a4fbe77f02fa64a4b9bd94b41211dec085da6cdb)) 15 | * **mookme:** add beta documentation in readme ([94f97f2](https://github.com/Escape-Technologies/mookme/commit/94f97f250fd33e4fa98a247219efdea9aa806128)) 16 | * **mookme:** add readme in JSON pkg ([f713d0e](https://github.com/Escape-Technologies/mookme/commit/f713d0e3a277a8eb23b7ee92cbefb84f6a60fbd4)) 17 | * **mookme:** bump mookme version used in project ([e8f490f](https://github.com/Escape-Technologies/mookme/commit/e8f490f04711ee41572aa942d891a48056af27c1)) 18 | * **mookme:** clean hub's dead code ([01b888a](https://github.com/Escape-Technologies/mookme/commit/01b888a759ded4b34feb2a6e51153a951db80ba9)) 19 | * **mookme:** fix add-pkg command ([6d25077](https://github.com/Escape-Technologies/mookme/commit/6d25077f6a0a36cd8a9e50c046999f0c0d93cdbd)) 20 | * **mookme:** fix dependencies ([11fa8a3](https://github.com/Escape-Technologies/mookme/commit/11fa8a38383f0730980a4c5e2b31a4ec56425d3d)) 21 | * **mookme:** fix docs deployment ([0c49fa6](https://github.com/Escape-Technologies/mookme/commit/0c49fa6583d800914a6589bf6672f7e37130d1d1)) 22 | * **mookme:** fix docs deployment ([4f35f5f](https://github.com/Escape-Technologies/mookme/commit/4f35f5f59d7237d654fb56ef694524ee988cb47c)) 23 | * **mookme:** fix init command to log every file modification ([1069ff3](https://github.com/Escape-Technologies/mookme/commit/1069ff3afb92f7932a8bba91d432068ba482bb7b)) 24 | * **mookme:** fix init for local hooks ([b056550](https://github.com/Escape-Technologies/mookme/commit/b0565502b7d011c3103015f89c6fe3a2aec097c1)) 25 | * **mookme:** fix package vcs determination ([a591059](https://github.com/Escape-Technologies/mookme/commit/a591059d3f329c2b37e658816016885b6b5c6024)) 26 | * **mookme:** fix package.json ([1659d2b](https://github.com/Escape-Technologies/mookme/commit/1659d2b819209634aa43431c709bc269bc77c7e6)) 27 | * **mookme:** improve docs ([93d43d4](https://github.com/Escape-Technologies/mookme/commit/93d43d4e4f6995554cc2e3f2931eaed85a63605d)) 28 | * **mookme:** remove useless log ([5dd1ced](https://github.com/Escape-Technologies/mookme/commit/5dd1ced134b3c47f70ff80f1747b042a69eb41da)) 29 | * **package:** fix CI ([24019c1](https://github.com/Escape-Technologies/mookme/commit/24019c1f8e5b909e5e5711048143e7b3bee8ccfb)) 30 | * **tests:** remove logs ([9b29dbc](https://github.com/Escape-Technologies/mookme/commit/9b29dbcfdebebcdce3cc623a940cc8aeb56ef233)) 31 | 32 | ### Features 33 | 34 | * **docs:** added local steps to the documentation ([9e1bb52](https://github.com/Escape-Technologies/mookme/commit/9e1bb520adf52327e691ae2cf9322166d23be365)) 35 | * **mookme:** add basic testing framework ([cdfab59](https://github.com/Escape-Technologies/mookme/commit/cdfab59cde387896348aa460aefcc7f5970e85b5)) 36 | * **mookme:** add beta release channel ([b9dd8b2](https://github.com/Escape-Technologies/mookme/commit/b9dd8b2ca56800c3441c013841742d8d7e7b009b)) 37 | * **mookme:** add beta workflows ([0706e36](https://github.com/Escape-Technologies/mookme/commit/0706e363b21b81b1194abbd6cb7d037cbaa82ff0)) 38 | * **mookme:** add support for gitignored steps ([d2a8a93](https://github.com/Escape-Technologies/mookme/commit/d2a8a937feeb4ac15de81056301a6c57d75339ea)) 39 | * **mookme:** add support for more commit hook types ([f3bfe6d](https://github.com/Escape-Technologies/mookme/commit/f3bfe6d83bd3bfa2075e51168fe526b5cd415a9e)) 40 | * **mookme:** add unit testing scaffold ([5564975](https://github.com/Escape-Technologies/mookme/commit/5564975e7b384a349c13cc41e54a3e0ad7446c6c)) 41 | * **mookme:** bump ci node versions ([164c0aa](https://github.com/Escape-Technologies/mookme/commit/164c0aa99a3991fabd0d578d7b49a1a53413e7b3)) 42 | * **mookme:** bump mookme to node 16 ([da3f5ca](https://github.com/Escape-Technologies/mookme/commit/da3f5ca3b0fd57c9648fb89c90bc3d904a7a4f30)) 43 | * **mookme:** deploy beta-docs ([e2e649c](https://github.com/Escape-Technologies/mookme/commit/e2e649c8d5f9282a61865698300706e8d855df79)) 44 | * **mookme:** deploy beta-docs ([d204b7e](https://github.com/Escape-Technologies/mookme/commit/d204b7ee43e25488fc29800b86248c84200bc6e6)) 45 | * **mookme:** init beta channel ([08b227d](https://github.com/Escape-Technologies/mookme/commit/08b227d9dc96d95a751340de33f76aa8502374de)) 46 | * **mookme:** select hook types at project initialization ([b2921b7](https://github.com/Escape-Technologies/mookme/commit/b2921b70418751469de64911c54cc8ca1fbfa69d)) 47 | 48 | # [1.2.0-beta.11](https://github.com/Escape-Technologies/mookme/compare/v1.2.0-beta.10...v1.2.0-beta.11) (2022-01-04) 49 | 50 | ### Bug Fixes 51 | 52 | * **mookme:** fix package vcs determination ([a591059](https://github.com/Escape-Technologies/mookme/commit/a591059d3f329c2b37e658816016885b6b5c6024)) 53 | 54 | # [1.2.0-beta.10](https://github.com/Escape-Technologies/mookme/compare/v1.2.0-beta.9...v1.2.0-beta.10) (2021-12-31) 55 | 56 | ### Bug Fixes 57 | 58 | * **docs:** add changelog in docs ([a4fbe77](https://github.com/Escape-Technologies/mookme/commit/a4fbe77f02fa64a4b9bd94b41211dec085da6cdb)) 59 | * **mookme:** add beta documentation in readme ([94f97f2](https://github.com/Escape-Technologies/mookme/commit/94f97f250fd33e4fa98a247219efdea9aa806128)) 60 | 61 | # [1.2.0-beta.9](https://github.com/Escape-Technologies/mookme/compare/v1.2.0-beta.8...v1.2.0-beta.9) (2021-12-31) 62 | 63 | ### Bug Fixes 64 | 65 | * **mookme:** add readme in JSON pkg ([f713d0e](https://github.com/Escape-Technologies/mookme/commit/f713d0e3a277a8eb23b7ee92cbefb84f6a60fbd4)) 66 | 67 | # [1.2.0-beta.8](https://github.com/Escape-Technologies/mookme/compare/v1.2.0-beta.7...v1.2.0-beta.8) (2021-12-31) 68 | 69 | ### Bug Fixes 70 | 71 | * **mookme:** fix init command to log every file modification ([1069ff3](https://github.com/Escape-Technologies/mookme/commit/1069ff3afb92f7932a8bba91d432068ba482bb7b)) 72 | * **mookme:** improve docs ([93d43d4](https://github.com/Escape-Technologies/mookme/commit/93d43d4e4f6995554cc2e3f2931eaed85a63605d)) 73 | 74 | ### Features 75 | 76 | * **mookme:** select hook types at project initialization ([b2921b7](https://github.com/Escape-Technologies/mookme/commit/b2921b70418751469de64911c54cc8ca1fbfa69d)) 77 | 78 | # [1.2.0-beta.7](https://github.com/Escape-Technologies/mookme/compare/v1.2.0-beta.6...v1.2.0-beta.7) (2021-12-31) 79 | 80 | ### Bug Fixes 81 | 82 | * **mookme:** fix docs deployment ([0c49fa6](https://github.com/Escape-Technologies/mookme/commit/0c49fa6583d800914a6589bf6672f7e37130d1d1)) 83 | 84 | # [1.2.0-beta.6](https://github.com/Escape-Technologies/mookme/compare/v1.2.0-beta.5...v1.2.0-beta.6) (2021-12-31) 85 | 86 | ### Bug Fixes 87 | 88 | * **mookme:** fix docs deployment ([4f35f5f](https://github.com/Escape-Technologies/mookme/commit/4f35f5f59d7237d654fb56ef694524ee988cb47c)) 89 | 90 | # [1.2.0-beta.5](https://github.com/Escape-Technologies/mookme/compare/v1.2.0-beta.4...v1.2.0-beta.5) (2021-12-31) 91 | 92 | ### Features 93 | 94 | * **mookme:** deploy beta-docs ([e2e649c](https://github.com/Escape-Technologies/mookme/commit/e2e649c8d5f9282a61865698300706e8d855df79)) 95 | 96 | # [1.2.0-beta.4](https://github.com/Escape-Technologies/mookme/compare/v1.2.0-beta.3...v1.2.0-beta.4) (2021-12-31) 97 | 98 | ### Features 99 | 100 | * **mookme:** deploy beta-docs ([d204b7e](https://github.com/Escape-Technologies/mookme/commit/d204b7ee43e25488fc29800b86248c84200bc6e6)) 101 | 102 | # [1.2.0-beta.3](https://github.com/Escape-Technologies/mookme/compare/v1.2.0-beta.2...v1.2.0-beta.3) (2021-12-31) 103 | 104 | ### Bug Fixes 105 | 106 | * **mookme:** bump mookme version used in project ([e8f490f](https://github.com/Escape-Technologies/mookme/commit/e8f490f04711ee41572aa942d891a48056af27c1)) 107 | 108 | # [1.2.0-beta.2](https://github.com/Escape-Technologies/mookme/compare/v1.2.0-beta.1...v1.2.0-beta.2) (2021-12-31) 109 | 110 | ### Bug Fixes 111 | 112 | * **mookme:** fix dependencies ([11fa8a3](https://github.com/Escape-Technologies/mookme/commit/11fa8a38383f0730980a4c5e2b31a4ec56425d3d)) 113 | * **mookme:** fix init for local hooks ([b056550](https://github.com/Escape-Technologies/mookme/commit/b0565502b7d011c3103015f89c6fe3a2aec097c1)) 114 | 115 | ### Features 116 | 117 | * **mookme:** add support for more commit hook types ([f3bfe6d](https://github.com/Escape-Technologies/mookme/commit/f3bfe6d83bd3bfa2075e51168fe526b5cd415a9e)) 118 | 119 | # [1.2.0-beta.1](https://github.com/Escape-Technologies/mookme/compare/v1.1.0...v1.2.0-beta.1) (2021-12-28) 120 | 121 | ### Bug Fixes 122 | 123 | * **ci:** fix release ci ([e318c32](https://github.com/Escape-Technologies/mookme/commit/e318c328181d3f9615abb352655c3b8b1f47d73e)) 124 | * **mookme:** fix add-pkg command ([6d25077](https://github.com/Escape-Technologies/mookme/commit/6d25077f6a0a36cd8a9e50c046999f0c0d93cdbd)) 125 | * **mookme:** fix package.json ([1659d2b](https://github.com/Escape-Technologies/mookme/commit/1659d2b819209634aa43431c709bc269bc77c7e6)) 126 | * **mookme:** remove useless log ([5dd1ced](https://github.com/Escape-Technologies/mookme/commit/5dd1ced134b3c47f70ff80f1747b042a69eb41da)) 127 | * **tests:** remove logs ([9b29dbc](https://github.com/Escape-Technologies/mookme/commit/9b29dbcfdebebcdce3cc623a940cc8aeb56ef233)) 128 | 129 | ### Features 130 | 131 | * **docs:** added local steps to the documentation ([9e1bb52](https://github.com/Escape-Technologies/mookme/commit/9e1bb520adf52327e691ae2cf9322166d23be365)) 132 | * **mookme:** add basic testing framework ([cdfab59](https://github.com/Escape-Technologies/mookme/commit/cdfab59cde387896348aa460aefcc7f5970e85b5)) 133 | * **mookme:** add beta release channel ([b9dd8b2](https://github.com/Escape-Technologies/mookme/commit/b9dd8b2ca56800c3441c013841742d8d7e7b009b)) 134 | * **mookme:** add beta workflows ([0706e36](https://github.com/Escape-Technologies/mookme/commit/0706e363b21b81b1194abbd6cb7d037cbaa82ff0)) 135 | * **mookme:** add support for gitignored steps ([d2a8a93](https://github.com/Escape-Technologies/mookme/commit/d2a8a937feeb4ac15de81056301a6c57d75339ea)) 136 | * **mookme:** add unit testing scaffold ([5564975](https://github.com/Escape-Technologies/mookme/commit/5564975e7b384a349c13cc41e54a3e0ad7446c6c)) 137 | * **mookme:** bump ci node versions ([164c0aa](https://github.com/Escape-Technologies/mookme/commit/164c0aa99a3991fabd0d578d7b49a1a53413e7b3)) 138 | * **mookme:** bump mookme to node 16 ([da3f5ca](https://github.com/Escape-Technologies/mookme/commit/da3f5ca3b0fd57c9648fb89c90bc3d904a7a4f30)) 139 | * **mookme:** init beta channel ([08b227d](https://github.com/Escape-Technologies/mookme/commit/08b227d9dc96d95a751340de33f76aa8502374de)) 140 | 141 | # [1.1.0-beta.7](https://github.com/Escape-Technologies/mookme/compare/v1.1.0-beta.6...v1.1.0-beta.7) (2021-12-28) 142 | 143 | ### Bug Fixes 144 | 145 | * **mookme:** fix package.json ([1659d2b](https://github.com/Escape-Technologies/mookme/commit/1659d2b819209634aa43431c709bc269bc77c7e6)) 146 | * **mookme:** remove useless log ([5dd1ced](https://github.com/Escape-Technologies/mookme/commit/5dd1ced134b3c47f70ff80f1747b042a69eb41da)) 147 | 148 | # [1.1.0-beta.6](https://github.com/Escape-Technologies/mookme/compare/v1.1.0-beta.5...v1.1.0-beta.6) (2021-12-28) 149 | 150 | ### Bug Fixes 151 | 152 | * **mookme:** fix add-pkg command ([6d25077](https://github.com/Escape-Technologies/mookme/commit/6d25077f6a0a36cd8a9e50c046999f0c0d93cdbd)) 153 | * **tests:** remove logs ([9b29dbc](https://github.com/Escape-Technologies/mookme/commit/9b29dbcfdebebcdce3cc623a940cc8aeb56ef233)) 154 | 155 | ### Features 156 | 157 | * **docs:** added local steps to the documentation ([9e1bb52](https://github.com/Escape-Technologies/mookme/commit/9e1bb520adf52327e691ae2cf9322166d23be365)) 158 | * **mookme:** add support for gitignored steps ([d2a8a93](https://github.com/Escape-Technologies/mookme/commit/d2a8a937feeb4ac15de81056301a6c57d75339ea)) 159 | 160 | # [1.1.0-beta.5](https://github.com/Escape-Technologies/mookme/compare/v1.1.0-beta.4...v1.1.0-beta.5) (2021-12-09) 161 | 162 | ### Features 163 | 164 | * **mookme:** add basic testing framework ([cdfab59](https://github.com/Escape-Technologies/mookme/commit/cdfab59cde387896348aa460aefcc7f5970e85b5)) 165 | * **mookme:** add unit testing scaffold ([5564975](https://github.com/Escape-Technologies/mookme/commit/5564975e7b384a349c13cc41e54a3e0ad7446c6c)) 166 | 167 | # [1.1.0-beta.4](https://github.com/Escape-Technologies/mookme/compare/v1.1.0-beta.3...v1.1.0-beta.4) (2021-11-05) 168 | 169 | ### Bug Fixes 170 | 171 | * **ci:** fix release ci ([e318c32](https://github.com/Escape-Technologies/mookme/commit/e318c328181d3f9615abb352655c3b8b1f47d73e)) 172 | 173 | ### Features 174 | 175 | * **mookme:** bump ci node versions ([164c0aa](https://github.com/Escape-Technologies/mookme/commit/164c0aa99a3991fabd0d578d7b49a1a53413e7b3)) 176 | 177 | # [1.1.0-beta.3](https://github.com/Escape-Technologies/mookme/compare/v1.1.0-beta.2...v1.1.0-beta.3) (2021-11-05) 178 | 179 | # [1.1.0](https://github.com/Escape-Technologies/mookme/compare/v1.0.3...v1.1.0) (2021-12-07) 180 | 181 | ### Features 182 | 183 | * **mookme:** bump mookme to node 16 ([da3f5ca](https://github.com/Escape-Technologies/mookme/commit/da3f5ca3b0fd57c9648fb89c90bc3d904a7a4f30)) 184 | 185 | # [1.1.0-beta.2](https://github.com/Escape-Technologies/mookme/compare/v1.1.0-beta.1...v1.1.0-beta.2) (2021-11-05) 186 | 187 | ### Bug Fixes 188 | 189 | * markdownlint ([10baed0](https://github.com/Escape-Technologies/mookme/commit/10baed07a7bcaaaab357da670866f3850192c71e)) 190 | * **mookme:** fix command execution shell ([518e36a](https://github.com/Escape-Technologies/mookme/commit/518e36ac414027de0a773cf095f9d36de1e73b1d)) 191 | * **mookme:** fix package.json resolution ([1a5547a](https://github.com/Escape-Technologies/mookme/commit/1a5547a498c0deb4873f64ac0824f8d77df19e1e)) 192 | * updating docs with examples and fixing typos ([9fb32af](https://github.com/Escape-Technologies/mookme/commit/9fb32af8c9a06a05bcd7dd7b5277743305fc4745)) 193 | 194 | # [1.1.0-beta.1](https://github.com/Escape-Technologies/mookme/compare/v1.0.0...v1.1.0-beta.1) (2021-10-22) 195 | 196 | ### Features 197 | 198 | * **mookme:** add beta release channel ([b9dd8b2](https://github.com/Escape-Technologies/mookme/commit/b9dd8b2ca56800c3441c013841742d8d7e7b009b)) 199 | * **mookme:** add beta workflows ([0706e36](https://github.com/Escape-Technologies/mookme/commit/0706e363b21b81b1194abbd6cb7d037cbaa82ff0)) 200 | * **mookme:** init beta channel ([08b227d](https://github.com/Escape-Technologies/mookme/commit/08b227d9dc96d95a751340de33f76aa8502374de)) 201 | 202 | # [0.6.0-beta.1](https://github.com/Escape-Technologies/mookme/compare/v0.5.0...v0.6.0-beta.1) (2021-10-22) 203 | 204 | ### Features 205 | 206 | * **mookme:** add beta release channel ([c3ef31d](https://github.com/Escape-Technologies/mookme/commit/c3ef31d8977d3a408ef81fefd29079dc69875ea5)) 207 | * **mookme:** add beta workflows ([8393db1](https://github.com/Escape-Technologies/mookme/commit/8393db1282c174611b0294483700b44ad813021d)) 208 | * **mookme:** use released mookme on mookme ([0c87ed8](https://github.com/Escape-Technologies/mookme/commit/0c87ed8657e03a45ce1223660b03f5ccd829c023)) 209 | 210 | ## [1.0.3](https://github.com/Escape-Technologies/mookme/compare/v1.0.2...v1.0.3) (2021-11-03) 211 | 212 | ### Bug Fixes 213 | 214 | * markdownlint ([10baed0](https://github.com/Escape-Technologies/mookme/commit/10baed07a7bcaaaab357da670866f3850192c71e)) 215 | * updating docs with examples and fixing typos ([9fb32af](https://github.com/Escape-Technologies/mookme/commit/9fb32af8c9a06a05bcd7dd7b5277743305fc4745)) 216 | 217 | ## [1.0.2](https://github.com/Escape-Technologies/mookme/compare/v1.0.1...v1.0.2) (2021-10-25) 218 | 219 | ### Bug Fixes 220 | 221 | * **mookme:** fix command execution shell ([518e36a](https://github.com/Escape-Technologies/mookme/commit/518e36ac414027de0a773cf095f9d36de1e73b1d)) 222 | 223 | ## [1.0.1](https://github.com/Escape-Technologies/mookme/compare/v1.0.0...v1.0.1) (2021-10-25) 224 | 225 | ### Bug Fixes 226 | 227 | * **mookme:** fix package.json resolution ([1a5547a](https://github.com/Escape-Technologies/mookme/commit/1a5547a498c0deb4873f64ac0824f8d77df19e1e)) 228 | 229 | # [0.5.0](https://github.com/Escape-Technologies/mookme/compare/v0.4.1...v0.5.0) (2021-10-22) 230 | 231 | ### Features 232 | 233 | * **mookme:** add publish ci workflow ([486ce28](https://github.com/Escape-Technologies/mookme/commit/486ce28cdaeed718e758279055e1bb23424e8957)) 234 | 235 | ## [0.4.1](https://github.com/Escape-Technologies/mookme/compare/v0.4.0...v0.4.1) (2021-10-22) 236 | 237 | ### Bug Fixes 238 | 239 | * **ci:** remove automation ([d3534c6](https://github.com/Escape-Technologies/mookme/commit/d3534c6669e215ad9b26249f01a1b73b82ba5f9b)) 240 | * **mookme:** fix ci release ([b77c714](https://github.com/Escape-Technologies/mookme/commit/b77c71464a8fa607dfaf6324dff9c82fcfb9b724)) 241 | 242 | ## [0.3.1](https://github.com/Escape-Technologies/mookme/compare/v0.3.0...v0.3.1) (2021-10-22) 243 | 244 | ### Bug Fixes 245 | 246 | * **mookme:** fix changelog version ([78b845f](https://github.com/Escape-Technologies/mookme/commit/78b845f8c34a4e0d763a39bf05c85cba06e05967)) 247 | 248 | # [0.3.0](https://github.com/Escape-Technologies/mookme/compare/v0.2.0...v0.3.0) (2021-10-22) 249 | 250 | ### Features 251 | 252 | * **mookme:** enable npm publication ([d5f27c9](https://github.com/Escape-Technologies/mookme/commit/d5f27c9b1617109fd58c11f79f65a645a5560344)) 253 | 254 | # [0.2.0](https://github.com/Escape-Technologies/mookme/compare/v0.1.0...v0.2.0) (2021-10-22) 255 | 256 | ### Features 257 | 258 | * **mookme:** add semantic release configuration ([cae746a](https://github.com/Escape-Technologies/mookme/commit/cae746af32a3955243d3d3f5369d99ae7a9e40ec)) 259 | * **mookme:** add semantic release configurationfor git ([2023e64](https://github.com/Escape-Technologies/mookme/commit/2023e6486093fe8de1d21a6ac76d7310d7160692)) 260 | 261 | # [0.1.0](https://github.com/Escape-Technologies/mookme/compare/v0.0.21...v0.1.0) (2021-10-22) 262 | 263 | ### Bug Fixes 264 | 265 | * 0.0.21 ([12fca4a](https://github.com/Escape-Technologies/mookme/commit/12fca4a05aea9d19b74b1a42868cb99925209d7d)) 266 | * **docs:** document serial usage in doc ([565c72f](https://github.com/Escape-Technologies/mookme/commit/565c72fb3b05f67db8324c16e276b48604dd612f)) 267 | * **docs:** document serial usage in doc ([6b0e61f](https://github.com/Escape-Technologies/mookme/commit/6b0e61fa94261aa4a986d88d62f39dea6271364b)) 268 | * **hub-front:** remove angular starter ([1ceee32](https://github.com/Escape-Technologies/mookme/commit/1ceee32cfa042b834c415aa9c3079883bf4b9af6)) 269 | * **hub:** archive temporary hub project ([b24248a](https://github.com/Escape-Technologies/mookme/commit/b24248a109f77cdb615c4b259ede6bb767fc481b)) 270 | * **mookme:** add only changed files when using add and continue ([3b5767e](https://github.com/Escape-Technologies/mookme/commit/3b5767e62e76cdea598d4a1b93fc4489e747155a)) 271 | * **mookme:** add some comments in package hook processing ([77465f0](https://github.com/Escape-Technologies/mookme/commit/77465f0a2e14fd7ca24698a2b1ba4e854c1f5a34)) 272 | * **mookme:** fix build command ([5f8f2f3](https://github.com/Escape-Technologies/mookme/commit/5f8f2f3bb7bbfd2003c3583457f04d5260ccf62f)) 273 | * **mookme:** fix ci ([63c9398](https://github.com/Escape-Technologies/mookme/commit/63c9398f592f13c527c09e1d351de046ec7ef381)) 274 | * **mookme:** fix command too long broken display ([2fd77de](https://github.com/Escape-Technologies/mookme/commit/2fd77de06885c1b96cf75692b7a22fecbbd68123)) 275 | * **mookme:** fix global commands cwd ([f26904d](https://github.com/Escape-Technologies/mookme/commit/f26904d8a491bef77eb6896b8061f14846ae090c)) 276 | * **mookme:** fix no-credentials mode ([e037d6b](https://github.com/Escape-Technologies/mookme/commit/e037d6bb2a398fcafff499ff9f8e0f281da0c41b)) 277 | * **mookme:** fix package hook result display ([6f08a41](https://github.com/Escape-Technologies/mookme/commit/6f08a4158475384b6d6382387580991f23583049)) 278 | * **mookme:** fix package to check evaluation ([ab3645a](https://github.com/Escape-Technologies/mookme/commit/ab3645a1471de1d7d67dab0cab5950a17500e7ce)) 279 | * **mookme:** fix publish command ([744d33c](https://github.com/Escape-Technologies/mookme/commit/744d33cef3f7bab106428988ac592d1acdfd1520)) 280 | * **mookme:** fix run command ([7efe787](https://github.com/Escape-Technologies/mookme/commit/7efe78754ff920be0a4b50f8ee537a84c4ef8e3f)) 281 | * **mookme:** fix step status display ([c09d871](https://github.com/Escape-Technologies/mookme/commit/c09d8713881cb6909670723307ff4926437ce422)) 282 | * **mookme:** prevent large gif from being packaged ([afe5381](https://github.com/Escape-Technologies/mookme/commit/afe53813ffe3f77bb5590fb957d7c257f46a804c)) 283 | * **mookme:** proprify ([ce09ec5](https://github.com/Escape-Technologies/mookme/commit/ce09ec584704f2785fb67b427377354f52abf585)) 284 | * **mookme:** remove ??? ([dc8038f](https://github.com/Escape-Technologies/mookme/commit/dc8038fc4936ee6f678961df667bf89236165848)) 285 | * **mookme:** remove deprecated postinstall script ([3698852](https://github.com/Escape-Technologies/mookme/commit/369885200be5a200a6160c07529bab54bf3c5548)) 286 | * **mookme:** removed dead code ([72fb58f](https://github.com/Escape-Technologies/mookme/commit/72fb58f0d4622c29975a7feece52c203c49a75bd)) 287 | * **mookme:** reordered UI module ([d47526b](https://github.com/Escape-Technologies/mookme/commit/d47526b98fe0bafeaf50f13fc65bfdd6453cd6b3)) 288 | * **mookme:** small attempt at factorizing step error handling ([8ca2184](https://github.com/Escape-Technologies/mookme/commit/8ca21844e45388a32beb4f624d227b8b684d21f3)) 289 | * **mookme:** small attempt at factorizing step error handling ([5421692](https://github.com/Escape-Technologies/mookme/commit/5421692a79310f3c54e3dcaecb24f8f728bfe388)) 290 | * move published files in cli package ([06820ad](https://github.com/Escape-Technologies/mookme/commit/06820ad55b5f178eaa58edde70eea15793ea452e)) 291 | * **repo:** move README to root folder ([612f0c0](https://github.com/Escape-Technologies/mookme/commit/612f0c07d4303886682a05235252f87b55fa68bf)) 292 | * **repo:** move README to root folder ([c6482e2](https://github.com/Escape-Technologies/mookme/commit/c6482e29bc14dbe9fd7eb7b18eae0c63d69a0044)) 293 | 294 | ### Features 295 | 296 | * **cli:** npx usage ([b1695b2](https://github.com/Escape-Technologies/mookme/commit/b1695b29500cbe685909350fc33d550df7b71b69)) 297 | * **front:** first version of list page ([11ecd0d](https://github.com/Escape-Technologies/mookme/commit/11ecd0d94b02817a3fb17c60d62168c3c2e2fba4)) 298 | * **hub:** add backend hub ([b259a0e](https://github.com/Escape-Technologies/mookme/commit/b259a0e8086dfc6be0bb6b3fe3b1ae0722ecdf33)) 299 | * **hub:** add empty nest project ([97f732f](https://github.com/Escape-Technologies/mookme/commit/97f732fed5734dcfa1fd3ec1599b73e96152b9db)) 300 | * **hub:** add pre-commit step ([be99edf](https://github.com/Escape-Technologies/mookme/commit/be99edfd55d74a57bc529fe5e5f9fa3e6f1dce9a)) 301 | * **hub:** first scaffold for hub ([d5624d8](https://github.com/Escape-Technologies/mookme/commit/d5624d82327382e95d360285fff54f5eaae56fd0)) 302 | * **mookme-hub:** add frontend scaffold ([28fa5e0](https://github.com/Escape-Technologies/mookme/commit/28fa5e009cbea082f289f880c1483f15bcf6bbfd)) 303 | * **mookme+backend:** add command for publishing step on mookme from cli ([fb8e807](https://github.com/Escape-Technologies/mookme/commit/fb8e807fd99ffc19ce4cade42d031078b5c3e7f1)) 304 | * **mookme:** add a client class ([da04efe](https://github.com/Escape-Technologies/mookme/commit/da04efef9611542a18ef6877893bcbe00c6faa0d)) 305 | * **mookme:** add build and publish scripts ([45af9bb](https://github.com/Escape-Technologies/mookme/commit/45af9bbb27abeca3a651fb825e92f9c0aebe2ae4)) 306 | * **mookme:** add migration process from pkg.json to .mookme ([1a14497](https://github.com/Escape-Technologies/mookme/commit/1a14497a0dce66503e7faccc463737f5737467b2)) 307 | * **mookme:** add missing commands for cli flow ([dc67c60](https://github.com/Escape-Technologies/mookme/commit/dc67c60ea81a5c2fb9ab3a30ac311124517fd9c3)) 308 | * **mookme:** add partial script support by extending path variable ([77c519b](https://github.com/Escape-Technologies/mookme/commit/77c519bead0f9c4602aea465dc3fb2dbdaf9ac05)) 309 | * **mookme:** add registration through cli ([b84df05](https://github.com/Escape-Technologies/mookme/commit/b84df05183783f166e35391c2b886d0ac619b0be)) 310 | * **mookme:** add serial step option ([da0a5b8](https://github.com/Escape-Technologies/mookme/commit/da0a5b8b1b1891b613644e918958f5b8f03e32bf)) 311 | * **mookme:** add serial step option ([55d7e32](https://github.com/Escape-Technologies/mookme/commit/55d7e32ac36c6f6d7ae258045945b28248342d35)) 312 | * **mookme:** add setter for runner args and staged files ([d95d426](https://github.com/Escape-Technologies/mookme/commit/d95d4265b9c907d7a9405d69df6ff0f679a0ff33)) 313 | * **mookme:** allow for hooks sharing ([e67864f](https://github.com/Escape-Technologies/mookme/commit/e67864ff77f40239a4ad3c0364a34b1ff35c4701)) 314 | * **mookme:** differentiate between scheduled and runnibng step ([aa8dfc6](https://github.com/Escape-Technologies/mookme/commit/aa8dfc634bfb9e31bc379cbc4d564e38e6850d8d)) 315 | * **mookme:** differentiate between scheduled and runnibng step ([7bd3a31](https://github.com/Escape-Technologies/mookme/commit/7bd3a31a3b09264cd6112c241d61e1aa3f4ebb84)) 316 | * **mookme:** factorize logger in class ([a99b8a1](https://github.com/Escape-Technologies/mookme/commit/a99b8a17f5150ba326b6307936b63e9f664af775)) 317 | * **mookme:** move ui functions under POO ([0848a10](https://github.com/Escape-Technologies/mookme/commit/0848a1047d67c63d190cfdcaf44e49182c3135cc)) 318 | * **mookme:** remove extraneous log ([8af10da](https://github.com/Escape-Technologies/mookme/commit/8af10da036247d82d3708c8b68927ab8a989bc1c)) 319 | -------------------------------------------------------------------------------- /packages/mookme/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Escape Technologies Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /packages/mookme/README.md: -------------------------------------------------------------------------------- 1 | # MookMe 2 | 3 | *A simple and easy-to-use, yet powerful and language agnostic git hook for monorepos.* 4 | 5 | **[see the documentation](https://mookme.org)** 6 | 7 | **[see the *beta* documentation](https://beta.mookme.org)** 8 | -------------------------------------------------------------------------------- /packages/mookme/bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | process.env.MOOKME_CLI_VERSION = require('../package.json').version; 3 | if (process.argv.find((arg) => arg === '-V')) { 4 | console.log(process.env.MOOKME_CLI_VERSION); 5 | process.exit(0); 6 | } 7 | 8 | // Utilitary line for usage in dev environment 9 | // console.log(`Running mookme from path : ${__filename}`); 10 | require('../dist/index.js'); 11 | -------------------------------------------------------------------------------- /packages/mookme/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@escape.tech/mookme", 3 | "version": "2.5.1", 4 | "description": "A git hook manager designed for monorepos", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "bin": { 8 | "mookme": "bin/index.js" 9 | }, 10 | "files": [ 11 | "dist/**/*.js" 12 | ], 13 | "scripts": { 14 | "prebuild": "rimraf -f ./.tmp/ts-build-infos.json && rimraf -rf dist", 15 | "build": "tsc", 16 | "dev": "tsc -w", 17 | "test": "echo \"Error: no test specified\" && exit 1", 18 | "prettier": "prettier -c src/**/*.ts", 19 | "prettier:fix": "prettier --write src/**/*.ts", 20 | "eslint": "eslint src/**/*.ts", 21 | "eslint:fix": "eslint --fix src/**/*.ts", 22 | "prepublishOnly": "npm run build" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/Escape-Technologies/mookme" 27 | }, 28 | "keywords": [ 29 | "pre", 30 | "commit", 31 | "pre-commit", 32 | "hook", 33 | "monorepos" 34 | ], 35 | "author": { 36 | "name": "Maxence Lecanu", 37 | "email": "lecanu.maxence@gmail.com", 38 | "url": "https://me.mlecanu.com" 39 | }, 40 | "license": "MIT", 41 | "homepage": "https://mookme.org", 42 | "bugs": { 43 | "url": "https://github.com/Escape-Technologies/mookme/issues", 44 | "email": "lecanu.maxence@gmail.com" 45 | }, 46 | "devDependencies": { 47 | "@semantic-release/changelog": "^6.0.0", 48 | "@semantic-release/git": "^10.0.0", 49 | "@tsconfig/node10": "^1.0.7", 50 | "@types/debug": "^4.1.7", 51 | "@types/inquirer": "^7.3.1", 52 | "@types/node": "^15.0.2", 53 | "@typescript-eslint/eslint-plugin": "4.22.1", 54 | "@typescript-eslint/parser": "4.22.1", 55 | "eslint": "^7.26.0", 56 | "eslint-config-prettier": "8.3.0", 57 | "eslint-plugin-import": "^2.22.1", 58 | "eslint-plugin-prettier": "3.4.0", 59 | "eslint-plugin-tsdoc": "^0.2.14", 60 | "prettier": "2.3.0", 61 | "rimraf": "^3.0.2", 62 | "semantic-release": "^18.0.0", 63 | "typescript": "^4.2.4" 64 | }, 65 | "dependencies": { 66 | "chalk": "^4.1.1", 67 | "commander": "^7.2.0", 68 | "debug": "^4.3.4", 69 | "inquirer": "^8.0.0", 70 | "wildcard-match": "^5.1.0" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/mookme/src/commands/index.ts: -------------------------------------------------------------------------------- 1 | export { addInit } from './init'; 2 | export { addRun } from './run'; 3 | export { addInspect } from './inspect'; 4 | -------------------------------------------------------------------------------- /packages/mookme/src/commands/init.ts: -------------------------------------------------------------------------------- 1 | import commander from 'commander'; 2 | import { GitToolkit } from '../utils/git'; 3 | import { InitOptions, InitRunner } from '../runner/init'; 4 | import Debug from 'debug'; 5 | 6 | const debug = Debug('mookme'); 7 | 8 | export function addInit(program: commander.Command): void { 9 | program 10 | .command('init') 11 | .option('--only-hook', 'Skip packages definition and only write .git/hooks/${hook-type} files') 12 | .option('--added-behaviour ', 'Provide added behaviour and skip the associated prompter') 13 | .option('--skip-types-selection', 'Skip hook types selection') 14 | .option('--yes', 'Skip confirmation prompter') 15 | .option( 16 | '-t, --types ', 17 | 'A list of valid git hooks ("pre-commit", "prepare-commit", "commit-msg", "post-commit")', 18 | ) 19 | .action(async (opts: InitOptions) => { 20 | debug('Running init command with options', opts); 21 | const git = new GitToolkit(); 22 | const initRunner = new InitRunner(git); 23 | await initRunner.run(opts); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /packages/mookme/src/commands/inspect.ts: -------------------------------------------------------------------------------- 1 | import commander from 'commander'; 2 | import { FilterStrategy } from '../loaders/filter-strategies/base-filter'; 3 | import { ListOfFilesPathFilter } from '../loaders/filter-strategies/list-of-files-filter'; 4 | import { HooksResolver } from '../loaders/hooks-resolver'; 5 | import { InspectRunner } from '../runner/inspect'; 6 | import { HookType } from '../types/hook.types'; 7 | import { GitToolkit } from '../utils/git'; 8 | 9 | export function addInspect(program: commander.Command): void { 10 | program 11 | .command('inspect') 12 | .requiredOption( 13 | '-t, --type ', 14 | 'A valid git hook type ("pre-commit", "prepare-commit", "commit-msg", "post-commit")', 15 | ) 16 | .option( 17 | '-f, --files [files...]', 18 | 'A list of files paths to inspect. Paths must be relative from the repository root', 19 | ) 20 | .description('Manually test wich packages are discovered and assess if your hooks are properly configured.') 21 | .action(async (opts: { type: HookType; files?: string[] }) => { 22 | const git = new GitToolkit(); 23 | 24 | let customStrategy: FilterStrategy | undefined = undefined; 25 | if (opts.files && Array.isArray(opts.files)) { 26 | customStrategy = new ListOfFilesPathFilter(git, false, opts.files); 27 | } 28 | 29 | const resolver = new HooksResolver(git, opts.type, { customStrategy: customStrategy }); 30 | const inspect = new InspectRunner(resolver); 31 | await inspect.run(); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /packages/mookme/src/commands/run.ts: -------------------------------------------------------------------------------- 1 | import commander from 'commander'; 2 | 3 | import { GitToolkit } from '../utils/git'; 4 | import { Config } from '../config'; 5 | import { MookmeUI } from '../ui'; 6 | import { RunOptions, RunRunner } from '../runner/run'; 7 | import { HooksResolver } from '../loaders/hooks-resolver'; 8 | import Debug from 'debug'; 9 | import { NoClearRenderer } from '../ui/renderers/noclear-renderer'; 10 | import { FancyRenderer } from '../ui/renderers/fancy-renderer'; 11 | 12 | const debug = Debug('mookme'); 13 | 14 | /** 15 | * Extend an existing commander program instance with the `run` command of Mookme 16 | * 17 | * @param program - the instance of the commander program to extend 18 | */ 19 | export function addRun(program: commander.Command): void { 20 | program 21 | .command('run') 22 | .requiredOption( 23 | '-t, --type ', 24 | 'A valid git hook type ("pre-commit", "prepare-commit", "commit-msg", "post-commit")', 25 | ) 26 | .option('-a, --all', 'Run hooks for all packages', '') 27 | .option('--from ', 'Starting git reference used to evaluate hooks to run', '') 28 | .option('--to ', 'Ending git reference used to evaluate hooks to run', '') 29 | .option('--args ', 'The arguments being passed to the hooks', '') 30 | .option('--config-root ', 'The path of the folder where the mookme configuration file is stored', '') 31 | .action(async (opts: RunOptions) => { 32 | debug('Running run command with options', opts); 33 | const git = new GitToolkit(); 34 | 35 | // Load the different config files 36 | const config = new Config(opts.configRoot || git.rootDir); 37 | 38 | // Initialize the UI 39 | const ui = new MookmeUI(false, config.noClearRenderer ? new NoClearRenderer() : new FancyRenderer()); 40 | const resolver = new HooksResolver(git, opts.type, { useAllFiles: opts.all, from: opts.from, to: opts.to }); 41 | 42 | // Instanciate a runner instance for the run command, and start it against the provided options 43 | const runner = new RunRunner(ui, config, git, resolver); 44 | await runner.run(opts); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /packages/mookme/src/config/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import logger from '../utils/logger'; 4 | import { ADDED_BEHAVIORS } from './types'; 5 | 6 | /** 7 | * This class holds the types and attributes retrieved from `.mookme.json` 8 | */ 9 | export class Config { 10 | addedBehavior: ADDED_BEHAVIORS; 11 | noClearRenderer = false; 12 | 13 | /** 14 | * Instanciate a config object by loading the JSON config file, or defaults to a default configuration. 15 | * @param rootDir - the base absolute path where the `.mookme.json should be found` 16 | */ 17 | constructor(rootDir: string) { 18 | // Determine the absolute path to the .mookme.json file 19 | const configFilePath = path.join(rootDir, '.mookme.json'); 20 | 21 | // Test it's existence, and default to the default configuration if there is an issue 22 | if (!fs.existsSync(configFilePath)) { 23 | logger.warning(`No \`.mookme.json\` file found at path ${rootDir} proceeding with a default configuration`); 24 | this.addedBehavior = ADDED_BEHAVIORS.ADD_AND_COMMIT; 25 | } else { 26 | // Load the file and add it's content to the correct attributes 27 | // @TODO: verify the content of the config file 28 | const configFromFile = JSON.parse(fs.readFileSync(configFilePath).toString()); 29 | this.addedBehavior = configFromFile.addedBehavior 30 | ? (configFromFile.addedBehavior as ADDED_BEHAVIORS) 31 | : ADDED_BEHAVIORS.ADD_AND_COMMIT; 32 | this.noClearRenderer = configFromFile.noClearRenderer || false; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/mookme/src/config/types.ts: -------------------------------------------------------------------------------- 1 | export enum ADDED_BEHAVIORS { 2 | ADD_AND_COMMIT = 'addAndCommit', 3 | EXIT = 'exit', 4 | } 5 | -------------------------------------------------------------------------------- /packages/mookme/src/debug.ts: -------------------------------------------------------------------------------- 1 | import Debug from 'debug'; 2 | export const debug = Debug('mookme'); 3 | -------------------------------------------------------------------------------- /packages/mookme/src/events/bus.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | import { Events } from './events'; 3 | 4 | export class EventBus { 5 | private _eventEmitter: EventEmitter; 6 | 7 | constructor() { 8 | this._eventEmitter = new EventEmitter(); 9 | } 10 | 11 | on(s: K, listeners: Array<(v: T[K]) => void>): void { 12 | this._eventEmitter.on(s.toString(), (v) => { 13 | for (const listener of listeners) { 14 | listener(v); 15 | } 16 | }); 17 | } 18 | 19 | emit(s: K, payload: T[K]): void { 20 | this._eventEmitter.emit(s.toString(), payload); 21 | } 22 | } 23 | 24 | export const bus = new EventBus(); 25 | -------------------------------------------------------------------------------- /packages/mookme/src/events/events.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionStatus } from '../types/status.types'; 2 | 3 | /** 4 | * The list of available event when using the `run` command 5 | */ 6 | export enum EventType { 7 | StepRegistered = 'stepRegistered', 8 | StepStatusChanged = 'stepStatusChanged', 9 | PackageRegistered = 'packageRegistered', 10 | } 11 | 12 | interface StepRegisteredPayload { 13 | /** 14 | * The name of the package to update 15 | */ 16 | packageName: string; 17 | /** 18 | * The new step to add to the package 19 | */ 20 | step: { name: string; command: string }; 21 | } 22 | 23 | interface StepStatusChangedPayload { 24 | /** 25 | * The name of the package to update 26 | */ 27 | packageName: string; 28 | /** 29 | * The name of the step to update 30 | */ 31 | stepName: string; 32 | /** 33 | * The new status of the step 34 | */ 35 | status: ExecutionStatus; 36 | } 37 | 38 | interface PackageRegisteredPayload { 39 | /** 40 | * The name of the package to update 41 | */ 42 | name: string; 43 | /** 44 | * The list of steps already defined in the package 45 | */ 46 | steps?: { name: string; command: string }[]; 47 | } 48 | 49 | /** 50 | * Map event types with their payload types 51 | */ 52 | export type Events = { 53 | [EventType.StepRegistered]: StepRegisteredPayload; 54 | [EventType.StepStatusChanged]: StepStatusChangedPayload; 55 | [EventType.PackageRegistered]: PackageRegisteredPayload; 56 | }; 57 | -------------------------------------------------------------------------------- /packages/mookme/src/events/index.ts: -------------------------------------------------------------------------------- 1 | export { bus, EventBus } from './bus'; 2 | export { Events, EventType } from './events'; 3 | -------------------------------------------------------------------------------- /packages/mookme/src/executor/index.ts: -------------------------------------------------------------------------------- 1 | export { StepExecutor } from './step-executor'; 2 | -------------------------------------------------------------------------------- /packages/mookme/src/executor/package-executor.ts: -------------------------------------------------------------------------------- 1 | import { PackageHook } from '../types/hook.types'; 2 | import { StepExecutor } from './step-executor'; 3 | import { StepCommand, StepError } from '../types/step.types'; 4 | import { bus, EventType } from '../events'; 5 | import { ExecutionStatus } from '../types/status.types'; 6 | 7 | export interface PackageExecutorOptions { 8 | /** 9 | * The absolute path pointing towards the root directory 10 | */ 11 | rootDir: string; 12 | } 13 | 14 | /** 15 | * A class for handling the hook execution for a package. It runs it's steps and process their results 16 | */ 17 | export class PackageExecutor { 18 | /** 19 | * The package object being used for executing the steps 20 | */ 21 | package: PackageHook; 22 | /** 23 | * The list of step executors attached to this package's steps 24 | */ 25 | stepExecutors: StepExecutor[]; 26 | 27 | options: PackageExecutorOptions; 28 | 29 | constructor(pkg: PackageHook, options: PackageExecutorOptions) { 30 | this.package = pkg; 31 | this.options = options; 32 | this.stepExecutors = pkg.steps.map( 33 | (step) => 34 | new StepExecutor(step, { 35 | packageName: pkg.name, 36 | packagePath: pkg.cwd, 37 | type: pkg.type, 38 | venvActivate: pkg.venvActivate, 39 | condaEnv: pkg.condaEnv, 40 | rootDir: options.rootDir, 41 | }), 42 | ); 43 | 44 | // Notify the application that this package is being hooked 45 | bus.emit(EventType.PackageRegistered, { 46 | name: this.package.name, 47 | steps: this.package.steps, 48 | }); 49 | } 50 | 51 | /** 52 | * Run asynchronously every steps of the package attached to this executor. 53 | * 54 | * @returns A promised list containing every errors that occured during the package's steps 55 | */ 56 | async executePackageSteps(): Promise { 57 | const promises = []; 58 | const errors: StepError[] = []; 59 | 60 | for (const executor of this.stepExecutors) { 61 | const step = executor.step; 62 | try { 63 | // Start the step 64 | const stepPromise: Promise<{ step: StepCommand; msg: Error } | null> = executor.run(); 65 | 66 | // Regardless of whether the step is serial or not, it will be awaited at the end of this function 67 | promises.push(stepPromise); 68 | 69 | if (step.serial) { 70 | // Serial steps are blocking 71 | const stepError = await stepPromise; 72 | if (stepError !== null) { 73 | errors.push({ 74 | hook: this.package, 75 | step: stepError.step, 76 | error: stepError.msg, 77 | }); 78 | } 79 | } else { 80 | // Non-serial steps are just launched and result is processed in a callback 81 | stepPromise.then((stepError) => { 82 | if (stepError !== null) { 83 | errors.push({ 84 | hook: this.package, 85 | step: stepError.step, 86 | error: stepError.msg, 87 | }); 88 | } 89 | }); 90 | } 91 | } catch (err) { 92 | // If this code is executed, there is some serious business going on, because the step execution is 93 | // supposed to be wrapped in a catch-all block 94 | bus.emit(EventType.StepStatusChanged, { 95 | packageName: this.package.name, 96 | stepName: step.name, 97 | status: ExecutionStatus.FAILURE, 98 | }); 99 | throw err; 100 | } 101 | } 102 | 103 | // Wait for the end of every step 104 | await Promise.all(promises); 105 | 106 | return errors; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /packages/mookme/src/executor/step-executor.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import { exec, ExecOptions } from 'child_process'; 3 | import { bus, EventType } from '../events'; 4 | import { PackageType } from '../types/hook.types'; 5 | import { ExecutionStatus } from '../types/status.types'; 6 | import { StepCommand } from '../types/step.types'; 7 | import os from 'os'; 8 | 9 | export interface ExecuteStepOptions { 10 | /** 11 | * The name of the package using this step 12 | */ 13 | packageName: string; 14 | /** 15 | * The absolute path pointing towards the loacation of the package holding this step 16 | */ 17 | packagePath: string; 18 | /** 19 | * The absolute path pointing towards the root directory 20 | */ 21 | rootDir: string; 22 | /** 23 | * The type of the step to run. 24 | */ 25 | type?: PackageType; 26 | /** 27 | * An optional path to a virtualenv to use (only used if type is {@link PackageType.PYTHON}) 28 | */ 29 | venvActivate?: string; 30 | /** 31 | * An optional conda env to use 32 | */ 33 | condaEnv?: string; 34 | } 35 | 36 | /** 37 | * A class responsible for executing a step in a separate child process. 38 | * 39 | * It handles eventual error from the child process and return the exit code 40 | */ 41 | export class StepExecutor { 42 | /** 43 | * The step object representing the task to run 44 | */ 45 | step: StepCommand; 46 | /** 47 | * Executor options, see {@link ExecuteStepOptions} 48 | */ 49 | options: ExecuteStepOptions; 50 | /** 51 | * A boolean denoting if the step should be skipped. Computed during instanciation or with {@link StepExecutor.isSkipped} 52 | */ 53 | skipped = false; 54 | 55 | /** 56 | * 57 | * @param step - The step object representing the task to run 58 | * @param options - Executor options, see {@link ExecuteStepOptions} 59 | */ 60 | constructor(step: StepCommand, options: ExecuteStepOptions) { 61 | this.step = step; 62 | this.options = options; 63 | 64 | // Determine if step is skipped 65 | this.skipped = this.isSkipped(); 66 | } 67 | 68 | /** 69 | * // Determine if the step is skipped 70 | * @returns true if {@link StepCommand.onlyOn} is not set, otherwise true is a changed file of the package matches the pattern, otherwise false 71 | */ 72 | isSkipped(): boolean { 73 | return this.step.matchedFiles.length === 0; 74 | } 75 | 76 | /** 77 | * Notify the application that a step has a new status 78 | * 79 | * @param status - the new status of the step 80 | */ 81 | emitStepStatus(status: ExecutionStatus): void { 82 | bus.emit(EventType.StepStatusChanged, { 83 | packageName: this.options.packageName, 84 | stepName: this.step.name, 85 | status, 86 | }); 87 | } 88 | 89 | /** 90 | * Compute the command associated with the step 91 | * 92 | * @returns the full command to pass to a child process 93 | */ 94 | computeExecutedCommand(): string { 95 | // Add eventual virtual env to activate before the command 96 | const { type, venvActivate, condaEnv } = this.options; 97 | const { command } = this.step; 98 | const env_execute = 99 | type === 'python' && venvActivate ? `source ${venvActivate} && ${command} && deactivate` : command; 100 | const execute = condaEnv ? `conda run -n ${condaEnv} ""${env_execute}""` : env_execute; 101 | 102 | return execute; 103 | } 104 | 105 | /** 106 | * Start the step execution, and process it's result 107 | * 108 | * @returns a promise, containing eithor nothing (null) if the execution is succesful, or an error if it failed 109 | */ 110 | run(): Promise<{ step: StepCommand; msg: Error } | null> { 111 | this.emitStepStatus(ExecutionStatus.RUNNING); 112 | 113 | if (this.skipped) { 114 | this.emitStepStatus(ExecutionStatus.SKIPPED); 115 | return Promise.resolve(null); 116 | } 117 | 118 | return new Promise((resolve) => { 119 | const command = this.computeExecutedCommand(); 120 | const options: ExecOptions = { cwd: this.options.packagePath }; 121 | if (os.platform() === 'linux') { 122 | options.shell = '/bin/bash'; 123 | } 124 | const cp = exec(command, options); 125 | 126 | /* handle command outputs */ 127 | let out = ''; 128 | cp.stdout?.on('data', (chunk) => { 129 | out += `\n${chunk}`; 130 | }); 131 | 132 | let error = ''; 133 | cp.stderr?.on('data', (chunk) => { 134 | error += `\n${chunk}`; 135 | }); 136 | 137 | /* handle command success or failure */ 138 | cp.on('exit', (code) => { 139 | if (code === 0) { 140 | this.emitStepStatus(ExecutionStatus.SUCCESS); 141 | resolve(null); 142 | } else { 143 | this.emitStepStatus(ExecutionStatus.FAILURE); 144 | resolve({ 145 | step: this.step, 146 | msg: new Error(error + chalk.bold('\nstdout :\n') + out), 147 | }); 148 | } 149 | }); 150 | }); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /packages/mookme/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | 3 | import { addInit, addInspect, addRun } from './commands'; 4 | 5 | const program = new Command(); 6 | 7 | program.version(process.env.MOOKME_CLI_VERSION || 'no-version'); 8 | addInit(program); 9 | addRun(program); 10 | addInspect(program); 11 | 12 | program.parse(); 13 | -------------------------------------------------------------------------------- /packages/mookme/src/loaders/filter-strategies/base-filter.ts: -------------------------------------------------------------------------------- 1 | import Debug from 'debug'; 2 | import path from 'path'; 3 | import { PackageHook, UnprocessedPackageHook } from '../../types/hook.types'; 4 | import { GitToolkit } from '../../utils/git'; 5 | 6 | const debug = Debug('mookme:filtering-strategy'); 7 | 8 | /** 9 | * A base class for denoting a strategy used to filter hooks to run 10 | */ 11 | export abstract class FilterStrategy { 12 | gitToolkit: GitToolkit; 13 | useAllFiles: boolean; 14 | 15 | constructor(gitToolkit: GitToolkit, useAllFiles: boolean) { 16 | this.gitToolkit = gitToolkit; 17 | this.useAllFiles = useAllFiles; 18 | } 19 | 20 | abstract getFilesPathList(): string[]; 21 | 22 | getAllFilesPathList(): string[] { 23 | return this.gitToolkit.getAllTrackedFiles(); 24 | } 25 | 26 | matchExactPath(filePath: string, to_match: string): boolean { 27 | const position = filePath.indexOf(to_match); 28 | if (position === -1) { 29 | return false; 30 | } 31 | const remainingPath = filePath.slice(position + to_match.length); 32 | return remainingPath.length > 0 ? remainingPath.startsWith('/') : true; 33 | } 34 | 35 | /** 36 | * Filter a list of packages based on a list of files path relative from the git root directory. 37 | * @param hooks - the list of {@link PackageHook} to filter 38 | * @returns the filtered list of {@link PackageHook} based on their consistency with the files staged in VCS. 39 | */ 40 | async filter(hooks: UnprocessedPackageHook[]): Promise { 41 | debug(this.useAllFiles ? 'Using getAllFilesPathList' : 'Using getFilesPathList'); 42 | const filesPathList = this.useAllFiles ? this.getAllFilesPathList() : this.getFilesPathList(); 43 | debug(`Filtering will be performed with list ${filesPathList}`); 44 | 45 | const filtered: PackageHook[] = []; 46 | 47 | for (const unprocessedHook of hooks) { 48 | // Detect if the current commit includes files concerning this hook 49 | const matchedFiles = filesPathList.filter((file) => 50 | this.matchExactPath(path.join(this.gitToolkit.rootDir, file), unprocessedHook.cwd), 51 | ); 52 | if (matchedFiles.length > 0) { 53 | debug(`Adding ${unprocessedHook.name} because there are ${matchedFiles.length} files matching it's path`); 54 | // Include the matched files in the hook and it's steps 55 | filtered.push({ 56 | ...unprocessedHook, 57 | steps: unprocessedHook.steps.map((step) => ({ 58 | ...step, 59 | // Make the step matched paths relative to the cwd of the package they belong to 60 | matchedFiles: matchedFiles.map((fPath) => 61 | path.join(this.gitToolkit.rootDir, fPath).replace(`${unprocessedHook.cwd}/`, ''), 62 | ), 63 | })), 64 | matchedFiles, 65 | }); 66 | } else { 67 | debug(`Skipping hook ${unprocessedHook.name} because there are no match between it's path and staged files`); 68 | } 69 | } 70 | 71 | return filtered; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/mookme/src/loaders/filter-strategies/current-commit-filter.ts: -------------------------------------------------------------------------------- 1 | import { GitToolkit } from '../../utils/git'; 2 | import { FilterStrategy } from './base-filter'; 3 | 4 | /** 5 | * Filter a list of packages based on the VCS state, and the staged files it holds. 6 | */ 7 | export class CurrentCommitFilterStrategy extends FilterStrategy { 8 | constructor(gitToolkit: GitToolkit, useAllFiles = false) { 9 | super(gitToolkit, useAllFiles); 10 | } 11 | 12 | getFilesPathList(): string[] { 13 | return this.useAllFiles ? this.gitToolkit.getAllTrackedFiles() : this.gitToolkit.getStagedFiles(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/mookme/src/loaders/filter-strategies/from-to.filter.ts: -------------------------------------------------------------------------------- 1 | import { GitToolkit } from '../../utils/git'; 2 | import { FilterStrategy } from './base-filter'; 3 | import Debug from 'debug'; 4 | 5 | const debug = Debug('mookme:from-to-filter-strategy'); 6 | 7 | /** 8 | * Filter a list of packages based on the VCS state, and the staged files it holds. 9 | */ 10 | export class FromToFilterStrategy extends FilterStrategy { 11 | from: string; 12 | to: string; 13 | 14 | /** 15 | * @param from - the Git reference where to keep changed files from 16 | * @param to - the Git reference until where to keep changed files 17 | */ 18 | constructor(gitToolkit: GitToolkit, useAllFiles = false, from: string, to: string) { 19 | super(gitToolkit, useAllFiles); 20 | this.from = from; 21 | this.to = to; 22 | } 23 | 24 | getFilesPathList(): string[] { 25 | debug(`Filtering files between ${this.from} and ${this.to}`); 26 | return this.gitToolkit.getFilesChangedBetweenRefs(this.from, this.to); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/mookme/src/loaders/filter-strategies/list-of-files-filter.ts: -------------------------------------------------------------------------------- 1 | import { GitToolkit } from '../../utils/git'; 2 | import { FilterStrategy } from './base-filter'; 3 | 4 | import Debug from 'debug'; 5 | 6 | const debug = Debug('mookme:list-of-files-filtering-strategy'); 7 | 8 | export class ListOfFilesPathFilter extends FilterStrategy { 9 | filePaths: string[]; 10 | 11 | constructor(gitToolkit: GitToolkit, useAllFiles = false, filePaths: string[]) { 12 | super(gitToolkit, useAllFiles); 13 | this.filePaths = filePaths; 14 | } 15 | 16 | getFilesPathList(): string[] { 17 | debug(`ListOfFilesPathFilter.getFilesPathList returning ${this.filePaths}`); 18 | return this.filePaths; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/mookme/src/loaders/filter-strategies/not-pushed-files-filter.ts: -------------------------------------------------------------------------------- 1 | import { GitToolkit } from '../../utils/git'; 2 | import { FilterStrategy } from './base-filter'; 3 | 4 | import Debug from 'debug'; 5 | 6 | const debug = Debug('mookme:not-pushed-files-filter-strategy'); 7 | 8 | export class NotPushedFilesFilterStrategy extends FilterStrategy { 9 | constructor(gitToolkit: GitToolkit, useAllFiles = false) { 10 | super(gitToolkit, useAllFiles); 11 | } 12 | 13 | getFilesPathList(): string[] { 14 | const files = this.useAllFiles ? this.gitToolkit.getAllTrackedFiles() : this.gitToolkit.getFilesToPush(); 15 | debug(`Using files ${files.join(', ')} for pre-push stategy`); 16 | return files; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/mookme/src/loaders/filter-strategies/previous-commit-filter.ts: -------------------------------------------------------------------------------- 1 | import { GitToolkit } from '../../utils/git'; 2 | import { FilterStrategy } from './base-filter'; 3 | 4 | /** 5 | * A base class for denoting a strategy used to filter hooks to run 6 | */ 7 | export class PreviousCommitFilterStrategy extends FilterStrategy { 8 | constructor(gitToolkit: GitToolkit, useAllFiles = false) { 9 | super(gitToolkit, useAllFiles); 10 | } 11 | 12 | getFilesPathList(): string[] { 13 | return this.useAllFiles ? this.gitToolkit.getAllTrackedFiles() : this.gitToolkit.getPreviouslyCommitedFiles(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/mookme/src/loaders/hooks-resolver.ts: -------------------------------------------------------------------------------- 1 | import Debug from 'debug'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import { HookType, PackageHook, UnprocessedPackageHook } from '../types/hook.types'; 5 | import { StepCommand } from '../types/step.types'; 6 | import { GitToolkit } from '../utils/git'; 7 | import logger from '../utils/logger'; 8 | import { FilterStrategy } from './filter-strategies/base-filter'; 9 | import { CurrentCommitFilterStrategy } from './filter-strategies/current-commit-filter'; 10 | import { PreviousCommitFilterStrategy } from './filter-strategies/previous-commit-filter'; 11 | import wcmatch from 'wildcard-match'; 12 | import { FromToFilterStrategy } from './filter-strategies/from-to.filter'; 13 | import { NotPushedFilesFilterStrategy } from './filter-strategies/not-pushed-files-filter'; 14 | 15 | const debug = Debug('mookme:hooks-resolver'); 16 | 17 | export interface HooksResolverOptions { 18 | useAllFiles?: boolean; 19 | from?: string; 20 | to?: string; 21 | customStrategy?: FilterStrategy; 22 | } 23 | 24 | /** 25 | * A class defining several utilitaries used to load and prepare packages hooks to be executed 26 | */ 27 | export class HooksResolver { 28 | gitToolkit: GitToolkit; 29 | root: string; 30 | hookType: string; 31 | strategy: FilterStrategy; 32 | 33 | /** 34 | * A class defining several utilitaries used to load and prepare packages hooks to be executed 35 | * 36 | * @param gitToolkit - the {@link GitToolkit} instance to use to manage the VCS state 37 | */ 38 | constructor(gitToolkit: GitToolkit, hookType: HookType, opts: HooksResolverOptions = {}) { 39 | this.gitToolkit = gitToolkit; 40 | this.root = gitToolkit.rootDir; 41 | this.hookType = hookType; 42 | 43 | const useAllFiles = opts.useAllFiles || false; 44 | const from = opts.from || null; 45 | const to = opts.to || null; 46 | const customStrategy = opts.customStrategy || null; 47 | 48 | // Perform filtering based on a selected strategy 49 | if (customStrategy) { 50 | debug(`Using a custom strategy`); 51 | this.strategy = customStrategy; 52 | } else if (from && to) { 53 | debug(`Using strategy FromToFilterStrategy from ${from} to ${to}`); 54 | this.strategy = new FromToFilterStrategy(this.gitToolkit, useAllFiles, from, to); 55 | } else { 56 | switch (this.hookType) { 57 | case HookType.PRE_PUSH: 58 | debug(`Using strategy NotPushedFilesFilter`); 59 | this.strategy = new NotPushedFilesFilterStrategy(this.gitToolkit, useAllFiles); 60 | break; 61 | case HookType.POST_COMMIT: 62 | debug(`Using strategy PreviousCommitFilterStrategy`); 63 | this.strategy = new PreviousCommitFilterStrategy(this.gitToolkit, useAllFiles); 64 | break; 65 | default: 66 | debug(`Using strategy CurrentCommitFilterStrategy`); 67 | this.strategy = new CurrentCommitFilterStrategy(this.gitToolkit, useAllFiles); 68 | break; 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * Recursively retrieve the list of folders containing a hook specification with their absolute paths. 75 | * @param depth - the current depth of the folder exploration. Defaults to 0 for the initial call, should be increased across the recursive calls. 76 | * @param maxDepth - the max value accepted for the depth parameter before stopping the future recursions 77 | * @returns a list of strings denoting the absolute paths of the detected pakcages 78 | */ 79 | extractPackagesPaths(depth = 0, maxDepth = 3, source?: string): string[] { 80 | const paths: string[] = []; 81 | const root = source || this.root; 82 | 83 | // Retrieve the list of directories in the root folder 84 | const folders = fs.readdirSync(root, { withFileTypes: true }).filter((item) => item.isDirectory()); 85 | 86 | // For each directory, if it has a `.hooks` folder in it, add it's path to the list of packages path 87 | if (folders.find((folder) => folder.name === '.hooks')) { 88 | debug(`.hooks folder found in ${root}`); 89 | paths.push(root); 90 | } 91 | 92 | // Otherwise, scan it's content for eventual nested directories if the max depth is not reached 93 | for (const folder of folders) { 94 | // Skip vendor folders. This needs to be improved along usage 95 | if (['node_modules', '.venv', '.git', '.hooks'].includes(folder.name)) { 96 | continue; 97 | } 98 | if (depth < maxDepth) { 99 | paths.push(...this.extractPackagesPaths(depth + 1, maxDepth, path.join(root, folder.name))); 100 | } 101 | } 102 | 103 | return paths; 104 | } 105 | 106 | /** 107 | * Filter a list of folders absolute paths, based on if they provide a steps definition file for the provided hook type. 108 | * @param packagesPaths - a list of string containing the absolute paths to test 109 | * @returns the filtered list of absolute paths, pointing towards the folders where steps for the desired hook type are defined. 110 | */ 111 | filterPackageForHookType(packagesPaths: string[]): string[] { 112 | return packagesPaths.filter((packagePath) => { 113 | const hooksFilePath = path.join(packagePath, '.hooks', `${this.hookType}.json`); 114 | return fs.existsSync(hooksFilePath); 115 | }); 116 | } 117 | 118 | /** 119 | * Load a {@link UnprocessedPackageHook} object from the absolute path of the package's folder. 120 | * @param packagePath - the absolute path to this package 121 | * @param name - the displayed name of this package 122 | * @returns the created package hook instance 123 | */ 124 | loadPackage(packagePath: string, name: string): UnprocessedPackageHook { 125 | const hooksFilePath = path.join(packagePath, '.hooks', `${this.hookType}.json`); 126 | const locallHooksFilePath = path.join(packagePath, '.hooks', `${this.hookType}.local.json`); 127 | 128 | // The assumption that the file exists can be made because of `extractPackagesPaths` 129 | const hooksDefinition = JSON.parse(fs.readFileSync(hooksFilePath, 'utf-8')); 130 | 131 | // @TODO: add data validation on the parsed object 132 | // @TODO: retrieve the type of the package from a separate file to ensure consistency between the local and shared steps 133 | const unprocessedPackageHook: UnprocessedPackageHook = { 134 | name, 135 | cwd: packagePath, 136 | type: hooksDefinition.type, 137 | venvActivate: hooksDefinition.venvActivate, 138 | condaEnv: hooksDefinition.condaEnv, 139 | steps: hooksDefinition.steps, 140 | }; 141 | 142 | // If local steps are also defined, add them to the list of steps 143 | if (fs.existsSync(locallHooksFilePath)) { 144 | const localHooksDefinition = JSON.parse(fs.readFileSync(locallHooksFilePath, 'utf-8')); 145 | // Extend the local steps by adding the "local" suffix to their name 146 | const localSteps = localHooksDefinition.steps.map((step: { name: string }) => ({ 147 | ...step, 148 | name: `${step.name} (local)`, 149 | })); 150 | unprocessedPackageHook.steps.push(...localSteps); 151 | } 152 | 153 | return unprocessedPackageHook; 154 | } 155 | 156 | /** 157 | * Load packages associated to a list of folder absolute paths 158 | * @param packagesPath - the list of absolute paths to the packages to load 159 | * @returns the list of loaded {@link UnprocessedPackageHook} 160 | */ 161 | loadPackages(packagesPath: string[]): UnprocessedPackageHook[] { 162 | const unprocessedPackages: UnprocessedPackageHook[] = []; 163 | for (const packagePath of packagesPath) { 164 | // Properly format the package's name: Turn the absolute path into a relative path from the project's root 165 | let packageName = packagePath.replace(`${this.root}`, ''); 166 | if (packageName.startsWith('/')) { 167 | packageName = packageName.substring(1); 168 | } 169 | // The only path leading to an empty string here is the package located at the project's root path, ie the global steps. 170 | packageName = packageName || 'global'; 171 | // Load the package and add it to the list 172 | unprocessedPackages.push(this.loadPackage(packagePath, packageName)); 173 | } 174 | return unprocessedPackages; 175 | } 176 | 177 | /** 178 | * Load every shared steps in the project and expose their content in a plain object 179 | * @param sharedFolderPath - the absolute path to the folder holding the shared steps. 180 | * @returns a dict containing the different shared steps as {@link StepCommand}, indexed with their name 181 | */ 182 | loadSharedSteps(): { [key: string]: StepCommand } { 183 | const sharedPath = path.join(this.root, '.hooks', 'shared'); 184 | 185 | // Return an empty collection if the folder does not exist 186 | if (!fs.existsSync(sharedPath)) { 187 | return {}; 188 | } 189 | 190 | return fs.readdirSync(sharedPath).reduce((acc, sharedHookFileName) => { 191 | // Ensure the shared step is a valid json file 192 | // @TODO: Make sure the step has a valid content 193 | if (sharedHookFileName.split('.').pop() !== 'json') { 194 | return acc; 195 | } 196 | // Retrieve the shared step's name 197 | const sharedHookName = sharedHookFileName.replace('.json', ''); 198 | return { 199 | ...acc, 200 | [sharedHookName]: JSON.parse( 201 | fs.readFileSync(path.join(sharedPath, sharedHookFileName), 'utf-8'), 202 | ) as StepCommand, 203 | }; 204 | }, {}); 205 | } 206 | 207 | /** 208 | * Transformed shared steps into the real step to execute, with their real command and configuration. 209 | * @param hooks - the list of {@link PackageHook} with steps to interpolate 210 | * @param sharedFolderPath - the absolute path to the folder holding the shared steps. 211 | * @returns the list of {@link PackageHook} with interpolated steps 212 | */ 213 | interpolateSharedSteps(hooks: UnprocessedPackageHook[]): UnprocessedPackageHook[] { 214 | const sharedSteps = this.loadSharedSteps(); 215 | 216 | for (const hook of hooks) { 217 | const interpolatedSteps = []; 218 | for (const step of hook.steps) { 219 | if (step.from) { 220 | if (!sharedSteps[step.from]) { 221 | logger.failure(`Shared step \`${step.from}\` is referenced in hook \`${hook.name}\` but is not defined`); 222 | process.exit(1); 223 | } 224 | interpolatedSteps.push(sharedSteps[step.from]); 225 | } else { 226 | interpolatedSteps.push(step); 227 | } 228 | } 229 | hook.steps = interpolatedSteps; 230 | } 231 | 232 | return hooks; 233 | } 234 | 235 | /** 236 | * Extend the $PATH shell variable with the scripts defined in /.hooks/partials 237 | * 238 | * @param root - the absolute path of the folder holding the `.mookme.json` file, where the global .hooks folder lives 239 | */ 240 | setupPATH(): void { 241 | const partialsPath = path.join(this.root, '.hooks', 'partials'); 242 | if (fs.existsSync(partialsPath)) { 243 | process.env.PATH = `${process.env.PATH}:${partialsPath}`; 244 | } 245 | } 246 | 247 | /** 248 | * Apply onlyOn pattern to a list of steps 249 | * 250 | * @param hooks - the list of {@link PackageHook} to perform the filtering on 251 | * @returns the list of package hooks, where each step of each hook has a `matchedFiles` attribute corresponding to an eventual `onlyOn` pattern 252 | */ 253 | applyOnlyOn(hooks: PackageHook[]): PackageHook[] { 254 | for (const hook of hooks) { 255 | debug(`Looking for steps with onlyOn in package ${hook.name}`); 256 | hook.steps = hook.steps.map((step) => { 257 | if (step.onlyOn) { 258 | debug(`Filtering matched files for step ${step.name} against pattern ${step.onlyOn}`); 259 | try { 260 | const matcher = wcmatch(step.onlyOn); 261 | step.matchedFiles = step.matchedFiles.filter((rPath: string) => { 262 | const match = matcher(rPath); 263 | debug(`Testing path ${rPath} -> ${match}`); 264 | return match; 265 | }); 266 | } catch (err) { 267 | throw new Error(`Invalid \`onlyOn\` pattern: ${step.onlyOn}\n${err}`); 268 | } 269 | } else { 270 | debug(`Skipping step ${step.name} because it has no onlyOn attribute`); 271 | } 272 | return step; 273 | }); 274 | } 275 | return hooks; 276 | } 277 | 278 | hydrateArguments(hooks: PackageHook[], hookArguments: string): PackageHook[] { 279 | debug('Performing command arguments replacements'); 280 | const args = hookArguments 281 | .split(' ') 282 | .filter((arg) => arg !== '') 283 | .join(' '); 284 | debug(`{args} will become ${args}`); 285 | 286 | for (const hook of hooks) { 287 | for (const step of hook.steps) { 288 | if (step.command.includes('{args}')) { 289 | debug(`matched {args} for step ${hook.name} -> ${step.name}`); 290 | const oldCommand = step.command; 291 | step.command = step.command.replace('{args}', `"${args}"`); 292 | debug(`"${oldCommand}" -> "${step.command}"`); 293 | } 294 | if (step.command.includes('{matchedFiles}')) { 295 | debug(`matched {matchedFiles} for step ${hook.name} -> ${step.name}`); 296 | const oldCommand = step.command; 297 | step.command = step.command.replace('{matchedFiles}', step.matchedFiles.join(' ')); 298 | debug(`"${oldCommand}" -> "${step.command}"`); 299 | } 300 | if (step.command.includes('{packageFiles}')) { 301 | debug(`matched {packageFiles} for step ${hook.name} -> ${step.name}`); 302 | const oldCommand = step.command; 303 | step.command = step.command.replace('{packageFiles}', hook.matchedFiles.join(' ')); 304 | debug(`"${oldCommand}" -> "${step.command}"`); 305 | } 306 | } 307 | } 308 | 309 | return hooks; 310 | } 311 | 312 | /** 313 | * A wrapper for executing the packages-retrieval flow. 314 | * @returns the list of prepared packages to hook, filtered based on the VCS state and including interpolated shared steps. 315 | */ 316 | async getPreparedHooks(): Promise { 317 | // Retrieve every hookable package 318 | const allPackages: string[] = this.extractPackagesPaths(); 319 | debug(`Identified the following packages: ${allPackages}`); 320 | 321 | // Filter them to keep only the ones with hooks of the target hook type 322 | const packagesPathsForHookType: string[] = this.filterPackageForHookType(allPackages); 323 | debug( 324 | `Identified the following packages with hooks matching hook type ${this.hookType} ${packagesPathsForHookType}`, 325 | ); 326 | 327 | debug('Loading unprocessed hooks'); 328 | // Build the list of all the available steps for this hook type, including local ones. 329 | // Also load the package information 330 | let unprocessedHooks: UnprocessedPackageHook[] = this.loadPackages(packagesPathsForHookType); 331 | 332 | // Perform shared steps interpolation if needed 333 | unprocessedHooks = this.interpolateSharedSteps(unprocessedHooks); 334 | debug(`Done loading ${unprocessedHooks.length} hooks`); 335 | 336 | const hooks: PackageHook[] = await this.strategy.filter(unprocessedHooks); 337 | return hooks; 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /packages/mookme/src/prompts/init.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import inquirer from 'inquirer'; 3 | import { ADDED_BEHAVIORS } from '../config/types'; 4 | import { HookType, hookTypes } from '../types/hook.types'; 5 | 6 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 7 | export const choiceQuestion = (name: string, message: string, choices: string[]) => ({ 8 | type: 'checkbox', 9 | name, 10 | message, 11 | choices, 12 | pageSize: process.stdout.rows / 2, 13 | }); 14 | 15 | // @todo(lecanu.maxence@gmail.com): standardize arguments of this function as a unique argument being an array of types 16 | export async function selectHookTypes(skip = false, hookCollection?: HookType[]): Promise { 17 | let typesToHook: HookType[]; 18 | 19 | if (skip) { 20 | // If skip is provided, skip everything and return the hook types 21 | typesToHook = hookTypes; 22 | } else if (hookCollection) { 23 | // If skip is not provided, but types are provided, return the types 24 | typesToHook = hookCollection; 25 | } else { 26 | // Prompt the user for the hook types 27 | const { types } = (await inquirer.prompt([ 28 | choiceQuestion('types', 'Select git events to hook :\n', hookTypes), 29 | ])) as { types: HookType[] }; 30 | typesToHook = types; 31 | } 32 | 33 | return typesToHook; 34 | } 35 | 36 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 37 | export const confirmQuestion = (name: string, message: string) => ({ 38 | type: 'confirm', 39 | name, 40 | message, 41 | default: false, 42 | }); 43 | 44 | export const addedBehaviorQuestion = { 45 | type: 'list', 46 | name: 'addedBehavior', 47 | message: 'How should mookme behave when files are changed during hooks execution :\n', 48 | choices: [ 49 | { 50 | name: `${chalk.bold('Exit (recommended):')} fail and exit without performing the commit`, 51 | value: ADDED_BEHAVIORS.EXIT, 52 | }, 53 | { 54 | name: `${chalk.bold('Add them and keep going: ')} run \`git add .\` and continue`, 55 | value: ADDED_BEHAVIORS.ADD_AND_COMMIT, 56 | }, 57 | ], 58 | }; 59 | -------------------------------------------------------------------------------- /packages/mookme/src/runner/init.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import inquirer from 'inquirer'; 3 | import path from 'path'; 4 | import { ADDED_BEHAVIORS } from '../config/types'; 5 | import { addedBehaviorQuestion, selectHookTypes } from '../prompts/init'; 6 | import { GitToolkit } from '../utils/git'; 7 | import logger from '../utils/logger'; 8 | import { HookType } from '../types/hook.types'; 9 | 10 | const clear = () => process.stdout.write('\x1Bc'); 11 | 12 | function createDirIfNeeded(path: string) { 13 | if (!fs.existsSync(path)) { 14 | fs.mkdirSync(path); 15 | } 16 | } 17 | 18 | /** 19 | * The available options for the `init` command. See `addInit` 20 | */ 21 | export interface InitOptions { 22 | /** 23 | * Skip packages definition and only write .git/hooks/ files 24 | */ 25 | onlyHook?: boolean; 26 | /** 27 | * Provide packages list and skip the associated prompter 28 | */ 29 | addedBehaviour?: ADDED_BEHAVIORS; 30 | /** 31 | * Skip hook types selection 32 | */ 33 | skipTypesSelection?: boolean; 34 | /** 35 | * Skip confirmation prompter 36 | */ 37 | yes?: boolean; 38 | /** 39 | * The hook types executed. See {@link HookType} 40 | */ 41 | types?: HookType[]; 42 | } 43 | 44 | export class InitRunner { 45 | gitToolkit: GitToolkit; 46 | 47 | constructor(gitToolkit: GitToolkit) { 48 | this.gitToolkit = gitToolkit; 49 | } 50 | 51 | async run(opts: InitOptions): Promise { 52 | const root = this.gitToolkit.rootDir; 53 | 54 | if (opts.onlyHook) { 55 | const hookTypes = await selectHookTypes(opts.skipTypesSelection, opts.types); 56 | this.gitToolkit.writeGitHooksFiles(hookTypes); 57 | process.exit(0); 58 | } 59 | 60 | clear(); 61 | let addedBehavior; 62 | if (opts.addedBehaviour) { 63 | addedBehavior = opts.addedBehaviour; 64 | } else { 65 | addedBehavior = (await inquirer.prompt([addedBehaviorQuestion])).addedBehavior; 66 | } 67 | 68 | const mookMeConfig = { 69 | addedBehavior, 70 | }; 71 | 72 | const hookTypes = await selectHookTypes(opts.skipTypesSelection, opts.types); 73 | 74 | clear(); 75 | logger.info(`The following configuration will be written into \`${root}/.mookme.json\`:`); 76 | logger.log(JSON.stringify(mookMeConfig, null, 2)); 77 | 78 | logger.info(''); 79 | logger.info('The following git hooks will be created:'); 80 | hookTypes.forEach((t) => logger.log(`${root}/.git/hooks/${t}`)); 81 | 82 | logger.info(''); 83 | logger.info(`The following entries will be added into \`${root}/.gitignore\`:`); 84 | hookTypes.forEach((t) => logger.log(`.${t}.local.json`)); 85 | 86 | let confirm; 87 | logger.log(''); 88 | 89 | if (opts.yes) { 90 | confirm = true; 91 | } else { 92 | confirm = ( 93 | await inquirer.prompt([ 94 | { 95 | type: 'confirm', 96 | name: 'confirm', 97 | message: 'Do you confirm ?', 98 | default: true, 99 | }, 100 | ]) 101 | ).confirm; 102 | } 103 | 104 | if (confirm) { 105 | logger.warning(''); 106 | logger.warning('Writing configuration...'); 107 | fs.writeFileSync('.mookme.json', JSON.stringify(mookMeConfig, null, 2)); 108 | logger.success('Done.'); 109 | 110 | logger.warning('Initializing hooks folders...'); 111 | createDirIfNeeded(path.join(root, '.hooks')); 112 | createDirIfNeeded(path.join(root, '.hooks', 'shared')); 113 | fs.writeFileSync(path.join(root, '.hooks', 'shared', '.gitkeep'), ''); 114 | createDirIfNeeded(path.join(root, '.hooks', 'partials')); 115 | fs.writeFileSync(path.join(root, '.hooks', 'partials', '.gitkeep'), ''); 116 | const example = { 117 | steps: [ 118 | { 119 | name: 'Example hook', 120 | command: 'echo "Hello world!"', 121 | }, 122 | ], 123 | }; 124 | fs.writeFileSync('./.hooks/pre-commit.json', JSON.stringify(example, null, 2)); 125 | logger.success('An example hook has been written in `./.hooks/pre-commit.json`'); 126 | 127 | this.gitToolkit.writeGitHooksFiles(hookTypes); 128 | this.gitToolkit.writeGitIgnoreFiles(hookTypes); 129 | } 130 | 131 | logger.success('Your hooks are configured.'); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /packages/mookme/src/runner/inspect.ts: -------------------------------------------------------------------------------- 1 | import { HooksResolver } from '../loaders/hooks-resolver'; 2 | import logger from '../utils/logger'; 3 | 4 | /** 5 | * A class holding the code executed in the `discover` command of Mookme. 6 | */ 7 | export class InspectRunner { 8 | resolver: HooksResolver; 9 | 10 | constructor(resolver: HooksResolver) { 11 | this.resolver = resolver; 12 | } 13 | 14 | async run(): Promise { 15 | const root = process.cwd(); 16 | const hookType = this.resolver.hookType; 17 | 18 | logger.info(''); 19 | logger.info(`Step 1: Looking for packages under the folder '${root}'`); 20 | logger.info(''); 21 | 22 | // Step 1: Retrieve every hookable package 23 | const allPackages: string[] = this.resolver.extractPackagesPaths(); 24 | 25 | if (allPackages.length > 0) { 26 | logger.success(`Succesfully discovered ${allPackages.length} packages:`); 27 | } else { 28 | logger.failure('No packages found.'); 29 | } 30 | for (const pkg of allPackages) { 31 | logger.info(`- ${pkg}`); 32 | } 33 | 34 | logger.info(''); 35 | logger.info(`Step 2: Filtering packages including definitions for hook ${hookType}`); 36 | logger.info(''); 37 | 38 | // Step 2: Filter them to keep only the ones with hooks of the target hook type 39 | const packagesPathForHookType: string[] = this.resolver.filterPackageForHookType(allPackages); 40 | 41 | if (packagesPathForHookType.length > 0) { 42 | logger.success(`Found ${packagesPathForHookType.length} packages for hook type ${hookType}:`); 43 | } else { 44 | logger.failure(`No packages found for hook type ${hookType}.`); 45 | } 46 | for (const pkg of packagesPathForHookType) { 47 | logger.info(`- ${pkg}`); 48 | } 49 | 50 | logger.info(''); 51 | logger.info(`Step 3: Build the list of available steps`); 52 | logger.info(''); 53 | 54 | let hooks = await this.resolver.getPreparedHooks(); 55 | hooks = this.resolver.applyOnlyOn(hooks); 56 | 57 | for (const hook of hooks) { 58 | logger.info(`* ${hook.name}`); 59 | logger.log(`Path: ${hook.cwd}`); 60 | for (const step of hook.steps) { 61 | logger.log( 62 | step.matchedFiles.length === 0 63 | ? `- [SKIPPED] ${step.name} (${step.command})` 64 | : `- ${step.name} (${step.command})`, 65 | ); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/mookme/src/runner/run.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '../config'; 2 | import { PackageExecutor } from '../executor/package-executor'; 3 | import { HooksResolver } from '../loaders/hooks-resolver'; 4 | import { HookType, VCSSensitiveHook } from '../types/hook.types'; 5 | import { MookmeUI } from '../ui'; 6 | import { GitToolkit } from '../utils/git'; 7 | import logger from '../utils/logger'; 8 | import { processResults } from '../utils/run-helpers'; 9 | 10 | /** 11 | * The available options for the `run` command. See `addRun` 12 | */ 13 | export interface RunOptions { 14 | /** 15 | * The hook type executed. See {@link HookType} 16 | */ 17 | type: HookType; 18 | /** 19 | * The args provided in the git command. See https://git-scm.com/docs/githooks 20 | */ 21 | args: string; 22 | /** 23 | * A boolean parameter to detect if the whole hook suite should be ran, regardless of the VCS state 24 | */ 25 | all: boolean; 26 | /** 27 | * A boolean parameter to detect if the whole hook suite should be ran, regardless of the VCS state 28 | */ 29 | from: string; 30 | /** 31 | * A boolean parameter to detect if the whole hook suite should be ran, regardless of the VCS state 32 | */ 33 | to: string; 34 | /** 35 | * The path of the folder where the mookme configuration file is stored 36 | */ 37 | configRoot: string; 38 | } 39 | 40 | /** 41 | * A class holding the code executed in the `run` command of Mookme. 42 | */ 43 | export class RunRunner { 44 | ui: MookmeUI; 45 | config: Config; 46 | gitToolkit: GitToolkit; 47 | hooksResolver: HooksResolver; 48 | 49 | /** 50 | * A class holding the code executed in the `run` command of Mookme. 51 | * 52 | * @param ui - the {@link MookmeUI} instance of Mookme to use to output the command execution 53 | * @param config - the {@link Config} instance to use to parametrize the execution 54 | * @param gitToolkit - the {@link GitToolkit} instance to use to manage the VCS state 55 | * @param hooksResolver - the {@link HooksResolver} instance to use to load the hooks to run 56 | */ 57 | constructor(ui: MookmeUI, config: Config, gitToolkit: GitToolkit, hooksResolver: HooksResolver) { 58 | this.ui = ui; 59 | this.config = config; 60 | this.gitToolkit = gitToolkit; 61 | this.hooksResolver = hooksResolver; 62 | } 63 | 64 | /** 65 | * Run a suite of git hooks defined for Mookme. 66 | * 67 | * @param opts - the command options passed to Commander. 68 | */ 69 | async run(opts: RunOptions): Promise { 70 | // Initiate console UI output 71 | this.ui.start(); 72 | 73 | // Load the VCS state 74 | const initialNotStagedFiles = this.gitToolkit.getNotStagedFiles(); 75 | 76 | // Retrieve mookme command options 77 | const { args: hookArguments } = opts; 78 | 79 | // Extend the path with partial commands 80 | this.hooksResolver.setupPATH(); 81 | 82 | // Load packages hooks to run 83 | let hooks = await this.hooksResolver.getPreparedHooks(); 84 | hooks = this.hooksResolver.applyOnlyOn(hooks); 85 | 86 | hooks = this.hooksResolver.hydrateArguments(hooks, hookArguments); 87 | 88 | // Instanciate the package executors 89 | const packageExecutors = hooks.map( 90 | (pkg) => 91 | new PackageExecutor(pkg, { 92 | rootDir: this.gitToolkit.rootDir, 93 | }), 94 | ); 95 | 96 | // Run them concurrently and await the results 97 | const executions = packageExecutors.map((executor) => executor.executePackageSteps()); 98 | const packagesErrors = await Promise.all(executions).catch((err) => { 99 | logger.failure(' Unexpected error ! '); 100 | console.error(err); 101 | process.exit(1); 102 | }); 103 | 104 | // Wait for events to be processed 105 | setTimeout(() => { 106 | processResults(packagesErrors); 107 | this.ui.stop(); 108 | }, 100); 109 | 110 | // Do not start modified files procedure, unless we are about to commit 111 | if (VCSSensitiveHook.includes(opts.type)) { 112 | this.gitToolkit.detectAndProcessModifiedFiles(initialNotStagedFiles, this.config.addedBehavior); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /packages/mookme/src/types/hook.types.ts: -------------------------------------------------------------------------------- 1 | import { StepCommand, UnprocessedStepCommand } from './step.types'; 2 | 3 | /** 4 | * An enum of supported git hook types 5 | * 6 | */ 7 | export enum HookType { 8 | PRE_COMMIT = 'pre-commit', 9 | PREPARE_COMMIT = 'prepare-commit-msg', 10 | COMMIT_MSG = 'commit-msg', 11 | POST_COMMIT = 'post-commit', 12 | POST_MERGE = 'post-merge', 13 | POST_REWRITE = 'post-rewrite', 14 | PRE_REBASE = 'pre-rebase', 15 | POST_CHECKOUT = 'post-checkout', 16 | PRE_PUSH = 'pre-push', 17 | } 18 | 19 | /** 20 | * The list of hook types that should watch for modified files to add 21 | * 22 | */ 23 | export const VCSSensitiveHook = [HookType.PRE_COMMIT, HookType.PREPARE_COMMIT]; 24 | 25 | /** 26 | * The list of hook types 27 | * 28 | */ 29 | export const hookTypes = Object.values(HookType); 30 | 31 | /** 32 | * An interface describing the package hook object used across the codebase 33 | * 34 | */ 35 | 36 | export enum PackageType { 37 | PYTHON = 'python', 38 | JS = 'js', 39 | } 40 | export interface UnprocessedPackageHook { 41 | /** 42 | * The displayed name of the package 43 | */ 44 | name: string; 45 | /** 46 | * The list of step descriptors executed with the hook 47 | */ 48 | steps: UnprocessedStepCommand[]; 49 | /** 50 | * The directory where the package is stored 51 | */ 52 | cwd: string; 53 | /** 54 | * The type of the hook 55 | */ 56 | type?: PackageType; 57 | /** 58 | * A boolean denoting whether a virtualenv is started of not for this hook (eg for Python) 59 | */ 60 | venvActivate?: string; 61 | /** 62 | * The conda environment we want to use to start this hook. Optional 63 | */ 64 | condaEnv?: string; 65 | } 66 | 67 | export interface PackageHook extends UnprocessedPackageHook { 68 | matchedFiles: string[]; 69 | steps: StepCommand[]; 70 | } 71 | -------------------------------------------------------------------------------- /packages/mookme/src/types/status.types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An enum denoting the different state in which a step or package item can be 3 | */ 4 | export enum ExecutionStatus { 5 | CREATED = 'CREATED', 6 | RUNNING = 'RUNNING', 7 | SUCCESS = 'SUCCESS', 8 | FAILURE = 'FAILURE', 9 | SKIPPED = 'SKIPPED', 10 | } 11 | -------------------------------------------------------------------------------- /packages/mookme/src/types/step.types.ts: -------------------------------------------------------------------------------- 1 | import { PackageHook } from './hook.types'; 2 | 3 | /** 4 | * An interface describing the step object used across the codebase 5 | * 6 | */ 7 | export interface UnprocessedStepCommand { 8 | /** 9 | * The named of the step. Displayed in the UI and used in it to index steps and hooks 10 | */ 11 | name: string; 12 | /** 13 | * The command that will be invoked in `execSync` 14 | */ 15 | command: string; 16 | /** 17 | * A pattern string describing which changed files will trigger this step 18 | */ 19 | onlyOn?: string; 20 | /** 21 | * Should this step be awaited before starting the next one 22 | */ 23 | serial?: boolean; 24 | /** 25 | * Does this step extend a shared step 26 | */ 27 | from?: string; 28 | } 29 | 30 | export interface StepCommand extends UnprocessedStepCommand { 31 | matchedFiles: string[]; 32 | } 33 | 34 | /** 35 | * An interface describing eventual errors throws in the step executor 36 | * 37 | */ 38 | export interface StepError { 39 | /** 40 | * The hook where the error occured 41 | */ 42 | hook: PackageHook; 43 | /** 44 | * The step of the hook where the error occured 45 | */ 46 | step: StepCommand; 47 | /** 48 | * The actual error 49 | */ 50 | error: Error; 51 | } 52 | -------------------------------------------------------------------------------- /packages/mookme/src/ui/index.ts: -------------------------------------------------------------------------------- 1 | import { bus, Events, EventType } from '../events'; 2 | import { ExecutionStatus } from '../types/status.types'; 3 | import { UIPackageItem } from './types'; 4 | 5 | import Debug from 'debug'; 6 | import { FancyRenderer } from './renderers/fancy-renderer'; 7 | import { Renderer } from './renderers/renderer'; 8 | const debug = Debug('mookme:ui'); 9 | 10 | /** 11 | * A class for managing the UI of Mookme. It provides a logical abstraction of the state of the application. 12 | * 13 | * @remarks 14 | * This abstraction is provided to a renderer instance for being actaully printed 15 | * 16 | * @see {@link EventType} for the events to which the UI is sensitive 17 | */ 18 | export class MookmeUI { 19 | /** 20 | * The list of packages currently stored and managed by the UI 21 | */ 22 | packages: UIPackageItem[] = []; 23 | /** 24 | * A reference to the interval used for the rendering loop. 25 | */ 26 | started = true; 27 | /** 28 | * The renderer instance used to render the UI state into the console 29 | */ 30 | renderer: Renderer; 31 | 32 | /** 33 | * 34 | * @param start - a boolean denoting if the UI should be automatically started 35 | * @param renderer - an instance of the {@link Renderer} class used to render the UI state 36 | */ 37 | constructor(start = true, renderer?: Renderer) { 38 | this.renderer = renderer || new FancyRenderer(); 39 | this.started = start; 40 | 41 | bus.on(EventType.PackageRegistered, [this.onPackageRegistered.bind(this), this.render.bind(this)]); 42 | bus.on(EventType.StepRegistered, [this.onStepRegistered.bind(this), this.render.bind(this)]); 43 | bus.on(EventType.StepStatusChanged, [this.onStepStatusChange.bind(this), this.render.bind(this)]); 44 | } 45 | 46 | /** 47 | * Trigger a rendering of the UI if {@link MookmeUI.started} is true, eg. if {@link MookmeUI.start} has been called. 48 | */ 49 | render(): void { 50 | // Skip if the UI instance has not been asked to display the ui so far 51 | if (this.started) { 52 | this.renderer.render(this.packages); 53 | } 54 | } 55 | 56 | /** 57 | * Start the UI watchers for rendering 58 | */ 59 | start(): void { 60 | debug('Starting UI'); 61 | this.started = true; 62 | } 63 | 64 | /** 65 | * Stop the UI watchers for rendering 66 | */ 67 | stop(): void { 68 | debug('Stopping UI'); 69 | this.started = false; 70 | this.renderer.stop(); 71 | } 72 | 73 | /** 74 | * A helper for updating the status of a package, based on the steps it contains and their statuses 75 | * 76 | * @param name - the name of the package 77 | */ 78 | updatePackageStatus(name: string): void { 79 | debug(`Updating status of package ${name}`); 80 | const pkg = this.packages.find((pkg) => pkg.name === name); 81 | if (pkg) { 82 | if ( 83 | pkg.steps.every((step) => step.status === ExecutionStatus.SKIPPED || step.status === ExecutionStatus.SUCCESS) 84 | ) { 85 | debug( 86 | `Package has only steps in ${ExecutionStatus.SKIPPED} or ${ExecutionStatus.SUCCESS} => marking it as success`, 87 | ); 88 | pkg.status = ExecutionStatus.SUCCESS; 89 | return; 90 | } 91 | if (pkg.steps.some((step) => step.status === ExecutionStatus.FAILURE)) { 92 | debug(`Package has at least one step in ${ExecutionStatus.FAILURE} => marking it as failure`); 93 | pkg.status = ExecutionStatus.FAILURE; 94 | return; 95 | } 96 | if (pkg.steps.some((step) => step.status === ExecutionStatus.RUNNING)) { 97 | debug( 98 | `Package has at least one step in ${ExecutionStatus.RUNNING} and no step in ${ExecutionStatus.FAILURE} => marking it as running`, 99 | ); 100 | pkg.status = ExecutionStatus.RUNNING; 101 | return; 102 | } 103 | debug('Marking package as created (default)'); 104 | pkg.status = ExecutionStatus.CREATED; 105 | } 106 | } 107 | 108 | /** 109 | * Event handler for when a package is registered 110 | * 111 | * @param data - the event payload 112 | * @see {@link Events} for payload's description 113 | */ 114 | onPackageRegistered(data: Events[EventType.PackageRegistered]): void { 115 | debug(`Received event "PackageRegistered" with payload ${JSON.stringify(data)}`); 116 | this.packages.push({ 117 | name: data.name, 118 | status: ExecutionStatus.CREATED, 119 | steps: (data.steps || []).map((step) => ({ 120 | ...step, 121 | status: ExecutionStatus.CREATED, 122 | })), 123 | }); 124 | } 125 | 126 | /** 127 | * Event handler for when a step is registered 128 | * 129 | * @param data - the event payload 130 | * @see {@link Events} for payload's description 131 | */ 132 | onStepRegistered(data: Events[EventType.StepRegistered]): void { 133 | debug(`Received event "StepRegistered" with payload ${JSON.stringify(data)}`); 134 | const pkg = this.packages.find((pkg) => pkg.name === data.packageName); 135 | if (pkg) { 136 | pkg.steps.push({ 137 | ...data.step, 138 | status: ExecutionStatus.CREATED, 139 | }); 140 | // Update the new state of the step's package 141 | this.updatePackageStatus(pkg.name); 142 | } 143 | } 144 | 145 | /** 146 | * Event handler for when a step's state change 147 | * 148 | * @param data - the event payload 149 | * @see {@link Events} for payload's description 150 | */ 151 | onStepStatusChange(data: Events[EventType.StepStatusChanged]): void { 152 | debug(`Received event "StepStatusChange" with payload ${JSON.stringify(data)}`); 153 | const pkg = this.packages.find((pkg) => pkg.name === data.packageName); 154 | if (pkg) { 155 | const step = pkg.steps.find((step) => step.name === data.stepName); 156 | if (step) { 157 | step.status = data.status; 158 | // Update the new state of the step's package 159 | this.updatePackageStatus(pkg.name); 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /packages/mookme/src/ui/renderers/fancy-renderer.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import { Renderer } from './renderer'; 3 | import { ExecutionStatus } from '../../types/status.types'; 4 | import { UIPackageItem, UIStepItem } from '../types'; 5 | import { ConsoleCanvas } from '../writer'; 6 | 7 | const stepDisplayedStatuses = { 8 | [ExecutionStatus.CREATED]: '🗓 Created', 9 | [ExecutionStatus.RUNNING]: '🦾 Running', 10 | [ExecutionStatus.FAILURE]: '❌ Error.', 11 | [ExecutionStatus.SUCCESS]: '✅ Done.', 12 | [ExecutionStatus.SKIPPED]: '⏩ Skipped.', 13 | }; 14 | 15 | /** 16 | * A class designed for rendering the UI state object into console statements 17 | */ 18 | export class FancyRenderer implements Renderer { 19 | writer: ConsoleCanvas; 20 | dots = ['.. ', '. .', ' ..']; 21 | currentDotIndex = 0; 22 | interval: NodeJS.Timeout; 23 | 24 | currentPackages?: UIPackageItem[]; 25 | 26 | /** 27 | * Constructor for the renderer class 28 | * 29 | * @param canvas - a pre-defined instance of the canvas to use. It is rendered otherwise 30 | */ 31 | constructor(canvas?: ConsoleCanvas) { 32 | this.writer = canvas || new ConsoleCanvas(); 33 | this.interval = setInterval(() => { 34 | this.currentDotIndex = (this.currentDotIndex + 1) % 3; 35 | this.currentPackages && this.render(this.currentPackages); 36 | }, 100); 37 | } 38 | 39 | stop(): void { 40 | this.interval && clearInterval(this.interval); 41 | } 42 | 43 | /** 44 | * Renders a step item onto the console canvas 45 | * 46 | * @param step - the step item to render 47 | */ 48 | _renderStep(step: UIStepItem): void { 49 | let displayedCommand = step.command; 50 | if (step.command.length > process.stdout.columns - (step.name.length + 10)) { 51 | displayedCommand = step.command.substring(0, process.stdout.columns - step.name.length - 15) + '...'; 52 | } 53 | this.writer.write(`→ ${chalk.bold(step.name)} > ${displayedCommand} `); 54 | if (step.status === ExecutionStatus.RUNNING) { 55 | this.writer.write(`${stepDisplayedStatuses[step.status]}${this.dots[this.currentDotIndex]} `); 56 | } else { 57 | this.writer.write(`${stepDisplayedStatuses[step.status]} `); 58 | } 59 | } 60 | 61 | /** 62 | * Renders a package item onto the console canvas 63 | * 64 | * @param pkg - the package item to render 65 | */ 66 | _renderPacakage(pkg: UIPackageItem): void { 67 | this.writer.write(); 68 | let message: string; 69 | switch (pkg.status) { 70 | case ExecutionStatus.CREATED: 71 | message = ` Created${this.dots[this.currentDotIndex]} `; 72 | this.writer.write(`${chalk.bold.inverse(` Hooks : ${pkg.name} `)}${chalk.bold.inverse(message)}`); 73 | break; 74 | case ExecutionStatus.RUNNING: 75 | message = ` Running${this.dots[this.currentDotIndex]} `; 76 | this.writer.write(`${chalk.bold.inverse(` Hooks : ${pkg.name} `)}${chalk.bgBlueBright.bold(message)}`); 77 | break; 78 | case ExecutionStatus.SUCCESS: 79 | message = ' Done ✓ '; 80 | this.writer.write(`${chalk.bold.inverse(` Hooks : ${pkg.name} `)}${chalk.bgGreen.bold(message)}`); 81 | break; 82 | case ExecutionStatus.FAILURE: 83 | message = ' Error × '; 84 | this.writer.write(`${chalk.bold.inverse(` Hooks : ${pkg.name} `)}${chalk.bgRed.bold(message)}`); 85 | break; 86 | } 87 | for (const step of pkg.steps) { 88 | this._renderStep(step); 89 | } 90 | } 91 | 92 | /** 93 | * Render the UI state onto the console canvas 94 | * 95 | * @param packages - the list of packages to render, resuming the UI current state 96 | */ 97 | render(packages: UIPackageItem[]): void { 98 | this.writer.clear(); 99 | 100 | for (const pkg of packages) { 101 | this._renderPacakage(pkg); 102 | } 103 | 104 | this.currentPackages = packages; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /packages/mookme/src/ui/renderers/noclear-renderer.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import { Renderer } from './renderer'; 3 | import { ExecutionStatus } from '../../types/status.types'; 4 | import { UIPackageItem } from '../types'; 5 | import { ConsoleCanvas } from '../writer'; 6 | 7 | const randColorHexFactory = () => { 8 | const color = chalk.hex('#' + ((Math.random() * 0xffffff) << 0).toString(16)); 9 | return (msg: string) => color(msg); 10 | }; 11 | 12 | const stepDisplayedStatuses = { 13 | [ExecutionStatus.CREATED]: 'created', 14 | [ExecutionStatus.RUNNING]: 'running', 15 | [ExecutionStatus.FAILURE]: 'error', 16 | [ExecutionStatus.SUCCESS]: 'done', 17 | [ExecutionStatus.SKIPPED]: 'skipped', 18 | }; 19 | 20 | /** 21 | * A class designed for rendering the UI state object into console statements 22 | */ 23 | export class NoClearRenderer implements Renderer { 24 | writer: ConsoleCanvas; 25 | currentPackages?: UIPackageItem[]; 26 | packagesColorizers: Map string> = new Map(); 27 | 28 | /** 29 | * Constructor for the renderer class 30 | * 31 | * @param canvas - a pre-defined instance of the canvas to use. It is rendered otherwise 32 | */ 33 | constructor(canvas?: ConsoleCanvas) { 34 | this.writer = canvas || new ConsoleCanvas(); 35 | } 36 | 37 | stop(): void { 38 | return; 39 | } 40 | 41 | get packagesStatusMap(): Map }> { 42 | const packagesStatusMap = new Map }>(); 43 | 44 | for (const pkg of this.currentPackages || []) { 45 | const stepsStatusMap = new Map(); 46 | for (const step of pkg.steps) { 47 | stepsStatusMap.set(step.name, step.status); 48 | } 49 | packagesStatusMap.set(pkg.name, { 50 | status: pkg.status, 51 | steps: stepsStatusMap, 52 | }); 53 | } 54 | 55 | return packagesStatusMap; 56 | } 57 | 58 | /** 59 | * Render the UI state onto the console canvas 60 | * 61 | * @param packages - the list of packages to render, resuming the UI current state 62 | */ 63 | render(packages: UIPackageItem[]): void { 64 | for (const pkg of packages) { 65 | if (!this.packagesColorizers.has(pkg.name)) this.packagesColorizers.set(pkg.name, randColorHexFactory()); 66 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 67 | const colorizer = this.packagesColorizers.get(pkg.name)!; 68 | 69 | const pkgStatusMap = this.packagesStatusMap.get(pkg.name); 70 | 71 | if (!pkgStatusMap) { 72 | const pkgLine = colorizer(`${pkg.name}:${stepDisplayedStatuses[pkg.status]}`); 73 | this.writer.write(pkgLine); 74 | for (const step of pkg.steps) { 75 | const stepLine = colorizer(`${pkg.name}:${step.name}:${stepDisplayedStatuses[pkg.status]}`); 76 | this.writer.write(stepLine); 77 | } 78 | continue; 79 | } 80 | 81 | if (pkgStatusMap.status !== pkg.status) { 82 | const pkgLine = colorizer(`${pkg.name}:${stepDisplayedStatuses[pkg.status]}`); 83 | this.writer.write(pkgLine); 84 | } 85 | 86 | for (const step of pkg.steps) { 87 | if (pkgStatusMap.steps.get(step.name) !== step.status) { 88 | const stepLine = colorizer(`${pkg.name}:${step.name}:${stepDisplayedStatuses[pkg.status]}`); 89 | this.writer.write(stepLine); 90 | } 91 | } 92 | } 93 | 94 | this.currentPackages = packages.map((pkg) => ({ 95 | ...pkg, 96 | steps: pkg.steps.map((step) => ({ 97 | ...step, 98 | })), 99 | })); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /packages/mookme/src/ui/renderers/renderer.ts: -------------------------------------------------------------------------------- 1 | import { UIPackageItem } from '../types'; 2 | import { ConsoleCanvas } from '../writer'; 3 | 4 | export interface Renderer { 5 | writer: ConsoleCanvas; 6 | render(packages: UIPackageItem[]): void; 7 | stop(): void; 8 | } 9 | -------------------------------------------------------------------------------- /packages/mookme/src/ui/types.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionStatus } from '../types/status.types'; 2 | 3 | /** 4 | * An interface for the representation of a step in the UI 5 | */ 6 | export interface UIStepItem { 7 | /** 8 | * Displayed name of the step, also used for indexing 9 | */ 10 | name: string; 11 | /** 12 | * The command executed in this step 13 | */ 14 | command: string; 15 | /** 16 | * The current status of the step 17 | */ 18 | status: ExecutionStatus; 19 | } 20 | 21 | /** 22 | * An interface for the representation of a package in the UI 23 | */ 24 | export interface UIPackageItem { 25 | /** 26 | * Displayed name of the package, also used for indexing 27 | */ 28 | name: string; 29 | /** 30 | * The current status of the package. Mostly computed from it's steps. 31 | */ 32 | status: ExecutionStatus; 33 | /** 34 | * The list of steps used by the package 35 | */ 36 | steps: UIStepItem[]; 37 | } 38 | -------------------------------------------------------------------------------- /packages/mookme/src/ui/writer.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import Debug from 'debug'; 4 | const debug = Debug('mookme:writer'); 5 | 6 | /** 7 | * A small wrapper around process.stdout.write, ensuring 8 | * we do not log below the console, making it scroll. 9 | * Scrolling breaks the display because of the `console.clear` used 10 | * in the rendering loop. 11 | */ 12 | export class ConsoleCanvas { 13 | private _currentRow = 0; 14 | private _warningWritten = false; 15 | 16 | private debugMode: boolean; 17 | 18 | constructor() { 19 | this.debugMode = process.env.DEBUG ? process.env.DEBUG.includes('mookme') : false; 20 | } 21 | 22 | /** 23 | * Clear the console, and reset the lines count 24 | */ 25 | clear(): void { 26 | if (this.debugMode) { 27 | return; 28 | } 29 | this._currentRow = 0; 30 | this._warningWritten = false; 31 | console.clear(); 32 | } 33 | 34 | /** 35 | * Write a line into the console and increment the line counts 36 | * 37 | * @param line - the line to write 38 | */ 39 | write(line = ''): void { 40 | if (this.debugMode) { 41 | debug(`Line to write with value "${line}"`); 42 | return; 43 | } 44 | if (this._currentRow + 1 < process.stdout.rows - 1) { 45 | process.stdout.write(line); 46 | process.stdout.write('\n'); 47 | this._currentRow++; 48 | return; 49 | } 50 | if (this._warningWritten) { 51 | return; 52 | } 53 | process.stdout.write(chalk.yellow.bold('Logs have been truncated due to a small output area')); 54 | process.stdout.write('\n'); 55 | this._warningWritten = true; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/mookme/src/utils/git.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { execSync } from 'child_process'; 3 | import { HookType } from '../types/hook.types'; 4 | import { ADDED_BEHAVIORS } from '../config/types'; 5 | import logger from './logger'; 6 | import { getRootDir } from './root-dir'; 7 | 8 | import Debug from 'debug'; 9 | const debug = Debug('mookme:git'); 10 | 11 | /** 12 | * A helper class for performing git operations 13 | */ 14 | export class GitToolkit { 15 | /** 16 | * The absolute path to the root directory holding the git folder 17 | */ 18 | rootDir: string; 19 | 20 | /** 21 | * constructor for the GitToolkit class 22 | * 23 | */ 24 | constructor() { 25 | debug('Initializing git toolkit'); 26 | const rootDir = getRootDir('.git'); 27 | debug(`Found root at path ${rootDir}`); 28 | 29 | if (!rootDir) { 30 | logger.failure('Could not find a git project'); 31 | process.exit(0); 32 | } 33 | this.rootDir = rootDir; 34 | } 35 | 36 | getNotStagedFiles(): string[] { 37 | debug(`getNotStagedFiles called`); 38 | const notStagedFiles = execSync('git diff --name-only --diff-filter=d').toString().split('\n'); 39 | debug(`Retrieved the following files not staged: ${notStagedFiles}`); 40 | return notStagedFiles; 41 | } 42 | 43 | getStagedFiles(): string[] { 44 | debug(`getStagedFiles called`); 45 | const stagedFiles = execSync('git diff --cached --name-only --diff-filter=d').toString().split('\n'); 46 | debug(`Retrieved the following files staged: ${stagedFiles}`); 47 | return stagedFiles; 48 | } 49 | 50 | getPreviouslyCommitedFiles(nCommitsBeforeHead = 1): string[] { 51 | debug(`getPreviouslyCommitedFiles(${nCommitsBeforeHead}) called`); 52 | const commitedFiles = execSync(`git diff-tree --no-commit-id --name-only -r HEAD~${nCommitsBeforeHead}`) 53 | .toString() 54 | .split('\n'); 55 | debug(`Retrieved the following files commited: ${commitedFiles}`); 56 | return commitedFiles; 57 | } 58 | 59 | getFilesChangedBetweenRefs(from: string, to: string): string[] { 60 | debug(`getFilesChangedBetweenRefs(${from}, ${to}) called`); 61 | const changedFiles = execSync(`git diff --diff-filter=d ${from} ${to} --name-only`).toString().split('\n'); 62 | 63 | debug(`Retrieved the following files commited: ${changedFiles}`); 64 | return changedFiles; 65 | } 66 | 67 | getCurrentBranchName(): string { 68 | return execSync('git rev-parse --abbrev-ref HEAD').toString(); 69 | } 70 | 71 | getVCSState(): { staged: string[]; notStaged: string[] } { 72 | debug(`getVCSState called`); 73 | return { 74 | staged: this.getStagedFiles(), 75 | notStaged: this.getNotStagedFiles(), 76 | }; 77 | } 78 | 79 | getAllTrackedFiles(): string[] { 80 | debug(`getAllTrackedFiles called`); 81 | return execSync('git ls-tree -r HEAD --name-only', { cwd: this.rootDir }).toString().split('\n'); 82 | } 83 | 84 | getFilesToPush(): string[] { 85 | debug(`getFilesToPush called`); 86 | let commits: string[] = []; 87 | try { 88 | commits = execSync('git rev-list @{push}^..', { cwd: this.rootDir }).toString().split('\n').filter(Boolean); 89 | } catch (e) { 90 | logger.warning('Failed to retrieve the list of commits to push.'); 91 | debug(e); 92 | } 93 | if (commits.length === 0) return []; 94 | return this.getFilesChangedBetweenRefs(commits[commits.length - 1], commits[0]); 95 | } 96 | 97 | detectAndProcessModifiedFiles(initialNotStagedFiles: string[], behavior: ADDED_BEHAVIORS): void { 98 | const notStagedFiles = this.getNotStagedFiles(); 99 | const changedFiles = notStagedFiles.filter((file) => !initialNotStagedFiles.includes(file)); 100 | if (changedFiles.length > 0) { 101 | switch (behavior) { 102 | case ADDED_BEHAVIORS.ADD_AND_COMMIT: 103 | logger.warning('Files were changed during hook execution !'); 104 | logger.info('Following the defined behavior : Add and continue.'); 105 | for (const file of changedFiles) { 106 | execSync(`git add "${this.rootDir}/${file}"`); 107 | } 108 | break; 109 | case ADDED_BEHAVIORS.EXIT: 110 | logger.warning(' Files were changed during hook execution ! '); 111 | logger.info('Following the defined behavior : Exit.'); 112 | process.exit(1); 113 | } 114 | } 115 | } 116 | 117 | writeGitHooksFiles(hookTypes: HookType[]): void { 118 | const gitFolderPath = `${this.rootDir}/.git`; 119 | if (!fs.existsSync(`${gitFolderPath}/hooks`)) { 120 | fs.mkdirSync(`${gitFolderPath}/hooks`); 121 | } 122 | 123 | logger.info('Writing Git hooks files'); 124 | 125 | hookTypes.forEach((type) => { 126 | logger.info(`- ${gitFolderPath}/hooks/${type}`); 127 | const mookmeCmd = `npx @escape.tech/mookme -- run --type ${type} --args "$1"`; 128 | if (fs.existsSync(`${gitFolderPath}/hooks/${type}`)) { 129 | const hook = fs.readFileSync(`${gitFolderPath}/hooks/${type}`).toString(); 130 | if (!hook.includes(mookmeCmd)) { 131 | fs.appendFileSync(`${gitFolderPath}/hooks/${type}`, `\n${mookmeCmd}`, { flag: 'a+', mode: 0o0755 }); 132 | } else { 133 | logger.log(`Hook ${type} is already declared, skipping...`); 134 | } 135 | } else { 136 | logger.warning(`Hook ${type} does not exist, creating file...`); 137 | fs.appendFileSync(`${gitFolderPath}/hooks/${type}`, `#!/bin/bash\n${mookmeCmd}`, { flag: 'a+', mode: 0o0755 }); 138 | } 139 | }); 140 | } 141 | 142 | writeGitIgnoreFiles(hookTypes: HookType[]): void { 143 | logger.info('Writing `.gitignore files`'); 144 | 145 | const root = this.rootDir; 146 | const lines = hookTypes.map((t) => `.hooks/${t}.local.json`); 147 | 148 | if (!fs.existsSync(`${root}/.gitignore`)) { 149 | logger.warning(`Project root has no \`.gitignore\` file, creating it...`); 150 | fs.writeFileSync(`${root}/.gitignore`, lines.join('\n')); 151 | } else { 152 | const gitignoreContent = fs.readFileSync(`${root}/.gitignore`).toString(); 153 | for (const line of lines) { 154 | if (gitignoreContent.includes(line)) { 155 | fs.appendFileSync(`${root}/.gitignore`, `\n.${line}n\n`, { flag: 'a+' }); 156 | } 157 | } 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /packages/mookme/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export class Logger { 4 | success(log: string): void { 5 | console.log(chalk.green.bold(log)); 6 | } 7 | 8 | failure(log: string): void { 9 | console.log(chalk.red.bold(log)); 10 | } 11 | 12 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 13 | log(log: string): void { 14 | console.log(log); 15 | } 16 | 17 | info(log: string): void { 18 | console.log(chalk.bold(log)); 19 | } 20 | 21 | warning(log: string): void { 22 | console.log(chalk.yellow.bold(log)); 23 | } 24 | } 25 | 26 | export default new Logger(); 27 | -------------------------------------------------------------------------------- /packages/mookme/src/utils/root-dir.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | 4 | export function getRootDir(target: string): string | undefined { 5 | let isRoot = false; 6 | let rootDir = process.cwd(); 7 | let i = 0; 8 | while (!isRoot && i < 20) { 9 | isRoot = fs.existsSync(`${rootDir}/${target}`); 10 | if (!isRoot) { 11 | rootDir = `${rootDir}/..`; 12 | } 13 | i++; 14 | } 15 | if (!isRoot) { 16 | return undefined; 17 | } 18 | 19 | return path.resolve(rootDir); 20 | } 21 | -------------------------------------------------------------------------------- /packages/mookme/src/utils/run-helpers.ts: -------------------------------------------------------------------------------- 1 | import { StepError } from '../types/step.types'; 2 | import logger from './logger'; 3 | 4 | export function processResults(results: StepError[][]): void { 5 | results.forEach((packageErrors) => { 6 | packageErrors.forEach((err) => { 7 | logger.failure(`\nHook of package ${err.hook.name} failed at step ${err.step.name} `); 8 | logger.log(err.error.message); 9 | }); 10 | if (packageErrors.length > 0) { 11 | process.exit(1); 12 | } 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /packages/mookme/tests/cases/non-existant-command.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TESTS_DIR=$(mktemp -d) 4 | 5 | ROOT_FOLDER=$(realpath $(dirname "$0")/../..) 6 | npm run build &> /dev/null 7 | 8 | # create and cd in tmp folder 9 | cd $TESTS_DIR 10 | 11 | git init 12 | mkdir -p package1 13 | mkdir -p package1/.hooks 14 | 15 | node $ROOT_FOLDER/dist/index.js init \ 16 | --yes \ 17 | --added-behaviour exit \ 18 | --skip-types-selection \ 19 | 20 | echo '{"steps": [{"name": "Custom Command", "command": "get-dir-name"}]}' > package1/.hooks/pre-commit.json 21 | git add . 22 | ! node $ROOT_FOLDER/dist/index.js run -t pre-commit -------------------------------------------------------------------------------- /packages/mookme/tests/cases/pre-commit-fails.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TESTS_DIR=$(mktemp -d) 4 | 5 | ROOT_FOLDER=$(realpath $(dirname "$0")/../..) 6 | npm run build &> /dev/null 7 | 8 | # create and cd in tmp folder 9 | cd $TESTS_DIR 10 | 11 | git init 12 | mkdir -p package1 13 | mkdir -p package1/.hooks 14 | 15 | node $ROOT_FOLDER/dist/index.js init \ 16 | --yes \ 17 | --added-behaviour exit \ 18 | --skip-types-selection \ 19 | 20 | echo '{"steps": [{"name": "Oups", "command": "exit 1"}]}' > package1/.hooks/pre-commit.json 21 | 22 | git add . 23 | ! node $ROOT_FOLDER/dist/index.js run -t pre-commit -------------------------------------------------------------------------------- /packages/mookme/tests/cases/pre-commit-succeeds.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TESTS_DIR=$(mktemp -d) 4 | 5 | ROOT_FOLDER=$(realpath $(dirname "$0")/../..) 6 | npm run build &> /dev/null 7 | 8 | # create and cd in tmp folder 9 | cd $TESTS_DIR 10 | 11 | git init 12 | git commit --allow-empty -m "first commit" 13 | mkdir -p package1 14 | mkdir -p package1/.hooks 15 | mkdir -p parent1/package2 16 | mkdir -p parent1/package3 17 | mkdir -p parent1/package3/.hooks 18 | touch package1/tobedeleted.txt 19 | 20 | node $ROOT_FOLDER/dist/index.js init \ 21 | --yes \ 22 | --added-behaviour exit \ 23 | --skip-types-selection 24 | 25 | { 26 | echo '#!/usr/bin/env bash' 27 | echo 'basename "$(pwd)"' 28 | } > .hooks/partials/get-dir-name 29 | chmod +x .hooks/partials/get-dir-name 30 | 31 | echo '{"steps": [{"name": "Hello world !", "command": "echo 'hello'"}]}' > .hooks/pre-commit.json 32 | echo '{"type": "txt", "steps": [{"name": "Ignore deleted files", "command": "cat {matchedFiles}"}]}' > package1/.hooks/pre-commit.json 33 | echo '{"steps": [{"name": "Hello world !", "command": "[ $(get-dir-name) = 'package3' ]"}]}' > parent1/package3/.hooks/pre-commit.json 34 | 35 | git add . 36 | node $ROOT_FOLDER/dist/index.js run -t pre-commit 37 | 38 | git commit -m "test: commit" --no-verify 39 | rm package1/tobedeleted.txt 40 | git add . 41 | 42 | node $ROOT_FOLDER/dist/index.js run -t pre-commit 43 | -------------------------------------------------------------------------------- /packages/mookme/tests/cases/use-local-hook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TESTS_DIR=$(mktemp -d) 4 | 5 | ROOT_FOLDER=$(realpath $(dirname "$0")/../..) 6 | npm run build &> /dev/null 7 | 8 | # create and cd in tmp folder 9 | cd $TESTS_DIR 10 | 11 | git init 12 | mkdir -p package1 13 | mkdir -p package1/.hooks 14 | 15 | node $ROOT_FOLDER/dist/index.js init \ 16 | --yes \ 17 | --added-behaviour exit \ 18 | --skip-types-selection \ 19 | 20 | echo '{"steps": [{"name": "Hello world !", "command": "echo 'hello'"}]}' > .hooks/pre-commit.json 21 | echo '{"steps": [{"name": "Hello world !", "command": "echo 'hello from local' > test.txt"}]}' > .hooks/pre-commit.local.json 22 | 23 | echo '{"steps": [{"name": "Hello world !", "command": "echo 'hello'"}]}' > package1/.hooks/pre-commit.json 24 | echo '{"steps": [{"name": "Hello world !", "command": "echo 'hello from package 1' > test-package.txt"}]}' > package1/.hooks/pre-commit.local.json 25 | 26 | git add . 27 | node $ROOT_FOLDER/dist/index.js run -t pre-commit 28 | 29 | if [ ! "$(grep -c ".hooks/pre-commit.local.json" .gitignore)" -eq 1 ]; then exit 1; fi; 30 | if [ ! "$(grep -c "hello from local" test.txt)" -eq 1 ]; then exit 1; fi; 31 | if [ ! "$(grep -c "hello from package 1" package1/test-package.txt)" -eq 1 ]; then exit 1; fi; -------------------------------------------------------------------------------- /packages/mookme/tests/logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Escape-Technologies/mookme/24433c2fb91700a71e597138db2e2d1881237bdf/packages/mookme/tests/logs/.gitkeep -------------------------------------------------------------------------------- /packages/mookme/tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CASES_FOLDER=$(realpath $(dirname "$0")/cases) 4 | 5 | cd $CASES_FOLDER 6 | 7 | for CASE in ./* 8 | do 9 | ./$CASE > ../logs/$CASE.output.log 10 | if [ $? == 0 ]; then echo "$CASE > success"; else echo "$CASE > failure" && exit 1; fi 11 | done 12 | -------------------------------------------------------------------------------- /packages/mookme/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node10/tsconfig.json", 3 | "compilerOptions": { 4 | "incremental": true, 5 | "declaration": true, 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "tsBuildInfoFile": "./.tmp/ts-build-infos.json", 9 | "removeComments": true, 10 | "strict": true, 11 | "resolveJsonModule": true, 12 | "esModuleInterop": true 13 | }, 14 | "exclude": [ 15 | "node_modules", 16 | "dist" 17 | ] 18 | } 19 | --------------------------------------------------------------------------------