@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/dist/commit.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
// Awaited here, so the folder stays until `work` ends.
|
|
50
|
+
return await work(snapshot.path);
|
|
51
|
+
}
|
|
52
|
+
// The tracked files whose working-tree copy differs from the staged copy.
|
|
53
|
+
async modified() {
|
|
54
|
+
const output = await this.#git('diff', '--name-only', '-z');
|
|
55
|
+
return output.split('\0').filter((path) => path !== '');
|
|
56
|
+
}
|
|
57
|
+
// Stages files that a step wrote itself. A step never stages anything else.
|
|
58
|
+
async stage(paths) {
|
|
59
|
+
await this.#git('add', '--', ...paths);
|
|
60
|
+
}
|
|
61
|
+
// Runs the repo's own nx with no daemon, so a hook started from a GUI or an
|
|
62
|
+
// agent shell does not leave a daemon behind or read its stale graph.
|
|
63
|
+
async nx(...args) {
|
|
64
|
+
const { stdout } = await execFileAsync(join(this.root, 'node_modules/.bin/nx'), args, {
|
|
65
|
+
cwd: this.root,
|
|
66
|
+
env: { ...this.#environment, NX_DAEMON: 'false' },
|
|
67
|
+
encoding: 'utf8',
|
|
68
|
+
maxBuffer: 2 ** 30,
|
|
69
|
+
});
|
|
70
|
+
return stdout;
|
|
71
|
+
}
|
|
72
|
+
// The nx projects that the staged files affect. The files come from the
|
|
73
|
+
// staged commit, so an edit that is only in the working tree affects nothing.
|
|
74
|
+
async affected() {
|
|
75
|
+
const staged = await this.staged();
|
|
76
|
+
if (staged.length === 0)
|
|
77
|
+
return [];
|
|
78
|
+
const output = await this.nx('show', 'projects', '--affected', `--files=${staged.join(',')}`, '--json');
|
|
79
|
+
const projects = JSON.parse(output);
|
|
80
|
+
if (!Array.isArray(projects) ||
|
|
81
|
+
!projects.every((project) => typeof project === 'string'))
|
|
82
|
+
throw new TypeError(`nx show projects gave ${output.trim()}, not a list of names`);
|
|
83
|
+
return projects;
|
|
84
|
+
}
|
|
85
|
+
async #git(...args) {
|
|
86
|
+
const { stdout } = await execFileAsync('git', args, {
|
|
87
|
+
cwd: this.root,
|
|
88
|
+
env: this.#environment,
|
|
89
|
+
encoding: 'utf8',
|
|
90
|
+
// A staged file of any size, not Node's default of 1 MiB.
|
|
91
|
+
maxBuffer: 2 ** 30,
|
|
92
|
+
});
|
|
93
|
+
return stdout;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../src/commit.ts"],"names":[],"mappings":"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"}
|
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,CAAC,MAAM,CAAC;IACtC,QAAQ,CAAC,IAAI,YAAY;IAEnB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAmC5C"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
const one = unformatted.length === 1;
|
|
35
|
+
return this.fail([
|
|
36
|
+
one
|
|
37
|
+
? '1 staged file is not formatted:'
|
|
38
|
+
: `${unformatted.length} staged files are not formatted:`,
|
|
39
|
+
...unformatted.map(({ path, reason }) => reason === undefined ? ` ${path}` : ` ${path} (${reason})`),
|
|
40
|
+
`Run \`npx prettier --write ${paths.join(' ')}\`, stage ${one ? 'it' : 'them'}, and commit again.`,
|
|
41
|
+
].join('\n'));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# 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,IAAY;IAC7B,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,MAAM,GAAG,GAAG,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC,IAAI,CACd;YACE,GAAG;gBACD,CAAC,CAAC,iCAAiC;gBACnC,CAAC,CAAC,GAAG,WAAW,CAAC,MAAM,kCAAkC;YAC3D,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,aAAa,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,qBAAqB;SACnG,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACJ,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { Build } from './build.ts';
|
|
2
|
+
export { Codegen } from './codegen.ts';
|
|
3
|
+
export { Commit } from './commit.ts';
|
|
4
|
+
export { Format } from './format.ts';
|
|
5
|
+
export { Push } from './push.ts';
|
|
6
|
+
export type { RefUpdate } from './ref-update.ts';
|
|
7
|
+
export { preCommit } from './scripts/pre-commit.ts';
|
|
8
|
+
export { prePush } from './scripts/pre-push.ts';
|
|
9
|
+
export { type Outcome, Step } from './step.ts';
|
|
10
|
+
export { Sync } from './sync.ts';
|
|
11
|
+
//# 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,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,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,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,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,10 @@
|
|
|
1
|
+
export { Build } from "./build.js";
|
|
2
|
+
export { Codegen } from "./codegen.js";
|
|
3
|
+
export { Commit } from "./commit.js";
|
|
4
|
+
export { Format } from "./format.js";
|
|
5
|
+
export { Push } from "./push.js";
|
|
6
|
+
export { preCommit } from "./scripts/pre-commit.js";
|
|
7
|
+
export { prePush } from "./scripts/pre-push.js";
|
|
8
|
+
export { Step } from "./step.js";
|
|
9
|
+
export { Sync } from "./sync.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,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,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAgB,IAAI,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/push.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type RefUpdate } from './ref-update.ts';
|
|
2
|
+
export declare class MissingLockfileError extends Error {
|
|
3
|
+
readonly sha: string;
|
|
4
|
+
constructor(sha: string);
|
|
5
|
+
}
|
|
6
|
+
interface PushOptions {
|
|
7
|
+
readonly root: string;
|
|
8
|
+
readonly environment: NodeJS.ProcessEnv;
|
|
9
|
+
readonly remote: {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly url: string;
|
|
12
|
+
};
|
|
13
|
+
readonly input: string;
|
|
14
|
+
readonly notice: (message: string) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare class Push {
|
|
17
|
+
#private;
|
|
18
|
+
readonly remote: {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly url: string;
|
|
21
|
+
};
|
|
22
|
+
readonly updates: readonly RefUpdate[];
|
|
23
|
+
private constructor();
|
|
24
|
+
static read(options: PushOptions): Promise<Push>;
|
|
25
|
+
inSnapshot<T>(sha: string, work: (folder: string, environment: NodeJS.ProcessEnv) => Promise<T>): Promise<T>;
|
|
26
|
+
nx(sha: string, ...args: string[]): Promise<string>;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=push.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../src/push.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,SAAS,EAAkB,MAAM,iBAAiB,CAAC;AASjE,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,GAAG,EAAE,MAAM;CAKxB;AAED,UAAU,WAAW;IAEnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;IAExC,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAEjE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAKD,qBAAa,IAAI;;IACf,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,QAAQ,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;IAKvC,OAAO;WAWM,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAchD,UAAU,CAAC,CAAC,EAChB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GACnE,OAAO,CAAC,CAAC,CAAC;IA6Db,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAcpD"}
|
package/dist/push.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { mkdir, mkdtempDisposable, realpath } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
import { FileLock } from '@zukhruf/fs';
|
|
9
|
+
import { readRefUpdates } from "./ref-update.js";
|
|
10
|
+
import { writeCommitFiles } from "./snapshot/commit-files.js";
|
|
11
|
+
import { hasInstallDrift } from "./snapshot/drift.js";
|
|
12
|
+
import { installFor } from "./snapshot/installs.js";
|
|
13
|
+
import { runNx } from "./snapshot/nx.js";
|
|
14
|
+
import { linkPackages } from "./snapshot/packages.js";
|
|
15
|
+
const execFileAsync = promisify(execFile);
|
|
16
|
+
export class MissingLockfileError extends Error {
|
|
17
|
+
sha;
|
|
18
|
+
constructor(sha) {
|
|
19
|
+
super(`${sha.slice(0, 7)} has no package-lock.json`);
|
|
20
|
+
this.name = 'MissingLockfileError';
|
|
21
|
+
this.sha = sha;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// What `git push` is about to send: the remote and the ref updates. A step
|
|
25
|
+
// judges the pushed commits in snapshots, never the working tree, because
|
|
26
|
+
// other sessions can edit the working tree while the hook runs.
|
|
27
|
+
export class Push {
|
|
28
|
+
remote;
|
|
29
|
+
updates;
|
|
30
|
+
#root;
|
|
31
|
+
#environment;
|
|
32
|
+
#notice;
|
|
33
|
+
constructor({ root, environment, remote, notice }, updates) {
|
|
34
|
+
this.remote = remote;
|
|
35
|
+
this.updates = updates;
|
|
36
|
+
this.#root = root;
|
|
37
|
+
this.#environment = environment;
|
|
38
|
+
this.#notice = notice;
|
|
39
|
+
}
|
|
40
|
+
static async read(options) {
|
|
41
|
+
const git = (...args) => gitIn(options.root, options.environment, ...args);
|
|
42
|
+
return new Push(options, await readRefUpdates(git, options.remote.name, options.input));
|
|
43
|
+
}
|
|
44
|
+
// Runs `work` on the snapshot of one commit: its files in a temporary
|
|
45
|
+
// folder, with a node_modules that a build can use. `environment` makes git
|
|
46
|
+
// see a clean checkout of the commit there, and holds the nx cache of the
|
|
47
|
+
// state folder. One snapshot is open at a time in a repo; the snapshot is
|
|
48
|
+
// deleted after `work`.
|
|
49
|
+
async inSnapshot(sha, work) {
|
|
50
|
+
const state = join((await this.#git('rev-parse', '--path-format=absolute', '--git-common-dir')).trim(), 'git-hooks');
|
|
51
|
+
await mkdir(state, { recursive: true });
|
|
52
|
+
using lock = FileLock.open(join(state, 'lock'));
|
|
53
|
+
if (!lock.tryLock()) {
|
|
54
|
+
this.#notice('waiting for another pre-push build in this repo');
|
|
55
|
+
while (!lock.tryLock())
|
|
56
|
+
await delay(200);
|
|
57
|
+
}
|
|
58
|
+
await using temporary = await mkdtempDisposable(join(tmpdir(), 'git-hooks-push-'));
|
|
59
|
+
const top = await realpath(temporary.path);
|
|
60
|
+
const folder = join(top, 'tree');
|
|
61
|
+
await writeCommitFiles((...args) => this.#git(...args), { cwd: this.#root, env: this.#environment }, sha, folder);
|
|
62
|
+
if (!existsSync(join(folder, 'package-lock.json')))
|
|
63
|
+
throw new MissingLockfileError(sha);
|
|
64
|
+
const installed = (await hasInstallDrift(join(folder, 'package-lock.json'), join(this.#root, 'node_modules/.package-lock.json')))
|
|
65
|
+
? await installFor(state, folder, this.#environment, () => this.#notice(`${sha.slice(0, 7)}: the installed packages differ from package-lock.json; installing it with npm ci (first time for this lockfile)`))
|
|
66
|
+
: this.#root;
|
|
67
|
+
await linkPackages(installed, folder);
|
|
68
|
+
const cache = join(state, 'nx');
|
|
69
|
+
const environment = {
|
|
70
|
+
...this.#environment,
|
|
71
|
+
GIT_DIR: (await this.#git('rev-parse', '--absolute-git-dir')).trim(),
|
|
72
|
+
GIT_WORK_TREE: folder,
|
|
73
|
+
GIT_INDEX_FILE: join(top, 'index'),
|
|
74
|
+
NX_DAEMON: 'false',
|
|
75
|
+
NX_CACHE_DIRECTORY: cache,
|
|
76
|
+
NX_WORKSPACE_DATA_DIRECTORY: join(cache, 'workspace-data'),
|
|
77
|
+
};
|
|
78
|
+
// A temporary index of the commit, refreshed against the snapshot, so git
|
|
79
|
+
// finds no changed and no untracked files.
|
|
80
|
+
await gitIn(folder, environment, 'read-tree', sha);
|
|
81
|
+
await gitIn(folder, environment, 'update-index', '-q', '--refresh');
|
|
82
|
+
// Awaited here, so the snapshot and the lock stay until `work` ends.
|
|
83
|
+
return await work(folder, environment);
|
|
84
|
+
}
|
|
85
|
+
// Runs nx in the snapshot of one commit, and gives its standard output.
|
|
86
|
+
// When nx exits with a code other than 0, it throws.
|
|
87
|
+
nx(sha, ...args) {
|
|
88
|
+
return this.inSnapshot(sha, async (folder, environment) => {
|
|
89
|
+
const { code, stdout, stderr } = await runNx(folder, environment, args);
|
|
90
|
+
if (code !== 0)
|
|
91
|
+
throw new Error(`nx ${args.join(' ')} exited with ${code}:\n${`${stdout}${stderr}`.trimEnd()}`);
|
|
92
|
+
return stdout;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
#git(...args) {
|
|
96
|
+
return gitIn(this.#root, this.#environment, ...args);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function gitIn(cwd, environment, ...args) {
|
|
100
|
+
const { stdout } = await execFileAsync('git', args, {
|
|
101
|
+
cwd,
|
|
102
|
+
env: environment,
|
|
103
|
+
encoding: 'utf8',
|
|
104
|
+
maxBuffer: 2 ** 30,
|
|
105
|
+
});
|
|
106
|
+
return stdout;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=push.js.map
|
package/dist/push.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.js","sourceRoot":"","sources":["../src/push.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface RefLine {
|
|
2
|
+
readonly localRef: string;
|
|
3
|
+
readonly localSha: string;
|
|
4
|
+
readonly remoteRef: string;
|
|
5
|
+
readonly remoteSha: string;
|
|
6
|
+
}
|
|
7
|
+
export type RefUpdate = (RefLine & {
|
|
8
|
+
readonly kind: 'create' | 'update';
|
|
9
|
+
readonly head: string;
|
|
10
|
+
readonly base: string | undefined;
|
|
11
|
+
}) | (RefLine & {
|
|
12
|
+
readonly kind: 'delete';
|
|
13
|
+
});
|
|
14
|
+
type Git = (...args: string[]) => Promise<string>;
|
|
15
|
+
export declare function readRefUpdates(git: Git, remote: string, input: string): Promise<RefUpdate[]>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=ref-update.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ref-update.d.ts","sourceRoot":"","sources":["../src/ref-update.ts"],"names":[],"mappings":"AAEA,UAAU,OAAO;IACf,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAKD,MAAM,MAAM,SAAS,GACjB,CAAC,OAAO,GAAG;IACT,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC,CAAC,GACF,CAAC,OAAO,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC;AAE5C,KAAK,GAAG,GAAG,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAIlD,wBAAsB,cAAc,CAClC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,SAAS,EAAE,CAAC,CAoBtB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const absent = /^0+$/;
|
|
2
|
+
export async function readRefUpdates(git, remote, input) {
|
|
3
|
+
const updates = [];
|
|
4
|
+
for (const line of input.split('\n')) {
|
|
5
|
+
if (line.trim() === '')
|
|
6
|
+
continue;
|
|
7
|
+
const [localRef = '', localSha = '', remoteRef = '', remoteSha = ''] = line
|
|
8
|
+
.trim()
|
|
9
|
+
.split(' ');
|
|
10
|
+
const refLine = { localRef, localSha, remoteRef, remoteSha };
|
|
11
|
+
if (absent.test(localSha)) {
|
|
12
|
+
updates.push({ ...refLine, kind: 'delete' });
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const kind = absent.test(remoteSha) ? 'create' : 'update';
|
|
16
|
+
const base = kind === 'update' && (await isLocal(git, remoteSha))
|
|
17
|
+
? remoteSha
|
|
18
|
+
: await firstUnseenParent(git, remote, localSha);
|
|
19
|
+
updates.push({ ...refLine, kind, head: localSha, base });
|
|
20
|
+
}
|
|
21
|
+
return updates;
|
|
22
|
+
}
|
|
23
|
+
async function isLocal(git, sha) {
|
|
24
|
+
try {
|
|
25
|
+
await git('cat-file', '-e', `${sha}^{commit}`);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// The parent of the first commit that no ref of the remote has, as the
|
|
33
|
+
// pre-commit framework finds the "from" ref of its pre-push stage.
|
|
34
|
+
async function firstUnseenParent(git, remote, head) {
|
|
35
|
+
const unseen = await git('rev-list', head, '--topo-order', '--reverse', '--not', `--remotes=${remote}`);
|
|
36
|
+
const [first = ''] = unseen.split('\n', 1);
|
|
37
|
+
if (first === '')
|
|
38
|
+
return head;
|
|
39
|
+
const parents = await git('rev-list', '--parents', '--max-count=1', first);
|
|
40
|
+
const [, parent] = parents.trim().split(' ');
|
|
41
|
+
return parent;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=ref-update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ref-update.js","sourceRoot":"","sources":["../src/ref-update.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,GAAG,MAAM,CAAC;AAEtB,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAQ,EACR,MAAc,EACd,KAAa;IAEb,MAAM,OAAO,GAAgB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,SAAS;QACjC,MAAM,CAAC,QAAQ,GAAG,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE,SAAS,GAAG,EAAE,EAAE,SAAS,GAAG,EAAE,CAAC,GAAG,IAAI;aACxE,IAAI,EAAE;aACN,KAAK,CAAC,GAAG,CAAC,CAAC;QACd,MAAM,OAAO,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;QAC7D,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC7C,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC1D,MAAM,IAAI,GACR,IAAI,KAAK,QAAQ,IAAI,CAAC,MAAM,OAAO,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YAClD,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,MAAM,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;QACrD,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,GAAQ,EAAE,GAAW;IAC1C,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,GAAG,GAAG,WAAW,CAAC,CAAC;QAC/C,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,uEAAuE;AACvE,mEAAmE;AACnE,KAAK,UAAU,iBAAiB,CAC9B,GAAQ,EACR,MAAc,EACd,IAAY;IAEZ,MAAM,MAAM,GAAG,MAAM,GAAG,CACtB,UAAU,EACV,IAAI,EACJ,cAAc,EACd,WAAW,EACX,OAAO,EACP,aAAa,MAAM,EAAE,CACtB,CAAC;IACF,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3C,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,KAAK,CAAC,CAAC;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-commit.d.ts","sourceRoot":"","sources":["../../src/scripts/pre-commit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAKvC,wBAAsB,SAAS,CAAC,KAAK,EAAE,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAa7E"}
|
|
@@ -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,KAA8B;IAC5D,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-push.d.ts","sourceRoot":"","sources":["../../src/scripts/pre-push.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAOvC,wBAAsB,OAAO,CAAC,KAAK,EAAE,SAAS,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBzE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { text } from 'node:stream/consumers';
|
|
2
|
+
import { Push } from "../push.js";
|
|
3
|
+
// What a repo's `.husky/pre-push.ts` calls. Git runs the hook at the root of
|
|
4
|
+
// the work tree, with the name and the URL of the remote as arguments and one
|
|
5
|
+
// ref update on each line of the standard input. This is the one module that
|
|
6
|
+
// prints and reads the arguments, the input and the environment, as a script
|
|
7
|
+
// may.
|
|
8
|
+
export async function prePush(steps) {
|
|
9
|
+
let current = '';
|
|
10
|
+
const print = (name, message) => {
|
|
11
|
+
const [first = '', ...rest] = message.split('\n');
|
|
12
|
+
console.error(`${name.padEnd(9)} ${first}`);
|
|
13
|
+
for (const line of rest)
|
|
14
|
+
console.error(`${' '.repeat(10)}${line}`);
|
|
15
|
+
};
|
|
16
|
+
const [name = '', url = ''] = process.argv.slice(2);
|
|
17
|
+
const push = await Push.read({
|
|
18
|
+
root: process.cwd(),
|
|
19
|
+
environment: process.env,
|
|
20
|
+
remote: { name, url },
|
|
21
|
+
input: await text(process.stdin),
|
|
22
|
+
notice: (message) => print(current, message),
|
|
23
|
+
});
|
|
24
|
+
for (const step of steps) {
|
|
25
|
+
current = step.name;
|
|
26
|
+
const outcome = await step.run(push);
|
|
27
|
+
print(step.name, outcome.message);
|
|
28
|
+
if (outcome.kind === 'fail') {
|
|
29
|
+
console.error('✖ push stopped by the pre-push hook');
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=pre-push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-push.js","sourceRoot":"","sources":["../../src/scripts/pre-push.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAE7C,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGlC,6EAA6E;AAC7E,8EAA8E;AAC9E,6EAA6E;AAC7E,6EAA6E;AAC7E,OAAO;AACP,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,KAA4B;IACxD,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,EAAE;QAC9C,MAAM,CAAC,KAAK,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;QAC5C,KAAK,MAAM,IAAI,IAAI,IAAI;YAAE,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;IACrE,CAAC,CAAC;IACF,MAAM,CAAC,IAAI,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC;QAC3B,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE;QACnB,WAAW,EAAE,OAAO,CAAC,GAAG;QACxB,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE;QACrB,KAAK,EAAE,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAChC,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC;KAC7C,CAAC,CAAC;IACH,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC;QACpB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC5B,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;YACrD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit-files.d.ts","sourceRoot":"","sources":["../../src/snapshot/commit-files.ts"],"names":[],"mappings":"AAgBA,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,EAC3C,UAAU,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;CAAE,EACnD,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAqDf"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { once } from 'node:events';
|
|
3
|
+
import { chmod, mkdir, stat, symlink, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
// Writes the files of one commit into an empty folder, from the object store:
|
|
6
|
+
// `git ls-tree` names them and one `git cat-file --batch` gives their content.
|
|
7
|
+
// No filter applies (no autocrlf, no smudge, no LFS), and `export-ignore` does
|
|
8
|
+
// not apply. A submodule is not a file, so it is not written.
|
|
9
|
+
export async function writeCommitFiles(git, gitOptions, sha, folder) {
|
|
10
|
+
const blobs = (await git('ls-tree', '-r', '-z', '--full-tree', sha))
|
|
11
|
+
.split('\0')
|
|
12
|
+
.filter((line) => line !== '')
|
|
13
|
+
.map((line) => {
|
|
14
|
+
const tab = line.indexOf('\t');
|
|
15
|
+
const [mode = '', type = '', object = ''] = line.slice(0, tab).split(' ');
|
|
16
|
+
return { mode, type, object, path: line.slice(tab + 1) };
|
|
17
|
+
})
|
|
18
|
+
.filter(({ type }) => type === 'blob');
|
|
19
|
+
const catFile = spawn('git', ['cat-file', '--batch'], {
|
|
20
|
+
...gitOptions,
|
|
21
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
22
|
+
});
|
|
23
|
+
const exited = once(catFile, 'close');
|
|
24
|
+
catFile.stdin.end(blobs.map(({ object }) => `${object}\n`).join(''));
|
|
25
|
+
const records = new Records(catFile.stdout);
|
|
26
|
+
const links = [];
|
|
27
|
+
for (const blob of blobs) {
|
|
28
|
+
const header = (await records.line()).split(' ');
|
|
29
|
+
const size = Number(header[2]);
|
|
30
|
+
if (header[1] !== 'blob' || !Number.isInteger(size))
|
|
31
|
+
throw new Error(`git cat-file gave "${header.join(' ')}" for ${blob.path}`);
|
|
32
|
+
const content = await records.bytes(size + 1);
|
|
33
|
+
const path = join(folder, blob.path);
|
|
34
|
+
await mkdir(dirname(path), { recursive: true });
|
|
35
|
+
if (blob.mode === '120000')
|
|
36
|
+
links.push({ blob, target: content.subarray(0, size).toString() });
|
|
37
|
+
else {
|
|
38
|
+
await writeFile(path, content.subarray(0, size));
|
|
39
|
+
if (blob.mode === '100755')
|
|
40
|
+
await chmod(path, 0o755);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
await exited;
|
|
44
|
+
// The files come first: on Windows, a link becomes a junction when its
|
|
45
|
+
// target is a folder of the commit, or else a text file that holds the
|
|
46
|
+
// target, as git does with core.symlinks=false.
|
|
47
|
+
for (const { blob, target } of links) {
|
|
48
|
+
const path = join(folder, blob.path);
|
|
49
|
+
if (process.platform !== 'win32')
|
|
50
|
+
await symlink(target, path);
|
|
51
|
+
else {
|
|
52
|
+
const absolute = resolve(dirname(path), target);
|
|
53
|
+
const isFolder = await stat(absolute).then((stats) => stats.isDirectory(), () => false);
|
|
54
|
+
if (isFolder)
|
|
55
|
+
await symlink(absolute, path, 'junction');
|
|
56
|
+
else
|
|
57
|
+
await writeFile(path, target);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Reads the records of `git cat-file --batch`: a header line, then the
|
|
62
|
+
// content and a newline.
|
|
63
|
+
class Records {
|
|
64
|
+
#chunks;
|
|
65
|
+
#buffer = Buffer.alloc(0);
|
|
66
|
+
constructor(stream) {
|
|
67
|
+
this.#chunks = stream[Symbol.asyncIterator]();
|
|
68
|
+
}
|
|
69
|
+
async line() {
|
|
70
|
+
let end = this.#buffer.indexOf(0x0a);
|
|
71
|
+
while (end === -1) {
|
|
72
|
+
this.#buffer = Buffer.concat([this.#buffer, await this.#next()]);
|
|
73
|
+
end = this.#buffer.indexOf(0x0a);
|
|
74
|
+
}
|
|
75
|
+
const line = this.#buffer.subarray(0, end).toString();
|
|
76
|
+
this.#buffer = this.#buffer.subarray(end + 1);
|
|
77
|
+
return line;
|
|
78
|
+
}
|
|
79
|
+
async bytes(count) {
|
|
80
|
+
if (this.#buffer.length < count) {
|
|
81
|
+
const parts = [this.#buffer];
|
|
82
|
+
let length = this.#buffer.length;
|
|
83
|
+
while (length < count) {
|
|
84
|
+
const chunk = await this.#next();
|
|
85
|
+
parts.push(chunk);
|
|
86
|
+
length += chunk.length;
|
|
87
|
+
}
|
|
88
|
+
this.#buffer = Buffer.concat(parts, length);
|
|
89
|
+
}
|
|
90
|
+
const bytes = this.#buffer.subarray(0, count);
|
|
91
|
+
this.#buffer = this.#buffer.subarray(count);
|
|
92
|
+
return bytes;
|
|
93
|
+
}
|
|
94
|
+
async #next() {
|
|
95
|
+
const { done, value } = await this.#chunks.next();
|
|
96
|
+
if (done === true)
|
|
97
|
+
throw new Error('git cat-file ended before it gave each file');
|
|
98
|
+
return Buffer.isBuffer(value) ? value : Buffer.from(String(value));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=commit-files.js.map
|