@zukhruf/git-hooks 0.0.0-stage → 0.3.41
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 +104 -2
- 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 +95 -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 +41 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/scripts/pre-commit.d.ts +3 -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/step.d.ts +13 -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 +55 -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,105 @@
|
|
|
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 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
|
+
Tell git where the hooks are, in the `prepare` script of `package.json`:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{ "scripts": { "prepare": "git config core.hooksPath .husky || exit 0" } }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Check the format of the staged files
|
|
38
|
+
|
|
39
|
+
`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:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
format 2 staged files are not formatted:
|
|
43
|
+
src/app.ts
|
|
44
|
+
docs/notes.md (SyntaxError: Unexpected token (3:1))
|
|
45
|
+
Run `npx prettier --write src/app.ts docs/notes.md`, stage them, and commit again.
|
|
46
|
+
✖ commit stopped by the pre-commit hook
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The step changes no file and stages nothing. A file that is partly staged is judged as the commit holds it.
|
|
50
|
+
|
|
51
|
+
## Stage the files that nx sync writes
|
|
52
|
+
|
|
53
|
+
`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:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { Format, Sync, preCommit } from '@zukhruf/git-hooks';
|
|
57
|
+
|
|
58
|
+
await preCommit([
|
|
59
|
+
new Sync({ stage: ['**/tsconfig*.json', '**/styles.css'] }),
|
|
60
|
+
new Format(),
|
|
61
|
+
]);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
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.
|
|
65
|
+
|
|
66
|
+
## Build generated files
|
|
67
|
+
|
|
68
|
+
`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:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { Codegen, Format, Sync, preCommit } from '@zukhruf/git-hooks';
|
|
72
|
+
|
|
73
|
+
await preCommit([
|
|
74
|
+
new Sync(),
|
|
75
|
+
new Codegen({
|
|
76
|
+
when: ['backend', 'frontend'],
|
|
77
|
+
run: ['backend:build', 'frontend:build'],
|
|
78
|
+
stage: ['openapi.json', 'packages/client/src'],
|
|
79
|
+
}),
|
|
80
|
+
new Format(),
|
|
81
|
+
]);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
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.
|
|
85
|
+
|
|
86
|
+
## Add a step of your own
|
|
87
|
+
|
|
88
|
+
A step is a subclass of `Step`. 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:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { type Commit, Step } from '@zukhruf/git-hooks';
|
|
92
|
+
|
|
93
|
+
export class DesignCheck extends Step {
|
|
94
|
+
readonly name = 'design';
|
|
95
|
+
|
|
96
|
+
async run(commit: Commit) {
|
|
97
|
+
if (!(await commit.touches(['DESIGN.md', 'apps/**/*.css'])))
|
|
98
|
+
return this.skip('no design files staged');
|
|
99
|
+
const valid = await commit.inSnapshot((folder) => checkDesign(folder));
|
|
100
|
+
return valid
|
|
101
|
+
? this.pass()
|
|
102
|
+
: this.fail('DESIGN.md disagrees with the styles');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
@@ -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 {
|
|
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;;IAC/B,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,IAAI;IACtB,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;IAS/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"}
|
package/dist/commit.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { mkdtempDisposable } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join, matchesGlob } from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
// The staged commit: the index of the repo at `root`. Each read of a staged
|
|
8
|
+
// file goes to its staged copy, never to the working tree, because other
|
|
9
|
+
// sessions can edit the working tree while the hook runs. The environment is
|
|
10
|
+
// the hook's own, so git keeps the index that `git commit` gave it in
|
|
11
|
+
// GIT_INDEX_FILE.
|
|
12
|
+
export class Commit {
|
|
13
|
+
root;
|
|
14
|
+
#environment;
|
|
15
|
+
constructor(root, environment) {
|
|
16
|
+
this.root = root;
|
|
17
|
+
this.#environment = environment;
|
|
18
|
+
}
|
|
19
|
+
// The paths that the commit adds, copies, modifies or renames.
|
|
20
|
+
async staged() {
|
|
21
|
+
const output = await this.#git('diff', '--cached', '--name-only', '--diff-filter=ACMR', '-z');
|
|
22
|
+
return output.split('\0').filter((path) => path !== '');
|
|
23
|
+
}
|
|
24
|
+
// The staged paths whose staged copy is the content of a file. The staged
|
|
25
|
+
// copy of a link is the path that it points to, and that of a submodule is
|
|
26
|
+
// a commit id, so a check of their content would judge the wrong text.
|
|
27
|
+
async files() {
|
|
28
|
+
const entries = await this.#git('ls-files', '--stage', '-z');
|
|
29
|
+
const files = new Set(entries
|
|
30
|
+
.split('\0')
|
|
31
|
+
.filter((entry) => /^100(644|755) /.test(entry))
|
|
32
|
+
.map((entry) => entry.slice(entry.indexOf('\t') + 1)));
|
|
33
|
+
return (await this.staged()).filter((path) => files.has(path));
|
|
34
|
+
}
|
|
35
|
+
// The staged copy of one file.
|
|
36
|
+
blob(path) {
|
|
37
|
+
return this.#git('cat-file', 'blob', `:${path}`);
|
|
38
|
+
}
|
|
39
|
+
// Whether the commit stages a path that matches one of the globs.
|
|
40
|
+
async touches(globs) {
|
|
41
|
+
const staged = await this.staged();
|
|
42
|
+
return staged.some((path) => globs.some((glob) => matchesGlob(path, glob)));
|
|
43
|
+
}
|
|
44
|
+
// Runs `work` on a copy of the staged tree in a temporary folder, for a
|
|
45
|
+
// check that reads many files at once. The folder is removed afterwards.
|
|
46
|
+
async inSnapshot(work) {
|
|
47
|
+
await using snapshot = await mkdtempDisposable(join(tmpdir(), 'git-hooks-'));
|
|
48
|
+
await this.#git('checkout-index', '--all', `--prefix=${snapshot.path}/`);
|
|
49
|
+
return work(snapshot.path);
|
|
50
|
+
}
|
|
51
|
+
// The tracked files whose working-tree copy differs from the staged copy.
|
|
52
|
+
async modified() {
|
|
53
|
+
const output = await this.#git('diff', '--name-only', '-z');
|
|
54
|
+
return output.split('\0').filter((path) => path !== '');
|
|
55
|
+
}
|
|
56
|
+
// Stages files that a step wrote itself. A step never stages anything else.
|
|
57
|
+
async stage(paths) {
|
|
58
|
+
await this.#git('add', '--', ...paths);
|
|
59
|
+
}
|
|
60
|
+
// Runs the repo's own nx with no daemon, so a hook started from a GUI or an
|
|
61
|
+
// agent shell does not leave a daemon behind or read its stale graph.
|
|
62
|
+
async nx(...args) {
|
|
63
|
+
const { stdout } = await execFileAsync(join(this.root, 'node_modules/.bin/nx'), args, {
|
|
64
|
+
cwd: this.root,
|
|
65
|
+
env: { ...this.#environment, NX_DAEMON: 'false' },
|
|
66
|
+
encoding: 'utf8',
|
|
67
|
+
maxBuffer: 2 ** 30,
|
|
68
|
+
});
|
|
69
|
+
return stdout;
|
|
70
|
+
}
|
|
71
|
+
// The nx projects that the staged files affect. The files come from the
|
|
72
|
+
// staged commit, so an edit that is only in the working tree affects nothing.
|
|
73
|
+
async affected() {
|
|
74
|
+
const staged = await this.staged();
|
|
75
|
+
if (staged.length === 0)
|
|
76
|
+
return [];
|
|
77
|
+
const output = await this.nx('show', 'projects', '--affected', `--files=${staged.join(',')}`, '--json');
|
|
78
|
+
const projects = JSON.parse(output);
|
|
79
|
+
if (!Array.isArray(projects) ||
|
|
80
|
+
!projects.every((project) => typeof project === 'string'))
|
|
81
|
+
throw new TypeError(`nx show projects gave ${output.trim()}, not a list of names`);
|
|
82
|
+
return projects;
|
|
83
|
+
}
|
|
84
|
+
async #git(...args) {
|
|
85
|
+
const { stdout } = await execFileAsync('git', args, {
|
|
86
|
+
cwd: this.root,
|
|
87
|
+
env: this.#environment,
|
|
88
|
+
encoding: 'utf8',
|
|
89
|
+
// A staged file of any size, not Node's default of 1 MiB.
|
|
90
|
+
maxBuffer: 2 ** 30,
|
|
91
|
+
});
|
|
92
|
+
return stdout;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../src/commit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,4EAA4E;AAC5E,yEAAyE;AACzE,6EAA6E;AAC7E,sEAAsE;AACtE,kBAAkB;AAClB,MAAM,OAAO,MAAM;IACR,IAAI,CAAS;IACb,YAAY,CAAoB;IAEzC,YAAY,IAAY,EAAE,WAA8B;QACtD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;IAClC,CAAC;IAED,+DAA+D;IAC/D,KAAK,CAAC,MAAM;QACV,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAC5B,MAAM,EACN,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,IAAI,CACL,CAAC;QACF,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,0EAA0E;IAC1E,2EAA2E;IAC3E,uEAAuE;IACvE,KAAK,CAAC,KAAK;QACT,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QAC7D,MAAM,KAAK,GAAG,IAAI,GAAG,CACnB,OAAO;aACJ,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;aAC/C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CACxD,CAAC;QACF,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,+BAA+B;IAC/B,IAAI,CAAC,IAAY;QACf,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,kEAAkE;IAClE,KAAK,CAAC,OAAO,CAAC,KAAwB;QACpC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACnC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC;IAED,wEAAwE;IACxE,yEAAyE;IACzE,KAAK,CAAC,UAAU,CAAI,IAAoC;QACtD,YAAY,QAAQ,GAAG,MAAM,iBAAiB,CAC5C,IAAI,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,CAC7B,CAAC;QACF,MAAM,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,OAAO,EAAE,YAAY,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC;QACzE,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,0EAA0E;IAC1E,KAAK,CAAC,QAAQ;QACZ,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;QAC5D,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,4EAA4E;IAC5E,KAAK,CAAC,KAAK,CAAC,KAAwB;QAClC,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC;IACzC,CAAC;IAED,4EAA4E;IAC5E,sEAAsE;IACtE,KAAK,CAAC,EAAE,CAAC,GAAG,IAAc;QACxB,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CACpC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,sBAAsB,CAAC,EACvC,IAAI,EACJ;YACE,GAAG,EAAE,IAAI,CAAC,IAAI;YACd,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE;YACjD,QAAQ,EAAE,MAAM;YAChB,SAAS,EAAE,CAAC,IAAI,EAAE;SACnB,CACF,CAAC;QACF,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,wEAAwE;IACxE,8EAA8E;IAC9E,KAAK,CAAC,QAAQ;QACZ,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACnC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,EAAE,CAC1B,MAAM,EACN,UAAU,EACV,YAAY,EACZ,WAAW,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAC7B,QAAQ,CACT,CAAC;QACF,MAAM,QAAQ,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC7C,IACE,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;YACxB,CAAC,QAAQ,CAAC,KAAK,CACb,CAAC,OAAO,EAAqB,EAAE,CAAC,OAAO,OAAO,KAAK,QAAQ,CAC5D;YAED,MAAM,IAAI,SAAS,CACjB,yBAAyB,MAAM,CAAC,IAAI,EAAE,uBAAuB,CAC9D,CAAC;QACJ,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAG,IAAc;QAC1B,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE;YAClD,GAAG,EAAE,IAAI,CAAC,IAAI;YACd,GAAG,EAAE,IAAI,CAAC,YAAY;YACtB,QAAQ,EAAE,MAAM;YAChB,0DAA0D;YAC1D,SAAS,EAAE,CAAC,IAAI,EAAE;SACnB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,KAAK,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAK/C,qBAAa,MAAO,SAAQ,IAAI;IAC9B,QAAQ,CAAC,IAAI,YAAY;IAEnB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAgC5C"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { check, getFileInfo, resolveConfig } from 'prettier';
|
|
3
|
+
import { Step } from "./step.js";
|
|
4
|
+
// Checks the staged copy of each staged file with the repo's Prettier config,
|
|
5
|
+
// and changes nothing. A step that formatted and staged files would also stage
|
|
6
|
+
// the edits that another session made to them at the same moment.
|
|
7
|
+
export class Format extends Step {
|
|
8
|
+
name = 'format';
|
|
9
|
+
async run(commit) {
|
|
10
|
+
const unformatted = [];
|
|
11
|
+
const ignorePath = join(commit.root, '.prettierignore');
|
|
12
|
+
for (const path of await commit.files()) {
|
|
13
|
+
const file = join(commit.root, path);
|
|
14
|
+
// Prettier gives no parser for a file that .prettierignore names or that
|
|
15
|
+
// it cannot format, so no list of extensions here.
|
|
16
|
+
const { inferredParser } = await getFileInfo(file, { ignorePath });
|
|
17
|
+
if (inferredParser === null)
|
|
18
|
+
continue;
|
|
19
|
+
const options = await resolveConfig(file);
|
|
20
|
+
try {
|
|
21
|
+
const source = await commit.blob(path);
|
|
22
|
+
const formatted = await check(source, { ...options, filepath: file });
|
|
23
|
+
if (!formatted)
|
|
24
|
+
unformatted.push({ path });
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
const [reason = ''] = String(error).split('\n');
|
|
28
|
+
unformatted.push({ path, reason });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (unformatted.length === 0)
|
|
32
|
+
return this.pass('the staged files are formatted');
|
|
33
|
+
const paths = unformatted.map(({ path }) => path);
|
|
34
|
+
return this.fail([
|
|
35
|
+
`${unformatted.length} staged files are not formatted:`,
|
|
36
|
+
...unformatted.map(({ path, reason }) => reason === undefined ? ` ${path}` : ` ${path} (${reason})`),
|
|
37
|
+
`Run \`npx prettier --write ${paths.join(' ')}\`, stage them, and commit again.`,
|
|
38
|
+
].join('\n'));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG7D,OAAO,EAAgB,IAAI,EAAE,MAAM,WAAW,CAAC;AAE/C,8EAA8E;AAC9E,+EAA+E;AAC/E,kEAAkE;AAClE,MAAM,OAAO,MAAO,SAAQ,IAAI;IACrB,IAAI,GAAG,QAAQ,CAAC;IAEzB,KAAK,CAAC,GAAG,CAAC,MAAc;QACtB,MAAM,WAAW,GAAwC,EAAE,CAAC;QAC5D,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;QACxD,KAAK,MAAM,IAAI,IAAI,MAAM,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACrC,yEAAyE;YACzE,mDAAmD;YACnD,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;YACnE,IAAI,cAAc,KAAK,IAAI;gBAAE,SAAS;YACtC,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;YAC1C,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACvC,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;gBACtE,IAAI,CAAC,SAAS;oBAAE,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAChD,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;QACD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QACrD,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,IAAI,CACd;YACE,GAAG,WAAW,CAAC,MAAM,kCAAkC;YACvD,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CACtC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,KAAK,MAAM,GAAG,CAC7D;YACD,8BAA8B,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,mCAAmC;SACjF,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACJ,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Codegen } from './codegen.ts';
|
|
2
|
+
export { Commit } from './commit.ts';
|
|
3
|
+
export { Format } from './format.ts';
|
|
4
|
+
export { preCommit } from './scripts/pre-commit.ts';
|
|
5
|
+
export { type Outcome, Step } from './step.ts';
|
|
6
|
+
export { Sync } from './sync.ts';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,KAAK,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Codegen } from "./codegen.js";
|
|
2
|
+
export { Commit } from "./commit.js";
|
|
3
|
+
export { Format } from "./format.js";
|
|
4
|
+
export { preCommit } from "./scripts/pre-commit.js";
|
|
5
|
+
export { Step } from "./step.js";
|
|
6
|
+
export { Sync } from "./sync.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAgB,IAAI,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-commit.d.ts","sourceRoot":"","sources":["../../src/scripts/pre-commit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAKvC,wBAAsB,SAAS,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAarE"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Commit } from "../commit.js";
|
|
2
|
+
// What a repo's `.husky/pre-commit.ts` calls. Git runs the hook at the root of
|
|
3
|
+
// the work tree, so the current folder is the repo. This is the one module
|
|
4
|
+
// that prints and reads the environment, as a script may.
|
|
5
|
+
export async function preCommit(steps) {
|
|
6
|
+
const commit = new Commit(process.cwd(), process.env);
|
|
7
|
+
for (const step of steps) {
|
|
8
|
+
const outcome = await step.run(commit);
|
|
9
|
+
const [first = '', ...rest] = outcome.message.split('\n');
|
|
10
|
+
console.error(`${step.name.padEnd(9)} ${first}`);
|
|
11
|
+
for (const line of rest)
|
|
12
|
+
console.error(`${' '.repeat(10)}${line}`);
|
|
13
|
+
if (outcome.kind === 'fail') {
|
|
14
|
+
console.error('✖ commit stopped by the pre-commit hook');
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=pre-commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-commit.js","sourceRoot":"","sources":["../../src/scripts/pre-commit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAGtC,+EAA+E;AAC/E,2EAA2E;AAC3E,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAsB;IACpD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACtD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1D,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;QACjD,KAAK,MAAM,IAAI,IAAI,IAAI;YAAE,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QACnE,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC5B,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YACzD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/dist/step.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Commit } from './commit.ts';
|
|
2
|
+
export interface Outcome {
|
|
3
|
+
readonly kind: 'pass' | 'skip' | 'fail';
|
|
4
|
+
readonly message: string;
|
|
5
|
+
}
|
|
6
|
+
export declare abstract class Step {
|
|
7
|
+
abstract readonly name: string;
|
|
8
|
+
abstract run(commit: Commit): Promise<Outcome>;
|
|
9
|
+
protected pass(message?: string): Outcome;
|
|
10
|
+
protected skip(message: string): Outcome;
|
|
11
|
+
protected fail(message: string): Outcome;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=step.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step.d.ts","sourceRoot":"","sources":["../src/step.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAI1C,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAKD,8BAAsB,IAAI;IACxB,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAE/B,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAE9C,SAAS,CAAC,IAAI,CAAC,OAAO,SAAK,GAAG,OAAO;IAIrC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAIxC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;CAGzC"}
|
package/dist/step.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// One check or task of a hook. A subclass names itself and says what it does
|
|
2
|
+
// with the staged commit; the hook runs the steps in order and stops the
|
|
3
|
+
// commit at the first fail.
|
|
4
|
+
export class Step {
|
|
5
|
+
pass(message = '') {
|
|
6
|
+
return { kind: 'pass', message };
|
|
7
|
+
}
|
|
8
|
+
skip(message) {
|
|
9
|
+
return { kind: 'skip', message };
|
|
10
|
+
}
|
|
11
|
+
fail(message) {
|
|
12
|
+
return { kind: 'fail', message };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=step.js.map
|
package/dist/step.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step.js","sourceRoot":"","sources":["../src/step.ts"],"names":[],"mappings":"AASA,6EAA6E;AAC7E,yEAAyE;AACzE,4BAA4B;AAC5B,MAAM,OAAgB,IAAI;IAKd,IAAI,CAAC,OAAO,GAAG,EAAE;QACzB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACnC,CAAC;IAES,IAAI,CAAC,OAAe;QAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACnC,CAAC;IAES,IAAI,CAAC,OAAe;QAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACnC,CAAC;CACF"}
|
package/dist/sync.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Commit } from './commit.ts';
|
|
2
|
+
import { type Outcome, Step } from './step.ts';
|
|
3
|
+
export declare class Sync extends Step {
|
|
4
|
+
#private;
|
|
5
|
+
readonly name = "sync";
|
|
6
|
+
constructor({ stage, }?: {
|
|
7
|
+
stage?: readonly string[];
|
|
8
|
+
});
|
|
9
|
+
run(commit: Commit): Promise<Outcome>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,KAAK,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAU/C,qBAAa,IAAK,SAAQ,IAAI;;IAC5B,QAAQ,CAAC,IAAI,UAAU;gBAGX,EACV,KAA6B,GAC9B,GAAE;QAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAO;IAK/B,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAmB5C"}
|
package/dist/sync.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { matchesGlob } from 'node:path';
|
|
2
|
+
import { Step } from "./step.js";
|
|
3
|
+
// Runs `nx sync`, then stages the files that it wrote. `sync.applyChanges`
|
|
4
|
+
// in nx.json applies only in an interactive terminal, so a commit from a GUI
|
|
5
|
+
// client or an agent shell would miss the synced files.
|
|
6
|
+
//
|
|
7
|
+
// A file counts as synced when it changed during the run and matches `stage`:
|
|
8
|
+
// the file names that the repo's sync generators write. Any other change
|
|
9
|
+
// during the run, or a file that was already modified before it, belongs to
|
|
10
|
+
// another session that edits the same working tree.
|
|
11
|
+
export class Sync extends Step {
|
|
12
|
+
name = 'sync';
|
|
13
|
+
#globs;
|
|
14
|
+
constructor({ stage = ['**/tsconfig*.json'], } = {}) {
|
|
15
|
+
super();
|
|
16
|
+
this.#globs = stage;
|
|
17
|
+
}
|
|
18
|
+
async run(commit) {
|
|
19
|
+
const before = new Set(await commit.modified());
|
|
20
|
+
try {
|
|
21
|
+
await commit.nx('sync');
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
// A failed sync is the step's outcome: the author reads nx's reason, not
|
|
25
|
+
// a stack trace of the hook.
|
|
26
|
+
if (!(error instanceof Error))
|
|
27
|
+
throw error;
|
|
28
|
+
return this.fail(error.message.trimEnd());
|
|
29
|
+
}
|
|
30
|
+
const synced = (await commit.modified()).filter((path) => !before.has(path) &&
|
|
31
|
+
this.#globs.some((glob) => matchesGlob(path, glob)));
|
|
32
|
+
if (synced.length === 0)
|
|
33
|
+
return this.pass('nx sync wrote nothing to stage');
|
|
34
|
+
await commit.stage(synced);
|
|
35
|
+
return this.pass(`staged what nx sync wrote: ${synced.join(', ')}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=sync.js.map
|
package/dist/sync.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGxC,OAAO,EAAgB,IAAI,EAAE,MAAM,WAAW,CAAC;AAE/C,2EAA2E;AAC3E,6EAA6E;AAC7E,wDAAwD;AACxD,EAAE;AACF,8EAA8E;AAC9E,yEAAyE;AACzE,4EAA4E;AAC5E,oDAAoD;AACpD,MAAM,OAAO,IAAK,SAAQ,IAAI;IACnB,IAAI,GAAG,MAAM,CAAC;IACd,MAAM,CAAoB;IAEnC,YAAY,EACV,KAAK,GAAG,CAAC,mBAAmB,CAAC,MACI,EAAE;QACnC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAc;QACtB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,yEAAyE;YACzE,6BAA6B;YAC7B,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,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAC7C,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YACjB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CACtD,CAAC;QACF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QAC5E,MAAM,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC,IAAI,CAAC,8BAA8B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zukhruf/git-hooks",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.3.41",
|
|
4
|
+
"description": "Git hooks written in TypeScript. A pre-commit hook runs steps in order against the staged commit: it checks the staged copy of each file with Prettier and changes nothing, runs nx sync and stages only the files that sync wrote, and builds generated files when nx says their projects are affected. A repo adds a step of its own as a class.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/JanuaryLabs/zukhruf",
|
|
9
|
+
"directory": "packages/git-hooks"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/JanuaryLabs/zukhruf/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/JanuaryLabs/zukhruf/tree/main/packages/git-hooks#readme",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"git",
|
|
17
|
+
"hooks",
|
|
18
|
+
"pre-commit",
|
|
19
|
+
"prettier",
|
|
20
|
+
"staged",
|
|
21
|
+
"index",
|
|
22
|
+
"nx",
|
|
23
|
+
"sync",
|
|
24
|
+
"codegen"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
"./package.json": "./package.json",
|
|
29
|
+
".": {
|
|
30
|
+
"@zukhruf/source": "./src/index.ts",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"!dist/spec/**",
|
|
39
|
+
"!**/*.tsbuildinfo",
|
|
40
|
+
"!**/*.test.*"
|
|
41
|
+
],
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"nx": ">=21.0.0",
|
|
47
|
+
"prettier": "^3.0.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"nx": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"prettier": "^3.9.9"
|
|
56
|
+
}
|
|
57
|
+
}
|