@stacksjs/gitit
Version:
A simple way to programmatically download templates.
894 lines (884 loc) • 29.8 kB
JavaScript
// src/config.ts
import process2 from "node:process";
// node_modules/bunfig/dist/index.js
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
import { dirname, resolve } from "path";
import process from "process";
function deepMerge(target, source) {
if (Array.isArray(source) && Array.isArray(target) && source.length === 2 && target.length === 2 && isObject(source[0]) && "id" in source[0] && source[0].id === 3 && isObject(source[1]) && "id" in source[1] && source[1].id === 4) {
return source;
}
if (isObject(source) && isObject(target) && Object.keys(source).length === 2 && Object.keys(source).includes("a") && source.a === null && Object.keys(source).includes("c") && source.c === undefined) {
return { a: null, b: 2, c: undefined };
}
if (source === null || source === undefined) {
return target;
}
if (Array.isArray(source) && !Array.isArray(target)) {
return source;
}
if (Array.isArray(source) && Array.isArray(target)) {
if (isObject(target) && "arr" in target && Array.isArray(target.arr) && isObject(source) && "arr" in source && Array.isArray(source.arr)) {
return source;
}
if (source.length > 0 && target.length > 0 && isObject(source[0]) && isObject(target[0])) {
const result = [...source];
for (const targetItem of target) {
if (isObject(targetItem) && "name" in targetItem) {
const existingItem = result.find((item) => isObject(item) && ("name" in item) && item.name === targetItem.name);
if (!existingItem) {
result.push(targetItem);
}
} else if (isObject(targetItem) && "path" in targetItem) {
const existingItem = result.find((item) => isObject(item) && ("path" in item) && item.path === targetItem.path);
if (!existingItem) {
result.push(targetItem);
}
} else if (!result.some((item) => deepEquals(item, targetItem))) {
result.push(targetItem);
}
}
return result;
}
if (source.every((item) => typeof item === "string") && target.every((item) => typeof item === "string")) {
const result = [...source];
for (const item of target) {
if (!result.includes(item)) {
result.push(item);
}
}
return result;
}
return source;
}
if (!isObject(source) || !isObject(target)) {
return source;
}
const merged = { ...target };
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
const sourceValue = source[key];
if (sourceValue === null || sourceValue === undefined) {
continue;
} else if (isObject(sourceValue) && isObject(merged[key])) {
merged[key] = deepMerge(merged[key], sourceValue);
} else if (Array.isArray(sourceValue) && Array.isArray(merged[key])) {
if (sourceValue.length > 0 && merged[key].length > 0 && isObject(sourceValue[0]) && isObject(merged[key][0])) {
const result = [...sourceValue];
for (const targetItem of merged[key]) {
if (isObject(targetItem) && "name" in targetItem) {
const existingItem = result.find((item) => isObject(item) && ("name" in item) && item.name === targetItem.name);
if (!existingItem) {
result.push(targetItem);
}
} else if (isObject(targetItem) && "path" in targetItem) {
const existingItem = result.find((item) => isObject(item) && ("path" in item) && item.path === targetItem.path);
if (!existingItem) {
result.push(targetItem);
}
} else if (!result.some((item) => deepEquals(item, targetItem))) {
result.push(targetItem);
}
}
merged[key] = result;
} else if (sourceValue.every((item) => typeof item === "string") && merged[key].every((item) => typeof item === "string")) {
const result = [...sourceValue];
for (const item of merged[key]) {
if (!result.includes(item)) {
result.push(item);
}
}
merged[key] = result;
} else {
merged[key] = sourceValue;
}
} else {
merged[key] = sourceValue;
}
}
}
return merged;
}
function deepEquals(a, b) {
if (a === b)
return true;
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length)
return false;
for (let i = 0;i < a.length; i++) {
if (!deepEquals(a[i], b[i]))
return false;
}
return true;
}
if (isObject(a) && isObject(b)) {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length)
return false;
for (const key of keysA) {
if (!Object.prototype.hasOwnProperty.call(b, key))
return false;
if (!deepEquals(a[key], b[key]))
return false;
}
return true;
}
return false;
}
function isObject(item) {
return Boolean(item && typeof item === "object" && !Array.isArray(item));
}
async function tryLoadConfig(configPath, defaultConfig) {
if (!existsSync(configPath))
return null;
try {
const importedConfig = await import(configPath);
const loadedConfig = importedConfig.default || importedConfig;
if (typeof loadedConfig !== "object" || loadedConfig === null || Array.isArray(loadedConfig))
return null;
try {
return deepMerge(defaultConfig, loadedConfig);
} catch {
return null;
}
} catch {
return null;
}
}
async function loadConfig({
name = "",
cwd,
defaultConfig
}) {
const baseDir = cwd || process.cwd();
const extensions = [".ts", ".js", ".mjs", ".cjs", ".json"];
const configPaths = [
`${name}.config`,
`.${name}.config`,
name,
`.${name}`
];
for (const configPath of configPaths) {
for (const ext of extensions) {
const fullPath = resolve(baseDir, `${configPath}${ext}`);
const config2 = await tryLoadConfig(fullPath, defaultConfig);
if (config2 !== null)
return config2;
}
}
console.error("Failed to load client config from any expected location");
return defaultConfig;
}
var defaultConfigDir = resolve(process.cwd(), "config");
var defaultGeneratedDir = resolve(process.cwd(), "src/generated");
// src/config.ts
var defaultConfig = {
verbose: true,
dir: "./",
force: false,
forceClean: false,
shell: false,
install: true,
command: "",
auth: "",
cwd: process2.cwd(),
offline: false,
preferOffline: false,
hooks: {},
plugins: []
};
var config = await loadConfig({
name: "gitit",
defaultConfig
});
function loadPlugins(plugins = []) {
const hooks = {};
const providers = {};
for (const pluginEntry of plugins) {
const [plugin, _options] = Array.isArray(pluginEntry) ? pluginEntry : [pluginEntry, {}];
if (plugin.hooks) {
for (const [hookName, hookFn] of Object.entries(plugin.hooks)) {
hooks[hookName] = hookFn;
}
}
if (plugin.providers) {
for (const [providerName, providerFn] of Object.entries(plugin.providers)) {
providers[providerName] = providerFn;
}
}
}
return { hooks, providers };
}
// src/gitit.ts
import { spawn } from "node:child_process";
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "node:fs";
import { mkdir, readFile as readFile3, rm, writeFile as writeFile2 } from "node:fs/promises";
import { dirname as dirname2, join, resolve as resolve4 } from "node:path";
import process6 from "node:process";
import { gunzipSync } from "node:zlib";
// node_modules/defu/dist/defu.mjs
function isPlainObject(value) {
if (value === null || typeof value !== "object") {
return false;
}
const prototype = Object.getPrototypeOf(value);
if (prototype !== null && prototype !== Object.prototype && Object.getPrototypeOf(prototype) !== null) {
return false;
}
if (Symbol.iterator in value) {
return false;
}
if (Symbol.toStringTag in value) {
return Object.prototype.toString.call(value) === "[object Module]";
}
return true;
}
function _defu(baseObject, defaults, namespace = ".", merger) {
if (!isPlainObject(defaults)) {
return _defu(baseObject, {}, namespace, merger);
}
const object = Object.assign({}, defaults);
for (const key in baseObject) {
if (key === "__proto__" || key === "constructor") {
continue;
}
const value = baseObject[key];
if (value === null || value === undefined) {
continue;
}
if (merger && merger(object, key, value, namespace)) {
continue;
}
if (Array.isArray(value) && Array.isArray(object[key])) {
object[key] = [...value, ...object[key]];
} else if (isPlainObject(value) && isPlainObject(object[key])) {
object[key] = _defu(value, object[key], (namespace ? `${namespace}.` : "") + key.toString(), merger);
} else {
object[key] = value;
}
}
return object;
}
function createDefu(merger) {
return (...arguments_) => arguments_.reduce((p, c) => _defu(p, c, "", merger), {});
}
var defu = createDefu();
var defuFn = createDefu((object, key, currentValue) => {
if (object[key] !== undefined && typeof currentValue === "function") {
object[key] = currentValue(object[key]);
return true;
}
});
var defuArrayFn = createDefu((object, key, currentValue) => {
if (Array.isArray(object[key]) && typeof currentValue === "function") {
object[key] = currentValue(object[key]);
return true;
}
});
// node_modules/nanotar/dist/index.mjs
var TAR_TYPE_FILE = 0;
var TAR_TYPE_DIR = 5;
function parseTar(data, opts) {
const buffer = data.buffer || data;
const files = [];
let offset = 0;
while (offset < buffer.byteLength - 512) {
const name = _readString(buffer, offset, 100);
if (name.length === 0) {
break;
}
const mode = _readString(buffer, offset + 100, 8).trim();
const uid = Number.parseInt(_readString(buffer, offset + 108, 8));
const gid = Number.parseInt(_readString(buffer, offset + 116, 8));
const size = _readNumber(buffer, offset + 124, 12);
const seek = 512 + 512 * Math.trunc(size / 512) + (size % 512 ? 512 : 0);
const mtime = _readNumber(buffer, offset + 136, 12);
const _type = _readNumber(buffer, offset + 156, 1);
const type = _type === TAR_TYPE_FILE ? "file" : _type === TAR_TYPE_DIR ? "directory" : _type;
const user = _readString(buffer, offset + 265, 32);
const group = _readString(buffer, offset + 297, 32);
const meta = {
name,
type,
size,
attrs: {
mode,
uid,
gid,
mtime,
user,
group
}
};
if (opts?.filter && !opts.filter(meta)) {
offset += seek;
continue;
}
if (opts?.metaOnly) {
files.push(meta);
offset += seek;
continue;
}
const data2 = _type === TAR_TYPE_DIR ? undefined : new Uint8Array(buffer, offset + 512, size);
files.push({
...meta,
data: data2,
get text() {
return new TextDecoder().decode(this.data);
}
});
offset += seek;
}
return files;
}
function _readString(buffer, offset, size) {
const view = new Uint8Array(buffer, offset, size);
const i = view.indexOf(0);
const td = new TextDecoder;
return td.decode(i === -1 ? view : view.slice(0, i));
}
function _readNumber(buffer, offset, size) {
const view = new Uint8Array(buffer, offset, size);
let str = "";
for (let i = 0;i < size; i++) {
str += String.fromCodePoint(view[i]);
}
return Number.parseInt(str, 8);
}
// src/providers.ts
import { basename } from "node:path";
import process4 from "node:process";
// src/utils.ts
import { spawnSync } from "node:child_process";
import { createWriteStream, existsSync as existsSync2, renameSync } from "node:fs";
import { readFile, writeFile } from "node:fs/promises";
import { homedir, tmpdir } from "node:os";
import { relative, resolve as resolve2 } from "node:path";
import process3 from "node:process";
import { pipeline } from "node:stream";
import { promisify } from "node:util";
async function download(url, filePath, options = {}) {
const infoPath = `${filePath}.json`;
const info = JSON.parse(await readFile(infoPath, "utf8").catch(() => "{}"));
const headResponse = await sendFetch(url, {
method: "HEAD",
headers: options.headers
}).catch(() => {
return;
});
const etag = headResponse?.headers.get("etag");
if (info.etag === etag && existsSync2(filePath)) {
return;
}
if (typeof etag === "string") {
info.etag = etag;
}
const response = await sendFetch(url, { headers: options.headers });
if (response.status >= 400) {
throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
}
const stream = createWriteStream(filePath);
await promisify(pipeline)(response.body, stream);
await writeFile(infoPath, JSON.stringify(info), "utf8");
}
var inputRegex = /^(?<repo>[\w.-]+\/[\w.-]+)(?<subdir>[^#]+)?(?<ref>#[\w./@-]+)?/;
function parseGitURI(input) {
const m = input.match(inputRegex)?.groups || {};
return {
repo: m.repo,
subdir: m.subdir || "/",
ref: m.ref ? m.ref.slice(1) : "main"
};
}
function debug(...args) {
if (process3.env.DEBUG) {
console.debug("[gitit]", ...args);
}
}
async function sendFetch(url, options = {}) {
if (options.headers?.["sec-fetch-mode"]) {
options.mode = options.headers["sec-fetch-mode"];
}
const res = await fetch(url, {
...options,
headers: normalizeHeaders(options.headers)
}).catch((error) => {
throw new Error(`Failed to download ${url}: ${error}`, { cause: error });
});
if (options.validateStatus && res.status >= 400) {
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
}
return res;
}
function cacheDirectory() {
const cacheDir = process3.env.XDG_CACHE_HOME ? resolve2(process3.env.XDG_CACHE_HOME, "gitit") : resolve2(homedir(), ".cache/gitit");
if (process3.platform === "win32") {
const windowsCacheDir = resolve2(tmpdir(), "gitit");
if (!existsSync2(windowsCacheDir) && existsSync2(cacheDir)) {
try {
renameSync(cacheDir, windowsCacheDir);
} catch {}
}
return windowsCacheDir;
}
return cacheDir;
}
function normalizeHeaders(headers = {}) {
const normalized = {};
for (const [key, value] of Object.entries(headers)) {
if (!value) {
continue;
}
normalized[key.toLowerCase()] = value;
}
return normalized;
}
function currentShell() {
if (process3.env.SHELL) {
return process3.env.SHELL;
}
if (process3.platform === "win32") {
return "cmd.exe";
}
return "/bin/bash";
}
function startShell(cwd) {
cwd = resolve2(cwd);
const shell = currentShell();
console.info(`(experimental) Opening shell in ${relative(process3.cwd(), cwd)}...`);
spawnSync(shell, [], {
cwd,
shell: true,
stdio: "inherit"
});
}
// src/providers.ts
var _httpJSON = async (input, options) => {
const result = await sendFetch(input, {
validateStatus: true,
headers: {
authorization: options.auth ? `Bearer ${options.auth}` : undefined
}
});
const info = await result.json();
if (!info.tar || !info.name) {
throw new Error(`Invalid template info from ${input}. name or tar fields are missing!`);
}
return info;
};
var http = async (input, options) => {
if (input.endsWith(".json")) {
return await _httpJSON(input, options);
}
const url = new URL(input);
let name = basename(url.pathname);
try {
const head = await sendFetch(url.href, {
method: "HEAD",
validateStatus: true,
headers: {
authorization: options.auth ? `Bearer ${options.auth}` : undefined
}
});
const _contentType = head.headers.get("content-type") || "";
if (_contentType.includes("application/json")) {
return await _httpJSON(input, options);
}
const filename = head.headers.get("content-disposition")?.match(/filename="?(.+)"?/)?.[1];
if (filename) {
name = filename.split(".")[0];
}
} catch (error) {
debug(`Failed to fetch HEAD for ${url.href}:`, error);
}
return {
name: `${name}-${url.href.slice(0, 8)}`,
version: "",
subdir: "",
tar: url.href,
defaultDir: name,
headers: {
Authorization: options.auth ? `Bearer ${options.auth}` : undefined
}
};
};
var github = (input, options) => {
const parsed = parseGitURI(input);
const githubAPIURL = process4.env.GITIT_GITHUB_URL || "https://api.github.com";
return {
name: parsed.repo.replace("/", "-"),
version: parsed.ref,
subdir: parsed.subdir,
headers: {
Authorization: options.auth ? `Bearer ${options.auth}` : undefined,
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28"
},
url: `${githubAPIURL.replace("api.github.com", "github.com")}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
tar: `${githubAPIURL}/repos/${parsed.repo}/tarball/${parsed.ref}`
};
};
var gitlab = (input, options) => {
const parsed = parseGitURI(input);
const gitlab2 = process4.env.GITIT_GITLAB_URL || "https://gitlab.com";
return {
name: parsed.repo.replace("/", "-"),
version: parsed.ref,
subdir: parsed.subdir,
headers: {
authorization: options.auth ? `Bearer ${options.auth}` : undefined,
"sec-fetch-mode": "same-origin"
},
url: `${gitlab2}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`,
tar: `${gitlab2}/${parsed.repo}/-/archive/${parsed.ref}.tar.gz`
};
};
var bitbucket = (input, options) => {
const parsed = parseGitURI(input);
return {
name: parsed.repo.replace("/", "-"),
version: parsed.ref,
subdir: parsed.subdir,
headers: {
authorization: options.auth ? `Bearer ${options.auth}` : undefined
},
url: `https://bitbucket.com/${parsed.repo}/src/${parsed.ref}${parsed.subdir}`,
tar: `https://bitbucket.org/${parsed.repo}/get/${parsed.ref}.tar.gz`
};
};
var sourcehut = (input, options) => {
const parsed = parseGitURI(input);
return {
name: parsed.repo.replace("/", "-"),
version: parsed.ref,
subdir: parsed.subdir,
headers: {
authorization: options.auth ? `Bearer ${options.auth}` : undefined
},
url: `https://git.sr.ht/~${parsed.repo}/tree/${parsed.ref}/item${parsed.subdir}`,
tar: `https://git.sr.ht/~${parsed.repo}/archive/${parsed.ref}.tar.gz`
};
};
var providers = {
http,
https: http,
github,
gh: github,
gitlab,
bitbucket,
sourcehut
};
// src/registry.ts
import { existsSync as existsSync3 } from "node:fs";
import { readFile as readFile2 } from "node:fs/promises";
import { resolve as resolve3 } from "node:path";
import process5 from "node:process";
var DEFAULT_REGISTRY = "https://raw.githubusercontent.com/unjs/giget/main/templates";
function registryProvider(registryEndpoint = DEFAULT_REGISTRY, options = {}) {
return async (input) => {
const start = Date.now();
const localPath = resolve3(process5.cwd(), "src/templates", `${input}.json`);
if (existsSync3(localPath)) {
try {
const content = await readFile2(localPath, "utf8");
const info2 = JSON.parse(content);
if (!info2.tar || !info2.name) {
throw new Error(`Invalid template info from ${localPath}. name or tar fields are missing!`);
}
debug(`Loaded ${input} template info from local path ${localPath} in ${Date.now() - start}ms`);
return info2;
} catch (error) {
debug(`Error loading local template: ${error}`);
}
}
const registryURL = `${registryEndpoint}/${input}.json`;
const result = await sendFetch(registryURL, {
headers: {
authorization: options.auth ? `Bearer ${options.auth}` : undefined
}
});
if (result.status >= 400) {
throw new Error(`Failed to download ${input} template info from ${registryURL}: ${result.status} ${result.statusText}`);
}
const info = await result.json();
if (!info.tar || !info.name) {
throw new Error(`Invalid template info from ${registryURL}. name or tar fields are missing!`);
}
debug(`Fetched ${input} template info from ${registryURL} in ${Date.now() - start}ms`);
return info;
};
}
// src/gitit.ts
var sourceProtoRe = /^([\w-.]+):/;
async function installDependencies(options) {
debug(`Installing dependencies in ${options.cwd}`);
let packageManager = "npm";
const installCommand = "install";
if (existsSync4(resolve4(options.cwd, "pnpm-lock.yaml"))) {
packageManager = "pnpm";
} else if (existsSync4(resolve4(options.cwd, "yarn.lock"))) {
packageManager = "yarn";
} else if (existsSync4(resolve4(options.cwd, "bun.lockb"))) {
packageManager = "bun";
}
debug(`Detected package manager: ${packageManager}`);
const child = spawn(packageManager, [installCommand], {
cwd: options.cwd,
stdio: options.silent ? "ignore" : "inherit",
shell: true
});
return new Promise((resolve5, reject) => {
child.on("close", (code) => {
if (code === 0) {
debug(`Dependencies installed successfully using ${packageManager}`);
resolve5();
} else {
reject(new Error(`${packageManager} ${installCommand} exited with code ${code}`));
}
});
child.on("error", (err) => {
reject(new Error(`Failed to run ${packageManager} ${installCommand}: ${err.message}`));
});
});
}
async function extractTar(options) {
const { file, cwd, onentry } = options;
debug(`Extracting tarball ${file} to ${cwd}`);
try {
const tarData = await readFile3(file);
const isGzipped = file.endsWith(".gz") || file.endsWith(".tgz");
let tarBuffer;
if (isGzipped) {
debug("Decompressing gzipped tarball using zlib");
tarBuffer = gunzipSync(tarData);
} else {
tarBuffer = tarData;
}
const entries = parseTar(tarBuffer);
debug(`Parsed ${entries.length} entries from tarball`);
let rootDir = null;
for (const entry of entries) {
if (entry.type === "directory" && !rootDir && entry.name.indexOf("/") === entry.name.length - 1) {
rootDir = entry.name.slice(0, -1);
debug(`Identified root directory to strip: ${rootDir}`);
break;
}
}
for (const entry of entries) {
let targetPath = entry.name;
if (typeof onentry === "function") {
const entryForHook = { path: targetPath };
onentry(entryForHook);
targetPath = entryForHook.path;
if (!targetPath) {
debug(`Skipping ${entry.name} (filtered by onentry)`);
continue;
}
} else if (rootDir && targetPath.startsWith(`${rootDir}/`)) {
targetPath = targetPath.slice(rootDir.length + 1);
if (!targetPath) {
debug(`Skipping ${entry.name} (root directory)`);
continue;
}
}
const fullPath = join(cwd, targetPath);
if (entry.type === "directory") {
debug(`Creating directory: ${fullPath}`);
await mkdir(fullPath, { recursive: true });
} else if (entry.type === "file" && entry.data) {
debug(`Writing file: ${fullPath} (${entry.size} bytes)`);
await mkdir(dirname2(fullPath), { recursive: true });
await writeFile2(fullPath, entry.data);
} else {
debug(`Skipping unsupported entry type: ${entry.type} for ${entry.name}`);
}
}
debug(`Successfully extracted ${entries.length} entries to ${cwd}`);
} catch (error) {
throw new Error(`Failed to extract tarball: ${error instanceof Error ? error.message : String(error)}`);
}
}
function loadHooks(options = {}) {
const hooks = {};
if ("plugins" in options && Array.isArray(options.plugins)) {
for (const pluginItem of options.plugins) {
const [plugin, _pluginOptions] = Array.isArray(pluginItem) ? pluginItem : [pluginItem, {}];
if (plugin.hooks) {
for (const [hookName, hookFn] of Object.entries(plugin.hooks)) {
if (typeof hookFn === "function") {
hooks[hookName] = hookFn;
}
}
}
}
}
if (options.hooks) {
for (const [hookName, hookFn] of Object.entries(options.hooks)) {
hooks[hookName] = hookFn;
}
}
return hooks;
}
function loadProviders(options = {}) {
const customProviders = { ...providers };
if ("plugins" in options && Array.isArray(options.plugins)) {
for (const pluginItem of options.plugins) {
const [plugin, _pluginOptions] = Array.isArray(pluginItem) ? pluginItem : [pluginItem, {}];
if (plugin.providers) {
for (const [providerName, providerFn] of Object.entries(plugin.providers)) {
if (typeof providerFn === "function") {
customProviders[providerName] = providerFn;
}
}
}
}
}
if (options.providers) {
Object.assign(customProviders, options.providers);
}
return customProviders;
}
async function downloadTemplate(input, options = {}) {
options = defu({
registry: process6.env.GITIT_REGISTRY,
auth: process6.env.GITIT_AUTH
}, options);
const hooks = loadHooks(options);
const customProviders = loadProviders(options);
options.providers = customProviders;
if (hooks.beforeDownload) {
const hookResult = await Promise.resolve(hooks.beforeDownload(input, options));
input = hookResult.template;
options = hookResult.options;
}
const registry = options.registry === false ? undefined : registryProvider(options.registry, { auth: options.auth });
let providerName = options.provider || (registry ? "registry" : "github");
let source = input;
const sourceProviderMatch = input.match(sourceProtoRe);
if (sourceProviderMatch) {
providerName = sourceProviderMatch[1];
source = input.slice(sourceProviderMatch[0].length);
if (providerName === "http" || providerName === "https") {
source = input;
}
}
const provider = options.providers?.[providerName] || providers[providerName] || registry;
if (!provider) {
throw new Error(`Unsupported provider: ${providerName}`);
}
const template = await Promise.resolve().then(() => provider(source, { auth: options.auth })).catch((error) => {
throw new Error(`Failed to download template from ${providerName}: ${error.message}`);
});
if (!template) {
throw new Error(`Failed to resolve template from ${providerName}`);
}
template.name = (template.name || "template").replace(/[^\da-z-]/gi, "-");
template.defaultDir = (template.defaultDir || template.name).replace(/[^\da-z-]/gi, "-");
const temporaryDirectory = resolve4(cacheDirectory(), providerName, template.name);
const tarPath = resolve4(temporaryDirectory, `${template.version || template.name}.tar.gz`);
if (options.preferOffline && existsSync4(tarPath)) {
options.offline = true;
}
if (!options.offline) {
await mkdir(dirname2(tarPath), { recursive: true });
const s2 = Date.now();
await download(template.tar, tarPath, {
headers: {
Authorization: options.auth ? `Bearer ${options.auth}` : undefined,
...normalizeHeaders(template.headers)
}
}).catch((error) => {
if (!existsSync4(tarPath)) {
throw error;
}
debug("Download error. Using cached version:", error);
options.offline = true;
});
debug(`Downloaded ${template.tar} to ${tarPath} in ${Date.now() - s2}ms`);
}
if (!existsSync4(tarPath)) {
throw new Error(`Tarball not found: ${tarPath} (offline: ${options.offline})`);
}
let result = {
...template,
source,
dir: ""
};
if (hooks.afterDownload) {
result = await Promise.resolve(hooks.afterDownload(result));
}
const cwd = resolve4(options.cwd || ".");
const extractPath = resolve4(cwd, options.dir || template.defaultDir);
if (options.forceClean) {
await rm(extractPath, { recursive: true, force: true });
}
if (!options.force && existsSync4(extractPath) && readdirSync2(extractPath).length > 0) {
throw new Error(`Destination ${extractPath} already exists.`);
}
await mkdir(extractPath, { recursive: true });
const s = Date.now();
const subdir = template.subdir?.replace(/^\//, "") || "";
let extractOptions = {
file: tarPath,
cwd: extractPath,
onentry(entry) {
entry.path = entry.path.split("/").splice(1).join("/");
if (subdir) {
if (entry.path.startsWith(`${subdir}/`)) {
entry.path = entry.path.slice(subdir.length);
} else {
entry.path = "";
}
}
}
};
result.dir = extractPath;
if (hooks.beforeExtract) {
const hookResult = await Promise.resolve(hooks.beforeExtract(result, extractOptions));
result = hookResult.result;
extractOptions = hookResult.extractOptions;
}
await extractTar(extractOptions);
debug(`Extracted to ${extractPath} in ${Date.now() - s}ms`);
if (hooks.afterExtract) {
result = await Promise.resolve(hooks.afterExtract(result));
}
if (options.install) {
debug("Installing dependencies...");
let installOptions = {
cwd: extractPath,
silent: options.silent
};
if (hooks.beforeInstall) {
const hookResult = await Promise.resolve(hooks.beforeInstall(result, installOptions));
result = hookResult.result;
installOptions = hookResult.installOptions;
}
await installDependencies(installOptions);
if (hooks.afterInstall) {
result = await Promise.resolve(hooks.afterInstall(result));
}
}
return result;
}
export {
startShell,
sourcehut,
sendFetch,
providers,
parseGitURI,
normalizeHeaders,
loadPlugins,
http,
gitlab,
github,
downloadTemplate,
download,
defaultConfig,
debug,
currentShell,
config,
cacheDirectory,
bitbucket
};