@vltpkg/git 1.0.0-rc.22 → 1.0.0-rc.24
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/dist/clone.d.ts +12 -0
- package/dist/clone.js +139 -0
- package/dist/find.d.ts +5 -0
- package/dist/find.js +14 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +8 -0
- package/dist/is-clean.d.ts +1 -0
- package/dist/is-clean.js +13 -0
- package/dist/is-windows.d.ts +2 -0
- package/dist/is-windows.js +1 -0
- package/dist/is.d.ts +3 -0
- package/dist/is.js +3 -0
- package/dist/lines-to-revs.d.ts +6 -0
- package/dist/lines-to-revs.js +152 -0
- package/dist/make-error.d.ts +2 -0
- package/dist/make-error.js +20 -0
- package/dist/opts.d.ts +3 -0
- package/dist/opts.js +14 -0
- package/dist/resolve.d.ts +16 -0
- package/dist/resolve.js +41 -0
- package/dist/revs.d.ts +3 -0
- package/dist/revs.js +30 -0
- package/dist/spawn.d.ts +3 -0
- package/dist/spawn.js +34 -0
- package/dist/user.d.ts +5 -0
- package/dist/user.js +38 -0
- package/dist/which.d.ts +4 -0
- package/dist/which.js +26 -0
- package/package.json +11 -11
package/dist/clone.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { GitOptions } from './index.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Only these whitelisted hosts get shallow cloning. Many hosts (including GHE)
|
|
4
|
+
* don't always support it. A failed shallow fetch takes a LOT longer than a
|
|
5
|
+
* full fetch in most cases, so we skip it entirely. Set opts.gitShallow =
|
|
6
|
+
* true/false to force this behavior one way or the other.
|
|
7
|
+
*
|
|
8
|
+
* If other hosts are added to this set, then they will be shallowly cloned
|
|
9
|
+
* as well.
|
|
10
|
+
*/
|
|
11
|
+
export declare const shallowHosts: Set<string>;
|
|
12
|
+
export declare const clone: (repo: string, ref?: string, target?: string, opts?: GitOptions) => Promise<string>;
|
package/dist/clone.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// The goal here is to minimize both git workload and
|
|
2
|
+
// the number of refs we download over the network.
|
|
3
|
+
//
|
|
4
|
+
// Every method ends up with the checked out working dir
|
|
5
|
+
// at the specified ref, and resolves with the git sha.
|
|
6
|
+
import { gitScpURL } from '@vltpkg/git-scp-url';
|
|
7
|
+
import { mkdir, stat } from 'node:fs/promises';
|
|
8
|
+
import { basename, resolve } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { isWindows } from "./is-windows.js";
|
|
11
|
+
import { resolveRef } from "./resolve.js";
|
|
12
|
+
import { revs as getRevs } from "./revs.js";
|
|
13
|
+
import { spawn } from "./spawn.js";
|
|
14
|
+
/**
|
|
15
|
+
* Only these whitelisted hosts get shallow cloning. Many hosts (including GHE)
|
|
16
|
+
* don't always support it. A failed shallow fetch takes a LOT longer than a
|
|
17
|
+
* full fetch in most cases, so we skip it entirely. Set opts.gitShallow =
|
|
18
|
+
* true/false to force this behavior one way or the other.
|
|
19
|
+
*
|
|
20
|
+
* If other hosts are added to this set, then they will be shallowly cloned
|
|
21
|
+
* as well.
|
|
22
|
+
*/
|
|
23
|
+
export const shallowHosts = new Set([
|
|
24
|
+
'github.com',
|
|
25
|
+
'gist.github.com',
|
|
26
|
+
'gitlab.com',
|
|
27
|
+
'bitbucket.com',
|
|
28
|
+
'bitbucket.org',
|
|
29
|
+
]);
|
|
30
|
+
export const clone = async (repo, ref = 'HEAD', target, opts = {}) => {
|
|
31
|
+
repo = String(gitScpURL(repo) ?? repo).replace(/^git\+/, '');
|
|
32
|
+
if (repo.startsWith('file://'))
|
|
33
|
+
repo = fileURLToPath(repo);
|
|
34
|
+
const revs = await getRevs(repo, opts);
|
|
35
|
+
return await clone_(repo, revs, ref, revs && resolveRef(revs, ref, opts), target || defaultTarget(repo, opts.cwd), opts);
|
|
36
|
+
};
|
|
37
|
+
const maybeShallow = (repo, opts) => {
|
|
38
|
+
if (opts['git-shallow'] === false || opts['git-shallow']) {
|
|
39
|
+
return opts['git-shallow'];
|
|
40
|
+
}
|
|
41
|
+
const host = gitScpURL(repo)?.host ?? '';
|
|
42
|
+
return shallowHosts.has(host);
|
|
43
|
+
};
|
|
44
|
+
const defaultTarget = (repo,
|
|
45
|
+
/* c8 ignore next */ cwd = process.cwd()) => resolve(cwd, basename(repo.replace(/[/\\]?\.git$/, '')));
|
|
46
|
+
const clone_ = (repo, revs, ref, revDoc, target, opts) => {
|
|
47
|
+
if (!revDoc || !revs) {
|
|
48
|
+
return unresolved(repo, ref, target, opts);
|
|
49
|
+
}
|
|
50
|
+
if (revDoc.sha === revs.refs.HEAD?.sha) {
|
|
51
|
+
return plain(repo, revDoc, target, opts);
|
|
52
|
+
}
|
|
53
|
+
if (revDoc.type === 'tag' || revDoc.type === 'branch') {
|
|
54
|
+
return branch(repo, revDoc, target, opts);
|
|
55
|
+
}
|
|
56
|
+
return other(repo, revDoc, target, opts);
|
|
57
|
+
};
|
|
58
|
+
// pull request or some other kind of advertised ref
|
|
59
|
+
const other = async (repo, revDoc, target, opts) => {
|
|
60
|
+
const shallow = maybeShallow(repo, opts);
|
|
61
|
+
const fetchOrigin = ['fetch', 'origin', revDoc.rawRef].concat(shallow ? ['--depth=1'] : []);
|
|
62
|
+
const git = (args) => spawn(args, { ...opts, cwd: target });
|
|
63
|
+
await mkdir(target, { recursive: true });
|
|
64
|
+
await git(['init']);
|
|
65
|
+
if (isWindows(opts)) {
|
|
66
|
+
await git([
|
|
67
|
+
'config',
|
|
68
|
+
'--local',
|
|
69
|
+
'--add',
|
|
70
|
+
'core.longpaths',
|
|
71
|
+
'true',
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
await git(['remote', 'add', 'origin', repo]);
|
|
75
|
+
await git(fetchOrigin);
|
|
76
|
+
await git(['checkout', revDoc.sha]);
|
|
77
|
+
await updateSubmodules(target, opts);
|
|
78
|
+
return revDoc.sha;
|
|
79
|
+
};
|
|
80
|
+
// tag or branches. use -b
|
|
81
|
+
const branch = async (repo, revDoc, target, opts) => {
|
|
82
|
+
const args = [
|
|
83
|
+
'clone',
|
|
84
|
+
'-b',
|
|
85
|
+
revDoc.ref,
|
|
86
|
+
repo,
|
|
87
|
+
target,
|
|
88
|
+
'--recurse-submodules',
|
|
89
|
+
];
|
|
90
|
+
if (maybeShallow(repo, opts)) {
|
|
91
|
+
args.push('--depth=1');
|
|
92
|
+
}
|
|
93
|
+
if (isWindows(opts)) {
|
|
94
|
+
args.push('--config', 'core.longpaths=true');
|
|
95
|
+
}
|
|
96
|
+
await spawn(args, opts);
|
|
97
|
+
return revDoc.sha;
|
|
98
|
+
};
|
|
99
|
+
// just the head. clone it
|
|
100
|
+
const plain = async (repo, revDoc, target, opts) => {
|
|
101
|
+
const args = ['clone', repo, target, '--recurse-submodules'];
|
|
102
|
+
if (maybeShallow(repo, opts)) {
|
|
103
|
+
args.push('--depth=1');
|
|
104
|
+
}
|
|
105
|
+
if (isWindows(opts)) {
|
|
106
|
+
args.push('--config', 'core.longpaths=true');
|
|
107
|
+
}
|
|
108
|
+
await spawn(args, opts);
|
|
109
|
+
return revDoc.sha;
|
|
110
|
+
};
|
|
111
|
+
const updateSubmodules = async (target, opts) => {
|
|
112
|
+
const hasSubmodules = await stat(`${target}/.gitmodules`)
|
|
113
|
+
.then(() => true)
|
|
114
|
+
.catch(() => false);
|
|
115
|
+
if (!hasSubmodules) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
await spawn(['submodule', 'update', '-q', '--init', '--recursive'], { ...opts, cwd: target });
|
|
119
|
+
};
|
|
120
|
+
const unresolved = async (repo, ref, target, opts) => {
|
|
121
|
+
// can't do this one shallowly, because the ref isn't advertised
|
|
122
|
+
// but we can avoid checking out the working dir twice, at least
|
|
123
|
+
const lp = isWindows(opts) ? ['--config', 'core.longpaths=true'] : [];
|
|
124
|
+
const cloneArgs = [
|
|
125
|
+
'clone',
|
|
126
|
+
'--mirror',
|
|
127
|
+
'-q',
|
|
128
|
+
repo,
|
|
129
|
+
target + '/.git',
|
|
130
|
+
];
|
|
131
|
+
const git = (args) => spawn(args, { ...opts, cwd: target });
|
|
132
|
+
await mkdir(target, { recursive: true });
|
|
133
|
+
await git(cloneArgs.concat(lp));
|
|
134
|
+
await git(['init']);
|
|
135
|
+
await git(['checkout', ref]);
|
|
136
|
+
await updateSubmodules(target, opts);
|
|
137
|
+
const result = await git(['rev-parse', '--revs-only', 'HEAD']);
|
|
138
|
+
return result.stdout;
|
|
139
|
+
};
|
package/dist/find.d.ts
ADDED
package/dist/find.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
|
+
import { is } from "./is.js";
|
|
3
|
+
export const find = async ({ cwd = process.cwd(), root, } = {}) => {
|
|
4
|
+
while (true) {
|
|
5
|
+
if (await is({ cwd })) {
|
|
6
|
+
return cwd;
|
|
7
|
+
}
|
|
8
|
+
const next = dirname(cwd);
|
|
9
|
+
if (cwd === root || cwd === next) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
cwd = next;
|
|
13
|
+
}
|
|
14
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { PickManifestOptions } from '@vltpkg/pick-manifest';
|
|
2
|
+
import type { Spec } from '@vltpkg/spec';
|
|
3
|
+
import type { SpawnOptions } from 'node:child_process';
|
|
4
|
+
import type { WrapOptions } from 'retry';
|
|
5
|
+
export * from './clone.ts';
|
|
6
|
+
export * from './find.ts';
|
|
7
|
+
export * from './is-clean.ts';
|
|
8
|
+
export * from './is.ts';
|
|
9
|
+
export * from './resolve.ts';
|
|
10
|
+
export * from './revs.ts';
|
|
11
|
+
export * from './spawn.ts';
|
|
12
|
+
export * from './user.ts';
|
|
13
|
+
/**
|
|
14
|
+
* This extends all options that can be passed to spawn() or pickManifest.
|
|
15
|
+
*/
|
|
16
|
+
export type GitOptions = PickManifestOptions & SpawnOptions & {
|
|
17
|
+
/** the path to git binary, or 'false' to prevent all git operations */
|
|
18
|
+
git?: string | false;
|
|
19
|
+
/** the current working directory to perform git operations in */
|
|
20
|
+
cwd?: string;
|
|
21
|
+
/** Parsed git specifier to be cloned, if we have one */
|
|
22
|
+
spec?: Spec;
|
|
23
|
+
/**
|
|
24
|
+
* Set to a boolean to force cloning with/without `--depth=1`. If left
|
|
25
|
+
* undefined, then shallow cloning will only be performed on hosts known to
|
|
26
|
+
* support it.
|
|
27
|
+
*/
|
|
28
|
+
'git-shallow'?: boolean;
|
|
29
|
+
/** Only relevant if `retry` is unset. Value for retry.retries, default 2 */
|
|
30
|
+
'fetch-retries'?: WrapOptions['retries'];
|
|
31
|
+
/** Only relevant if `retry` is unset. Value for retry.factor, default 10 */
|
|
32
|
+
'fetch-retry-factor'?: WrapOptions['factor'];
|
|
33
|
+
/**
|
|
34
|
+
* Only relevant if `retry` is unset. Value for retry.maxTimeout, default
|
|
35
|
+
* 60_000
|
|
36
|
+
*/
|
|
37
|
+
'fetch-retry-maxtimeout'?: WrapOptions['maxTimeout'];
|
|
38
|
+
/**
|
|
39
|
+
* Only relevant if `retry` is unset. Value for retry.minTimeout, default
|
|
40
|
+
* 1_000
|
|
41
|
+
*/
|
|
42
|
+
'fetch-retry-mintimeout'?: WrapOptions['minTimeout'];
|
|
43
|
+
/**
|
|
44
|
+
* Used to test platform-specific behavior.
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
fakePlatform?: NodeJS.Platform;
|
|
48
|
+
/**
|
|
49
|
+
* Just to test rev lookup without continually clearing the cache
|
|
50
|
+
*/
|
|
51
|
+
noGitRevCache?: boolean;
|
|
52
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const isClean: (opts?: {}) => Promise<boolean>;
|
package/dist/is-clean.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { error } from '@vltpkg/error-cause';
|
|
2
|
+
import { spawn } from "./spawn.js";
|
|
3
|
+
export const isClean = async (opts = {}) => {
|
|
4
|
+
const result = await spawn(['status', '--porcelain=v1', '-uno'], opts);
|
|
5
|
+
if (result.status || result.signal) {
|
|
6
|
+
throw error('git isClean check failed', result);
|
|
7
|
+
}
|
|
8
|
+
for (const line of result.stdout.split(/\r?\n+/)) {
|
|
9
|
+
if (line.trim())
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
13
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const isWindows = (opts) => (opts.fakePlatform || process.platform) === 'win32';
|
package/dist/is.d.ts
ADDED
package/dist/is.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { parse } from '@vltpkg/semver';
|
|
2
|
+
import { error } from '@vltpkg/error-cause';
|
|
3
|
+
/**
|
|
4
|
+
* turn an array of lines from `git ls-remote` into a thing
|
|
5
|
+
* vaguely resembling a packument, where docs are a resolved ref
|
|
6
|
+
*/
|
|
7
|
+
export const linesToRevs = (lines) => finish(lines.reduce(linesToRevsReducer, {
|
|
8
|
+
name: '',
|
|
9
|
+
versions: {},
|
|
10
|
+
'dist-tags': {},
|
|
11
|
+
refs: {},
|
|
12
|
+
shas: {},
|
|
13
|
+
}));
|
|
14
|
+
const finish = (revs) => distTags(versions(shaList(peelTags(revs))));
|
|
15
|
+
const versions = (revs) => {
|
|
16
|
+
for (const [version, entry] of Object.entries(revs.versions)) {
|
|
17
|
+
entry.version = version;
|
|
18
|
+
}
|
|
19
|
+
return revs;
|
|
20
|
+
};
|
|
21
|
+
// We can check out shallow clones on specific SHAs if we have a ref
|
|
22
|
+
const shaList = (revs) => {
|
|
23
|
+
Object.entries(revs.refs).forEach(([ref, doc]) => {
|
|
24
|
+
const shas = revs.shas[doc.sha];
|
|
25
|
+
if (!shas) {
|
|
26
|
+
revs.shas[doc.sha] = [ref];
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
shas.push(ref);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
return revs;
|
|
33
|
+
};
|
|
34
|
+
// Replace any tags with their ^{} counterparts, if those exist
|
|
35
|
+
const peelTags = (revs) => {
|
|
36
|
+
Object.entries(revs.refs)
|
|
37
|
+
.filter(([ref]) => ref.endsWith('^{}'))
|
|
38
|
+
.forEach(([ref, peeled]) => {
|
|
39
|
+
const unpeeled = revs.refs[ref.replace(/\^\{\}$/, '')];
|
|
40
|
+
if (unpeeled) {
|
|
41
|
+
unpeeled.sha = peeled.sha;
|
|
42
|
+
delete revs.refs[ref];
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return revs;
|
|
46
|
+
};
|
|
47
|
+
const distTags = (revs) => {
|
|
48
|
+
// not entirely sure what situations would result in an
|
|
49
|
+
// ichabod repo, but best to be careful in Sleepy Hollow anyway
|
|
50
|
+
/* c8 ignore start */
|
|
51
|
+
const HEAD = revs.refs.HEAD ?? {
|
|
52
|
+
sha: undefined,
|
|
53
|
+
};
|
|
54
|
+
/* c8 ignore stop */
|
|
55
|
+
for (const [v, ver] of Object.entries(revs.versions)) {
|
|
56
|
+
// simulate a dist-tags with latest pointing at the
|
|
57
|
+
// 'latest' branch if one exists and is a version,
|
|
58
|
+
// or HEAD if not.
|
|
59
|
+
if (ver.sha === revs.refs.latest?.sha) {
|
|
60
|
+
revs['dist-tags'].latest = v;
|
|
61
|
+
}
|
|
62
|
+
else if (ver.sha === HEAD.sha) {
|
|
63
|
+
revs['dist-tags'].HEAD = v;
|
|
64
|
+
if (!revs.refs.latest) {
|
|
65
|
+
revs['dist-tags'].latest = v;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return revs;
|
|
70
|
+
};
|
|
71
|
+
const refType = (ref) => {
|
|
72
|
+
if (ref.startsWith('refs/tags/')) {
|
|
73
|
+
return 'tag';
|
|
74
|
+
}
|
|
75
|
+
if (ref.startsWith('refs/heads/')) {
|
|
76
|
+
return 'branch';
|
|
77
|
+
}
|
|
78
|
+
if (ref.startsWith('refs/pull/')) {
|
|
79
|
+
return 'pull';
|
|
80
|
+
}
|
|
81
|
+
if (ref === 'HEAD') {
|
|
82
|
+
return 'head';
|
|
83
|
+
}
|
|
84
|
+
// Could be anything, ignore for now
|
|
85
|
+
/* c8 ignore next */
|
|
86
|
+
return 'other';
|
|
87
|
+
};
|
|
88
|
+
// return the doc, or null if we should ignore it.
|
|
89
|
+
const lineToRevDoc = (line) => {
|
|
90
|
+
let [sha, rawRef] = line.trim().split(/\s+/, 2);
|
|
91
|
+
if (sha === undefined || rawRef === undefined)
|
|
92
|
+
return undefined;
|
|
93
|
+
sha = sha.trim();
|
|
94
|
+
rawRef = rawRef.trim();
|
|
95
|
+
const type = refType(rawRef);
|
|
96
|
+
switch (type) {
|
|
97
|
+
case 'tag': {
|
|
98
|
+
// refs/tags/foo^{} is the 'peeled tag', ie the commit
|
|
99
|
+
// that is tagged by refs/tags/foo they resolve to the same
|
|
100
|
+
// content, just different objects in git's data structure.
|
|
101
|
+
// But, we care about the thing the tag POINTS to, not the tag
|
|
102
|
+
// object itself, so we only look at the peeled tag refs, and
|
|
103
|
+
// ignore the pointer.
|
|
104
|
+
// For now, though, we have to save both, because some tags
|
|
105
|
+
// don't have peels, if they were not annotated.
|
|
106
|
+
const ref = rawRef.slice('refs/tags/'.length);
|
|
107
|
+
return { name: '', version: '', sha, ref, rawRef, type };
|
|
108
|
+
}
|
|
109
|
+
case 'branch': {
|
|
110
|
+
const ref = rawRef.slice('refs/heads/'.length);
|
|
111
|
+
return { name: '', version: '', sha, ref, rawRef, type };
|
|
112
|
+
}
|
|
113
|
+
case 'pull': {
|
|
114
|
+
// NB: merged pull requests installable with #pull/123/merge
|
|
115
|
+
// for the merged pr, or #pull/123 for the PR head
|
|
116
|
+
const ref = rawRef.slice('refs/'.length).replace(/\/head$/, '');
|
|
117
|
+
return { name: '', version: '', sha, ref, rawRef, type };
|
|
118
|
+
}
|
|
119
|
+
case 'head': {
|
|
120
|
+
const ref = 'HEAD';
|
|
121
|
+
return { name: '', version: '', sha, ref, rawRef, type };
|
|
122
|
+
}
|
|
123
|
+
default:
|
|
124
|
+
// at this point, all we can do is leave the ref un-munged
|
|
125
|
+
return { name: '', version: '', sha, ref: rawRef, rawRef, type };
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const linesToRevsReducer = (revs, line) => {
|
|
129
|
+
const doc = lineToRevDoc(line);
|
|
130
|
+
if (!doc) {
|
|
131
|
+
return revs;
|
|
132
|
+
}
|
|
133
|
+
revs.refs[doc.ref] = doc;
|
|
134
|
+
revs.refs[doc.rawRef] = doc;
|
|
135
|
+
if (doc.type === 'tag') {
|
|
136
|
+
// try to pull a semver value out of tags like `release-v1.2.3`
|
|
137
|
+
// which is a pretty common pattern.
|
|
138
|
+
const match = doc.ref.endsWith('^{}') ?
|
|
139
|
+
null
|
|
140
|
+
: /v?(\d+\.\d+\.\d+(?:[-+].+)?)$/.exec(doc.ref);
|
|
141
|
+
if (match) {
|
|
142
|
+
/* c8 ignore start */
|
|
143
|
+
if (!match[1])
|
|
144
|
+
throw error(`invalid semver tag`, { found: doc.ref });
|
|
145
|
+
/* c8 ignore stop */
|
|
146
|
+
const v = parse(match[1]);
|
|
147
|
+
if (v)
|
|
148
|
+
revs.versions[String(v)] = doc;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return revs;
|
|
152
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { error } from '@vltpkg/error-cause';
|
|
2
|
+
const connectionErrorRe = new RegExp([
|
|
3
|
+
'remote error: Internal Server Error',
|
|
4
|
+
'The remote end hung up unexpectedly',
|
|
5
|
+
'Connection timed out',
|
|
6
|
+
'Operation timed out',
|
|
7
|
+
'Failed to connect to .* Timed out',
|
|
8
|
+
'Connection reset by peer',
|
|
9
|
+
'SSL_ERROR_SYSCALL',
|
|
10
|
+
'The requested URL returned error: 503',
|
|
11
|
+
].join('|'));
|
|
12
|
+
const missingPathspecRe = /pathspec .* did not match any file\(s\) known to git/;
|
|
13
|
+
export const makeError = (result) => connectionErrorRe.test(result.stderr) ?
|
|
14
|
+
[
|
|
15
|
+
(n) => n < 3,
|
|
16
|
+
error('A git connection error occurred', result),
|
|
17
|
+
]
|
|
18
|
+
: missingPathspecRe.test(result.stderr) ?
|
|
19
|
+
[null, error('The git reference could not be found', result)]
|
|
20
|
+
: [null, error('An unknown git error occurred', result)];
|
package/dist/opts.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { PromiseSpawnOptionsStderrString, PromiseSpawnOptionsStdoutString } from '@vltpkg/promise-spawn';
|
|
2
|
+
import type { SpawnOptions } from 'node:child_process';
|
|
3
|
+
export declare const opts: (opts?: SpawnOptions) => PromiseSpawnOptionsStderrString & PromiseSpawnOptionsStdoutString;
|
package/dist/opts.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Values we want to set if they're not already defined by the end user
|
|
2
|
+
// This defaults to accepting new ssh host key fingerprints
|
|
3
|
+
const gitEnv = {
|
|
4
|
+
GIT_ASKPASS: 'echo',
|
|
5
|
+
GIT_SSH_COMMAND: 'ssh -oStrictHostKeyChecking=accept-new',
|
|
6
|
+
};
|
|
7
|
+
export const opts = (opts = {}) => ({
|
|
8
|
+
acceptFail: true,
|
|
9
|
+
...opts,
|
|
10
|
+
env: opts.env ?? { ...gitEnv, ...process.env },
|
|
11
|
+
stdio: 'pipe',
|
|
12
|
+
stdioString: true,
|
|
13
|
+
shell: false,
|
|
14
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RevDoc } from '@vltpkg/types';
|
|
2
|
+
import type { GitOptions } from './index.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Given a repo and either a ref or a git specifier Spec object, resolve the
|
|
5
|
+
* appropriate ref that we should clone, ideally witout actually cloning the
|
|
6
|
+
* repository.
|
|
7
|
+
*
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
export declare const resolve: (repo: string, ref?: string, opts?: GitOptions) => Promise<import("@vltpkg/types").RevDocEntry | undefined>;
|
|
11
|
+
/**
|
|
12
|
+
* Given a repo's RevDoc object and either a ref or a git specifier Spec
|
|
13
|
+
* object, resolve the appropriate ref that we should clone, witout actually
|
|
14
|
+
* cloning the repository.
|
|
15
|
+
*/
|
|
16
|
+
export declare const resolveRef: (revDoc: RevDoc, ref?: string, opts?: GitOptions) => import("@vltpkg/types").RevDocEntry | undefined;
|
package/dist/resolve.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { pickManifest } from '@vltpkg/pick-manifest';
|
|
2
|
+
import { revs } from "./revs.js";
|
|
3
|
+
/**
|
|
4
|
+
* Given a repo and either a ref or a git specifier Spec object, resolve the
|
|
5
|
+
* appropriate ref that we should clone, ideally witout actually cloning the
|
|
6
|
+
* repository.
|
|
7
|
+
*
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
export const resolve = async (repo, ref = 'HEAD', opts = {}) => {
|
|
11
|
+
const revDoc = await revs(repo, opts);
|
|
12
|
+
/* no resolution possible if we can't read the repo */
|
|
13
|
+
if (!revDoc)
|
|
14
|
+
return undefined;
|
|
15
|
+
return resolveRef(revDoc, ref, opts);
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Given a repo's RevDoc object and either a ref or a git specifier Spec
|
|
19
|
+
* object, resolve the appropriate ref that we should clone, witout actually
|
|
20
|
+
* cloning the repository.
|
|
21
|
+
*/
|
|
22
|
+
export const resolveRef = (revDoc, ref = 'HEAD', opts = {}) => {
|
|
23
|
+
const { spec } = opts;
|
|
24
|
+
ref = spec?.gitCommittish || ref;
|
|
25
|
+
if (spec?.range) {
|
|
26
|
+
return pickManifest(revDoc, spec.range, opts);
|
|
27
|
+
}
|
|
28
|
+
if (!ref) {
|
|
29
|
+
return revDoc.refs.HEAD;
|
|
30
|
+
}
|
|
31
|
+
if (revDoc.refs[ref]) {
|
|
32
|
+
return revDoc.refs[ref];
|
|
33
|
+
}
|
|
34
|
+
/* c8 ignore start - typically found above, but just in case */
|
|
35
|
+
const sha = revDoc.shas[ref]?.[0];
|
|
36
|
+
if (sha) {
|
|
37
|
+
return revDoc.refs[sha];
|
|
38
|
+
}
|
|
39
|
+
/* c8 ignore stop */
|
|
40
|
+
return undefined;
|
|
41
|
+
};
|
package/dist/revs.d.ts
ADDED
package/dist/revs.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { gitScpURL } from '@vltpkg/git-scp-url';
|
|
2
|
+
import { LRUCache } from 'lru-cache';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { linesToRevs } from "./lines-to-revs.js";
|
|
5
|
+
import { spawn } from "./spawn.js";
|
|
6
|
+
const fetchMethod = async (repo, _, options) => {
|
|
7
|
+
const result = await spawn(['ls-remote', repo], options.context);
|
|
8
|
+
const revsDoc = linesToRevs(result.stdout.split('\n'));
|
|
9
|
+
return revsDoc;
|
|
10
|
+
};
|
|
11
|
+
const revsCache = new LRUCache({
|
|
12
|
+
max: 100,
|
|
13
|
+
ttl: 5 * 60 * 1000,
|
|
14
|
+
allowStaleOnFetchAbort: true,
|
|
15
|
+
allowStaleOnFetchRejection: true,
|
|
16
|
+
fetchMethod,
|
|
17
|
+
});
|
|
18
|
+
export const revs = async (repo, opts = {}) => {
|
|
19
|
+
repo = String(gitScpURL(repo) ?? repo).replace(/^git\+/, '');
|
|
20
|
+
if (repo.startsWith('file://'))
|
|
21
|
+
repo = fileURLToPath(repo);
|
|
22
|
+
if (opts.noGitRevCache) {
|
|
23
|
+
const result = await fetchMethod(repo, undefined, {
|
|
24
|
+
context: opts,
|
|
25
|
+
});
|
|
26
|
+
revsCache.set(repo, result);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
return await revsCache.fetch(repo, { context: opts });
|
|
30
|
+
};
|
package/dist/spawn.d.ts
ADDED
package/dist/spawn.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { promiseSpawn } from '@vltpkg/promise-spawn';
|
|
2
|
+
import promiseRetry from 'promise-retry';
|
|
3
|
+
import { makeError } from "./make-error.js";
|
|
4
|
+
import { opts as makeOpts } from "./opts.js";
|
|
5
|
+
import { which } from "./which.js";
|
|
6
|
+
export const spawn = async (gitArgs, opts = {}) => {
|
|
7
|
+
const gitPath = which(opts);
|
|
8
|
+
if (gitPath instanceof Error) {
|
|
9
|
+
throw gitPath;
|
|
10
|
+
}
|
|
11
|
+
/* c8 ignore start - undocumented option, only here for tests */
|
|
12
|
+
const args = (opts.allowReplace ||
|
|
13
|
+
gitArgs[0] === '--no-replace-objects') ?
|
|
14
|
+
gitArgs
|
|
15
|
+
: ['--no-replace-objects', ...gitArgs];
|
|
16
|
+
/* c8 ignore stop */
|
|
17
|
+
const retryOpts = {
|
|
18
|
+
retries: opts['fetch-retries'] || 3,
|
|
19
|
+
factor: opts['fetch-retry-factor'] || 2,
|
|
20
|
+
maxTimeout: opts['fetch-retry-maxtimeout'] || 60000,
|
|
21
|
+
minTimeout: opts['fetch-retry-mintimeout'] || 1000,
|
|
22
|
+
};
|
|
23
|
+
return promiseRetry(async (retryFn, num) => {
|
|
24
|
+
const result = await promiseSpawn(gitPath, args, makeOpts(opts));
|
|
25
|
+
if (result.status || result.signal) {
|
|
26
|
+
const [shouldRetry, gitError] = makeError(result);
|
|
27
|
+
if (!shouldRetry?.(num)) {
|
|
28
|
+
throw gitError;
|
|
29
|
+
}
|
|
30
|
+
retryFn(gitError);
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}, retryOpts);
|
|
34
|
+
};
|
package/dist/user.d.ts
ADDED
package/dist/user.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { spawn } from "./spawn.js";
|
|
2
|
+
export const getUser = async (opts = {}) => {
|
|
3
|
+
let name = '';
|
|
4
|
+
let email = '';
|
|
5
|
+
// retrieve user.name
|
|
6
|
+
const oldFlagUserNameResult = await spawn(['config', '--get', 'user.name'], opts);
|
|
7
|
+
if (oldFlagUserNameResult.status || oldFlagUserNameResult.signal) {
|
|
8
|
+
const userNameResult = await spawn(['config', 'get', 'user.name'], opts);
|
|
9
|
+
name =
|
|
10
|
+
userNameResult.status || userNameResult.signal ?
|
|
11
|
+
''
|
|
12
|
+
: userNameResult.stdout.trim();
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
name = oldFlagUserNameResult.stdout.trim();
|
|
16
|
+
}
|
|
17
|
+
// retrieve user.email
|
|
18
|
+
const oldFlagUserEmailResult = await spawn(['config', '--get', 'user.email'], opts);
|
|
19
|
+
if (oldFlagUserEmailResult.status ||
|
|
20
|
+
oldFlagUserEmailResult.signal) {
|
|
21
|
+
const userEmailResult = await spawn(['config', 'get', 'user.email'], opts);
|
|
22
|
+
email =
|
|
23
|
+
userEmailResult.status || userEmailResult.signal ?
|
|
24
|
+
''
|
|
25
|
+
: userEmailResult.stdout.trim();
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
email = oldFlagUserEmailResult.stdout.trim();
|
|
29
|
+
}
|
|
30
|
+
// if fails to find both name & email, then return undefined
|
|
31
|
+
if (!name && !email) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
name,
|
|
36
|
+
email,
|
|
37
|
+
};
|
|
38
|
+
};
|
package/dist/which.d.ts
ADDED
package/dist/which.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { error } from '@vltpkg/error-cause';
|
|
2
|
+
import { whichSync } from '@vltpkg/which';
|
|
3
|
+
let gitPath = undefined;
|
|
4
|
+
export const which = (opts = {}) => {
|
|
5
|
+
if (opts.git) {
|
|
6
|
+
return opts.git;
|
|
7
|
+
}
|
|
8
|
+
let whichError = undefined;
|
|
9
|
+
if (opts.git !== false) {
|
|
10
|
+
if (!gitPath) {
|
|
11
|
+
try {
|
|
12
|
+
gitPath = whichSync('git');
|
|
13
|
+
}
|
|
14
|
+
catch (er) {
|
|
15
|
+
whichError = er;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (!gitPath || opts.git === false) {
|
|
20
|
+
return error('No git binary found in $PATH', {
|
|
21
|
+
code: 'ENOGIT',
|
|
22
|
+
cause: whichError,
|
|
23
|
+
}, which);
|
|
24
|
+
}
|
|
25
|
+
return gitPath;
|
|
26
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vltpkg/git",
|
|
3
3
|
"description": "a util for spawning git from npm CLI contexts",
|
|
4
|
-
"version": "1.0.0-rc.
|
|
4
|
+
"version": "1.0.0-rc.24",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/vltpkg/vltpkg.git",
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@types/promise-retry": "^1.1.6",
|
|
16
|
-
"@vltpkg/error-cause": "1.0.0-rc.
|
|
17
|
-
"@vltpkg/git-scp-url": "1.0.0-rc.
|
|
18
|
-
"@vltpkg/pick-manifest": "1.0.0-rc.
|
|
19
|
-
"@vltpkg/promise-spawn": "1.0.0-rc.
|
|
20
|
-
"@vltpkg/semver": "1.0.0-rc.
|
|
21
|
-
"@vltpkg/spec": "1.0.0-rc.
|
|
22
|
-
"@vltpkg/which": "1.0.0-rc.
|
|
16
|
+
"@vltpkg/error-cause": "1.0.0-rc.24",
|
|
17
|
+
"@vltpkg/git-scp-url": "1.0.0-rc.24",
|
|
18
|
+
"@vltpkg/pick-manifest": "1.0.0-rc.24",
|
|
19
|
+
"@vltpkg/promise-spawn": "1.0.0-rc.24",
|
|
20
|
+
"@vltpkg/semver": "1.0.0-rc.24",
|
|
21
|
+
"@vltpkg/spec": "1.0.0-rc.24",
|
|
22
|
+
"@vltpkg/which": "1.0.0-rc.24",
|
|
23
23
|
"lru-cache": "^11.2.4",
|
|
24
24
|
"promise-retry": "^2.0.1",
|
|
25
25
|
"retry": "^0.13.1"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@eslint/js": "^9.39.1",
|
|
29
29
|
"@types/node": "^22.19.2",
|
|
30
30
|
"@types/retry": "^0.12.5",
|
|
31
|
-
"@vltpkg/types": "1.0.0-rc.
|
|
31
|
+
"@vltpkg/types": "1.0.0-rc.24",
|
|
32
32
|
"eslint": "^9.39.1",
|
|
33
33
|
"prettier": "^3.7.4",
|
|
34
34
|
"tap": "^21.5.0",
|
|
@@ -55,13 +55,13 @@
|
|
|
55
55
|
"extends": "../../tap-config.yaml"
|
|
56
56
|
},
|
|
57
57
|
"prettier": "../../.prettierrc.js",
|
|
58
|
-
"module": "./
|
|
58
|
+
"module": "./dist/index.js",
|
|
59
59
|
"type": "module",
|
|
60
60
|
"exports": {
|
|
61
61
|
"./package.json": "./package.json",
|
|
62
62
|
".": {
|
|
63
63
|
"import": {
|
|
64
|
-
"default": "./
|
|
64
|
+
"default": "./dist/index.js"
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
},
|