@zukhruf/git-hooks 0.0.0-stage → 0.3.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +258 -2
- package/dist/build.d.ts +15 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +163 -0
- package/dist/build.js.map +1 -0
- package/dist/codegen.d.ts +15 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/codegen.js +38 -0
- package/dist/codegen.js.map +1 -0
- package/dist/commit.d.ts +15 -0
- package/dist/commit.d.ts.map +1 -0
- package/dist/commit.js +96 -0
- package/dist/commit.js.map +1 -0
- package/dist/format.d.ts +7 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +44 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/push.d.ts +29 -0
- package/dist/push.d.ts.map +1 -0
- package/dist/push.js +108 -0
- package/dist/push.js.map +1 -0
- package/dist/ref-update.d.ts +17 -0
- package/dist/ref-update.d.ts.map +1 -0
- package/dist/ref-update.js +43 -0
- package/dist/ref-update.js.map +1 -0
- package/dist/scripts/pre-commit.d.ts +4 -0
- package/dist/scripts/pre-commit.d.ts.map +1 -0
- package/dist/scripts/pre-commit.js +20 -0
- package/dist/scripts/pre-commit.js.map +1 -0
- package/dist/scripts/pre-push.d.ts +4 -0
- package/dist/scripts/pre-push.d.ts.map +1 -0
- package/dist/scripts/pre-push.js +35 -0
- package/dist/scripts/pre-push.js.map +1 -0
- package/dist/snapshot/commit-files.d.ts +5 -0
- package/dist/snapshot/commit-files.d.ts.map +1 -0
- package/dist/snapshot/commit-files.js +101 -0
- package/dist/snapshot/commit-files.js.map +1 -0
- package/dist/snapshot/drift.d.ts +3 -0
- package/dist/snapshot/drift.d.ts.map +1 -0
- package/dist/snapshot/drift.js +72 -0
- package/dist/snapshot/drift.js.map +1 -0
- package/dist/snapshot/installs.d.ts +2 -0
- package/dist/snapshot/installs.d.ts.map +1 -0
- package/dist/snapshot/installs.js +83 -0
- package/dist/snapshot/installs.js.map +1 -0
- package/dist/snapshot/nx.d.ts +7 -0
- package/dist/snapshot/nx.d.ts.map +1 -0
- package/dist/snapshot/nx.js +20 -0
- package/dist/snapshot/nx.js.map +1 -0
- package/dist/snapshot/packages.d.ts +2 -0
- package/dist/snapshot/packages.d.ts.map +1 -0
- package/dist/snapshot/packages.js +72 -0
- package/dist/snapshot/packages.js.map +1 -0
- package/dist/snapshot/workspaces.d.ts +2 -0
- package/dist/snapshot/workspaces.d.ts.map +1 -0
- package/dist/snapshot/workspaces.js +26 -0
- package/dist/snapshot/workspaces.js.map +1 -0
- package/dist/step.d.ts +12 -0
- package/dist/step.d.ts.map +1 -0
- package/dist/step.js +15 -0
- package/dist/step.js.map +1 -0
- package/dist/sync.d.ts +11 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +38 -0
- package/dist/sync.js.map +1 -0
- package/package.json +58 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ezzabuzaid
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,259 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @zukhruf/git-hooks
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Git hooks written in TypeScript. A pre-commit hook runs steps in order against the staged commit. The steps check the staged copy of each file with Prettier and change nothing, run `nx sync` and stage only the files that sync wrote, and build generated files when nx says that their projects are affected. A pre-push hook runs steps in order against the push. Its build step builds each pushed commit in a snapshot, never the working tree. A repo adds a step of its own as a class.
|
|
4
|
+
|
|
5
|
+
The words in these documents have one meaning each. See the glossary in [CONTEXT.md](./CONTEXT.md).
|
|
6
|
+
|
|
7
|
+
## Install the hook
|
|
8
|
+
|
|
9
|
+
Add the package and the repo's own Prettier:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install --save-dev @zukhruf/git-hooks prettier
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Write the hook in `.husky/pre-commit.ts`, and make it executable:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
#!/usr/bin/env node
|
|
19
|
+
import { Format, preCommit } from '@zukhruf/git-hooks';
|
|
20
|
+
|
|
21
|
+
await preCommit([new Format()]);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Git runs a hook by its name, and Node runs a TypeScript file only when its name ends in `.ts`. So `.husky/pre-commit` is a link to the TypeScript file. Commit the link:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
chmod +x .husky/pre-commit.ts
|
|
28
|
+
ln -s pre-commit.ts .husky/pre-commit
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The hook uses `await` at the top level, so Node must read it as a module. When `package.json` has no `"type": "module"`, name the file `pre-commit.mts`, and link `.husky/pre-commit` to that name.
|
|
32
|
+
|
|
33
|
+
Tell git where the hooks are, in the `prepare` script of `package.json`:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{ "scripts": { "prepare": "git config core.hooksPath .husky || exit 0" } }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Check the format of the staged files
|
|
40
|
+
|
|
41
|
+
`Format` checks the staged copy of each staged file with the Prettier config of the repo. It skips the files that `.prettierignore` names, the files that Prettier has no parser for, and links, because the staged copy of a link is the path that it points to. When a file is not formatted, or Prettier cannot parse it, the commit stops and the hook names each file:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
format 2 staged files are not formatted:
|
|
45
|
+
src/app.ts
|
|
46
|
+
docs/notes.md (SyntaxError: Unexpected token (3:1))
|
|
47
|
+
Run `npx prettier --write src/app.ts docs/notes.md`, stage them, and commit again.
|
|
48
|
+
✖ commit stopped by the pre-commit hook
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The step changes no file and stages nothing. A file that is partly staged is judged as the commit holds it.
|
|
52
|
+
|
|
53
|
+
## Stage the files that nx sync writes
|
|
54
|
+
|
|
55
|
+
`Sync` runs `nx sync` and stages the files that it changed. The option `stage` names the files that the sync generators of the repo write. The default is `**/tsconfig*.json`. A repo with the Tailwind sync generator adds its style sheets:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { Format, Sync, preCommit } from '@zukhruf/git-hooks';
|
|
59
|
+
|
|
60
|
+
await preCommit([
|
|
61
|
+
new Sync({ stage: ['**/tsconfig*.json', '**/styles.css'] }),
|
|
62
|
+
new Format(),
|
|
63
|
+
]);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The step does not stage a file that was changed before the hook ran, or a changed file that `stage` does not name: another session can edit the same working tree. When `nx sync` fails, the commit stops, and the hook shows the reason that nx gave.
|
|
67
|
+
|
|
68
|
+
## Build generated files
|
|
69
|
+
|
|
70
|
+
`Codegen` runs nx targets that write generated files, and stages those files. With `when`, it builds only when nx says that the staged files affect one of those projects:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { Codegen, Format, Sync, preCommit } from '@zukhruf/git-hooks';
|
|
74
|
+
|
|
75
|
+
await preCommit([
|
|
76
|
+
new Sync(),
|
|
77
|
+
new Codegen({
|
|
78
|
+
when: ['backend', 'frontend'],
|
|
79
|
+
run: ['backend:build', 'frontend:build'],
|
|
80
|
+
stage: ['openapi.json', 'packages/client/src'],
|
|
81
|
+
}),
|
|
82
|
+
new Format(),
|
|
83
|
+
]);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The targets run in the order that `run` gives. When a target fails, the commit stops, and the hook shows the reason that the target gave. Put `Format` after `Codegen`, so that the generated files are checked too.
|
|
87
|
+
|
|
88
|
+
## Add a step of your own
|
|
89
|
+
|
|
90
|
+
A pre-commit step is a subclass of `Step<Commit>`. It names itself and gives an outcome: `this.pass()`, `this.skip(why)` or `this.fail(why)`. A fail stops the commit. `Commit` reads the staged commit: `staged()`, `files()` (the staged paths that hold file content, without links and submodules), `blob(path)`, `touches(globs)`, and `inSnapshot(work)`, which runs `work` on a copy of the staged tree in a temporary folder:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { type Commit, Step } from '@zukhruf/git-hooks';
|
|
94
|
+
|
|
95
|
+
export class DesignCheck extends Step<Commit> {
|
|
96
|
+
readonly name = 'design';
|
|
97
|
+
|
|
98
|
+
async run(commit: Commit) {
|
|
99
|
+
if (!(await commit.touches(['DESIGN.md', 'apps/**/*.css'])))
|
|
100
|
+
return this.skip('no design files staged');
|
|
101
|
+
const valid = await commit.inSnapshot((folder) => checkDesign(folder));
|
|
102
|
+
return valid
|
|
103
|
+
? this.pass()
|
|
104
|
+
: this.fail('DESIGN.md disagrees with the styles');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Install the pre-push hook
|
|
110
|
+
|
|
111
|
+
Git runs the pre-push hook before `git push` sends commits to a remote. Write the hook in `.husky/pre-push.ts`:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
#!/usr/bin/env node
|
|
115
|
+
import { Build, prePush } from '@zukhruf/git-hooks';
|
|
116
|
+
|
|
117
|
+
await prePush([new Build()]);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Make it executable, and commit a link to it, as for the pre-commit hook:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
chmod +x .husky/pre-push.ts
|
|
124
|
+
ln -s pre-push.ts .husky/pre-push
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The hook uses `await` at the top level, so Node must read it as a module. When `package.json` has no `"type": "module"`, name the file `pre-push.mts`, and link to that name. The `prepare` script in [Install the hook](#install-the-hook) tells git where both hooks are.
|
|
128
|
+
|
|
129
|
+
Git gives the hook the name and the URL of the remote, and one line for each ref that the push changes. `prePush` reads them into a `Push`. It runs the steps in order and prints the outcome of each step. At the first fail, the push stops.
|
|
130
|
+
|
|
131
|
+
## Build the pushed commits
|
|
132
|
+
|
|
133
|
+
`Build` builds each pushed commit with the production configuration of nx. It does not build the working tree. Another session can edit the working tree while you push. An edit that is not committed is not in the push, so it cannot stop a good push, and it cannot hide a broken one.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { Build, prePush } from '@zukhruf/git-hooks';
|
|
137
|
+
|
|
138
|
+
await prePush([
|
|
139
|
+
new Build({
|
|
140
|
+
targets: ['lint', 'build'],
|
|
141
|
+
parallel: 1,
|
|
142
|
+
env: { NODE_ENV: 'production' },
|
|
143
|
+
}),
|
|
144
|
+
]);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- `targets` are the nx targets to run on all projects. The default is `['build']`.
|
|
148
|
+
- `parallel` is the number of tasks that nx runs at the same time. Without it, nx uses its own default.
|
|
149
|
+
- `env` holds more environment variables for nx.
|
|
150
|
+
|
|
151
|
+
Build runs `nx run-many` with `--configuration=production` and with `NX_DAEMON=false`. `env` goes to nx only, never to `npm ci`: with `NODE_ENV=production`, npm does not install the devDependencies. Build builds each pushed commit one time, also when the push sends it to more than one ref. Build gives one line for each pushed commit: built, skipped or failed. Each line names the commit:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
build a1b2c3d: built 19 projects, 17 of 20 tasks from cache
|
|
155
|
+
d4e5f6a: built 19 projects, 20 of 20 tasks from cache
|
|
156
|
+
skipped: the remote already has 0b1c2d3
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
When a task fails, the push stops. The hook names the failed tasks and shows up to 20 lines of the error output of each failed task. When the output has more lines, the last line gives their number, such as `… 12 more lines`:
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
build a1b2c3d: 1 of 20 tasks failed: elements:build
|
|
163
|
+
packages/elements/src/lib/define.ts(34,14): error TS2322: Type 'string' is not assignable to type 'number'.
|
|
164
|
+
✖ push stopped by the pre-push hook
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
A push that only deletes refs has no commit to build:
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
build skipped: the push only deletes refs
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Build builds a pushed commit only when the push sends at least one commit that the remote does not have. A push of a commit that the remote has already, for example to a new branch, has nothing new to build:
|
|
174
|
+
|
|
175
|
+
```text
|
|
176
|
+
build skipped: the remote already has a1b2c3d
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Build works with npm workspaces. A pushed commit without `package-lock.json` stops the push at once. This is a setup error, so the first push shows it:
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
build a1b2c3d has no package-lock.json; Build works with npm workspaces
|
|
183
|
+
✖ push stopped by the pre-push hook
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
In one repo, one pre-push build runs at a time. A second push waits for the first build to end, and does not fail:
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
build waiting for another pre-push build in this repo
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Where a pre-push build runs
|
|
193
|
+
|
|
194
|
+
Each pushed commit is built in a snapshot: a copy of the files of that commit in a temporary folder outside the repo. The files come from the object store of git, so the snapshot holds what the commit holds, and nothing that is only in the working tree. A file that `.gitignore` names, such as `.env`, is not in the snapshot.
|
|
195
|
+
|
|
196
|
+
nx runs in the snapshot, and git commands in nx work there. The hook sets `GIT_DIR` to the git folder of the repo, `GIT_WORK_TREE` to the snapshot, and `GIT_INDEX_FILE` to a temporary index that it makes from the pushed commit. Thus git sees the snapshot as a clean checkout of that commit, with no changed and no untracked files, and `nx affected --base --head` works.
|
|
197
|
+
|
|
198
|
+
The snapshot uses the packages that the repo installed when they match the `package-lock.json` of the commit. Then the setup takes about 1 s. A workspace package resolves to its folder in the snapshot, not in the working tree.
|
|
199
|
+
|
|
200
|
+
When the installed packages do not match the `package-lock.json` of the commit, the hook installs that lockfile in a folder of its own, and the snapshot uses that install. This is install drift: a package that the lockfile names is missing or has a different version, or a package is installed that the lockfile does not name. An extra package is drift too, because code that imports it builds here and fails in CI. Install drift occurs when a peer changed the dependencies but did not commit the change, or when the push sends a commit with other dependencies. The hook prints a line before the install, because a full install takes some time:
|
|
201
|
+
|
|
202
|
+
```text
|
|
203
|
+
build a1b2c3d: the installed packages differ from package-lock.json; installing it with npm ci (first time for this lockfile)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The install runs `npm ci --ignore-scripts`, and then `npm rebuild` with the names of the dependencies that have install scripts. The lockfile marks these dependencies with `hasInstallScript`. Thus the install scripts of the dependencies run, such as those of esbuild and prisma. The scripts of the repo and of its workspace packages, such as `prepare`, do not run. The first push with a new lockfile waits for a full install. The next pushes with that lockfile use the same install. The hook keeps the two installs that it used last. A repo with no `node_modules` gets its install the same way.
|
|
207
|
+
|
|
208
|
+
The hook keeps the installs and the nx cache of the pre-push builds in its state folder: `git-hooks` in the common git folder of the repo (`git rev-parse --git-common-dir`). Linked worktrees use the same state folder. The nx cache of the working tree (`.nx`) is not used, and the hook does not write to it. A file lock in the state folder lets one pre-push build run at a time. The hook deletes each snapshot after its build.
|
|
209
|
+
|
|
210
|
+
On Windows, the snapshot links to folders with junctions, which need no administrator rights.
|
|
211
|
+
|
|
212
|
+
## Add a pre-push step of your own
|
|
213
|
+
|
|
214
|
+
A pre-push step is a subclass of `Step<Push>`. A step of `Step<Commit>`, such as `Format`, cannot go into `prePush`: at push time there is no staged commit.
|
|
215
|
+
|
|
216
|
+
`Push` has the `remote` (`name` and `url`) and the `updates`. Each ref update has `localRef`, `localSha`, `remoteRef`, `remoteSha` and a `kind`:
|
|
217
|
+
|
|
218
|
+
- `create`: the remote does not have the ref.
|
|
219
|
+
- `update`: the remote has the ref, and the push changes it.
|
|
220
|
+
- `delete`: the push deletes the ref from the remote.
|
|
221
|
+
|
|
222
|
+
A create or an update also has a `head` and a `base`. The `head` is the local commit. The `base` is the newest commit that the remote has:
|
|
223
|
+
|
|
224
|
+
- For an update, the `base` is the remote sha.
|
|
225
|
+
- For a create, the `base` is the parent of the first commit that no ref of the remote has. An update gets its `base` in the same way when the remote sha is not in the local repo, for example in a force push over commits that were never fetched.
|
|
226
|
+
- When the remote has the `head` already, the `base` is the `head`, and the range is empty.
|
|
227
|
+
- When the remote has no commit of that history, there is no `base`, and the step must check the whole history.
|
|
228
|
+
|
|
229
|
+
`push.inSnapshot(sha, work)` runs `work` on the snapshot of one commit. `work` gets the folder of the snapshot and an environment for the tools that it runs there: the git environment above, `NX_DAEMON=false`, and the nx cache of the state folder. `push.nx(sha, ...args)` runs nx in the snapshot of one commit, with the git environment above, and gives its standard output. When nx exits with a code other than 0, it throws.
|
|
230
|
+
|
|
231
|
+
This step runs the tests of the projects that each pushed range affects:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import { type Push, Step } from '@zukhruf/git-hooks';
|
|
235
|
+
|
|
236
|
+
export class AffectedTest extends Step<Push> {
|
|
237
|
+
readonly name = 'test';
|
|
238
|
+
|
|
239
|
+
async run(push: Push) {
|
|
240
|
+
const updates = push.updates.filter((update) => update.kind !== 'delete');
|
|
241
|
+
if (updates.length === 0) return this.skip('the push only deletes refs');
|
|
242
|
+
try {
|
|
243
|
+
for (const { base, head } of updates)
|
|
244
|
+
await push.nx(
|
|
245
|
+
head,
|
|
246
|
+
...(base === undefined
|
|
247
|
+
? ['run-many', '-t', 'test']
|
|
248
|
+
: ['affected', '-t', 'test', `--base=${base}`, `--head=${head}`]),
|
|
249
|
+
);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (!(error instanceof Error)) throw error;
|
|
252
|
+
return this.fail(error.message.trimEnd());
|
|
253
|
+
}
|
|
254
|
+
return this.pass();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Use the `base` and the `head` of each update, not `--base=origin/main --head=HEAD`. That pair checks the branch that is checked out, and a push can send other refs.
|
package/dist/build.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Push } from './push.ts';
|
|
2
|
+
import { type Outcome, Step } from './step.ts';
|
|
3
|
+
interface BuildOptions {
|
|
4
|
+
readonly targets?: readonly string[];
|
|
5
|
+
readonly parallel?: number;
|
|
6
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
7
|
+
}
|
|
8
|
+
export declare class Build extends Step<Push> {
|
|
9
|
+
#private;
|
|
10
|
+
readonly name = "build";
|
|
11
|
+
constructor({ targets, parallel, env }?: BuildOptions);
|
|
12
|
+
run(push: Push): Promise<Outcome>;
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAIA,OAAO,EAAwB,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAE5D,OAAO,EAAE,KAAK,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE/C,UAAU,YAAY;IAEpB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAErC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAG3B,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACjD;AAuBD,qBAAa,KAAM,SAAQ,IAAI,CAAC,IAAI,CAAC;;IACnC,QAAQ,CAAC,IAAI,WAAW;gBAKZ,EAAE,OAAmB,EAAE,QAAQ,EAAE,GAAQ,EAAE,GAAE,YAAiB;IAOpE,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;CAgGxC"}
|
package/dist/build.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { readFile, rm } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
4
|
+
import { MissingLockfileError } from "./push.js";
|
|
5
|
+
import { runNx } from "./snapshot/nx.js";
|
|
6
|
+
import { Step } from "./step.js";
|
|
7
|
+
// The lines of error output that a fail shows for each failed task.
|
|
8
|
+
const shownLines = 20;
|
|
9
|
+
// Builds each pushed commit with the production configuration of nx, in a
|
|
10
|
+
// snapshot of the commit, never in the working tree: an edit that is not
|
|
11
|
+
// committed is not in the push, so it cannot stop a good push or hide a
|
|
12
|
+
// broken one. A commit is built one time, also when the push sends it to more
|
|
13
|
+
// than one ref, and only when the remote does not have it.
|
|
14
|
+
export class Build extends Step {
|
|
15
|
+
name = 'build';
|
|
16
|
+
#targets;
|
|
17
|
+
#parallel;
|
|
18
|
+
#env;
|
|
19
|
+
constructor({ targets = ['build'], parallel, env = {} } = {}) {
|
|
20
|
+
super();
|
|
21
|
+
this.#targets = targets;
|
|
22
|
+
this.#parallel = parallel;
|
|
23
|
+
this.#env = env;
|
|
24
|
+
}
|
|
25
|
+
async run(push) {
|
|
26
|
+
const fresh = new Map();
|
|
27
|
+
for (const update of push.updates)
|
|
28
|
+
if (update.kind !== 'delete')
|
|
29
|
+
fresh.set(update.head, (fresh.get(update.head) ?? false) || update.base !== update.head);
|
|
30
|
+
if (fresh.size === 0)
|
|
31
|
+
return this.skip('skipped: the push only deletes refs');
|
|
32
|
+
const lines = [];
|
|
33
|
+
let built = false;
|
|
34
|
+
for (const [head, isNew] of fresh) {
|
|
35
|
+
if (!isNew) {
|
|
36
|
+
lines.push(`skipped: the remote already has ${head.slice(0, 7)}`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const result = await this.#build(push, head);
|
|
40
|
+
if (result.kind === 'failed')
|
|
41
|
+
return this.fail([...lines, ...result.lines].join('\n'));
|
|
42
|
+
lines.push(result.line);
|
|
43
|
+
built = true;
|
|
44
|
+
}
|
|
45
|
+
return built ? this.pass(lines.join('\n')) : this.skip(lines.join('\n'));
|
|
46
|
+
}
|
|
47
|
+
async #build(push, head) {
|
|
48
|
+
const short = head.slice(0, 7);
|
|
49
|
+
try {
|
|
50
|
+
return await push.inSnapshot(head, async (folder, environment) => {
|
|
51
|
+
const cache = environment.NX_CACHE_DIRECTORY ?? '';
|
|
52
|
+
// A run that ends before its tasks writes no summary, so the summary
|
|
53
|
+
// of an older run must not stay.
|
|
54
|
+
await rm(join(cache, 'run.json'), { force: true });
|
|
55
|
+
const { code, stdout, stderr } = await runNx(folder, { ...environment, ...this.#env }, [
|
|
56
|
+
'run-many',
|
|
57
|
+
`--targets=${this.#targets.join(',')}`,
|
|
58
|
+
'--configuration=production',
|
|
59
|
+
...(this.#parallel === undefined
|
|
60
|
+
? []
|
|
61
|
+
: [`--parallel=${this.#parallel}`]),
|
|
62
|
+
]);
|
|
63
|
+
const tasks = await lastRun(cache);
|
|
64
|
+
if (code === 0) {
|
|
65
|
+
const projects = new Set(tasks.map((task) => task.projectName));
|
|
66
|
+
const cached = tasks.filter((task) => task.cacheStatus !== 'cache-miss');
|
|
67
|
+
return {
|
|
68
|
+
kind: 'built',
|
|
69
|
+
line: `${short}: built ${projects.size} projects, ${cached.length} of ${tasks.length} tasks from cache`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// nx exits with 130 when a task fails, so each code other than 0 is a
|
|
73
|
+
// fail.
|
|
74
|
+
const failed = tasks.filter((task) => task.status !== 0);
|
|
75
|
+
if (failed.length === 0)
|
|
76
|
+
return {
|
|
77
|
+
kind: 'failed',
|
|
78
|
+
lines: [
|
|
79
|
+
`${short}: nx exited with ${code}`,
|
|
80
|
+
...shown(`${stdout}\n${stderr}`),
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
const lines = [
|
|
84
|
+
`${short}: ${failed.length} of ${tasks.length} tasks failed: ${failed.map(taskName).join(', ')}`,
|
|
85
|
+
];
|
|
86
|
+
for (const task of failed) {
|
|
87
|
+
if (failed.length > 1)
|
|
88
|
+
lines.push(` ${taskName(task)}`);
|
|
89
|
+
const output = await readFile(join(cache, 'terminalOutputs', task.hash), 'utf8').catch(() => '');
|
|
90
|
+
lines.push(...shown(output, true));
|
|
91
|
+
}
|
|
92
|
+
return { kind: 'failed', lines };
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (error instanceof MissingLockfileError)
|
|
97
|
+
return {
|
|
98
|
+
kind: 'failed',
|
|
99
|
+
lines: [
|
|
100
|
+
`${short} has no package-lock.json; Build works with npm workspaces`,
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
if (!(error instanceof Error))
|
|
104
|
+
throw error;
|
|
105
|
+
return {
|
|
106
|
+
kind: 'failed',
|
|
107
|
+
lines: [`${short}: ${error.message.trimEnd()}`],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Up to 20 lines of output, indented, and the number of the other lines. The
|
|
113
|
+
// first line of a task's output is the command that nx echoes.
|
|
114
|
+
function shown(output, echoed = false) {
|
|
115
|
+
const lines = stripVTControlCharacters(output)
|
|
116
|
+
.split(/\r?\n/)
|
|
117
|
+
.filter((line, index) => line.trim() !== '' && !(echoed && index === 0 && line.startsWith('> ')));
|
|
118
|
+
const more = lines.length - shownLines;
|
|
119
|
+
return [
|
|
120
|
+
...lines.slice(0, shownLines).map((line) => ` ${line}`),
|
|
121
|
+
...(more > 0 ? [` … ${more} more lines`] : []),
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
async function lastRun(cache) {
|
|
125
|
+
let summary;
|
|
126
|
+
try {
|
|
127
|
+
summary = JSON.parse(await readFile(join(cache, 'run.json'), 'utf8'));
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
|
|
131
|
+
return [];
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
const tasks = typeof summary === 'object' && summary !== null && 'tasks' in summary
|
|
135
|
+
? summary.tasks
|
|
136
|
+
: undefined;
|
|
137
|
+
if (!Array.isArray(tasks))
|
|
138
|
+
throw new TypeError(`nx wrote a run.json without tasks: ${JSON.stringify(summary)}`);
|
|
139
|
+
return tasks.map((task) => {
|
|
140
|
+
if (!isTask(task))
|
|
141
|
+
throw new TypeError(`nx wrote a task that is not known: ${JSON.stringify(task)}`);
|
|
142
|
+
return task;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function isTask(value) {
|
|
146
|
+
return (typeof value === 'object' &&
|
|
147
|
+
value !== null &&
|
|
148
|
+
'projectName' in value &&
|
|
149
|
+
typeof value.projectName === 'string' &&
|
|
150
|
+
'target' in value &&
|
|
151
|
+
typeof value.target === 'string' &&
|
|
152
|
+
'hash' in value &&
|
|
153
|
+
typeof value.hash === 'string' &&
|
|
154
|
+
'cacheStatus' in value &&
|
|
155
|
+
typeof value.cacheStatus === 'string' &&
|
|
156
|
+
'status' in value &&
|
|
157
|
+
typeof value.status === 'number');
|
|
158
|
+
}
|
|
159
|
+
// The task as a person runs it: nx's task id also holds the configuration.
|
|
160
|
+
function taskName(task) {
|
|
161
|
+
return `${task.projectName}:${task.target}`;
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Commit } from './commit.ts';
|
|
2
|
+
import { type Outcome, Step } from './step.ts';
|
|
3
|
+
interface CodegenOptions {
|
|
4
|
+
readonly when?: readonly string[];
|
|
5
|
+
readonly run: readonly string[];
|
|
6
|
+
readonly stage: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export declare class Codegen extends Step<Commit> {
|
|
9
|
+
#private;
|
|
10
|
+
readonly name = "codegen";
|
|
11
|
+
constructor({ when, run, stage }: CodegenOptions);
|
|
12
|
+
run(commit: Commit): Promise<Outcome>;
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=codegen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../src/codegen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,KAAK,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE/C,UAAU,cAAc;IAGtB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAElC,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;IAEhC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAMD,qBAAa,OAAQ,SAAQ,IAAI,CAAC,MAAM,CAAC;;IACvC,QAAQ,CAAC,IAAI,aAAa;gBAKd,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,cAAc;IAO1C,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAmB5C"}
|
package/dist/codegen.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Step } from "./step.js";
|
|
2
|
+
// Builds generated files, such as an OpenAPI spec and the client made from it,
|
|
3
|
+
// and stages them. With `when`, it builds only when nx says that one of those
|
|
4
|
+
// projects is affected by the staged files: a hand-kept list of inputs would
|
|
5
|
+
// miss a file, and an edit that is not staged is not part of the commit.
|
|
6
|
+
export class Codegen extends Step {
|
|
7
|
+
name = 'codegen';
|
|
8
|
+
#when;
|
|
9
|
+
#run;
|
|
10
|
+
#stage;
|
|
11
|
+
constructor({ when, run, stage }) {
|
|
12
|
+
super();
|
|
13
|
+
this.#when = when;
|
|
14
|
+
this.#run = run;
|
|
15
|
+
this.#stage = stage;
|
|
16
|
+
}
|
|
17
|
+
async run(commit) {
|
|
18
|
+
if (this.#when !== undefined) {
|
|
19
|
+
const affected = new Set(await commit.affected());
|
|
20
|
+
if (!this.#when.some((project) => affected.has(project)))
|
|
21
|
+
return this.skip(`none of ${this.#when.join(', ')} is affected`);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
for (const target of this.#run)
|
|
25
|
+
await commit.nx('run', target);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
// A failed build is the step's outcome: the author reads the reason that
|
|
29
|
+
// the target gave, not a stack trace of the hook.
|
|
30
|
+
if (!(error instanceof Error))
|
|
31
|
+
throw error;
|
|
32
|
+
return this.fail(error.message.trimEnd());
|
|
33
|
+
}
|
|
34
|
+
await commit.stage(this.#stage);
|
|
35
|
+
return this.pass(`ran ${this.#run.join(', ')}; staged ${this.#stage.join(', ')}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=codegen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codegen.js","sourceRoot":"","sources":["../src/codegen.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,IAAI,EAAE,MAAM,WAAW,CAAC;AAY/C,+EAA+E;AAC/E,8EAA8E;AAC9E,6EAA6E;AAC7E,yEAAyE;AACzE,MAAM,OAAO,OAAQ,SAAQ,IAAY;IAC9B,IAAI,GAAG,SAAS,CAAC;IACjB,KAAK,CAAgC;IACrC,IAAI,CAAoB;IACxB,MAAM,CAAoB;IAEnC,YAAY,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAkB;QAC9C,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAc;QACtB,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;YAClD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBACtD,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACrE,CAAC;QACD,IAAI,CAAC;YACH,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,IAAI;gBAAE,MAAM,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,yEAAyE;YACzE,kDAAkD;YAClD,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;gBAAE,MAAM,KAAK,CAAC;YAC3C,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5C,CAAC;QACD,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC,IAAI,CACd,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChE,CAAC;IACJ,CAAC;CACF"}
|
package/dist/commit.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare class Commit {
|
|
2
|
+
#private;
|
|
3
|
+
readonly root: string;
|
|
4
|
+
constructor(root: string, environment: NodeJS.ProcessEnv);
|
|
5
|
+
staged(): Promise<string[]>;
|
|
6
|
+
files(): Promise<string[]>;
|
|
7
|
+
blob(path: string): Promise<string>;
|
|
8
|
+
touches(globs: readonly string[]): Promise<boolean>;
|
|
9
|
+
inSnapshot<T>(work: (folder: string) => Promise<T>): Promise<T>;
|
|
10
|
+
modified(): Promise<string[]>;
|
|
11
|
+
stage(paths: readonly string[]): Promise<void>;
|
|
12
|
+
nx(...args: string[]): Promise<string>;
|
|
13
|
+
affected(): Promise<string[]>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=commit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit.d.ts","sourceRoot":"","sources":["../src/commit.ts"],"names":[],"mappings":"AAaA,qBAAa,MAAM;;IACjB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAGV,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,UAAU;IAMlD,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAc3B,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAYhC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK7B,OAAO,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAOnD,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAU/D,QAAQ,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAM7B,KAAK,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9C,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAgBtC,QAAQ,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;CAiCpC"}
|