giget 1.0.0 → 1.1.1
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 +23 -17
- 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.5e7ec864.mjs → giget.c3d868f5.mjs} +105 -33
- package/dist/shared/{giget.1028e17a.cjs → giget.e7e76a83.cjs} +105 -33
- 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
|
|
|
@@ -113,10 +113,10 @@ Import:
|
|
|
113
113
|
|
|
114
114
|
```js
|
|
115
115
|
// ESM
|
|
116
|
-
import { downloadTemplate } from
|
|
116
|
+
import { downloadTemplate } from "giget";
|
|
117
117
|
|
|
118
118
|
// CommonJS
|
|
119
|
-
const { downloadTemplate } = require(
|
|
119
|
+
const { downloadTemplate } = require("giget");
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
### `downloadTemplate(source, options?)`
|
|
@@ -124,14 +124,14 @@ const { downloadTemplate } = require('giget')
|
|
|
124
124
|
**Example:**
|
|
125
125
|
|
|
126
126
|
```js
|
|
127
|
-
const { source, dir } = await downloadTemplate(
|
|
127
|
+
const { source, dir } = await downloadTemplate("github:unjs/template");
|
|
128
128
|
```
|
|
129
129
|
|
|
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`.
|
|
@@ -159,19 +159,21 @@ The return value is a promise that resolves to the resolved template.
|
|
|
159
159
|
Using programmatic method, you can make your own custom template providers.
|
|
160
160
|
|
|
161
161
|
```ts
|
|
162
|
-
import type { TemplateProvider } from
|
|
162
|
+
import type { TemplateProvider } from "giget";
|
|
163
163
|
|
|
164
164
|
const rainbow: TemplateProvider = async (input, { auth }) => {
|
|
165
165
|
return {
|
|
166
|
-
name:
|
|
166
|
+
name: "rainbow",
|
|
167
167
|
version: input,
|
|
168
|
-
headers: {
|
|
168
|
+
headers: { authorization: auth },
|
|
169
169
|
url: `https://rainbow.template/?variant=${input}`,
|
|
170
|
-
tar: `https://rainbow.template/dl/rainbow.${input}.tar.gz
|
|
171
|
-
}
|
|
172
|
-
}
|
|
170
|
+
tar: `https://rainbow.template/dl/rainbow.${input}.tar.gz`,
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
173
|
|
|
174
|
-
const { source, dir } = await downloadRepo(
|
|
174
|
+
const { source, dir } = await downloadRepo("rainbow:one", {
|
|
175
|
+
providers: { rainbow },
|
|
176
|
+
});
|
|
175
177
|
```
|
|
176
178
|
|
|
177
179
|
### Custom Registry Providers
|
|
@@ -179,16 +181,20 @@ const { source, dir } = await downloadRepo('rainbow:one', { providers: { rainbow
|
|
|
179
181
|
You can define additional [custom registry](#custom-registry) providers using `registryProvider` utility and register to `providers`.
|
|
180
182
|
|
|
181
183
|
```ts
|
|
182
|
-
import { registryProvider } from
|
|
184
|
+
import { registryProvider } from "giget";
|
|
183
185
|
|
|
184
|
-
const themes = registryProvider(
|
|
186
|
+
const themes = registryProvider(
|
|
187
|
+
"https://raw.githubusercontent.com/unjs/giget/main/templates"
|
|
188
|
+
);
|
|
185
189
|
|
|
186
|
-
const { source, dir } = await downloadRepo(
|
|
190
|
+
const { source, dir } = await downloadRepo("themes:test", {
|
|
191
|
+
providers: { themes },
|
|
192
|
+
});
|
|
187
193
|
```
|
|
188
194
|
|
|
189
195
|
## Related projects
|
|
190
196
|
|
|
191
|
-
Giget wouldn't be possible without
|
|
197
|
+
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
198
|
|
|
193
199
|
- https://github.com/samsonjs/gitter
|
|
194
200
|
- 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.e7e76a83.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.c3d868f5.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.e7e76a83.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.c3d868f5.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,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);
|
|
@@ -41,10 +48,30 @@ function debug(...arguments_) {
|
|
|
41
48
|
console.debug("[giget]", ...arguments_);
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
|
-
async function sendFetch(url, options) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
async function sendFetch(url, options = {}) {
|
|
52
|
+
if (!options.agent) {
|
|
53
|
+
const proxyEnv = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
|
|
54
|
+
if (proxyEnv) {
|
|
55
|
+
options.agent = createHttpsProxyAgent(proxyEnv);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (options?.headers) {
|
|
59
|
+
options.headers = normalizeHeaders(options.headers);
|
|
60
|
+
}
|
|
61
|
+
return await fetch(url, options);
|
|
62
|
+
}
|
|
63
|
+
function cacheDirectory() {
|
|
64
|
+
return process.env.XDG_CACHE_HOME ? resolve(process.env.XDG_CACHE_HOME, "giget") : resolve(homedir(), ".cache/giget");
|
|
65
|
+
}
|
|
66
|
+
function normalizeHeaders(headers) {
|
|
67
|
+
const normalized = {};
|
|
68
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
69
|
+
if (!value) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
normalized[key.toLowerCase()] = value;
|
|
73
|
+
}
|
|
74
|
+
return normalized;
|
|
48
75
|
}
|
|
49
76
|
function currentShell() {
|
|
50
77
|
if (process.env.SHELL) {
|
|
@@ -58,7 +85,9 @@ function currentShell() {
|
|
|
58
85
|
function startShell(cwd) {
|
|
59
86
|
cwd = resolve(cwd);
|
|
60
87
|
const shell = currentShell();
|
|
61
|
-
console.info(
|
|
88
|
+
console.info(
|
|
89
|
+
`(experimental) Opening shell in ${relative(process.cwd(), cwd)}...`
|
|
90
|
+
);
|
|
62
91
|
spawnSync(shell, [], {
|
|
63
92
|
cwd,
|
|
64
93
|
shell: true,
|
|
@@ -68,24 +97,30 @@ function startShell(cwd) {
|
|
|
68
97
|
|
|
69
98
|
const github = (input, options) => {
|
|
70
99
|
const parsed = parseGitURI(input);
|
|
100
|
+
const github2 = process.env.GIGET_GITHUB_URL || "https://github.com";
|
|
71
101
|
return {
|
|
72
102
|
name: parsed.repo.replace("/", "-"),
|
|
73
103
|
version: parsed.ref,
|
|
74
104
|
subdir: parsed.subdir,
|
|
75
|
-
headers: {
|
|
76
|
-
|
|
77
|
-
|
|
105
|
+
headers: {
|
|
106
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
107
|
+
},
|
|
108
|
+
url: `${github2}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
109
|
+
tar: `${github2}/${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
78
110
|
};
|
|
79
111
|
};
|
|
80
112
|
const gitlab = (input, options) => {
|
|
81
113
|
const parsed = parseGitURI(input);
|
|
114
|
+
const gitlab2 = process.env.GIGET_GITLAB_URL || "https://gitlab.com";
|
|
82
115
|
return {
|
|
83
116
|
name: parsed.repo.replace("/", "-"),
|
|
84
117
|
version: parsed.ref,
|
|
85
118
|
subdir: parsed.subdir,
|
|
86
|
-
headers: {
|
|
87
|
-
|
|
88
|
-
|
|
119
|
+
headers: {
|
|
120
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
121
|
+
},
|
|
122
|
+
url: `${gitlab2}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
123
|
+
tar: `${gitlab2}/${parsed.repo}/-/archive/${parsed.ref}.tar.gz`
|
|
89
124
|
};
|
|
90
125
|
};
|
|
91
126
|
const bitbucket = (input, options) => {
|
|
@@ -94,7 +129,9 @@ const bitbucket = (input, options) => {
|
|
|
94
129
|
name: parsed.repo.replace("/", "-"),
|
|
95
130
|
version: parsed.ref,
|
|
96
131
|
subdir: parsed.subdir,
|
|
97
|
-
headers: {
|
|
132
|
+
headers: {
|
|
133
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
134
|
+
},
|
|
98
135
|
url: `https://bitbucket.com/${parsed.repo}/src/${parsed.ref}${parsed.subdir}`,
|
|
99
136
|
tar: `https://bitbucket.org/${parsed.repo}/get/${parsed.ref}.tar.gz`
|
|
100
137
|
};
|
|
@@ -105,7 +142,9 @@ const sourcehut = (input, options) => {
|
|
|
105
142
|
name: parsed.repo.replace("/", "-"),
|
|
106
143
|
version: parsed.ref,
|
|
107
144
|
subdir: parsed.subdir,
|
|
108
|
-
headers: {
|
|
145
|
+
headers: {
|
|
146
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
147
|
+
},
|
|
109
148
|
url: `https://git.sr.ht/~${parsed.repo}/tree/${parsed.ref}/item${parsed.subdir}`,
|
|
110
149
|
tar: `https://git.sr.ht/~${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
111
150
|
};
|
|
@@ -119,30 +158,44 @@ const providers = {
|
|
|
119
158
|
};
|
|
120
159
|
|
|
121
160
|
const DEFAULT_REGISTRY = "https://raw.githubusercontent.com/unjs/giget/main/templates";
|
|
122
|
-
const registryProvider = (registryEndpoint = DEFAULT_REGISTRY) => {
|
|
161
|
+
const registryProvider = (registryEndpoint = DEFAULT_REGISTRY, options) => {
|
|
162
|
+
options = options || {};
|
|
123
163
|
return async (input) => {
|
|
124
164
|
const start = Date.now();
|
|
125
165
|
const registryURL = `${registryEndpoint}/${input}.json`;
|
|
126
|
-
const result = await sendFetch(registryURL
|
|
166
|
+
const result = await sendFetch(registryURL, {
|
|
167
|
+
headers: {
|
|
168
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
169
|
+
}
|
|
170
|
+
});
|
|
127
171
|
if (result.status >= 400) {
|
|
128
|
-
throw new Error(
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Failed to download ${input} template info from ${registryURL}: ${result.status} ${result.statusText}`
|
|
174
|
+
);
|
|
129
175
|
}
|
|
130
176
|
const info = await result.json();
|
|
131
177
|
if (!info.tar || !info.name) {
|
|
132
|
-
throw new Error(
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Invalid template info from ${registryURL}. name or tar fields are missing!`
|
|
180
|
+
);
|
|
133
181
|
}
|
|
134
|
-
debug(
|
|
182
|
+
debug(
|
|
183
|
+
`Fetched ${input} template info from ${registryURL} in ${Date.now() - start}ms`
|
|
184
|
+
);
|
|
135
185
|
return info;
|
|
136
186
|
};
|
|
137
187
|
};
|
|
138
188
|
|
|
139
189
|
const sourceProtoRe = /^([\w-.]+):/;
|
|
140
190
|
async function downloadTemplate(input, options = {}) {
|
|
141
|
-
options = defu(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
191
|
+
options = defu(
|
|
192
|
+
{
|
|
193
|
+
registry: process.env.GIGET_REGISTRY,
|
|
194
|
+
auth: process.env.GIGET_AUTH
|
|
195
|
+
},
|
|
196
|
+
options
|
|
197
|
+
);
|
|
198
|
+
const registry = options.registry !== false ? registryProvider(options.registry, { auth: options.auth }) : void 0;
|
|
146
199
|
let providerName = options.provider || (registryProvider ? "registry" : "github");
|
|
147
200
|
let source = input;
|
|
148
201
|
const sourceProvierMatch = input.match(sourceProtoRe);
|
|
@@ -155,10 +208,15 @@ async function downloadTemplate(input, options = {}) {
|
|
|
155
208
|
throw new Error(`Unsupported provider: ${providerName}`);
|
|
156
209
|
}
|
|
157
210
|
const template = await Promise.resolve().then(() => provider(source, { auth: options.auth })).catch((error) => {
|
|
158
|
-
throw new Error(
|
|
211
|
+
throw new Error(
|
|
212
|
+
`Failed to download template from ${providerName}: ${error.message}`
|
|
213
|
+
);
|
|
159
214
|
});
|
|
160
215
|
template.name = (template.name || "template").replace(/[^\da-z-]/gi, "-");
|
|
161
|
-
template.defaultDir = (template.defaultDir || template.name).replace(
|
|
216
|
+
template.defaultDir = (template.defaultDir || template.name).replace(
|
|
217
|
+
/[^\da-z-]/gi,
|
|
218
|
+
"-"
|
|
219
|
+
);
|
|
162
220
|
const cwd = resolve(options.cwd || ".");
|
|
163
221
|
const extractPath = resolve(cwd, options.dir || template.defaultDir);
|
|
164
222
|
if (options.forceClean) {
|
|
@@ -168,15 +226,27 @@ async function downloadTemplate(input, options = {}) {
|
|
|
168
226
|
throw new Error(`Destination ${extractPath} already exists.`);
|
|
169
227
|
}
|
|
170
228
|
await mkdir(extractPath, { recursive: true });
|
|
171
|
-
const temporaryDirectory = resolve(
|
|
172
|
-
|
|
229
|
+
const temporaryDirectory = resolve(
|
|
230
|
+
cacheDirectory(),
|
|
231
|
+
options.provider,
|
|
232
|
+
template.name
|
|
233
|
+
);
|
|
234
|
+
const tarPath = resolve(
|
|
235
|
+
temporaryDirectory,
|
|
236
|
+
(template.version || template.name) + ".tar.gz"
|
|
237
|
+
);
|
|
173
238
|
if (options.preferOffline && existsSync(tarPath)) {
|
|
174
239
|
options.offline = true;
|
|
175
240
|
}
|
|
176
241
|
if (!options.offline) {
|
|
177
242
|
await mkdir(dirname(tarPath), { recursive: true });
|
|
178
243
|
const s2 = Date.now();
|
|
179
|
-
await download(template.tar, tarPath, {
|
|
244
|
+
await download(template.tar, tarPath, {
|
|
245
|
+
headers: {
|
|
246
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0,
|
|
247
|
+
...normalizeHeaders(template.headers)
|
|
248
|
+
}
|
|
249
|
+
}).catch((error) => {
|
|
180
250
|
if (!existsSync(tarPath)) {
|
|
181
251
|
throw error;
|
|
182
252
|
}
|
|
@@ -186,7 +256,9 @@ async function downloadTemplate(input, options = {}) {
|
|
|
186
256
|
debug(`Downloaded ${template.tar} to ${tarPath} in ${Date.now() - s2}ms`);
|
|
187
257
|
}
|
|
188
258
|
if (!existsSync(tarPath)) {
|
|
189
|
-
throw new Error(
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Tarball not found: ${tarPath} (offline: ${options.offline})`
|
|
261
|
+
);
|
|
190
262
|
}
|
|
191
263
|
const s = Date.now();
|
|
192
264
|
const subdir = template.subdir?.replace(/^\//, "") || "";
|
|
@@ -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);
|
|
@@ -43,10 +50,30 @@ function debug(...arguments_) {
|
|
|
43
50
|
console.debug("[giget]", ...arguments_);
|
|
44
51
|
}
|
|
45
52
|
}
|
|
46
|
-
async function sendFetch(url, options) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
async function sendFetch(url, options = {}) {
|
|
54
|
+
if (!options.agent) {
|
|
55
|
+
const proxyEnv = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
|
|
56
|
+
if (proxyEnv) {
|
|
57
|
+
options.agent = createHttpsProxyAgent(proxyEnv);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (options?.headers) {
|
|
61
|
+
options.headers = normalizeHeaders(options.headers);
|
|
62
|
+
}
|
|
63
|
+
return await nodeFetchNative.fetch(url, options);
|
|
64
|
+
}
|
|
65
|
+
function cacheDirectory() {
|
|
66
|
+
return process.env.XDG_CACHE_HOME ? pathe.resolve(process.env.XDG_CACHE_HOME, "giget") : pathe.resolve(node_os.homedir(), ".cache/giget");
|
|
67
|
+
}
|
|
68
|
+
function normalizeHeaders(headers) {
|
|
69
|
+
const normalized = {};
|
|
70
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
71
|
+
if (!value) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
normalized[key.toLowerCase()] = value;
|
|
75
|
+
}
|
|
76
|
+
return normalized;
|
|
50
77
|
}
|
|
51
78
|
function currentShell() {
|
|
52
79
|
if (process.env.SHELL) {
|
|
@@ -60,7 +87,9 @@ function currentShell() {
|
|
|
60
87
|
function startShell(cwd) {
|
|
61
88
|
cwd = pathe.resolve(cwd);
|
|
62
89
|
const shell = currentShell();
|
|
63
|
-
console.info(
|
|
90
|
+
console.info(
|
|
91
|
+
`(experimental) Opening shell in ${pathe.relative(process.cwd(), cwd)}...`
|
|
92
|
+
);
|
|
64
93
|
node_child_process.spawnSync(shell, [], {
|
|
65
94
|
cwd,
|
|
66
95
|
shell: true,
|
|
@@ -70,24 +99,30 @@ function startShell(cwd) {
|
|
|
70
99
|
|
|
71
100
|
const github = (input, options) => {
|
|
72
101
|
const parsed = parseGitURI(input);
|
|
102
|
+
const github2 = process.env.GIGET_GITHUB_URL || "https://github.com";
|
|
73
103
|
return {
|
|
74
104
|
name: parsed.repo.replace("/", "-"),
|
|
75
105
|
version: parsed.ref,
|
|
76
106
|
subdir: parsed.subdir,
|
|
77
|
-
headers: {
|
|
78
|
-
|
|
79
|
-
|
|
107
|
+
headers: {
|
|
108
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
109
|
+
},
|
|
110
|
+
url: `${github2}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
111
|
+
tar: `${github2}/${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
80
112
|
};
|
|
81
113
|
};
|
|
82
114
|
const gitlab = (input, options) => {
|
|
83
115
|
const parsed = parseGitURI(input);
|
|
116
|
+
const gitlab2 = process.env.GIGET_GITLAB_URL || "https://gitlab.com";
|
|
84
117
|
return {
|
|
85
118
|
name: parsed.repo.replace("/", "-"),
|
|
86
119
|
version: parsed.ref,
|
|
87
120
|
subdir: parsed.subdir,
|
|
88
|
-
headers: {
|
|
89
|
-
|
|
90
|
-
|
|
121
|
+
headers: {
|
|
122
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
123
|
+
},
|
|
124
|
+
url: `${gitlab2}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
|
|
125
|
+
tar: `${gitlab2}/${parsed.repo}/-/archive/${parsed.ref}.tar.gz`
|
|
91
126
|
};
|
|
92
127
|
};
|
|
93
128
|
const bitbucket = (input, options) => {
|
|
@@ -96,7 +131,9 @@ const bitbucket = (input, options) => {
|
|
|
96
131
|
name: parsed.repo.replace("/", "-"),
|
|
97
132
|
version: parsed.ref,
|
|
98
133
|
subdir: parsed.subdir,
|
|
99
|
-
headers: {
|
|
134
|
+
headers: {
|
|
135
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
136
|
+
},
|
|
100
137
|
url: `https://bitbucket.com/${parsed.repo}/src/${parsed.ref}${parsed.subdir}`,
|
|
101
138
|
tar: `https://bitbucket.org/${parsed.repo}/get/${parsed.ref}.tar.gz`
|
|
102
139
|
};
|
|
@@ -107,7 +144,9 @@ const sourcehut = (input, options) => {
|
|
|
107
144
|
name: parsed.repo.replace("/", "-"),
|
|
108
145
|
version: parsed.ref,
|
|
109
146
|
subdir: parsed.subdir,
|
|
110
|
-
headers: {
|
|
147
|
+
headers: {
|
|
148
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
149
|
+
},
|
|
111
150
|
url: `https://git.sr.ht/~${parsed.repo}/tree/${parsed.ref}/item${parsed.subdir}`,
|
|
112
151
|
tar: `https://git.sr.ht/~${parsed.repo}/archive/${parsed.ref}.tar.gz`
|
|
113
152
|
};
|
|
@@ -121,30 +160,44 @@ const providers = {
|
|
|
121
160
|
};
|
|
122
161
|
|
|
123
162
|
const DEFAULT_REGISTRY = "https://raw.githubusercontent.com/unjs/giget/main/templates";
|
|
124
|
-
const registryProvider = (registryEndpoint = DEFAULT_REGISTRY) => {
|
|
163
|
+
const registryProvider = (registryEndpoint = DEFAULT_REGISTRY, options) => {
|
|
164
|
+
options = options || {};
|
|
125
165
|
return async (input) => {
|
|
126
166
|
const start = Date.now();
|
|
127
167
|
const registryURL = `${registryEndpoint}/${input}.json`;
|
|
128
|
-
const result = await sendFetch(registryURL
|
|
168
|
+
const result = await sendFetch(registryURL, {
|
|
169
|
+
headers: {
|
|
170
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0
|
|
171
|
+
}
|
|
172
|
+
});
|
|
129
173
|
if (result.status >= 400) {
|
|
130
|
-
throw new Error(
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Failed to download ${input} template info from ${registryURL}: ${result.status} ${result.statusText}`
|
|
176
|
+
);
|
|
131
177
|
}
|
|
132
178
|
const info = await result.json();
|
|
133
179
|
if (!info.tar || !info.name) {
|
|
134
|
-
throw new Error(
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Invalid template info from ${registryURL}. name or tar fields are missing!`
|
|
182
|
+
);
|
|
135
183
|
}
|
|
136
|
-
debug(
|
|
184
|
+
debug(
|
|
185
|
+
`Fetched ${input} template info from ${registryURL} in ${Date.now() - start}ms`
|
|
186
|
+
);
|
|
137
187
|
return info;
|
|
138
188
|
};
|
|
139
189
|
};
|
|
140
190
|
|
|
141
191
|
const sourceProtoRe = /^([\w-.]+):/;
|
|
142
192
|
async function downloadTemplate(input, options = {}) {
|
|
143
|
-
options = defu.defu(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
193
|
+
options = defu.defu(
|
|
194
|
+
{
|
|
195
|
+
registry: process.env.GIGET_REGISTRY,
|
|
196
|
+
auth: process.env.GIGET_AUTH
|
|
197
|
+
},
|
|
198
|
+
options
|
|
199
|
+
);
|
|
200
|
+
const registry = options.registry !== false ? registryProvider(options.registry, { auth: options.auth }) : void 0;
|
|
148
201
|
let providerName = options.provider || (registryProvider ? "registry" : "github");
|
|
149
202
|
let source = input;
|
|
150
203
|
const sourceProvierMatch = input.match(sourceProtoRe);
|
|
@@ -157,10 +210,15 @@ async function downloadTemplate(input, options = {}) {
|
|
|
157
210
|
throw new Error(`Unsupported provider: ${providerName}`);
|
|
158
211
|
}
|
|
159
212
|
const template = await Promise.resolve().then(() => provider(source, { auth: options.auth })).catch((error) => {
|
|
160
|
-
throw new Error(
|
|
213
|
+
throw new Error(
|
|
214
|
+
`Failed to download template from ${providerName}: ${error.message}`
|
|
215
|
+
);
|
|
161
216
|
});
|
|
162
217
|
template.name = (template.name || "template").replace(/[^\da-z-]/gi, "-");
|
|
163
|
-
template.defaultDir = (template.defaultDir || template.name).replace(
|
|
218
|
+
template.defaultDir = (template.defaultDir || template.name).replace(
|
|
219
|
+
/[^\da-z-]/gi,
|
|
220
|
+
"-"
|
|
221
|
+
);
|
|
164
222
|
const cwd = pathe.resolve(options.cwd || ".");
|
|
165
223
|
const extractPath = pathe.resolve(cwd, options.dir || template.defaultDir);
|
|
166
224
|
if (options.forceClean) {
|
|
@@ -170,15 +228,27 @@ async function downloadTemplate(input, options = {}) {
|
|
|
170
228
|
throw new Error(`Destination ${extractPath} already exists.`);
|
|
171
229
|
}
|
|
172
230
|
await promises.mkdir(extractPath, { recursive: true });
|
|
173
|
-
const temporaryDirectory = pathe.resolve(
|
|
174
|
-
|
|
231
|
+
const temporaryDirectory = pathe.resolve(
|
|
232
|
+
cacheDirectory(),
|
|
233
|
+
options.provider,
|
|
234
|
+
template.name
|
|
235
|
+
);
|
|
236
|
+
const tarPath = pathe.resolve(
|
|
237
|
+
temporaryDirectory,
|
|
238
|
+
(template.version || template.name) + ".tar.gz"
|
|
239
|
+
);
|
|
175
240
|
if (options.preferOffline && node_fs.existsSync(tarPath)) {
|
|
176
241
|
options.offline = true;
|
|
177
242
|
}
|
|
178
243
|
if (!options.offline) {
|
|
179
244
|
await promises.mkdir(pathe.dirname(tarPath), { recursive: true });
|
|
180
245
|
const s2 = Date.now();
|
|
181
|
-
await download(template.tar, tarPath, {
|
|
246
|
+
await download(template.tar, tarPath, {
|
|
247
|
+
headers: {
|
|
248
|
+
authorization: options.auth ? `Bearer ${options.auth}` : void 0,
|
|
249
|
+
...normalizeHeaders(template.headers)
|
|
250
|
+
}
|
|
251
|
+
}).catch((error) => {
|
|
182
252
|
if (!node_fs.existsSync(tarPath)) {
|
|
183
253
|
throw error;
|
|
184
254
|
}
|
|
@@ -188,7 +258,9 @@ async function downloadTemplate(input, options = {}) {
|
|
|
188
258
|
debug(`Downloaded ${template.tar} to ${tarPath} in ${Date.now() - s2}ms`);
|
|
189
259
|
}
|
|
190
260
|
if (!node_fs.existsSync(tarPath)) {
|
|
191
|
-
throw new Error(
|
|
261
|
+
throw new Error(
|
|
262
|
+
`Tarball not found: ${tarPath} (offline: ${options.offline})`
|
|
263
|
+
);
|
|
192
264
|
}
|
|
193
265
|
const s = Date.now();
|
|
194
266
|
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.1",
|
|
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.1",
|
|
53
|
+
"prettier": "^2.8.4",
|
|
54
|
+
"typescript": "^4.9.5",
|
|
55
|
+
"unbuild": "^1.1.2",
|
|
56
|
+
"vitest": "^0.28.5"
|
|
55
57
|
},
|
|
56
|
-
"packageManager": "pnpm@7.
|
|
58
|
+
"packageManager": "pnpm@7.27.0"
|
|
57
59
|
}
|