giget 1.0.0 → 1.1.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/README.md +4 -4
- package/dist/cli.cjs +20 -6
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +20 -6
- package/dist/index.cjs +2 -2
- package/dist/index.d.ts +5 -3
- package/dist/index.mjs +2 -2
- package/dist/shared/{giget.1028e17a.cjs → giget.51477975.cjs} +84 -29
- package/dist/shared/{giget.5e7ec864.mjs → giget.6c52cb03.mjs} +84 -29
- package/package.json +20 -18
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ npx giget@latest <template> [<dir>] [...options]
|
|
|
36
36
|
|
|
37
37
|
### Options
|
|
38
38
|
|
|
39
|
-
- `--force`: Clone to
|
|
39
|
+
- `--force`: Clone to existing directory even if exists.
|
|
40
40
|
- `--offline`: Do not attempt to download and use cached version.
|
|
41
41
|
- `--prefer-offline`: Use cache if exists otherwise try to download.
|
|
42
42
|
- `--force-clean`: ⚠️ Remove any existing directory or file recusively before cloning.
|
|
@@ -92,7 +92,7 @@ A custom registry should provide an endpoint with dynamic path `/:template.json`
|
|
|
92
92
|
- `subdir`: (optional) Directory inside the tar file.
|
|
93
93
|
- `headers`: (optional) Custom headers to send while downloading template.
|
|
94
94
|
|
|
95
|
-
Because of the simplicity, you can even use a
|
|
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.
|
|
96
96
|
|
|
97
97
|
## Usage (Programmatic)
|
|
98
98
|
|
|
@@ -130,8 +130,8 @@ const { source, dir } = await downloadTemplate('github:unjs/template')
|
|
|
130
130
|
**Options:**
|
|
131
131
|
|
|
132
132
|
- `source`: (string) Input source in format of `[provider]:repo[/subpath][#ref]`.
|
|
133
|
-
- `dir`: (string) Destination directory to clone to. If not provided, `user-name` will be used relative to the current directory.
|
|
134
133
|
- `options`: (object) Options are usually inferred from the input string. You can customize them.
|
|
134
|
+
- `dir`: (string) Destination directory to clone to. If not provided, `user-name` will be used relative to the current directory.
|
|
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`.
|
|
@@ -188,7 +188,7 @@ const { source, dir } = await downloadRepo('themes:test', { providers: { themes
|
|
|
188
188
|
|
|
189
189
|
## Related projects
|
|
190
190
|
|
|
191
|
-
Giget wouldn't be possible without
|
|
191
|
+
Giget wouldn't be possible without inspiration from former projects. In comparison, giget does not depend on any local command which increases stability and performance, supports custom template providers, auth and many more features out of the box.
|
|
192
192
|
|
|
193
193
|
- https://github.com/samsonjs/gitter
|
|
194
194
|
- https://github.com/tiged/tiged
|
package/dist/cli.cjs
CHANGED
|
@@ -4,28 +4,38 @@
|
|
|
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.51477975.cjs');
|
|
8
8
|
require('node:fs/promises');
|
|
9
|
-
require('node:os');
|
|
10
9
|
require('node:fs');
|
|
11
10
|
require('tar');
|
|
12
11
|
require('pathe');
|
|
13
12
|
require('defu');
|
|
14
13
|
require('node:stream');
|
|
15
14
|
require('node:child_process');
|
|
15
|
+
require('node:os');
|
|
16
16
|
require('node:util');
|
|
17
17
|
require('node-fetch-native');
|
|
18
18
|
require('https-proxy-agent');
|
|
19
19
|
|
|
20
20
|
async function main() {
|
|
21
21
|
const arguments_ = mri(process.argv.slice(2), {
|
|
22
|
-
boolean: [
|
|
22
|
+
boolean: [
|
|
23
|
+
"help",
|
|
24
|
+
"force",
|
|
25
|
+
"force-clean",
|
|
26
|
+
"offline",
|
|
27
|
+
"prefer-offline",
|
|
28
|
+
"shell",
|
|
29
|
+
"verbose"
|
|
30
|
+
],
|
|
23
31
|
string: ["registry", "cwd", "auth"]
|
|
24
32
|
});
|
|
25
33
|
const input = arguments_._[0];
|
|
26
34
|
const dir = arguments_._[1];
|
|
27
35
|
if (!input || arguments_.help || arguments_.h) {
|
|
28
|
-
console.error(
|
|
36
|
+
console.error(
|
|
37
|
+
"Usage: npx giget@latest <input> [<dir>] [--force] [--force-clean] [--offline] [--prefer-offline] [--shell] [--registry] [--no-registry] [--verbose] [--cwd] [--auth]"
|
|
38
|
+
);
|
|
29
39
|
process.exit(1);
|
|
30
40
|
}
|
|
31
41
|
if (arguments_.verbose) {
|
|
@@ -40,8 +50,12 @@ async function main() {
|
|
|
40
50
|
cwd: arguments_.cwd,
|
|
41
51
|
auth: arguments_.auth
|
|
42
52
|
});
|
|
43
|
-
console.log(
|
|
44
|
-
|
|
53
|
+
console.log(
|
|
54
|
+
`\u2728 Successfully cloned ${colorette.cyan(r.name || r.url)} to ${colorette.cyan(
|
|
55
|
+
node_path.relative(process.cwd(), r.dir)
|
|
56
|
+
)}
|
|
57
|
+
`
|
|
58
|
+
);
|
|
45
59
|
if (arguments_.shell) {
|
|
46
60
|
giget.startShell(r.dir);
|
|
47
61
|
}
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.mjs
CHANGED
|
@@ -2,28 +2,38 @@
|
|
|
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.6c52cb03.mjs';
|
|
6
6
|
import 'node:fs/promises';
|
|
7
|
-
import 'node:os';
|
|
8
7
|
import 'node:fs';
|
|
9
8
|
import 'tar';
|
|
10
9
|
import 'pathe';
|
|
11
10
|
import 'defu';
|
|
12
11
|
import 'node:stream';
|
|
13
12
|
import 'node:child_process';
|
|
13
|
+
import 'node:os';
|
|
14
14
|
import 'node:util';
|
|
15
15
|
import 'node-fetch-native';
|
|
16
16
|
import 'https-proxy-agent';
|
|
17
17
|
|
|
18
18
|
async function main() {
|
|
19
19
|
const arguments_ = mri(process.argv.slice(2), {
|
|
20
|
-
boolean: [
|
|
20
|
+
boolean: [
|
|
21
|
+
"help",
|
|
22
|
+
"force",
|
|
23
|
+
"force-clean",
|
|
24
|
+
"offline",
|
|
25
|
+
"prefer-offline",
|
|
26
|
+
"shell",
|
|
27
|
+
"verbose"
|
|
28
|
+
],
|
|
21
29
|
string: ["registry", "cwd", "auth"]
|
|
22
30
|
});
|
|
23
31
|
const input = arguments_._[0];
|
|
24
32
|
const dir = arguments_._[1];
|
|
25
33
|
if (!input || arguments_.help || arguments_.h) {
|
|
26
|
-
console.error(
|
|
34
|
+
console.error(
|
|
35
|
+
"Usage: npx giget@latest <input> [<dir>] [--force] [--force-clean] [--offline] [--prefer-offline] [--shell] [--registry] [--no-registry] [--verbose] [--cwd] [--auth]"
|
|
36
|
+
);
|
|
27
37
|
process.exit(1);
|
|
28
38
|
}
|
|
29
39
|
if (arguments_.verbose) {
|
|
@@ -38,8 +48,12 @@ async function main() {
|
|
|
38
48
|
cwd: arguments_.cwd,
|
|
39
49
|
auth: arguments_.auth
|
|
40
50
|
});
|
|
41
|
-
console.log(
|
|
42
|
-
|
|
51
|
+
console.log(
|
|
52
|
+
`\u2728 Successfully cloned ${cyan(r.name || r.url)} to ${cyan(
|
|
53
|
+
relative(process.cwd(), r.dir)
|
|
54
|
+
)}
|
|
55
|
+
`
|
|
56
|
+
);
|
|
43
57
|
if (arguments_.shell) {
|
|
44
58
|
startShell(r.dir);
|
|
45
59
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const giget = require('./shared/giget.
|
|
3
|
+
const giget = require('./shared/giget.51477975.cjs');
|
|
4
4
|
require('node:fs/promises');
|
|
5
|
-
require('node:os');
|
|
6
5
|
require('node:fs');
|
|
7
6
|
require('tar');
|
|
8
7
|
require('pathe');
|
|
9
8
|
require('defu');
|
|
10
9
|
require('node:stream');
|
|
11
10
|
require('node:child_process');
|
|
11
|
+
require('node:os');
|
|
12
12
|
require('node:util');
|
|
13
13
|
require('node-fetch-native');
|
|
14
14
|
require('https-proxy-agent');
|
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ interface TemplateInfo {
|
|
|
16
16
|
dir?: never;
|
|
17
17
|
[key: string]: any;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
type TemplateProvider = (input: string, options: {
|
|
20
20
|
auth?: string;
|
|
21
21
|
}) => TemplateInfo | Promise<TemplateInfo> | null;
|
|
22
22
|
|
|
@@ -32,13 +32,15 @@ interface DownloadTemplateOptions {
|
|
|
32
32
|
cwd?: string;
|
|
33
33
|
auth?: string;
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
type DownloadTemplateResult = Omit<TemplateInfo, "dir" | "source"> & {
|
|
36
36
|
dir: string;
|
|
37
37
|
source: string;
|
|
38
38
|
};
|
|
39
39
|
declare function downloadTemplate(input: string, options?: DownloadTemplateOptions): Promise<DownloadTemplateResult>;
|
|
40
40
|
|
|
41
|
-
declare const registryProvider: (registryEndpoint?: string
|
|
41
|
+
declare const registryProvider: (registryEndpoint?: string, options?: {
|
|
42
|
+
auth?: string;
|
|
43
|
+
}) => TemplateProvider;
|
|
42
44
|
|
|
43
45
|
declare function startShell(cwd: string): void;
|
|
44
46
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +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.6c52cb03.mjs';
|
|
2
2
|
import 'node:fs/promises';
|
|
3
|
-
import 'node:os';
|
|
4
3
|
import 'node:fs';
|
|
5
4
|
import 'tar';
|
|
6
5
|
import 'pathe';
|
|
7
6
|
import 'defu';
|
|
8
7
|
import 'node:stream';
|
|
9
8
|
import 'node:child_process';
|
|
9
|
+
import 'node:os';
|
|
10
10
|
import 'node:util';
|
|
11
11
|
import 'node-fetch-native';
|
|
12
12
|
import 'https-proxy-agent';
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const promises = require('node:fs/promises');
|
|
4
|
-
const node_os = require('node:os');
|
|
5
4
|
const node_fs = require('node:fs');
|
|
6
5
|
const tar = require('tar');
|
|
7
6
|
const pathe = require('pathe');
|
|
8
7
|
const defu = require('defu');
|
|
9
8
|
const node_stream = require('node:stream');
|
|
10
9
|
const node_child_process = require('node:child_process');
|
|
10
|
+
const node_os = require('node:os');
|
|
11
11
|
const node_util = require('node:util');
|
|
12
12
|
const nodeFetchNative = require('node-fetch-native');
|
|
13
13
|
const createHttpsProxyAgent = require('https-proxy-agent');
|
|
14
14
|
|
|
15
15
|
async function download(url, filePath, options = {}) {
|
|
16
16
|
const infoPath = filePath + ".json";
|
|
17
|
-
const info = JSON.parse(
|
|
18
|
-
|
|
17
|
+
const info = JSON.parse(
|
|
18
|
+
await promises.readFile(infoPath, "utf8").catch(() => "{}")
|
|
19
|
+
);
|
|
20
|
+
const headResponse = await sendFetch(url, {
|
|
21
|
+
method: "HEAD",
|
|
22
|
+
headers: options.headers
|
|
23
|
+
}).catch(() => void 0);
|
|
19
24
|
const etag = headResponse?.headers.get("etag");
|
|
20
25
|
if (info.etag === etag && node_fs.existsSync(filePath)) {
|
|
21
26
|
return;
|
|
@@ -23,7 +28,9 @@ async function download(url, filePath, options = {}) {
|
|
|
23
28
|
info.etag = etag;
|
|
24
29
|
const response = await sendFetch(url, { headers: options.headers });
|
|
25
30
|
if (response.status >= 400) {
|
|
26
|
-
throw new Error(
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Failed to download ${url}: ${response.status} ${response.statusText}`
|
|
33
|
+
);
|
|
27
34
|
}
|
|
28
35
|
const stream = node_fs.createWriteStream(filePath);
|
|
29
36
|
await node_util.promisify(node_stream.pipeline)(response.body, stream);
|
|
@@ -48,6 +55,9 @@ async function sendFetch(url, options) {
|
|
|
48
55
|
const requestOptions = proxy ? { agent: createHttpsProxyAgent(proxy), ...options } : options;
|
|
49
56
|
return await nodeFetchNative.fetch(url, requestOptions);
|
|
50
57
|
}
|
|
58
|
+
function cacheDirectory() {
|
|
59
|
+
return process.env.XDG_CACHE_HOME ? pathe.resolve(process.env.XDG_CACHE_HOME, "giget") : pathe.resolve(node_os.homedir(), ".cache/giget");
|
|
60
|
+
}
|
|
51
61
|
function currentShell() {
|
|
52
62
|
if (process.env.SHELL) {
|
|
53
63
|
return process.env.SHELL;
|
|
@@ -60,7 +70,9 @@ function currentShell() {
|
|
|
60
70
|
function startShell(cwd) {
|
|
61
71
|
cwd = pathe.resolve(cwd);
|
|
62
72
|
const shell = currentShell();
|
|
63
|
-
console.info(
|
|
73
|
+
console.info(
|
|
74
|
+
`(experimental) Opening shell in ${pathe.relative(process.cwd(), cwd)}...`
|
|
75
|
+
);
|
|
64
76
|
node_child_process.spawnSync(shell, [], {
|
|
65
77
|
cwd,
|
|
66
78
|
shell: true,
|
|
@@ -70,24 +82,30 @@ function startShell(cwd) {
|
|
|
70
82
|
|
|
71
83
|
const github = (input, options) => {
|
|
72
84
|
const parsed = parseGitURI(input);
|
|
85
|
+
const github2 = process.env.GIGET_GITHUB_URL || "https://github.com";
|
|
73
86
|
return {
|
|
74
87
|
name: parsed.repo.replace("/", "-"),
|
|
75
88
|
version: parsed.ref,
|
|
76
89
|
subdir: parsed.subdir,
|
|
77
|
-
headers: {
|
|
78
|
-
|
|
79
|
-
|
|
90
|
+
headers: {
|
|
91
|
+
Authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
92
|
+
},
|
|
93
|
+
url: `${github2}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
94
|
+
tar: `${github2}/${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
80
95
|
};
|
|
81
96
|
};
|
|
82
97
|
const gitlab = (input, options) => {
|
|
83
98
|
const parsed = parseGitURI(input);
|
|
99
|
+
const gitlab2 = process.env.GIGET_GITLAB_URL || "https://gitlab.com";
|
|
84
100
|
return {
|
|
85
101
|
name: parsed.repo.replace("/", "-"),
|
|
86
102
|
version: parsed.ref,
|
|
87
103
|
subdir: parsed.subdir,
|
|
88
|
-
headers: {
|
|
89
|
-
|
|
90
|
-
|
|
104
|
+
headers: {
|
|
105
|
+
Authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
106
|
+
},
|
|
107
|
+
url: `${gitlab2}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
108
|
+
tar: `${gitlab2}/${parsed.repo}/-/archive/${parsed.ref}.tar.gz`
|
|
91
109
|
};
|
|
92
110
|
};
|
|
93
111
|
const bitbucket = (input, options) => {
|
|
@@ -96,7 +114,9 @@ const bitbucket = (input, options) => {
|
|
|
96
114
|
name: parsed.repo.replace("/", "-"),
|
|
97
115
|
version: parsed.ref,
|
|
98
116
|
subdir: parsed.subdir,
|
|
99
|
-
headers: {
|
|
117
|
+
headers: {
|
|
118
|
+
Authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
119
|
+
},
|
|
100
120
|
url: `https://bitbucket.com/${parsed.repo}/src/${parsed.ref}${parsed.subdir}`,
|
|
101
121
|
tar: `https://bitbucket.org/${parsed.repo}/get/${parsed.ref}.tar.gz`
|
|
102
122
|
};
|
|
@@ -107,7 +127,9 @@ const sourcehut = (input, options) => {
|
|
|
107
127
|
name: parsed.repo.replace("/", "-"),
|
|
108
128
|
version: parsed.ref,
|
|
109
129
|
subdir: parsed.subdir,
|
|
110
|
-
headers: {
|
|
130
|
+
headers: {
|
|
131
|
+
Authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
132
|
+
},
|
|
111
133
|
url: `https://git.sr.ht/~${parsed.repo}/tree/${parsed.ref}/item${parsed.subdir}`,
|
|
112
134
|
tar: `https://git.sr.ht/~${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
113
135
|
};
|
|
@@ -121,30 +143,44 @@ const providers = {
|
|
|
121
143
|
};
|
|
122
144
|
|
|
123
145
|
const DEFAULT_REGISTRY = "https://raw.githubusercontent.com/unjs/giget/main/templates";
|
|
124
|
-
const registryProvider = (registryEndpoint = DEFAULT_REGISTRY) => {
|
|
146
|
+
const registryProvider = (registryEndpoint = DEFAULT_REGISTRY, options) => {
|
|
147
|
+
options = options || {};
|
|
125
148
|
return async (input) => {
|
|
126
149
|
const start = Date.now();
|
|
127
150
|
const registryURL = `${registryEndpoint}/${input}.json`;
|
|
128
|
-
const result = await sendFetch(registryURL
|
|
151
|
+
const result = await sendFetch(registryURL, {
|
|
152
|
+
headers: {
|
|
153
|
+
Authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
154
|
+
}
|
|
155
|
+
});
|
|
129
156
|
if (result.status >= 400) {
|
|
130
|
-
throw new Error(
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Failed to download ${input} template info from ${registryURL}: ${result.status} ${result.statusText}`
|
|
159
|
+
);
|
|
131
160
|
}
|
|
132
161
|
const info = await result.json();
|
|
133
162
|
if (!info.tar || !info.name) {
|
|
134
|
-
throw new Error(
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Invalid template info from ${registryURL}. name or tar fields are missing!`
|
|
165
|
+
);
|
|
135
166
|
}
|
|
136
|
-
debug(
|
|
167
|
+
debug(
|
|
168
|
+
`Fetched ${input} template info from ${registryURL} in ${Date.now() - start}ms`
|
|
169
|
+
);
|
|
137
170
|
return info;
|
|
138
171
|
};
|
|
139
172
|
};
|
|
140
173
|
|
|
141
174
|
const sourceProtoRe = /^([\w-.]+):/;
|
|
142
175
|
async function downloadTemplate(input, options = {}) {
|
|
143
|
-
options = defu.defu(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
176
|
+
options = defu.defu(
|
|
177
|
+
{
|
|
178
|
+
registry: process.env.GIGET_REGISTRY,
|
|
179
|
+
auth: process.env.GIGET_AUTH
|
|
180
|
+
},
|
|
181
|
+
options
|
|
182
|
+
);
|
|
183
|
+
const registry = options.registry !== false ? registryProvider(options.registry, { auth: options.auth }) : void 0;
|
|
148
184
|
let providerName = options.provider || (registryProvider ? "registry" : "github");
|
|
149
185
|
let source = input;
|
|
150
186
|
const sourceProvierMatch = input.match(sourceProtoRe);
|
|
@@ -157,10 +193,15 @@ async function downloadTemplate(input, options = {}) {
|
|
|
157
193
|
throw new Error(`Unsupported provider: ${providerName}`);
|
|
158
194
|
}
|
|
159
195
|
const template = await Promise.resolve().then(() => provider(source, { auth: options.auth })).catch((error) => {
|
|
160
|
-
throw new Error(
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Failed to download template from ${providerName}: ${error.message}`
|
|
198
|
+
);
|
|
161
199
|
});
|
|
162
200
|
template.name = (template.name || "template").replace(/[^\da-z-]/gi, "-");
|
|
163
|
-
template.defaultDir = (template.defaultDir || template.name).replace(
|
|
201
|
+
template.defaultDir = (template.defaultDir || template.name).replace(
|
|
202
|
+
/[^\da-z-]/gi,
|
|
203
|
+
"-"
|
|
204
|
+
);
|
|
164
205
|
const cwd = pathe.resolve(options.cwd || ".");
|
|
165
206
|
const extractPath = pathe.resolve(cwd, options.dir || template.defaultDir);
|
|
166
207
|
if (options.forceClean) {
|
|
@@ -170,15 +211,27 @@ async function downloadTemplate(input, options = {}) {
|
|
|
170
211
|
throw new Error(`Destination ${extractPath} already exists.`);
|
|
171
212
|
}
|
|
172
213
|
await promises.mkdir(extractPath, { recursive: true });
|
|
173
|
-
const temporaryDirectory = pathe.resolve(
|
|
174
|
-
|
|
214
|
+
const temporaryDirectory = pathe.resolve(
|
|
215
|
+
cacheDirectory(),
|
|
216
|
+
options.provider,
|
|
217
|
+
template.name
|
|
218
|
+
);
|
|
219
|
+
const tarPath = pathe.resolve(
|
|
220
|
+
temporaryDirectory,
|
|
221
|
+
(template.version || template.name) + ".tar.gz"
|
|
222
|
+
);
|
|
175
223
|
if (options.preferOffline && node_fs.existsSync(tarPath)) {
|
|
176
224
|
options.offline = true;
|
|
177
225
|
}
|
|
178
226
|
if (!options.offline) {
|
|
179
227
|
await promises.mkdir(pathe.dirname(tarPath), { recursive: true });
|
|
180
228
|
const s2 = Date.now();
|
|
181
|
-
await download(template.tar, tarPath, {
|
|
229
|
+
await download(template.tar, tarPath, {
|
|
230
|
+
headers: {
|
|
231
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0,
|
|
232
|
+
...template.headers
|
|
233
|
+
}
|
|
234
|
+
}).catch((error) => {
|
|
182
235
|
if (!node_fs.existsSync(tarPath)) {
|
|
183
236
|
throw error;
|
|
184
237
|
}
|
|
@@ -188,7 +241,9 @@ async function downloadTemplate(input, options = {}) {
|
|
|
188
241
|
debug(`Downloaded ${template.tar} to ${tarPath} in ${Date.now() - s2}ms`);
|
|
189
242
|
}
|
|
190
243
|
if (!node_fs.existsSync(tarPath)) {
|
|
191
|
-
throw new Error(
|
|
244
|
+
throw new Error(
|
|
245
|
+
`Tarball not found: ${tarPath} (offline: ${options.offline})`
|
|
246
|
+
);
|
|
192
247
|
}
|
|
193
248
|
const s = Date.now();
|
|
194
249
|
const subdir = template.subdir?.replace(/^\//, "") || "";
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { readFile, writeFile, rm, mkdir } from 'node:fs/promises';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
2
|
import { existsSync, createWriteStream, readdirSync } from 'node:fs';
|
|
4
3
|
import { extract } from 'tar';
|
|
5
4
|
import { resolve, relative, dirname } from 'pathe';
|
|
6
5
|
import { defu } from 'defu';
|
|
7
6
|
import { pipeline } from 'node:stream';
|
|
8
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
9
|
import { promisify } from 'node:util';
|
|
10
10
|
import { fetch } from 'node-fetch-native';
|
|
11
11
|
import createHttpsProxyAgent from 'https-proxy-agent';
|
|
12
12
|
|
|
13
13
|
async function download(url, filePath, options = {}) {
|
|
14
14
|
const infoPath = filePath + ".json";
|
|
15
|
-
const info = JSON.parse(
|
|
16
|
-
|
|
15
|
+
const info = JSON.parse(
|
|
16
|
+
await readFile(infoPath, "utf8").catch(() => "{}")
|
|
17
|
+
);
|
|
18
|
+
const headResponse = await sendFetch(url, {
|
|
19
|
+
method: "HEAD",
|
|
20
|
+
headers: options.headers
|
|
21
|
+
}).catch(() => void 0);
|
|
17
22
|
const etag = headResponse?.headers.get("etag");
|
|
18
23
|
if (info.etag === etag && existsSync(filePath)) {
|
|
19
24
|
return;
|
|
@@ -21,7 +26,9 @@ async function download(url, filePath, options = {}) {
|
|
|
21
26
|
info.etag = etag;
|
|
22
27
|
const response = await sendFetch(url, { headers: options.headers });
|
|
23
28
|
if (response.status >= 400) {
|
|
24
|
-
throw new Error(
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Failed to download ${url}: ${response.status} ${response.statusText}`
|
|
31
|
+
);
|
|
25
32
|
}
|
|
26
33
|
const stream = createWriteStream(filePath);
|
|
27
34
|
await promisify(pipeline)(response.body, stream);
|
|
@@ -46,6 +53,9 @@ async function sendFetch(url, options) {
|
|
|
46
53
|
const requestOptions = proxy ? { agent: createHttpsProxyAgent(proxy), ...options } : options;
|
|
47
54
|
return await fetch(url, requestOptions);
|
|
48
55
|
}
|
|
56
|
+
function cacheDirectory() {
|
|
57
|
+
return process.env.XDG_CACHE_HOME ? resolve(process.env.XDG_CACHE_HOME, "giget") : resolve(homedir(), ".cache/giget");
|
|
58
|
+
}
|
|
49
59
|
function currentShell() {
|
|
50
60
|
if (process.env.SHELL) {
|
|
51
61
|
return process.env.SHELL;
|
|
@@ -58,7 +68,9 @@ function currentShell() {
|
|
|
58
68
|
function startShell(cwd) {
|
|
59
69
|
cwd = resolve(cwd);
|
|
60
70
|
const shell = currentShell();
|
|
61
|
-
console.info(
|
|
71
|
+
console.info(
|
|
72
|
+
`(experimental) Opening shell in ${relative(process.cwd(), cwd)}...`
|
|
73
|
+
);
|
|
62
74
|
spawnSync(shell, [], {
|
|
63
75
|
cwd,
|
|
64
76
|
shell: true,
|
|
@@ -68,24 +80,30 @@ function startShell(cwd) {
|
|
|
68
80
|
|
|
69
81
|
const github = (input, options) => {
|
|
70
82
|
const parsed = parseGitURI(input);
|
|
83
|
+
const github2 = process.env.GIGET_GITHUB_URL || "https://github.com";
|
|
71
84
|
return {
|
|
72
85
|
name: parsed.repo.replace("/", "-"),
|
|
73
86
|
version: parsed.ref,
|
|
74
87
|
subdir: parsed.subdir,
|
|
75
|
-
headers: {
|
|
76
|
-
|
|
77
|
-
|
|
88
|
+
headers: {
|
|
89
|
+
Authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
90
|
+
},
|
|
91
|
+
url: `${github2}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
92
|
+
tar: `${github2}/${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
78
93
|
};
|
|
79
94
|
};
|
|
80
95
|
const gitlab = (input, options) => {
|
|
81
96
|
const parsed = parseGitURI(input);
|
|
97
|
+
const gitlab2 = process.env.GIGET_GITLAB_URL || "https://gitlab.com";
|
|
82
98
|
return {
|
|
83
99
|
name: parsed.repo.replace("/", "-"),
|
|
84
100
|
version: parsed.ref,
|
|
85
101
|
subdir: parsed.subdir,
|
|
86
|
-
headers: {
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
headers: {
|
|
103
|
+
Authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
104
|
+
},
|
|
105
|
+
url: `${gitlab2}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
106
|
+
tar: `${gitlab2}/${parsed.repo}/-/archive/${parsed.ref}.tar.gz`
|
|
89
107
|
};
|
|
90
108
|
};
|
|
91
109
|
const bitbucket = (input, options) => {
|
|
@@ -94,7 +112,9 @@ const bitbucket = (input, options) => {
|
|
|
94
112
|
name: parsed.repo.replace("/", "-"),
|
|
95
113
|
version: parsed.ref,
|
|
96
114
|
subdir: parsed.subdir,
|
|
97
|
-
headers: {
|
|
115
|
+
headers: {
|
|
116
|
+
Authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
117
|
+
},
|
|
98
118
|
url: `https://bitbucket.com/${parsed.repo}/src/${parsed.ref}${parsed.subdir}`,
|
|
99
119
|
tar: `https://bitbucket.org/${parsed.repo}/get/${parsed.ref}.tar.gz`
|
|
100
120
|
};
|
|
@@ -105,7 +125,9 @@ const sourcehut = (input, options) => {
|
|
|
105
125
|
name: parsed.repo.replace("/", "-"),
|
|
106
126
|
version: parsed.ref,
|
|
107
127
|
subdir: parsed.subdir,
|
|
108
|
-
headers: {
|
|
128
|
+
headers: {
|
|
129
|
+
Authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
130
|
+
},
|
|
109
131
|
url: `https://git.sr.ht/~${parsed.repo}/tree/${parsed.ref}/item${parsed.subdir}`,
|
|
110
132
|
tar: `https://git.sr.ht/~${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
111
133
|
};
|
|
@@ -119,30 +141,44 @@ const providers = {
|
|
|
119
141
|
};
|
|
120
142
|
|
|
121
143
|
const DEFAULT_REGISTRY = "https://raw.githubusercontent.com/unjs/giget/main/templates";
|
|
122
|
-
const registryProvider = (registryEndpoint = DEFAULT_REGISTRY) => {
|
|
144
|
+
const registryProvider = (registryEndpoint = DEFAULT_REGISTRY, options) => {
|
|
145
|
+
options = options || {};
|
|
123
146
|
return async (input) => {
|
|
124
147
|
const start = Date.now();
|
|
125
148
|
const registryURL = `${registryEndpoint}/${input}.json`;
|
|
126
|
-
const result = await sendFetch(registryURL
|
|
149
|
+
const result = await sendFetch(registryURL, {
|
|
150
|
+
headers: {
|
|
151
|
+
Authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
152
|
+
}
|
|
153
|
+
});
|
|
127
154
|
if (result.status >= 400) {
|
|
128
|
-
throw new Error(
|
|
155
|
+
throw new Error(
|
|
156
|
+
`Failed to download ${input} template info from ${registryURL}: ${result.status} ${result.statusText}`
|
|
157
|
+
);
|
|
129
158
|
}
|
|
130
159
|
const info = await result.json();
|
|
131
160
|
if (!info.tar || !info.name) {
|
|
132
|
-
throw new Error(
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Invalid template info from ${registryURL}. name or tar fields are missing!`
|
|
163
|
+
);
|
|
133
164
|
}
|
|
134
|
-
debug(
|
|
165
|
+
debug(
|
|
166
|
+
`Fetched ${input} template info from ${registryURL} in ${Date.now() - start}ms`
|
|
167
|
+
);
|
|
135
168
|
return info;
|
|
136
169
|
};
|
|
137
170
|
};
|
|
138
171
|
|
|
139
172
|
const sourceProtoRe = /^([\w-.]+):/;
|
|
140
173
|
async function downloadTemplate(input, options = {}) {
|
|
141
|
-
options = defu(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
174
|
+
options = defu(
|
|
175
|
+
{
|
|
176
|
+
registry: process.env.GIGET_REGISTRY,
|
|
177
|
+
auth: process.env.GIGET_AUTH
|
|
178
|
+
},
|
|
179
|
+
options
|
|
180
|
+
);
|
|
181
|
+
const registry = options.registry !== false ? registryProvider(options.registry, { auth: options.auth }) : void 0;
|
|
146
182
|
let providerName = options.provider || (registryProvider ? "registry" : "github");
|
|
147
183
|
let source = input;
|
|
148
184
|
const sourceProvierMatch = input.match(sourceProtoRe);
|
|
@@ -155,10 +191,15 @@ async function downloadTemplate(input, options = {}) {
|
|
|
155
191
|
throw new Error(`Unsupported provider: ${providerName}`);
|
|
156
192
|
}
|
|
157
193
|
const template = await Promise.resolve().then(() => provider(source, { auth: options.auth })).catch((error) => {
|
|
158
|
-
throw new Error(
|
|
194
|
+
throw new Error(
|
|
195
|
+
`Failed to download template from ${providerName}: ${error.message}`
|
|
196
|
+
);
|
|
159
197
|
});
|
|
160
198
|
template.name = (template.name || "template").replace(/[^\da-z-]/gi, "-");
|
|
161
|
-
template.defaultDir = (template.defaultDir || template.name).replace(
|
|
199
|
+
template.defaultDir = (template.defaultDir || template.name).replace(
|
|
200
|
+
/[^\da-z-]/gi,
|
|
201
|
+
"-"
|
|
202
|
+
);
|
|
162
203
|
const cwd = resolve(options.cwd || ".");
|
|
163
204
|
const extractPath = resolve(cwd, options.dir || template.defaultDir);
|
|
164
205
|
if (options.forceClean) {
|
|
@@ -168,15 +209,27 @@ async function downloadTemplate(input, options = {}) {
|
|
|
168
209
|
throw new Error(`Destination ${extractPath} already exists.`);
|
|
169
210
|
}
|
|
170
211
|
await mkdir(extractPath, { recursive: true });
|
|
171
|
-
const temporaryDirectory = resolve(
|
|
172
|
-
|
|
212
|
+
const temporaryDirectory = resolve(
|
|
213
|
+
cacheDirectory(),
|
|
214
|
+
options.provider,
|
|
215
|
+
template.name
|
|
216
|
+
);
|
|
217
|
+
const tarPath = resolve(
|
|
218
|
+
temporaryDirectory,
|
|
219
|
+
(template.version || template.name) + ".tar.gz"
|
|
220
|
+
);
|
|
173
221
|
if (options.preferOffline && existsSync(tarPath)) {
|
|
174
222
|
options.offline = true;
|
|
175
223
|
}
|
|
176
224
|
if (!options.offline) {
|
|
177
225
|
await mkdir(dirname(tarPath), { recursive: true });
|
|
178
226
|
const s2 = Date.now();
|
|
179
|
-
await download(template.tar, tarPath, {
|
|
227
|
+
await download(template.tar, tarPath, {
|
|
228
|
+
headers: {
|
|
229
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0,
|
|
230
|
+
...template.headers
|
|
231
|
+
}
|
|
232
|
+
}).catch((error) => {
|
|
180
233
|
if (!existsSync(tarPath)) {
|
|
181
234
|
throw error;
|
|
182
235
|
}
|
|
@@ -186,7 +239,9 @@ async function downloadTemplate(input, options = {}) {
|
|
|
186
239
|
debug(`Downloaded ${template.tar} to ${tarPath} in ${Date.now() - s2}ms`);
|
|
187
240
|
}
|
|
188
241
|
if (!existsSync(tarPath)) {
|
|
189
|
-
throw new Error(
|
|
242
|
+
throw new Error(
|
|
243
|
+
`Tarball not found: ${tarPath} (offline: ${options.offline})`
|
|
244
|
+
);
|
|
190
245
|
}
|
|
191
246
|
const s = Date.now();
|
|
192
247
|
const subdir = template.subdir?.replace(/^\//, "") || "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "giget",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Download templates and git repositories with pleasure!",
|
|
5
5
|
"repository": "unjs/giget",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,32 +26,34 @@
|
|
|
26
26
|
"build": "unbuild",
|
|
27
27
|
"dev": "vitest dev",
|
|
28
28
|
"giget": "jiti ./src/cli.ts",
|
|
29
|
-
"lint": "eslint --ext .ts,.js,.mjs,.cjs .",
|
|
29
|
+
"lint": "eslint --ext .ts,.js,.mjs,.cjs . && prettier -c src test",
|
|
30
|
+
"lint:fix": "eslint --ext .ts,.js,.mjs,.cjs . --fix && prettier -w src test",
|
|
30
31
|
"prepack": "unbuild",
|
|
31
32
|
"play": "pnpm giget --force-clean --verbose unjs .tmp/clone",
|
|
32
|
-
"release": "pnpm test &&
|
|
33
|
+
"release": "pnpm test && changelogen --release && npm publish && git push --follow-tags",
|
|
33
34
|
"test": "pnpm lint && vitest run --coverage"
|
|
34
35
|
},
|
|
35
36
|
"dependencies": {
|
|
36
37
|
"colorette": "^2.0.19",
|
|
37
|
-
"defu": "^6.1.
|
|
38
|
+
"defu": "^6.1.2",
|
|
38
39
|
"https-proxy-agent": "^5.0.1",
|
|
39
40
|
"mri": "^1.2.0",
|
|
40
|
-
"node-fetch-native": "^1.0.
|
|
41
|
-
"pathe": "^1.
|
|
42
|
-
"tar": "^6.1.
|
|
41
|
+
"node-fetch-native": "^1.0.2",
|
|
42
|
+
"pathe": "^1.1.0",
|
|
43
|
+
"tar": "^6.1.13"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
|
-
"@types/node": "^18.
|
|
46
|
-
"@types/tar": "^6.1.
|
|
47
|
-
"@vitest/coverage-c8": "^0.
|
|
48
|
-
"
|
|
49
|
-
"eslint
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
46
|
+
"@types/node": "^18.13.0",
|
|
47
|
+
"@types/tar": "^6.1.4",
|
|
48
|
+
"@vitest/coverage-c8": "^0.28.5",
|
|
49
|
+
"changelogen": "^0.4.1",
|
|
50
|
+
"eslint": "^8.34.0",
|
|
51
|
+
"eslint-config-unjs": "^0.1.0",
|
|
52
|
+
"jiti": "^1.17.0",
|
|
53
|
+
"prettier": "^2.8.4",
|
|
54
|
+
"typescript": "^4.9.5",
|
|
55
|
+
"unbuild": "^1.1.1",
|
|
56
|
+
"vitest": "^0.28.5"
|
|
55
57
|
},
|
|
56
|
-
"packageManager": "pnpm@7.
|
|
58
|
+
"packageManager": "pnpm@7.27.0"
|
|
57
59
|
}
|