├── .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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------