giget 0.1.7 → 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 +16 -19
- package/dist/cli.mjs +16 -15
- package/dist/index.cjs +2 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +2 -1
- package/dist/shared/{giget.3522139c.cjs → giget.1028e17a.cjs} +55 -53
- package/dist/shared/{giget.dd19862e.mjs → giget.5e7ec864.mjs} +55 -49
- 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,7 +4,7 @@
|
|
|
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');
|
|
@@ -15,42 +15,39 @@ require('node:stream');
|
|
|
15
15
|
require('node:child_process');
|
|
16
16
|
require('node:util');
|
|
17
17
|
require('node-fetch-native');
|
|
18
|
-
|
|
19
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
|
|
20
|
-
|
|
21
|
-
const mri__default = /*#__PURE__*/_interopDefaultLegacy(mri);
|
|
18
|
+
require('https-proxy-agent');
|
|
22
19
|
|
|
23
20
|
async function main() {
|
|
24
|
-
const
|
|
21
|
+
const arguments_ = mri(process.argv.slice(2), {
|
|
25
22
|
boolean: ["help", "force", "force-clean", "offline", "prefer-offline", "shell", "verbose"],
|
|
26
23
|
string: ["registry", "cwd", "auth"]
|
|
27
24
|
});
|
|
28
|
-
const input =
|
|
29
|
-
const dir =
|
|
30
|
-
if (!input ||
|
|
25
|
+
const input = arguments_._[0];
|
|
26
|
+
const dir = arguments_._[1];
|
|
27
|
+
if (!input || arguments_.help || arguments_.h) {
|
|
31
28
|
console.error("Usage: npx getgit@latest <input> [<dir>] [--force] [--force-clean] [--offline] [--prefer-offline] [--shell] [--registry] [--no-registry] [--verbose] [--cwd] [--auth]");
|
|
32
29
|
process.exit(1);
|
|
33
30
|
}
|
|
34
|
-
if (
|
|
31
|
+
if (arguments_.verbose) {
|
|
35
32
|
process.env.DEBUG = process.env.DEBUG || "true";
|
|
36
33
|
}
|
|
37
34
|
const r = await giget.downloadTemplate(input, {
|
|
38
35
|
dir,
|
|
39
|
-
force:
|
|
40
|
-
forceClean:
|
|
41
|
-
offline:
|
|
42
|
-
registry:
|
|
43
|
-
cwd:
|
|
44
|
-
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
|
|
45
42
|
});
|
|
46
43
|
console.log(`\u2728 Successfully cloned ${colorette.cyan(r.name || r.url)} to ${colorette.cyan(node_path.relative(process.cwd(), r.dir))}
|
|
47
44
|
`);
|
|
48
|
-
if (
|
|
45
|
+
if (arguments_.shell) {
|
|
49
46
|
giget.startShell(r.dir);
|
|
50
47
|
}
|
|
51
48
|
process.exit(0);
|
|
52
49
|
}
|
|
53
|
-
main().catch((
|
|
54
|
-
console.error(
|
|
50
|
+
main().catch((error) => {
|
|
51
|
+
console.error(error);
|
|
55
52
|
process.exit(1);
|
|
56
53
|
});
|
package/dist/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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';
|
|
@@ -13,38 +13,39 @@ import 'node:stream';
|
|
|
13
13
|
import 'node:child_process';
|
|
14
14
|
import 'node:util';
|
|
15
15
|
import 'node-fetch-native';
|
|
16
|
+
import 'https-proxy-agent';
|
|
16
17
|
|
|
17
18
|
async function main() {
|
|
18
|
-
const
|
|
19
|
+
const arguments_ = mri(process.argv.slice(2), {
|
|
19
20
|
boolean: ["help", "force", "force-clean", "offline", "prefer-offline", "shell", "verbose"],
|
|
20
21
|
string: ["registry", "cwd", "auth"]
|
|
21
22
|
});
|
|
22
|
-
const input =
|
|
23
|
-
const dir =
|
|
24
|
-
if (!input ||
|
|
23
|
+
const input = arguments_._[0];
|
|
24
|
+
const dir = arguments_._[1];
|
|
25
|
+
if (!input || arguments_.help || arguments_.h) {
|
|
25
26
|
console.error("Usage: npx getgit@latest <input> [<dir>] [--force] [--force-clean] [--offline] [--prefer-offline] [--shell] [--registry] [--no-registry] [--verbose] [--cwd] [--auth]");
|
|
26
27
|
process.exit(1);
|
|
27
28
|
}
|
|
28
|
-
if (
|
|
29
|
+
if (arguments_.verbose) {
|
|
29
30
|
process.env.DEBUG = process.env.DEBUG || "true";
|
|
30
31
|
}
|
|
31
32
|
const r = await downloadTemplate(input, {
|
|
32
33
|
dir,
|
|
33
|
-
force:
|
|
34
|
-
forceClean:
|
|
35
|
-
offline:
|
|
36
|
-
registry:
|
|
37
|
-
cwd:
|
|
38
|
-
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
|
|
39
40
|
});
|
|
40
41
|
console.log(`\u2728 Successfully cloned ${cyan(r.name || r.url)} to ${cyan(relative(process.cwd(), r.dir))}
|
|
41
42
|
`);
|
|
42
|
-
if (
|
|
43
|
+
if (arguments_.shell) {
|
|
43
44
|
startShell(r.dir);
|
|
44
45
|
}
|
|
45
46
|
process.exit(0);
|
|
46
47
|
}
|
|
47
|
-
main().catch((
|
|
48
|
-
console.error(
|
|
48
|
+
main().catch((error) => {
|
|
49
|
+
console.error(error);
|
|
49
50
|
process.exit(1);
|
|
50
51
|
});
|
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const giget = require('./shared/giget.3522139c.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');
|
|
@@ -13,6 +11,7 @@ require('node:stream');
|
|
|
13
11
|
require('node:child_process');
|
|
14
12
|
require('node:util');
|
|
15
13
|
require('node-fetch-native');
|
|
14
|
+
require('https-proxy-agent');
|
|
16
15
|
|
|
17
16
|
|
|
18
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,4 +1,4 @@
|
|
|
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';
|
|
@@ -9,3 +9,4 @@ import 'node:stream';
|
|
|
9
9
|
import 'node:child_process';
|
|
10
10
|
import 'node:util';
|
|
11
11
|
import 'node-fetch-native';
|
|
12
|
+
import 'https-proxy-agent';
|
|
@@ -9,27 +9,24 @@ const defu = require('defu');
|
|
|
9
9
|
const node_stream = require('node:stream');
|
|
10
10
|
const node_child_process = require('node:child_process');
|
|
11
11
|
const node_util = require('node:util');
|
|
12
|
-
const
|
|
12
|
+
const nodeFetchNative = require('node-fetch-native');
|
|
13
|
+
const createHttpsProxyAgent = require('https-proxy-agent');
|
|
13
14
|
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
const fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
17
|
-
|
|
18
|
-
async function download(url, filePath, opts = {}) {
|
|
15
|
+
async function download(url, filePath, options = {}) {
|
|
19
16
|
const infoPath = filePath + ".json";
|
|
20
17
|
const info = JSON.parse(await promises.readFile(infoPath, "utf8").catch(() => "{}"));
|
|
21
|
-
const
|
|
22
|
-
const etag =
|
|
18
|
+
const headResponse = await sendFetch(url, { method: "HEAD", headers: options.headers }).catch(() => void 0);
|
|
19
|
+
const etag = headResponse?.headers.get("etag");
|
|
23
20
|
if (info.etag === etag && node_fs.existsSync(filePath)) {
|
|
24
21
|
return;
|
|
25
22
|
}
|
|
26
23
|
info.etag = etag;
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
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}`);
|
|
30
27
|
}
|
|
31
28
|
const stream = node_fs.createWriteStream(filePath);
|
|
32
|
-
await node_util.promisify(node_stream.pipeline)(
|
|
29
|
+
await node_util.promisify(node_stream.pipeline)(response.body, stream);
|
|
33
30
|
await promises.writeFile(infoPath, JSON.stringify(info), "utf8");
|
|
34
31
|
}
|
|
35
32
|
const inputRegex = /^(?<repo>[\w.-]+\/[\w.-]+)(?<subdir>[^#]+)?(?<ref>#[\w.-]+)?/;
|
|
@@ -38,14 +35,19 @@ function parseGitURI(input) {
|
|
|
38
35
|
return {
|
|
39
36
|
repo: m.repo,
|
|
40
37
|
subdir: m.subdir || "/",
|
|
41
|
-
ref: m.ref ? m.ref.
|
|
38
|
+
ref: m.ref ? m.ref.slice(1) : "main"
|
|
42
39
|
};
|
|
43
40
|
}
|
|
44
|
-
function debug(...
|
|
41
|
+
function debug(...arguments_) {
|
|
45
42
|
if (process.env.DEBUG) {
|
|
46
|
-
console.debug("[giget]", ...
|
|
43
|
+
console.debug("[giget]", ...arguments_);
|
|
47
44
|
}
|
|
48
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
|
+
}
|
|
49
51
|
function currentShell() {
|
|
50
52
|
if (process.env.SHELL) {
|
|
51
53
|
return process.env.SHELL;
|
|
@@ -66,46 +68,46 @@ function startShell(cwd) {
|
|
|
66
68
|
});
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
const github = (input,
|
|
71
|
+
const github = (input, options) => {
|
|
70
72
|
const parsed = parseGitURI(input);
|
|
71
73
|
return {
|
|
72
74
|
name: parsed.repo.replace("/", "-"),
|
|
73
75
|
version: parsed.ref,
|
|
74
76
|
subdir: parsed.subdir,
|
|
75
|
-
headers: { Authorization:
|
|
77
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
76
78
|
url: `https://github.com/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
77
79
|
tar: `https://github.com/${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
78
80
|
};
|
|
79
81
|
};
|
|
80
|
-
const gitlab = (input,
|
|
82
|
+
const gitlab = (input, options) => {
|
|
81
83
|
const parsed = parseGitURI(input);
|
|
82
84
|
return {
|
|
83
85
|
name: parsed.repo.replace("/", "-"),
|
|
84
86
|
version: parsed.ref,
|
|
85
87
|
subdir: parsed.subdir,
|
|
86
|
-
headers: { Authorization:
|
|
88
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
87
89
|
url: `https://gitlab.com/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
88
90
|
tar: `https://gitlab.com/${parsed.repo}/-/archive/${parsed.ref}.tar.gz`
|
|
89
91
|
};
|
|
90
92
|
};
|
|
91
|
-
const bitbucket = (input,
|
|
93
|
+
const bitbucket = (input, options) => {
|
|
92
94
|
const parsed = parseGitURI(input);
|
|
93
95
|
return {
|
|
94
96
|
name: parsed.repo.replace("/", "-"),
|
|
95
97
|
version: parsed.ref,
|
|
96
98
|
subdir: parsed.subdir,
|
|
97
|
-
headers: { Authorization:
|
|
99
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
98
100
|
url: `https://bitbucket.com/${parsed.repo}/src/${parsed.ref}${parsed.subdir}`,
|
|
99
101
|
tar: `https://bitbucket.org/${parsed.repo}/get/${parsed.ref}.tar.gz`
|
|
100
102
|
};
|
|
101
103
|
};
|
|
102
|
-
const sourcehut = (input,
|
|
104
|
+
const sourcehut = (input, options) => {
|
|
103
105
|
const parsed = parseGitURI(input);
|
|
104
106
|
return {
|
|
105
107
|
name: parsed.repo.replace("/", "-"),
|
|
106
108
|
version: parsed.ref,
|
|
107
109
|
subdir: parsed.subdir,
|
|
108
|
-
headers: { Authorization:
|
|
110
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
109
111
|
url: `https://git.sr.ht/~${parsed.repo}/tree/${parsed.ref}/item${parsed.subdir}`,
|
|
110
112
|
tar: `https://git.sr.ht/~${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
111
113
|
};
|
|
@@ -123,11 +125,11 @@ const registryProvider = (registryEndpoint = DEFAULT_REGISTRY) => {
|
|
|
123
125
|
return async (input) => {
|
|
124
126
|
const start = Date.now();
|
|
125
127
|
const registryURL = `${registryEndpoint}/${input}.json`;
|
|
126
|
-
const
|
|
127
|
-
if (
|
|
128
|
-
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}`);
|
|
129
131
|
}
|
|
130
|
-
const info = await
|
|
132
|
+
const info = await result.json();
|
|
131
133
|
if (!info.tar || !info.name) {
|
|
132
134
|
throw new Error(`Invalid template info from ${registryURL}. name or tar fields are missing!`);
|
|
133
135
|
}
|
|
@@ -137,56 +139,56 @@ const registryProvider = (registryEndpoint = DEFAULT_REGISTRY) => {
|
|
|
137
139
|
};
|
|
138
140
|
|
|
139
141
|
const sourceProtoRe = /^([\w-.]+):/;
|
|
140
|
-
async function downloadTemplate(input,
|
|
141
|
-
|
|
142
|
+
async function downloadTemplate(input, options = {}) {
|
|
143
|
+
options = defu.defu({
|
|
142
144
|
registry: process.env.GIGET_REGISTRY,
|
|
143
145
|
auth: process.env.GIGET_AUTH
|
|
144
|
-
},
|
|
145
|
-
const registry =
|
|
146
|
-
let providerName =
|
|
146
|
+
}, options);
|
|
147
|
+
const registry = options.registry !== false ? registryProvider(options.registry) : void 0;
|
|
148
|
+
let providerName = options.provider || (registryProvider ? "registry" : "github");
|
|
147
149
|
let source = input;
|
|
148
150
|
const sourceProvierMatch = input.match(sourceProtoRe);
|
|
149
151
|
if (sourceProvierMatch) {
|
|
150
152
|
providerName = sourceProvierMatch[1];
|
|
151
|
-
source = input.
|
|
153
|
+
source = input.slice(sourceProvierMatch[0].length);
|
|
152
154
|
}
|
|
153
|
-
const provider =
|
|
155
|
+
const provider = options.providers?.[providerName] || providers[providerName] || registry;
|
|
154
156
|
if (!provider) {
|
|
155
157
|
throw new Error(`Unsupported provider: ${providerName}`);
|
|
156
158
|
}
|
|
157
|
-
const template = await Promise.resolve().then(() => provider(source, { auth:
|
|
158
|
-
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}`);
|
|
159
161
|
});
|
|
160
|
-
template.name = (template.name || "template").replace(/[
|
|
161
|
-
template.defaultDir = (template.defaultDir || template.name).replace(/[
|
|
162
|
-
const cwd = pathe.resolve(
|
|
163
|
-
const extractPath = pathe.resolve(cwd,
|
|
164
|
-
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) {
|
|
165
167
|
await promises.rm(extractPath, { recursive: true, force: true });
|
|
166
168
|
}
|
|
167
|
-
if (!
|
|
169
|
+
if (!options.force && node_fs.existsSync(extractPath) && node_fs.readdirSync(extractPath).length > 0) {
|
|
168
170
|
throw new Error(`Destination ${extractPath} already exists.`);
|
|
169
171
|
}
|
|
170
172
|
await promises.mkdir(extractPath, { recursive: true });
|
|
171
|
-
const
|
|
172
|
-
const tarPath = pathe.resolve(
|
|
173
|
-
if (
|
|
174
|
-
|
|
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;
|
|
175
177
|
}
|
|
176
|
-
if (!
|
|
178
|
+
if (!options.offline) {
|
|
177
179
|
await promises.mkdir(pathe.dirname(tarPath), { recursive: true });
|
|
178
180
|
const s2 = Date.now();
|
|
179
|
-
await download(template.tar, tarPath, { headers: template.headers }).catch((
|
|
181
|
+
await download(template.tar, tarPath, { headers: template.headers }).catch((error) => {
|
|
180
182
|
if (!node_fs.existsSync(tarPath)) {
|
|
181
|
-
throw
|
|
183
|
+
throw error;
|
|
182
184
|
}
|
|
183
|
-
debug("Download error. Using cached version:",
|
|
184
|
-
|
|
185
|
+
debug("Download error. Using cached version:", error);
|
|
186
|
+
options.offline = true;
|
|
185
187
|
});
|
|
186
188
|
debug(`Downloaded ${template.tar} to ${tarPath} in ${Date.now() - s2}ms`);
|
|
187
189
|
}
|
|
188
190
|
if (!node_fs.existsSync(tarPath)) {
|
|
189
|
-
throw new Error(`Tarball not found: ${tarPath} (offline: ${
|
|
191
|
+
throw new Error(`Tarball not found: ${tarPath} (offline: ${options.offline})`);
|
|
190
192
|
}
|
|
191
193
|
const s = Date.now();
|
|
192
194
|
const subdir = template.subdir?.replace(/^\//, "") || "";
|
|
@@ -197,7 +199,7 @@ async function downloadTemplate(input, opts = {}) {
|
|
|
197
199
|
entry.path = entry.path.split("/").splice(1).join("/");
|
|
198
200
|
if (subdir) {
|
|
199
201
|
if (entry.path.startsWith(subdir + "/")) {
|
|
200
|
-
entry.path = entry.path.
|
|
202
|
+
entry.path = entry.path.slice(subdir.length);
|
|
201
203
|
} else {
|
|
202
204
|
entry.path = "";
|
|
203
205
|
}
|
|
@@ -7,23 +7,24 @@ import { defu } from 'defu';
|
|
|
7
7
|
import { pipeline } from 'node:stream';
|
|
8
8
|
import { spawnSync } from 'node:child_process';
|
|
9
9
|
import { promisify } from 'node:util';
|
|
10
|
-
import
|
|
10
|
+
import { fetch } from 'node-fetch-native';
|
|
11
|
+
import createHttpsProxyAgent from 'https-proxy-agent';
|
|
11
12
|
|
|
12
|
-
async function download(url, filePath,
|
|
13
|
+
async function download(url, filePath, options = {}) {
|
|
13
14
|
const infoPath = filePath + ".json";
|
|
14
15
|
const info = JSON.parse(await readFile(infoPath, "utf8").catch(() => "{}"));
|
|
15
|
-
const
|
|
16
|
-
const etag =
|
|
16
|
+
const headResponse = await sendFetch(url, { method: "HEAD", headers: options.headers }).catch(() => void 0);
|
|
17
|
+
const etag = headResponse?.headers.get("etag");
|
|
17
18
|
if (info.etag === etag && existsSync(filePath)) {
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
20
21
|
info.etag = etag;
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
23
|
-
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}`);
|
|
24
25
|
}
|
|
25
26
|
const stream = createWriteStream(filePath);
|
|
26
|
-
await promisify(pipeline)(
|
|
27
|
+
await promisify(pipeline)(response.body, stream);
|
|
27
28
|
await writeFile(infoPath, JSON.stringify(info), "utf8");
|
|
28
29
|
}
|
|
29
30
|
const inputRegex = /^(?<repo>[\w.-]+\/[\w.-]+)(?<subdir>[^#]+)?(?<ref>#[\w.-]+)?/;
|
|
@@ -32,14 +33,19 @@ function parseGitURI(input) {
|
|
|
32
33
|
return {
|
|
33
34
|
repo: m.repo,
|
|
34
35
|
subdir: m.subdir || "/",
|
|
35
|
-
ref: m.ref ? m.ref.
|
|
36
|
+
ref: m.ref ? m.ref.slice(1) : "main"
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
|
-
function debug(...
|
|
39
|
+
function debug(...arguments_) {
|
|
39
40
|
if (process.env.DEBUG) {
|
|
40
|
-
console.debug("[giget]", ...
|
|
41
|
+
console.debug("[giget]", ...arguments_);
|
|
41
42
|
}
|
|
42
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
|
+
}
|
|
43
49
|
function currentShell() {
|
|
44
50
|
if (process.env.SHELL) {
|
|
45
51
|
return process.env.SHELL;
|
|
@@ -60,46 +66,46 @@ function startShell(cwd) {
|
|
|
60
66
|
});
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
const github = (input,
|
|
69
|
+
const github = (input, options) => {
|
|
64
70
|
const parsed = parseGitURI(input);
|
|
65
71
|
return {
|
|
66
72
|
name: parsed.repo.replace("/", "-"),
|
|
67
73
|
version: parsed.ref,
|
|
68
74
|
subdir: parsed.subdir,
|
|
69
|
-
headers: { Authorization:
|
|
75
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
70
76
|
url: `https://github.com/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
71
77
|
tar: `https://github.com/${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
72
78
|
};
|
|
73
79
|
};
|
|
74
|
-
const gitlab = (input,
|
|
80
|
+
const gitlab = (input, options) => {
|
|
75
81
|
const parsed = parseGitURI(input);
|
|
76
82
|
return {
|
|
77
83
|
name: parsed.repo.replace("/", "-"),
|
|
78
84
|
version: parsed.ref,
|
|
79
85
|
subdir: parsed.subdir,
|
|
80
|
-
headers: { Authorization:
|
|
86
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
81
87
|
url: `https://gitlab.com/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
82
88
|
tar: `https://gitlab.com/${parsed.repo}/-/archive/${parsed.ref}.tar.gz`
|
|
83
89
|
};
|
|
84
90
|
};
|
|
85
|
-
const bitbucket = (input,
|
|
91
|
+
const bitbucket = (input, options) => {
|
|
86
92
|
const parsed = parseGitURI(input);
|
|
87
93
|
return {
|
|
88
94
|
name: parsed.repo.replace("/", "-"),
|
|
89
95
|
version: parsed.ref,
|
|
90
96
|
subdir: parsed.subdir,
|
|
91
|
-
headers: { Authorization:
|
|
97
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
92
98
|
url: `https://bitbucket.com/${parsed.repo}/src/${parsed.ref}${parsed.subdir}`,
|
|
93
99
|
tar: `https://bitbucket.org/${parsed.repo}/get/${parsed.ref}.tar.gz`
|
|
94
100
|
};
|
|
95
101
|
};
|
|
96
|
-
const sourcehut = (input,
|
|
102
|
+
const sourcehut = (input, options) => {
|
|
97
103
|
const parsed = parseGitURI(input);
|
|
98
104
|
return {
|
|
99
105
|
name: parsed.repo.replace("/", "-"),
|
|
100
106
|
version: parsed.ref,
|
|
101
107
|
subdir: parsed.subdir,
|
|
102
|
-
headers: { Authorization:
|
|
108
|
+
headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
|
|
103
109
|
url: `https://git.sr.ht/~${parsed.repo}/tree/${parsed.ref}/item${parsed.subdir}`,
|
|
104
110
|
tar: `https://git.sr.ht/~${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
105
111
|
};
|
|
@@ -117,11 +123,11 @@ const registryProvider = (registryEndpoint = DEFAULT_REGISTRY) => {
|
|
|
117
123
|
return async (input) => {
|
|
118
124
|
const start = Date.now();
|
|
119
125
|
const registryURL = `${registryEndpoint}/${input}.json`;
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
122
|
-
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}`);
|
|
123
129
|
}
|
|
124
|
-
const info = await
|
|
130
|
+
const info = await result.json();
|
|
125
131
|
if (!info.tar || !info.name) {
|
|
126
132
|
throw new Error(`Invalid template info from ${registryURL}. name or tar fields are missing!`);
|
|
127
133
|
}
|
|
@@ -131,56 +137,56 @@ const registryProvider = (registryEndpoint = DEFAULT_REGISTRY) => {
|
|
|
131
137
|
};
|
|
132
138
|
|
|
133
139
|
const sourceProtoRe = /^([\w-.]+):/;
|
|
134
|
-
async function downloadTemplate(input,
|
|
135
|
-
|
|
140
|
+
async function downloadTemplate(input, options = {}) {
|
|
141
|
+
options = defu({
|
|
136
142
|
registry: process.env.GIGET_REGISTRY,
|
|
137
143
|
auth: process.env.GIGET_AUTH
|
|
138
|
-
},
|
|
139
|
-
const registry =
|
|
140
|
-
let providerName =
|
|
144
|
+
}, options);
|
|
145
|
+
const registry = options.registry !== false ? registryProvider(options.registry) : void 0;
|
|
146
|
+
let providerName = options.provider || (registryProvider ? "registry" : "github");
|
|
141
147
|
let source = input;
|
|
142
148
|
const sourceProvierMatch = input.match(sourceProtoRe);
|
|
143
149
|
if (sourceProvierMatch) {
|
|
144
150
|
providerName = sourceProvierMatch[1];
|
|
145
|
-
source = input.
|
|
151
|
+
source = input.slice(sourceProvierMatch[0].length);
|
|
146
152
|
}
|
|
147
|
-
const provider =
|
|
153
|
+
const provider = options.providers?.[providerName] || providers[providerName] || registry;
|
|
148
154
|
if (!provider) {
|
|
149
155
|
throw new Error(`Unsupported provider: ${providerName}`);
|
|
150
156
|
}
|
|
151
|
-
const template = await Promise.resolve().then(() => provider(source, { auth:
|
|
152
|
-
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}`);
|
|
153
159
|
});
|
|
154
|
-
template.name = (template.name || "template").replace(/[
|
|
155
|
-
template.defaultDir = (template.defaultDir || template.name).replace(/[
|
|
156
|
-
const cwd = resolve(
|
|
157
|
-
const extractPath = resolve(cwd,
|
|
158
|
-
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) {
|
|
159
165
|
await rm(extractPath, { recursive: true, force: true });
|
|
160
166
|
}
|
|
161
|
-
if (!
|
|
167
|
+
if (!options.force && existsSync(extractPath) && readdirSync(extractPath).length > 0) {
|
|
162
168
|
throw new Error(`Destination ${extractPath} already exists.`);
|
|
163
169
|
}
|
|
164
170
|
await mkdir(extractPath, { recursive: true });
|
|
165
|
-
const
|
|
166
|
-
const tarPath = resolve(
|
|
167
|
-
if (
|
|
168
|
-
|
|
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;
|
|
169
175
|
}
|
|
170
|
-
if (!
|
|
176
|
+
if (!options.offline) {
|
|
171
177
|
await mkdir(dirname(tarPath), { recursive: true });
|
|
172
178
|
const s2 = Date.now();
|
|
173
|
-
await download(template.tar, tarPath, { headers: template.headers }).catch((
|
|
179
|
+
await download(template.tar, tarPath, { headers: template.headers }).catch((error) => {
|
|
174
180
|
if (!existsSync(tarPath)) {
|
|
175
|
-
throw
|
|
181
|
+
throw error;
|
|
176
182
|
}
|
|
177
|
-
debug("Download error. Using cached version:",
|
|
178
|
-
|
|
183
|
+
debug("Download error. Using cached version:", error);
|
|
184
|
+
options.offline = true;
|
|
179
185
|
});
|
|
180
186
|
debug(`Downloaded ${template.tar} to ${tarPath} in ${Date.now() - s2}ms`);
|
|
181
187
|
}
|
|
182
188
|
if (!existsSync(tarPath)) {
|
|
183
|
-
throw new Error(`Tarball not found: ${tarPath} (offline: ${
|
|
189
|
+
throw new Error(`Tarball not found: ${tarPath} (offline: ${options.offline})`);
|
|
184
190
|
}
|
|
185
191
|
const s = Date.now();
|
|
186
192
|
const subdir = template.subdir?.replace(/^\//, "") || "";
|
|
@@ -191,7 +197,7 @@ async function downloadTemplate(input, opts = {}) {
|
|
|
191
197
|
entry.path = entry.path.split("/").splice(1).join("/");
|
|
192
198
|
if (subdir) {
|
|
193
199
|
if (entry.path.startsWith(subdir + "/")) {
|
|
194
|
-
entry.path = entry.path.
|
|
200
|
+
entry.path = entry.path.slice(subdir.length);
|
|
195
201
|
} else {
|
|
196
202
|
entry.path = "";
|
|
197
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.8",
|
|
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.4",
|
|
37
|
-
"eslint": "^8.23.1",
|
|
38
|
-
"jiti": "^1.16.0",
|
|
39
|
-
"standard-version": "^9.5.0",
|
|
40
|
-
"typescript": "^4.8.3",
|
|
41
|
-
"unbuild": "^0.8.11",
|
|
42
|
-
"vitest": "^0.23.4"
|
|
43
|
-
},
|
|
44
|
-
"packageManager": "pnpm@7.12.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
|
+
}
|