giget 0.1.6 → 1.0.0
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 +1 -1
- package/README.md +2 -2
- package/dist/cli.cjs +18 -20
- package/dist/cli.mjs +18 -16
- package/dist/index.cjs +4 -4
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +4 -2
- package/dist/shared/{giget.73314e21.cjs → giget.1028e17a.cjs} +57 -54
- package/dist/shared/{giget.ae887001.mjs → giget.5e7ec864.mjs} +57 -50
- package/package.json +27 -24
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ A custom registry should provide an endpoint with dynamic path `/:template.json`
|
|
|
89
89
|
- `tar` (required) Link to the tar download link.
|
|
90
90
|
- `defaultDir`: (optional) Default cloning directory.
|
|
91
91
|
- `url`: (optional) Webpage of the template.
|
|
92
|
-
- `
|
|
92
|
+
- `subdir`: (optional) Directory inside the tar file.
|
|
93
93
|
- `headers`: (optional) Custom headers to send while downloading template.
|
|
94
94
|
|
|
95
95
|
Because of the simplicity, you can even use a github repository as template registry but also you can build something more powerful by bringing your own API.
|
|
@@ -135,7 +135,7 @@ const { source, dir } = await downloadTemplate('github:unjs/template')
|
|
|
135
135
|
- `provider`: (string) Either `github`, `gitlab`, `bitbucket` or `sourcehut`. The default is `github`.
|
|
136
136
|
- `repo`: (string) Name of repository in format of `{username}/{reponame}`.
|
|
137
137
|
- `ref`: (string) Git ref (branch or commit or tag). The default value is `main`.
|
|
138
|
-
- `
|
|
138
|
+
- `subdir`: (string) Directory of the repo to clone from. The default value is none.
|
|
139
139
|
- `force`: (boolean) Extract to the exisiting dir even if already exsists.
|
|
140
140
|
- `forceClean`: (boolean) ⚠️ Clean ups any existing directory or file before cloning.
|
|
141
141
|
- `offline`: (boolean) Do not attempt to download and use cached version.
|
package/dist/cli.cjs
CHANGED
|
@@ -4,52 +4,50 @@
|
|
|
4
4
|
const node_path = require('node:path');
|
|
5
5
|
const mri = require('mri');
|
|
6
6
|
const colorette = require('colorette');
|
|
7
|
-
const giget = require('./shared/giget.
|
|
7
|
+
const giget = require('./shared/giget.1028e17a.cjs');
|
|
8
8
|
require('node:fs/promises');
|
|
9
9
|
require('node:os');
|
|
10
10
|
require('node:fs');
|
|
11
11
|
require('tar');
|
|
12
12
|
require('pathe');
|
|
13
13
|
require('defu');
|
|
14
|
-
require('node:stream
|
|
14
|
+
require('node:stream');
|
|
15
15
|
require('node:child_process');
|
|
16
|
+
require('node:util');
|
|
16
17
|
require('node-fetch-native');
|
|
17
|
-
|
|
18
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
|
|
19
|
-
|
|
20
|
-
const mri__default = /*#__PURE__*/_interopDefaultLegacy(mri);
|
|
18
|
+
require('https-proxy-agent');
|
|
21
19
|
|
|
22
20
|
async function main() {
|
|
23
|
-
const
|
|
21
|
+
const arguments_ = mri(process.argv.slice(2), {
|
|
24
22
|
boolean: ["help", "force", "force-clean", "offline", "prefer-offline", "shell", "verbose"],
|
|
25
23
|
string: ["registry", "cwd", "auth"]
|
|
26
24
|
});
|
|
27
|
-
const input =
|
|
28
|
-
const dir =
|
|
29
|
-
if (!input ||
|
|
25
|
+
const input = arguments_._[0];
|
|
26
|
+
const dir = arguments_._[1];
|
|
27
|
+
if (!input || arguments_.help || arguments_.h) {
|
|
30
28
|
console.error("Usage: npx getgit@latest <input> [<dir>] [--force] [--force-clean] [--offline] [--prefer-offline] [--shell] [--registry] [--no-registry] [--verbose] [--cwd] [--auth]");
|
|
31
29
|
process.exit(1);
|
|
32
30
|
}
|
|
33
|
-
if (
|
|
31
|
+
if (arguments_.verbose) {
|
|
34
32
|
process.env.DEBUG = process.env.DEBUG || "true";
|
|
35
33
|
}
|
|
36
34
|
const r = await giget.downloadTemplate(input, {
|
|
37
35
|
dir,
|
|
38
|
-
force:
|
|
39
|
-
forceClean:
|
|
40
|
-
offline:
|
|
41
|
-
registry:
|
|
42
|
-
cwd:
|
|
43
|
-
auth:
|
|
36
|
+
force: arguments_.force,
|
|
37
|
+
forceClean: arguments_["force-clean"],
|
|
38
|
+
offline: arguments_.offline,
|
|
39
|
+
registry: arguments_.registry,
|
|
40
|
+
cwd: arguments_.cwd,
|
|
41
|
+
auth: arguments_.auth
|
|
44
42
|
});
|
|
45
43
|
console.log(`\u2728 Successfully cloned ${colorette.cyan(r.name || r.url)} to ${colorette.cyan(node_path.relative(process.cwd(), r.dir))}
|
|
46
44
|
`);
|
|
47
|
-
if (
|
|
45
|
+
if (arguments_.shell) {
|
|
48
46
|
giget.startShell(r.dir);
|
|
49
47
|
}
|
|
50
48
|
process.exit(0);
|
|
51
49
|
}
|
|
52
|
-
main().catch((
|
|
53
|
-
console.error(
|
|
50
|
+
main().catch((error) => {
|
|
51
|
+
console.error(error);
|
|
54
52
|
process.exit(1);
|
|
55
53
|
});
|
package/dist/cli.mjs
CHANGED
|
@@ -2,48 +2,50 @@
|
|
|
2
2
|
import { relative } from 'node:path';
|
|
3
3
|
import mri from 'mri';
|
|
4
4
|
import { cyan } from 'colorette';
|
|
5
|
-
import { d as downloadTemplate, s as startShell } from './shared/giget.
|
|
5
|
+
import { d as downloadTemplate, s as startShell } from './shared/giget.5e7ec864.mjs';
|
|
6
6
|
import 'node:fs/promises';
|
|
7
7
|
import 'node:os';
|
|
8
8
|
import 'node:fs';
|
|
9
9
|
import 'tar';
|
|
10
10
|
import 'pathe';
|
|
11
11
|
import 'defu';
|
|
12
|
-
import 'node:stream
|
|
12
|
+
import 'node:stream';
|
|
13
13
|
import 'node:child_process';
|
|
14
|
+
import 'node:util';
|
|
14
15
|
import 'node-fetch-native';
|
|
16
|
+
import 'https-proxy-agent';
|
|
15
17
|
|
|
16
18
|
async function main() {
|
|
17
|
-
const
|
|
19
|
+
const arguments_ = mri(process.argv.slice(2), {
|
|
18
20
|
boolean: ["help", "force", "force-clean", "offline", "prefer-offline", "shell", "verbose"],
|
|
19
21
|
string: ["registry", "cwd", "auth"]
|
|
20
22
|
});
|
|
21
|
-
const input =
|
|
22
|
-
const dir =
|
|
23
|
-
if (!input ||
|
|
23
|
+
const input = arguments_._[0];
|
|
24
|
+
const dir = arguments_._[1];
|
|
25
|
+
if (!input || arguments_.help || arguments_.h) {
|
|
24
26
|
console.error("Usage: npx getgit@latest <input> [<dir>] [--force] [--force-clean] [--offline] [--prefer-offline] [--shell] [--registry] [--no-registry] [--verbose] [--cwd] [--auth]");
|
|
25
27
|
process.exit(1);
|
|
26
28
|
}
|
|
27
|
-
if (
|
|
29
|
+
if (arguments_.verbose) {
|
|
28
30
|
process.env.DEBUG = process.env.DEBUG || "true";
|
|
29
31
|
}
|
|
30
32
|
const r = await downloadTemplate(input, {
|
|
31
33
|
dir,
|
|
32
|
-
force:
|
|
33
|
-
forceClean:
|
|
34
|
-
offline:
|
|
35
|
-
registry:
|
|
36
|
-
cwd:
|
|
37
|
-
auth:
|
|
34
|
+
force: arguments_.force,
|
|
35
|
+
forceClean: arguments_["force-clean"],
|
|
36
|
+
offline: arguments_.offline,
|
|
37
|
+
registry: arguments_.registry,
|
|
38
|
+
cwd: arguments_.cwd,
|
|
39
|
+
auth: arguments_.auth
|
|
38
40
|
});
|
|
39
41
|
console.log(`\u2728 Successfully cloned ${cyan(r.name || r.url)} to ${cyan(relative(process.cwd(), r.dir))}
|
|
40
42
|
`);
|
|
41
|
-
if (
|
|
43
|
+
if (arguments_.shell) {
|
|
42
44
|
startShell(r.dir);
|
|
43
45
|
}
|
|
44
46
|
process.exit(0);
|
|
45
47
|
}
|
|
46
|
-
main().catch((
|
|
47
|
-
console.error(
|
|
48
|
+
main().catch((error) => {
|
|
49
|
+
console.error(error);
|
|
48
50
|
process.exit(1);
|
|
49
51
|
});
|
package/dist/index.cjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const giget = require('./shared/giget.73314e21.cjs');
|
|
3
|
+
const giget = require('./shared/giget.1028e17a.cjs');
|
|
6
4
|
require('node:fs/promises');
|
|
7
5
|
require('node:os');
|
|
8
6
|
require('node:fs');
|
|
9
7
|
require('tar');
|
|
10
8
|
require('pathe');
|
|
11
9
|
require('defu');
|
|
12
|
-
require('node:stream
|
|
10
|
+
require('node:stream');
|
|
13
11
|
require('node:child_process');
|
|
12
|
+
require('node:util');
|
|
14
13
|
require('node-fetch-native');
|
|
14
|
+
require('https-proxy-agent');
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
interface GitInfo {
|
|
2
|
-
provider:
|
|
2
|
+
provider: "github" | "gitlab" | "bitbucket" | "sourcehut";
|
|
3
3
|
repo: string;
|
|
4
4
|
subdir: string;
|
|
5
5
|
ref: string;
|
|
@@ -32,11 +32,11 @@ interface DownloadTemplateOptions {
|
|
|
32
32
|
cwd?: string;
|
|
33
33
|
auth?: string;
|
|
34
34
|
}
|
|
35
|
-
declare type DownloadTemplateResult = Omit<TemplateInfo,
|
|
35
|
+
declare type DownloadTemplateResult = Omit<TemplateInfo, "dir" | "source"> & {
|
|
36
36
|
dir: string;
|
|
37
37
|
source: string;
|
|
38
38
|
};
|
|
39
|
-
declare function downloadTemplate(input: string,
|
|
39
|
+
declare function downloadTemplate(input: string, options?: DownloadTemplateOptions): Promise<DownloadTemplateResult>;
|
|
40
40
|
|
|
41
41
|
declare const registryProvider: (registryEndpoint?: string) => TemplateProvider;
|
|
42
42
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
export { d as downloadTemplate, r as registryProvider, s as startShell } from './shared/giget.
|
|
1
|
+
export { d as downloadTemplate, r as registryProvider, s as startShell } from './shared/giget.5e7ec864.mjs';
|
|
2
2
|
import 'node:fs/promises';
|
|
3
3
|
import 'node:os';
|
|
4
4
|
import 'node:fs';
|
|
5
5
|
import 'tar';
|
|
6
6
|
import 'pathe';
|
|
7
7
|
import 'defu';
|
|
8
|
-
import 'node:stream
|
|
8
|
+
import 'node:stream';
|
|
9
9
|
import 'node:child_process';
|
|
10
|
+
import 'node:util';
|
|
10
11
|
import 'node-fetch-native';
|
|
12
|
+
import 'https-proxy-agent';
|
|
@@ -6,29 +6,27 @@ const node_fs = require('node:fs');
|
|
|
6
6
|
const tar = require('tar');
|
|
7
7
|
const pathe = require('pathe');
|
|
8
8
|
const defu = require('defu');
|
|
9
|
-
const
|
|
9
|
+
const node_stream = require('node:stream');
|
|
10
10
|
const node_child_process = require('node:child_process');
|
|
11
|
-
const
|
|
11
|
+
const node_util = require('node:util');
|
|
12
|
+
const nodeFetchNative = require('node-fetch-native');
|
|
13
|
+
const createHttpsProxyAgent = require('https-proxy-agent');
|
|
12
14
|
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
const fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
16
|
-
|
|
17
|
-
async function download(url, filePath, opts = {}) {
|
|
15
|
+
async function download(url, filePath, options = {}) {
|
|
18
16
|
const infoPath = filePath + ".json";
|
|
19
17
|
const info = JSON.parse(await promises.readFile(infoPath, "utf8").catch(() => "{}"));
|
|
20
|
-
const
|
|
21
|
-
const etag =
|
|
18
|
+
const headResponse = await sendFetch(url, { method: "HEAD", headers: options.headers }).catch(() => void 0);
|
|
19
|
+
const etag = headResponse?.headers.get("etag");
|
|
22
20
|
if (info.etag === etag && node_fs.existsSync(filePath)) {
|
|
23
21
|
return;
|
|
24
22
|
}
|
|
25
23
|
info.etag = etag;
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
throw new Error(`Failed to download ${url}: ${
|
|
24
|
+
const response = await sendFetch(url, { headers: options.headers });
|
|
25
|
+
if (response.status >= 400) {
|
|
26
|
+
throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
|
|
29
27
|
}
|
|
30
28
|
const stream = node_fs.createWriteStream(filePath);
|
|
31
|
-
await
|
|
29
|
+
await node_util.promisify(node_stream.pipeline)(response.body, stream);
|
|
32
30
|
await promises.writeFile(infoPath, JSON.stringify(info), "utf8");
|
|
33
31
|
}
|
|
34
32
|
const inputRegex = /^(?<repo>[\w.-]+\/[\w.-]+)(?<subdir>[^#]+)?(?<ref>#[\w.-]+)?/;
|
|
@@ -37,14 +35,19 @@ function parseGitURI(input) {
|
|
|
37
35
|
return {
|
|
38
36
|
repo: m.repo,
|
|
39
37
|
subdir: m.subdir || "/",
|
|
40
|
-
ref: m.ref ? m.ref.
|
|
38
|
+
ref: m.ref ? m.ref.slice(1) : "main"
|
|
41
39
|
};
|
|
42
40
|
}
|
|
43
|
-
function debug(...
|
|
41
|
+
function debug(...arguments_) {
|
|
44
42
|
if (process.env.DEBUG) {
|
|
45
|
-
console.debug("[giget]", ...
|
|
43
|
+
console.debug("[giget]", ...arguments_);
|
|
46
44
|
}
|
|
47
45
|
}
|
|
46
|
+
async function sendFetch(url, options) {
|
|
47
|
+
const proxy = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
|
|
48
|
+
const requestOptions = proxy ? { agent: createHttpsProxyAgent(proxy), ...options } : options;
|
|
49
|
+
return await nodeFetchNative.fetch(url, requestOptions);
|
|
50
|
+
}
|
|
48
51
|
function currentShell() {
|
|
49
52
|
if (process.env.SHELL) {
|
|
50
53
|
return process.env.SHELL;
|
|
@@ -65,46 +68,46 @@ function startShell(cwd) {
|
|
|
65
68
|
});
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
const github = (input,
|
|
71
|
+
const github = (input, options) => {
|
|
69
72
|
const parsed = parseGitURI(input);
|
|
70
73
|
return {
|
|
71
74
|
name: parsed.repo.replace("/", "-"),
|
|
72
75
|
version: parsed.ref,
|
|
73
76
|
subdir: parsed.subdir,
|
|
74
|
-
headers: { Authorization:
|
|
77
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
75
78
|
url: `https://github.com/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
76
79
|
tar: `https://github.com/${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
77
80
|
};
|
|
78
81
|
};
|
|
79
|
-
const gitlab = (input,
|
|
82
|
+
const gitlab = (input, options) => {
|
|
80
83
|
const parsed = parseGitURI(input);
|
|
81
84
|
return {
|
|
82
85
|
name: parsed.repo.replace("/", "-"),
|
|
83
86
|
version: parsed.ref,
|
|
84
87
|
subdir: parsed.subdir,
|
|
85
|
-
headers: { Authorization:
|
|
88
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
86
89
|
url: `https://gitlab.com/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
87
90
|
tar: `https://gitlab.com/${parsed.repo}/-/archive/${parsed.ref}.tar.gz`
|
|
88
91
|
};
|
|
89
92
|
};
|
|
90
|
-
const bitbucket = (input,
|
|
93
|
+
const bitbucket = (input, options) => {
|
|
91
94
|
const parsed = parseGitURI(input);
|
|
92
95
|
return {
|
|
93
96
|
name: parsed.repo.replace("/", "-"),
|
|
94
97
|
version: parsed.ref,
|
|
95
98
|
subdir: parsed.subdir,
|
|
96
|
-
headers: { Authorization:
|
|
99
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
97
100
|
url: `https://bitbucket.com/${parsed.repo}/src/${parsed.ref}${parsed.subdir}`,
|
|
98
101
|
tar: `https://bitbucket.org/${parsed.repo}/get/${parsed.ref}.tar.gz`
|
|
99
102
|
};
|
|
100
103
|
};
|
|
101
|
-
const sourcehut = (input,
|
|
104
|
+
const sourcehut = (input, options) => {
|
|
102
105
|
const parsed = parseGitURI(input);
|
|
103
106
|
return {
|
|
104
107
|
name: parsed.repo.replace("/", "-"),
|
|
105
108
|
version: parsed.ref,
|
|
106
109
|
subdir: parsed.subdir,
|
|
107
|
-
headers: { Authorization:
|
|
110
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
108
111
|
url: `https://git.sr.ht/~${parsed.repo}/tree/${parsed.ref}/item${parsed.subdir}`,
|
|
109
112
|
tar: `https://git.sr.ht/~${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
110
113
|
};
|
|
@@ -122,11 +125,11 @@ const registryProvider = (registryEndpoint = DEFAULT_REGISTRY) => {
|
|
|
122
125
|
return async (input) => {
|
|
123
126
|
const start = Date.now();
|
|
124
127
|
const registryURL = `${registryEndpoint}/${input}.json`;
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
127
|
-
throw new Error(`Failed to download ${input} template info from ${registryURL}: ${
|
|
128
|
+
const result = await sendFetch(registryURL);
|
|
129
|
+
if (result.status >= 400) {
|
|
130
|
+
throw new Error(`Failed to download ${input} template info from ${registryURL}: ${result.status} ${result.statusText}`);
|
|
128
131
|
}
|
|
129
|
-
const info = await
|
|
132
|
+
const info = await result.json();
|
|
130
133
|
if (!info.tar || !info.name) {
|
|
131
134
|
throw new Error(`Invalid template info from ${registryURL}. name or tar fields are missing!`);
|
|
132
135
|
}
|
|
@@ -136,56 +139,56 @@ const registryProvider = (registryEndpoint = DEFAULT_REGISTRY) => {
|
|
|
136
139
|
};
|
|
137
140
|
|
|
138
141
|
const sourceProtoRe = /^([\w-.]+):/;
|
|
139
|
-
async function downloadTemplate(input,
|
|
140
|
-
|
|
142
|
+
async function downloadTemplate(input, options = {}) {
|
|
143
|
+
options = defu.defu({
|
|
141
144
|
registry: process.env.GIGET_REGISTRY,
|
|
142
145
|
auth: process.env.GIGET_AUTH
|
|
143
|
-
},
|
|
144
|
-
const registry =
|
|
145
|
-
let providerName =
|
|
146
|
+
}, options);
|
|
147
|
+
const registry = options.registry !== false ? registryProvider(options.registry) : void 0;
|
|
148
|
+
let providerName = options.provider || (registryProvider ? "registry" : "github");
|
|
146
149
|
let source = input;
|
|
147
150
|
const sourceProvierMatch = input.match(sourceProtoRe);
|
|
148
151
|
if (sourceProvierMatch) {
|
|
149
152
|
providerName = sourceProvierMatch[1];
|
|
150
|
-
source = input.
|
|
153
|
+
source = input.slice(sourceProvierMatch[0].length);
|
|
151
154
|
}
|
|
152
|
-
const provider =
|
|
155
|
+
const provider = options.providers?.[providerName] || providers[providerName] || registry;
|
|
153
156
|
if (!provider) {
|
|
154
157
|
throw new Error(`Unsupported provider: ${providerName}`);
|
|
155
158
|
}
|
|
156
|
-
const template = await Promise.resolve().then(() => provider(source, { auth:
|
|
157
|
-
throw new Error(`Failed to download template from ${providerName}: ${
|
|
159
|
+
const template = await Promise.resolve().then(() => provider(source, { auth: options.auth })).catch((error) => {
|
|
160
|
+
throw new Error(`Failed to download template from ${providerName}: ${error.message}`);
|
|
158
161
|
});
|
|
159
|
-
template.name = (template.name || "template").replace(/[
|
|
160
|
-
template.defaultDir = (template.defaultDir || template.name).replace(/[
|
|
161
|
-
const cwd = pathe.resolve(
|
|
162
|
-
const extractPath = pathe.resolve(cwd,
|
|
163
|
-
if (
|
|
162
|
+
template.name = (template.name || "template").replace(/[^\da-z-]/gi, "-");
|
|
163
|
+
template.defaultDir = (template.defaultDir || template.name).replace(/[^\da-z-]/gi, "-");
|
|
164
|
+
const cwd = pathe.resolve(options.cwd || ".");
|
|
165
|
+
const extractPath = pathe.resolve(cwd, options.dir || template.defaultDir);
|
|
166
|
+
if (options.forceClean) {
|
|
164
167
|
await promises.rm(extractPath, { recursive: true, force: true });
|
|
165
168
|
}
|
|
166
|
-
if (!
|
|
169
|
+
if (!options.force && node_fs.existsSync(extractPath) && node_fs.readdirSync(extractPath).length > 0) {
|
|
167
170
|
throw new Error(`Destination ${extractPath} already exists.`);
|
|
168
171
|
}
|
|
169
172
|
await promises.mkdir(extractPath, { recursive: true });
|
|
170
|
-
const
|
|
171
|
-
const tarPath = pathe.resolve(
|
|
172
|
-
if (
|
|
173
|
-
|
|
173
|
+
const temporaryDirectory = pathe.resolve(node_os.homedir(), ".giget", options.provider, template.name);
|
|
174
|
+
const tarPath = pathe.resolve(temporaryDirectory, (template.version || template.name) + ".tar.gz");
|
|
175
|
+
if (options.preferOffline && node_fs.existsSync(tarPath)) {
|
|
176
|
+
options.offline = true;
|
|
174
177
|
}
|
|
175
|
-
if (!
|
|
178
|
+
if (!options.offline) {
|
|
176
179
|
await promises.mkdir(pathe.dirname(tarPath), { recursive: true });
|
|
177
180
|
const s2 = Date.now();
|
|
178
|
-
await download(template.tar, tarPath, { headers: template.headers }).catch((
|
|
181
|
+
await download(template.tar, tarPath, { headers: template.headers }).catch((error) => {
|
|
179
182
|
if (!node_fs.existsSync(tarPath)) {
|
|
180
|
-
throw
|
|
183
|
+
throw error;
|
|
181
184
|
}
|
|
182
|
-
debug("Download error. Using cached version:",
|
|
183
|
-
|
|
185
|
+
debug("Download error. Using cached version:", error);
|
|
186
|
+
options.offline = true;
|
|
184
187
|
});
|
|
185
188
|
debug(`Downloaded ${template.tar} to ${tarPath} in ${Date.now() - s2}ms`);
|
|
186
189
|
}
|
|
187
190
|
if (!node_fs.existsSync(tarPath)) {
|
|
188
|
-
throw new Error(`Tarball not found: ${tarPath} (offline: ${
|
|
191
|
+
throw new Error(`Tarball not found: ${tarPath} (offline: ${options.offline})`);
|
|
189
192
|
}
|
|
190
193
|
const s = Date.now();
|
|
191
194
|
const subdir = template.subdir?.replace(/^\//, "") || "";
|
|
@@ -196,7 +199,7 @@ async function downloadTemplate(input, opts = {}) {
|
|
|
196
199
|
entry.path = entry.path.split("/").splice(1).join("/");
|
|
197
200
|
if (subdir) {
|
|
198
201
|
if (entry.path.startsWith(subdir + "/")) {
|
|
199
|
-
entry.path = entry.path.
|
|
202
|
+
entry.path = entry.path.slice(subdir.length);
|
|
200
203
|
} else {
|
|
201
204
|
entry.path = "";
|
|
202
205
|
}
|
|
@@ -4,25 +4,27 @@ import { existsSync, createWriteStream, readdirSync } from 'node:fs';
|
|
|
4
4
|
import { extract } from 'tar';
|
|
5
5
|
import { resolve, relative, dirname } from 'pathe';
|
|
6
6
|
import { defu } from 'defu';
|
|
7
|
-
import { pipeline } from 'node:stream
|
|
7
|
+
import { pipeline } from 'node:stream';
|
|
8
8
|
import { spawnSync } from 'node:child_process';
|
|
9
|
-
import
|
|
9
|
+
import { promisify } from 'node:util';
|
|
10
|
+
import { fetch } from 'node-fetch-native';
|
|
11
|
+
import createHttpsProxyAgent from 'https-proxy-agent';
|
|
10
12
|
|
|
11
|
-
async function download(url, filePath,
|
|
13
|
+
async function download(url, filePath, options = {}) {
|
|
12
14
|
const infoPath = filePath + ".json";
|
|
13
15
|
const info = JSON.parse(await readFile(infoPath, "utf8").catch(() => "{}"));
|
|
14
|
-
const
|
|
15
|
-
const etag =
|
|
16
|
+
const headResponse = await sendFetch(url, { method: "HEAD", headers: options.headers }).catch(() => void 0);
|
|
17
|
+
const etag = headResponse?.headers.get("etag");
|
|
16
18
|
if (info.etag === etag && existsSync(filePath)) {
|
|
17
19
|
return;
|
|
18
20
|
}
|
|
19
21
|
info.etag = etag;
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
throw new Error(`Failed to download ${url}: ${
|
|
22
|
+
const response = await sendFetch(url, { headers: options.headers });
|
|
23
|
+
if (response.status >= 400) {
|
|
24
|
+
throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
|
|
23
25
|
}
|
|
24
26
|
const stream = createWriteStream(filePath);
|
|
25
|
-
await pipeline(
|
|
27
|
+
await promisify(pipeline)(response.body, stream);
|
|
26
28
|
await writeFile(infoPath, JSON.stringify(info), "utf8");
|
|
27
29
|
}
|
|
28
30
|
const inputRegex = /^(?<repo>[\w.-]+\/[\w.-]+)(?<subdir>[^#]+)?(?<ref>#[\w.-]+)?/;
|
|
@@ -31,14 +33,19 @@ function parseGitURI(input) {
|
|
|
31
33
|
return {
|
|
32
34
|
repo: m.repo,
|
|
33
35
|
subdir: m.subdir || "/",
|
|
34
|
-
ref: m.ref ? m.ref.
|
|
36
|
+
ref: m.ref ? m.ref.slice(1) : "main"
|
|
35
37
|
};
|
|
36
38
|
}
|
|
37
|
-
function debug(...
|
|
39
|
+
function debug(...arguments_) {
|
|
38
40
|
if (process.env.DEBUG) {
|
|
39
|
-
console.debug("[giget]", ...
|
|
41
|
+
console.debug("[giget]", ...arguments_);
|
|
40
42
|
}
|
|
41
43
|
}
|
|
44
|
+
async function sendFetch(url, options) {
|
|
45
|
+
const proxy = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
|
|
46
|
+
const requestOptions = proxy ? { agent: createHttpsProxyAgent(proxy), ...options } : options;
|
|
47
|
+
return await fetch(url, requestOptions);
|
|
48
|
+
}
|
|
42
49
|
function currentShell() {
|
|
43
50
|
if (process.env.SHELL) {
|
|
44
51
|
return process.env.SHELL;
|
|
@@ -59,46 +66,46 @@ function startShell(cwd) {
|
|
|
59
66
|
});
|
|
60
67
|
}
|
|
61
68
|
|
|
62
|
-
const github = (input,
|
|
69
|
+
const github = (input, options) => {
|
|
63
70
|
const parsed = parseGitURI(input);
|
|
64
71
|
return {
|
|
65
72
|
name: parsed.repo.replace("/", "-"),
|
|
66
73
|
version: parsed.ref,
|
|
67
74
|
subdir: parsed.subdir,
|
|
68
|
-
headers: { Authorization:
|
|
75
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
69
76
|
url: `https://github.com/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
70
77
|
tar: `https://github.com/${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
71
78
|
};
|
|
72
79
|
};
|
|
73
|
-
const gitlab = (input,
|
|
80
|
+
const gitlab = (input, options) => {
|
|
74
81
|
const parsed = parseGitURI(input);
|
|
75
82
|
return {
|
|
76
83
|
name: parsed.repo.replace("/", "-"),
|
|
77
84
|
version: parsed.ref,
|
|
78
85
|
subdir: parsed.subdir,
|
|
79
|
-
headers: { Authorization:
|
|
86
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
80
87
|
url: `https://gitlab.com/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
81
88
|
tar: `https://gitlab.com/${parsed.repo}/-/archive/${parsed.ref}.tar.gz`
|
|
82
89
|
};
|
|
83
90
|
};
|
|
84
|
-
const bitbucket = (input,
|
|
91
|
+
const bitbucket = (input, options) => {
|
|
85
92
|
const parsed = parseGitURI(input);
|
|
86
93
|
return {
|
|
87
94
|
name: parsed.repo.replace("/", "-"),
|
|
88
95
|
version: parsed.ref,
|
|
89
96
|
subdir: parsed.subdir,
|
|
90
|
-
headers: { Authorization:
|
|
97
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
91
98
|
url: `https://bitbucket.com/${parsed.repo}/src/${parsed.ref}${parsed.subdir}`,
|
|
92
99
|
tar: `https://bitbucket.org/${parsed.repo}/get/${parsed.ref}.tar.gz`
|
|
93
100
|
};
|
|
94
101
|
};
|
|
95
|
-
const sourcehut = (input,
|
|
102
|
+
const sourcehut = (input, options) => {
|
|
96
103
|
const parsed = parseGitURI(input);
|
|
97
104
|
return {
|
|
98
105
|
name: parsed.repo.replace("/", "-"),
|
|
99
106
|
version: parsed.ref,
|
|
100
107
|
subdir: parsed.subdir,
|
|
101
|
-
headers: { Authorization:
|
|
108
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
102
109
|
url: `https://git.sr.ht/~${parsed.repo}/tree/${parsed.ref}/item${parsed.subdir}`,
|
|
103
110
|
tar: `https://git.sr.ht/~${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
104
111
|
};
|
|
@@ -116,11 +123,11 @@ const registryProvider = (registryEndpoint = DEFAULT_REGISTRY) => {
|
|
|
116
123
|
return async (input) => {
|
|
117
124
|
const start = Date.now();
|
|
118
125
|
const registryURL = `${registryEndpoint}/${input}.json`;
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
121
|
-
throw new Error(`Failed to download ${input} template info from ${registryURL}: ${
|
|
126
|
+
const result = await sendFetch(registryURL);
|
|
127
|
+
if (result.status >= 400) {
|
|
128
|
+
throw new Error(`Failed to download ${input} template info from ${registryURL}: ${result.status} ${result.statusText}`);
|
|
122
129
|
}
|
|
123
|
-
const info = await
|
|
130
|
+
const info = await result.json();
|
|
124
131
|
if (!info.tar || !info.name) {
|
|
125
132
|
throw new Error(`Invalid template info from ${registryURL}. name or tar fields are missing!`);
|
|
126
133
|
}
|
|
@@ -130,56 +137,56 @@ const registryProvider = (registryEndpoint = DEFAULT_REGISTRY) => {
|
|
|
130
137
|
};
|
|
131
138
|
|
|
132
139
|
const sourceProtoRe = /^([\w-.]+):/;
|
|
133
|
-
async function downloadTemplate(input,
|
|
134
|
-
|
|
140
|
+
async function downloadTemplate(input, options = {}) {
|
|
141
|
+
options = defu({
|
|
135
142
|
registry: process.env.GIGET_REGISTRY,
|
|
136
143
|
auth: process.env.GIGET_AUTH
|
|
137
|
-
},
|
|
138
|
-
const registry =
|
|
139
|
-
let providerName =
|
|
144
|
+
}, options);
|
|
145
|
+
const registry = options.registry !== false ? registryProvider(options.registry) : void 0;
|
|
146
|
+
let providerName = options.provider || (registryProvider ? "registry" : "github");
|
|
140
147
|
let source = input;
|
|
141
148
|
const sourceProvierMatch = input.match(sourceProtoRe);
|
|
142
149
|
if (sourceProvierMatch) {
|
|
143
150
|
providerName = sourceProvierMatch[1];
|
|
144
|
-
source = input.
|
|
151
|
+
source = input.slice(sourceProvierMatch[0].length);
|
|
145
152
|
}
|
|
146
|
-
const provider =
|
|
153
|
+
const provider = options.providers?.[providerName] || providers[providerName] || registry;
|
|
147
154
|
if (!provider) {
|
|
148
155
|
throw new Error(`Unsupported provider: ${providerName}`);
|
|
149
156
|
}
|
|
150
|
-
const template = await Promise.resolve().then(() => provider(source, { auth:
|
|
151
|
-
throw new Error(`Failed to download template from ${providerName}: ${
|
|
157
|
+
const template = await Promise.resolve().then(() => provider(source, { auth: options.auth })).catch((error) => {
|
|
158
|
+
throw new Error(`Failed to download template from ${providerName}: ${error.message}`);
|
|
152
159
|
});
|
|
153
|
-
template.name = (template.name || "template").replace(/[
|
|
154
|
-
template.defaultDir = (template.defaultDir || template.name).replace(/[
|
|
155
|
-
const cwd = resolve(
|
|
156
|
-
const extractPath = resolve(cwd,
|
|
157
|
-
if (
|
|
160
|
+
template.name = (template.name || "template").replace(/[^\da-z-]/gi, "-");
|
|
161
|
+
template.defaultDir = (template.defaultDir || template.name).replace(/[^\da-z-]/gi, "-");
|
|
162
|
+
const cwd = resolve(options.cwd || ".");
|
|
163
|
+
const extractPath = resolve(cwd, options.dir || template.defaultDir);
|
|
164
|
+
if (options.forceClean) {
|
|
158
165
|
await rm(extractPath, { recursive: true, force: true });
|
|
159
166
|
}
|
|
160
|
-
if (!
|
|
167
|
+
if (!options.force && existsSync(extractPath) && readdirSync(extractPath).length > 0) {
|
|
161
168
|
throw new Error(`Destination ${extractPath} already exists.`);
|
|
162
169
|
}
|
|
163
170
|
await mkdir(extractPath, { recursive: true });
|
|
164
|
-
const
|
|
165
|
-
const tarPath = resolve(
|
|
166
|
-
if (
|
|
167
|
-
|
|
171
|
+
const temporaryDirectory = resolve(homedir(), ".giget", options.provider, template.name);
|
|
172
|
+
const tarPath = resolve(temporaryDirectory, (template.version || template.name) + ".tar.gz");
|
|
173
|
+
if (options.preferOffline && existsSync(tarPath)) {
|
|
174
|
+
options.offline = true;
|
|
168
175
|
}
|
|
169
|
-
if (!
|
|
176
|
+
if (!options.offline) {
|
|
170
177
|
await mkdir(dirname(tarPath), { recursive: true });
|
|
171
178
|
const s2 = Date.now();
|
|
172
|
-
await download(template.tar, tarPath, { headers: template.headers }).catch((
|
|
179
|
+
await download(template.tar, tarPath, { headers: template.headers }).catch((error) => {
|
|
173
180
|
if (!existsSync(tarPath)) {
|
|
174
|
-
throw
|
|
181
|
+
throw error;
|
|
175
182
|
}
|
|
176
|
-
debug("Download error. Using cached version:",
|
|
177
|
-
|
|
183
|
+
debug("Download error. Using cached version:", error);
|
|
184
|
+
options.offline = true;
|
|
178
185
|
});
|
|
179
186
|
debug(`Downloaded ${template.tar} to ${tarPath} in ${Date.now() - s2}ms`);
|
|
180
187
|
}
|
|
181
188
|
if (!existsSync(tarPath)) {
|
|
182
|
-
throw new Error(`Tarball not found: ${tarPath} (offline: ${
|
|
189
|
+
throw new Error(`Tarball not found: ${tarPath} (offline: ${options.offline})`);
|
|
183
190
|
}
|
|
184
191
|
const s = Date.now();
|
|
185
192
|
const subdir = template.subdir?.replace(/^\//, "") || "";
|
|
@@ -190,7 +197,7 @@ async function downloadTemplate(input, opts = {}) {
|
|
|
190
197
|
entry.path = entry.path.split("/").splice(1).join("/");
|
|
191
198
|
if (subdir) {
|
|
192
199
|
if (entry.path.startsWith(subdir + "/")) {
|
|
193
|
-
entry.path = entry.path.
|
|
200
|
+
entry.path = entry.path.slice(subdir.length);
|
|
194
201
|
} else {
|
|
195
202
|
entry.path = "";
|
|
196
203
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "giget",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Download templates and git repositories with pleasure!",
|
|
5
5
|
"repository": "unjs/giget",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
14
|
"import": "./dist/index.mjs",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
15
16
|
"require": "./dist/index.cjs"
|
|
16
17
|
}
|
|
17
18
|
},
|
|
@@ -21,34 +22,36 @@
|
|
|
21
22
|
"files": [
|
|
22
23
|
"dist"
|
|
23
24
|
],
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"colorette": "^2.0.19",
|
|
26
|
-
"defu": "^6.1.0",
|
|
27
|
-
"mri": "^1.2.0",
|
|
28
|
-
"node-fetch-native": "^0.1.4",
|
|
29
|
-
"pathe": "^0.3.7",
|
|
30
|
-
"tar": "^6.1.11"
|
|
31
|
-
},
|
|
32
|
-
"devDependencies": {
|
|
33
|
-
"@nuxtjs/eslint-config-typescript": "^11.0.0",
|
|
34
|
-
"@types/node": "^18.7.16",
|
|
35
|
-
"@types/tar": "^6.1.2",
|
|
36
|
-
"@vitest/coverage-c8": "^0.23.2",
|
|
37
|
-
"eslint": "^8.23.1",
|
|
38
|
-
"jiti": "^1.15.0",
|
|
39
|
-
"standard-version": "^9.5.0",
|
|
40
|
-
"typescript": "^4.8.3",
|
|
41
|
-
"unbuild": "^0.8.11",
|
|
42
|
-
"vitest": "^0.23.2"
|
|
43
|
-
},
|
|
44
|
-
"packageManager": "pnpm@7.11.0",
|
|
45
25
|
"scripts": {
|
|
46
26
|
"build": "unbuild",
|
|
47
27
|
"dev": "vitest dev",
|
|
48
28
|
"giget": "jiti ./src/cli.ts",
|
|
49
29
|
"lint": "eslint --ext .ts,.js,.mjs,.cjs .",
|
|
30
|
+
"prepack": "unbuild",
|
|
50
31
|
"play": "pnpm giget --force-clean --verbose unjs .tmp/clone",
|
|
51
32
|
"release": "pnpm test && standard-version && git push --follow-tags && pnpm publish",
|
|
52
33
|
"test": "pnpm lint && vitest run --coverage"
|
|
53
|
-
}
|
|
54
|
-
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"colorette": "^2.0.19",
|
|
37
|
+
"defu": "^6.1.1",
|
|
38
|
+
"https-proxy-agent": "^5.0.1",
|
|
39
|
+
"mri": "^1.2.0",
|
|
40
|
+
"node-fetch-native": "^1.0.1",
|
|
41
|
+
"pathe": "^1.0.0",
|
|
42
|
+
"tar": "^6.1.12"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^18.11.9",
|
|
46
|
+
"@types/tar": "^6.1.3",
|
|
47
|
+
"@vitest/coverage-c8": "^0.25.2",
|
|
48
|
+
"eslint": "^8.27.0",
|
|
49
|
+
"eslint-config-unjs": "^0.0.2",
|
|
50
|
+
"jiti": "^1.16.0",
|
|
51
|
+
"standard-version": "^9.5.0",
|
|
52
|
+
"typescript": "^4.8.4",
|
|
53
|
+
"unbuild": "^0.9.4",
|
|
54
|
+
"vitest": "^0.25.2"
|
|
55
|
+
},
|
|
56
|
+
"packageManager": "pnpm@7.16.0"
|
|
57
|
+
}
|