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 CHANGED
@@ -36,7 +36,7 @@ npx giget@latest <template> [<dir>] [...options]
36
36
 
37
37
  ### Options
38
38
 
39
- - `--force`: Clone to exsiting directory even if exists.
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 github repository as template registry but also you can build something more powerful by bringing your own API.
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 'giget'
116
+ import { downloadTemplate } from "giget";
117
117
 
118
118
  // CommonJS
119
- const { downloadTemplate } = require('giget')
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('github:unjs/template')
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 'giget'
162
+ import type { TemplateProvider } from "giget";
163
163
 
164
164
  const rainbow: TemplateProvider = async (input, { auth }) => {
165
165
  return {
166
- name: 'rainbow',
166
+ name: "rainbow",
167
167
  version: input,
168
- headers: { Authorization: auth },
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('rainbow:one', { providers: { rainbow } })
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 'giget'
184
+ import { registryProvider } from "giget";
183
185
 
184
- const themes = registryProvider('https://raw.githubusercontent.com/unjs/giget/main/templates')
186
+ const themes = registryProvider(
187
+ "https://raw.githubusercontent.com/unjs/giget/main/templates"
188
+ );
185
189
 
186
- const { source, dir } = await downloadRepo('themes:test', { providers: { themes } })
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 inspering from former projects. In comparation 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.
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.1028e17a.cjs');
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: ["help", "force", "force-clean", "offline", "prefer-offline", "shell", "verbose"],
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("Usage: npx getgit@latest <input> [<dir>] [--force] [--force-clean] [--offline] [--prefer-offline] [--shell] [--registry] [--no-registry] [--verbose] [--cwd] [--auth]");
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(`\u2728 Successfully cloned ${colorette.cyan(r.name || r.url)} to ${colorette.cyan(node_path.relative(process.cwd(), r.dir))}
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
@@ -1 +1,2 @@
1
1
 
2
+ export { }
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.5e7ec864.mjs';
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: ["help", "force", "force-clean", "offline", "prefer-offline", "shell", "verbose"],
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("Usage: npx getgit@latest <input> [<dir>] [--force] [--force-clean] [--offline] [--prefer-offline] [--shell] [--registry] [--no-registry] [--verbose] [--cwd] [--auth]");
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(`\u2728 Successfully cloned ${cyan(r.name || r.url)} to ${cyan(relative(process.cwd(), r.dir))}
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.1028e17a.cjs');
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
- declare type TemplateProvider = (input: string, options: {
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
- declare type DownloadTemplateResult = Omit<TemplateInfo, "dir" | "source"> & {
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) => TemplateProvider;
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.5e7ec864.mjs';
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(await readFile(infoPath, "utf8").catch(() => "{}"));
16
- const headResponse = await sendFetch(url, { method: "HEAD", headers: options.headers }).catch(() => void 0);
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(`Failed to download ${url}: ${response.status} ${response.statusText}`);
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
- 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);
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(`(experimental) Opening shell in ${relative(process.cwd(), cwd)}...`);
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: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
76
- url: `https://github.com/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
77
- tar: `https://github.com/${parsed.repo}/archive/${parsed.ref}.tar.gz`
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: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
87
- url: `https://gitlab.com/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
88
- tar: `https://gitlab.com/${parsed.repo}/-/archive/${parsed.ref}.tar.gz`
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: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
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: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
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(`Failed to download ${input} template info from ${registryURL}: ${result.status} ${result.statusText}`);
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(`Invalid template info from ${registryURL}. name or tar fields are missing!`);
178
+ throw new Error(
179
+ `Invalid template info from ${registryURL}. name or tar fields are missing!`
180
+ );
133
181
  }
134
- debug(`Fetched ${input} template info from ${registryURL} in ${Date.now() - start}ms`);
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
- registry: process.env.GIGET_REGISTRY,
143
- auth: process.env.GIGET_AUTH
144
- }, options);
145
- const registry = options.registry !== false ? registryProvider(options.registry) : void 0;
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(`Failed to download template from ${providerName}: ${error.message}`);
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(/[^\da-z-]/gi, "-");
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(homedir(), ".giget", options.provider, template.name);
172
- const tarPath = resolve(temporaryDirectory, (template.version || template.name) + ".tar.gz");
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, { headers: template.headers }).catch((error) => {
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(`Tarball not found: ${tarPath} (offline: ${options.offline})`);
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(await promises.readFile(infoPath, "utf8").catch(() => "{}"));
18
- const headResponse = await sendFetch(url, { method: "HEAD", headers: options.headers }).catch(() => void 0);
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(`Failed to download ${url}: ${response.status} ${response.statusText}`);
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
- 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);
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(`(experimental) Opening shell in ${pathe.relative(process.cwd(), cwd)}...`);
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: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
78
- url: `https://github.com/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
79
- tar: `https://github.com/${parsed.repo}/archive/${parsed.ref}.tar.gz`
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: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
89
- url: `https://gitlab.com/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
90
- tar: `https://gitlab.com/${parsed.repo}/-/archive/${parsed.ref}.tar.gz`
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: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
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: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 },
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(`Failed to download ${input} template info from ${registryURL}: ${result.status} ${result.statusText}`);
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(`Invalid template info from ${registryURL}. name or tar fields are missing!`);
180
+ throw new Error(
181
+ `Invalid template info from ${registryURL}. name or tar fields are missing!`
182
+ );
135
183
  }
136
- debug(`Fetched ${input} template info from ${registryURL} in ${Date.now() - start}ms`);
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
- registry: process.env.GIGET_REGISTRY,
145
- auth: process.env.GIGET_AUTH
146
- }, options);
147
- const registry = options.registry !== false ? registryProvider(options.registry) : void 0;
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(`Failed to download template from ${providerName}: ${error.message}`);
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(/[^\da-z-]/gi, "-");
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(node_os.homedir(), ".giget", options.provider, template.name);
174
- const tarPath = pathe.resolve(temporaryDirectory, (template.version || template.name) + ".tar.gz");
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, { headers: template.headers }).catch((error) => {
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(`Tarball not found: ${tarPath} (offline: ${options.offline})`);
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.0.0",
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 && standard-version && git push --follow-tags && pnpm publish",
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.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.1",
41
- "pathe": "^1.0.0",
42
- "tar": "^6.1.12"
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.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"
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.16.0"
58
+ "packageManager": "pnpm@7.27.0"
57
59
  }