",
9 | "license": "MIT",
10 | "private": false,
11 | "type": "module",
12 | "engines": {
13 | "node": ">=18 <25"
14 | },
15 | "dependencies": {
16 | "cosmiconfig": "^9.0.0",
17 | "execa": "^9.5.2",
18 | "listr2": "^8.2.5",
19 | "micromatch": "^4.0.8",
20 | "string-argv": "^0.3.2"
21 | },
22 | "devDependencies": {
23 | "eslint": "^9.17.0",
24 | "eslint-plugin-json": "^4.0.1",
25 | "globals": "^16.0.0",
26 | "husky": "^9.1.7",
27 | "lint-staged": "^16.0.0"
28 | },
29 | "scripts": {
30 | "test": "eslint .",
31 | "preversion": "npm run test",
32 | "postversion": "git push --atomic && gh release create --generate-notes $npm_package_version",
33 | "run-if-changed": "./run-if-changed.js",
34 | "prepare": "husky"
35 | },
36 | "husky": {
37 | "hooks": {
38 | "pre-commit": "lint-staged",
39 | "post-commit": "npm run run-if-changed",
40 | "post-checkout": "npm run run-if-changed",
41 | "post-merge": "npm run run-if-changed",
42 | "post-rewrite": "npm run run-if-changed"
43 | }
44 | },
45 | "run-if-changed": {
46 | "package-lock.json": [
47 | "npm install --prefer-offline --no-audit --no-fund"
48 | ]
49 | },
50 | "lint-staged": {
51 | "*.{js,json}": "eslint --fix"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Node.js Package
2 |
3 | on:
4 | release:
5 | types: [created]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | node-version: [ 18, 20, 22, 24 ]
13 | steps:
14 | - uses: actions/checkout@v5
15 | - name: Use Node.js ${{ matrix.node-version }}
16 | uses: actions/setup-node@v4
17 | with:
18 | node-version: ${{ matrix.node-version }}
19 | cache: 'npm'
20 | - name: npm install, build, and test
21 | run: |
22 | npm ci
23 | npm run build --if-present
24 | npm test
25 |
26 | publish-npm:
27 | needs: build
28 | runs-on: ubuntu-latest
29 | permissions:
30 | contents: read
31 | id-token: write
32 | steps:
33 | - uses: actions/checkout@v5
34 | - uses: actions/setup-node@v4
35 | with:
36 | cache: 'npm'
37 | registry-url: https://registry.npmjs.org/
38 | - run: npm ci
39 | - run: npm publish --provenance --access public
40 | env:
41 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
42 |
43 | publish-gpr:
44 | needs: build
45 | runs-on: ubuntu-latest
46 | permissions:
47 | contents: read
48 | id-token: write
49 | packages: write
50 | steps:
51 | - uses: actions/checkout@v5
52 | - uses: actions/setup-node@v4
53 | with:
54 | cache: 'npm'
55 | registry-url: https://npm.pkg.github.com/
56 | - run: npm ci
57 | - run: npm publish --provenance --access public
58 | env:
59 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🔃 run-if-changed [](https://www.npmjs.com/package/@hkdobrev/run-if-changed)
2 |
3 | Run a command if a file changes via Git hooks.
4 | Useful for lock files or build systems to keep dependencies and generated files up to date when changing branches, pulling or commiting.
5 |
6 | Inspired by [`lint-staged`](https://github.com/okonet/lint-staged) and recommended to be used with [`husky`](https://github.com/typicode/husky).
7 |
8 | #### State of the project
9 |
10 | run-if-changed is functional as-is, but it's still quite basic and rough as it has just been published. So issues, feature requests and pull requests are most welcome!
11 |
12 | ## Installation and setup
13 |
14 |
15 | Install with npm
16 |
17 |
18 | npm install --save-dev husky @hkdobrev/run-if-changed
19 |
20 |
21 | Recommended setup
22 |
23 |
24 | "run-if-changed": {
25 | "package-lock.json": "npm install --prefer-offline --no-audit"
26 | }
27 |
28 |
29 |
30 |
31 | Install with Yarn
32 |
33 |
34 | yarn add --dev husky @hkdobrev/run-if-changed
35 |
36 |
37 | Recommended setup
38 |
39 |
40 | "run-if-changed": {
41 | "yarn.lock": "yarn install --prefer-offline --pure-lockfile --color=always"
42 | }
43 |
44 |
45 |
46 | ### Set up Git hooks
47 |
48 |
49 | Using husky
50 |
51 |
52 | echo "npx run-if-changed" > .husky/post-commit
53 | echo "npx run-if-changed" > .husky/post-checkout
54 | echo "npx run-if-changed" > .husky/post-merge
55 | echo "npx run-if-changed" > .husky/post-rewrite
56 |
57 |
58 |
59 |
60 |
61 | Just git hooks
62 |
63 |
64 | echo "npx run-if-changed" >> .git/hooks/post-commit && chmod +x .git/hooks/post-commit
65 | echo "npx run-if-changed" >> .git/hooks/post-checkout && chmod +x .git/hooks/post-checkout
66 | echo "npx run-if-changed" >> .git/hooks/post-merge && chmod +x .git/hooks/post-merge
67 | echo "npx run-if-changed" >> .git/hooks/post-rewrite && chmod +x .git/hooks/post-rewrite
68 |
69 |
70 |
71 |
72 | ## Why
73 |
74 | The use case for `run-if-changed` is mostly for a team working on a project and push and pull code in different branches. When you share dependencies, database migrations or compilable code in the shared Git repository often some commands need to be run when a file or folder gets updated.
75 |
76 | Check out the [common use cases](#use-cases).
77 |
78 | ## Configuration
79 |
80 | - `run-if-changed` object in your `package.json`
81 | - `.run-if-changedrc` file in JSON or YML format
82 | - `run-if-changed.config.js` file in JS format
83 |
84 | See [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) for more details on what formats are supported.
85 |
86 | Configuration should be an object where each key is a file or directory match pattern and the value is either a single command or an array of commands to run if the file have changed since the last Git operation.
87 |
88 | ## What commands are supported?
89 |
90 | Supported are any executables installed locally or globally via `npm` or Yarn as well as any executable from your `$PATH`.
91 |
92 | > Using globally installed scripts is discouraged, since run-if-changed may not work for someone who doesn't have it installed.
93 |
94 | `run-if-changed` is using [`execa`](https://github.com/sindresorhus/execa) to locate locally installed scripts and run them. So in your `.run-if-changedrc` you can just write and it would use the local version:
95 |
96 |
97 | {
98 | "src": "webpack"
99 | }
100 |
101 |
102 | Sequences of commands are supported. Pass an array of commands instead of a single one and they will run sequentially.
103 |
104 | ## Use cases
105 |
106 | #### Install or update dependencies when lock file changes
107 |
108 | If you use a dependency manager with a lock file like npm, Yarn, Composer, Bundler or others, you would usually add a dependency and the dependency manager would install it and add it to the lock file in a single run. However, when someone else has updated a dependency and you pull new code or checkout their branch you need to manually run the install command of your dependency manager.
109 |
110 | Here's example configuration of `run-if-changed`:
111 |
112 |
113 | npm
114 |
115 | package.json
116 |
117 |
118 | {
119 | "run-if-changed": {
120 | "package-lock.json": "npm install --prefer-offline --no-audit --no-fund"
121 | }
122 | }
123 |
124 |
125 | .run-if-changedrc
126 |
127 |
128 | {
129 | "package-lock.json": "npm install --prefer-offline --no-audit --no-fund"
130 | }
131 |
132 |
133 |
134 |
135 |
136 | Yarn
137 |
138 | package.json
139 |
140 |
141 | {
142 | "run-if-changed": {
143 | "yarn.lock": "yarn install --prefer-offline --pure-lockfile --color=always --non-interactive"
144 | }
145 | }
146 |
147 |
148 | .run-if-changedrc
149 |
150 |
151 | {
152 | "yarn.lock": "yarn install --prefer-offline --pure-lockfile --color=always --non-interactive"
153 | }
154 |
155 |
156 |
157 |
158 |
159 | Composer
160 |
161 | package.json
162 |
163 |
164 | {
165 | "run-if-changed": {
166 | "composer.lock": "composer install --ignore-platform-reqs --ansi"
167 | }
168 | }
169 |
170 |
171 |
172 |
173 |
174 | Bundler
175 |
176 | package.json
177 |
178 |
179 | {
180 | "run-if-changed": {
181 | "Gemfile.lock": "bundle install --prefer-local"
182 | }
183 | }
184 |
185 |
186 |
187 |
188 | #### Run database migrations if there are new migrations
189 |
190 | If you keep database migrations in your repository, you'd usually want to run them when you check out a branch or pull from master.
191 |
192 |
193 | Example of running Doctrine migrations when pulling or changing branches
194 |
195 | package.json
196 |
197 |
198 | {
199 | "run-if-changed": {
200 | "migrations": "./console db:migrate --allow-no-migration --no-interaction"
201 | }
202 | }
203 |
204 |
205 |
206 |
207 | #### Compile sources in a build folder after pulling new code.
208 |
209 |
210 | Example for running build on changing src folder when pulling or changing branches
211 |
212 | package.json
213 |
214 |
215 | {
216 | "run-if-changed": {
217 | "src": "npm run build"
218 | }
219 | }
220 |
221 |
222 |
223 |
--------------------------------------------------------------------------------