UNPKG

takin

Version:

Front end engineering base toolchain and scaffold

332 lines 11.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.download = exports.getName = exports.supportProtocol = exports.parseOptions = exports.getGitHash = exports.addSupportGitSite = void 0; const execa_1 = __importDefault(require("execa")); const fs_extra_1 = __importDefault(require("fs-extra")); const lodash_1 = __importDefault(require("lodash")); const path_1 = __importDefault(require("path")); const errors_1 = require("../errors"); const logger_1 = require("../logger"); const tar = __importStar(require("./tar")); const GIT_PROTOCOL_PREFIX = /^git:/; /** * 支持的链接类型: * - http://github.com/user/repo.git#v1.0.27 * - https://github.com/user/repo.git#v1.0.27 * - git@github.com:user/repo.git * - user/repo.git#v1.0.27 * - github:user/repo.git#v1.0.27 */ const GIT_URL_REGEXP = /^(?:(?:https?:\/\/)?([^:/]+\.[^:/]+)\/|git@([^:/]+)[:/]|([^/]+):)?([^/.~@/\s]+)\/([^/\s#]+)(?:((?:\/[^/\s#]+)+))?(?:\/)?(?:#(.+))?/; const DEFAULT_SITE = 'github.com'; /** * 支持的站点 */ const SUPPORTED_SITES = new Map([ [ 'github.com', { type: 'github', generateGitTarUrl: getTarUrlByRepo } ], [ 'gitlab.com', { type: 'gitlab', generateGitTarUrl: getTarUrlByRepo } ], [ 'bitbucket.org', { type: 'bitbucket', generateGitTarUrl: getTarUrlByRepo } ] ]); /** * 添加支持的 git 站点 * @param siteUrl - 站点地址, 如 github.com * @param siteType - 站点类型, 如 git / gitlab / bitbucket */ function addSupportGitSite(siteUrl, siteConfig) { if (SUPPORTED_SITES.has(siteUrl)) { return logger_1.logger.warn(`跳过添加 ${siteConfig.type} 类型的 Git 站点: ${siteUrl}, 原因: 已存在.`); } SUPPORTED_SITES.set(siteUrl, { type: siteConfig.type, generateGitTarUrl: siteConfig.generateGitTarUrl || getTarUrlByRepo }); } exports.addSupportGitSite = addSupportGitSite; function parseGitUrl(src) { const match = GIT_URL_REGEXP.exec(src); if (!match) { throw new errors_1.DownloaderError(`无法解析链接: ${src}`); } const site = match[1] || match[2] || match[3] || DEFAULT_SITE; const user = match[4]; const name = match[5].replace(/\.git$/, ''); const subdir = match[6]; const ref = match[7] || 'HEAD'; const url = `https://${site}/${user}/${name}`; const ssh = `git@${site}:${user}/${name}`; const mode = SUPPORTED_SITES.has(site) ? 'tar' : 'git'; return { site, user, name, ref, url, ssh, subdir, mode }; } /** * * @param repo - 仓库信息 * @returns 生成的 tar 地址 */ async function getTarUrlByRepo(repo) { var _a; const siteType = (_a = SUPPORTED_SITES.get(repo.site)) === null || _a === void 0 ? void 0 : _a.type; const hash = await getGitHash(repo); if (!hash) throw new errors_1.DownloaderError(`${repo.url} 未解析出有效的 hash 信息`); // 这里保存下 hash 信息 repo.hash = hash; const url = siteType === 'gitlab' ? `${repo.url}/-/archive/${hash}/${repo.name}-${hash}.tar.gz` : siteType === 'bitbucket' ? `${repo.url}/get/${hash}.tar.gz` : `${repo.url}/archive/${hash}.tar.gz`; const repoDir = `${repo.name}-${hash}`; const strip = (repo.subdir ? path_1.default.posix.join(repoDir, repo.subdir) : repoDir).split('/').length; return { ...lodash_1.default.omit(repo, [ 'site', 'user', 'name', 'ref', 'hash', 'ssh', 'subdir', 'mode' ]), url, strip }; } /** * 获取仓库 ref 信息 * @param repo - 仓库信息 * @param type - 获取 ref 信息的方式, 支持 url 或 ssh 两种 * @returns ref 信息数组 */ async function fetchGitRefs(repo, type) { try { const { stdout } = await execa_1.default.command(`git ls-remote ${repo[type]}`); return stdout .split('\n') .filter(Boolean) .map((row) => { const [hash, ref] = row.split('\t'); if (ref === 'HEAD') { return { type: 'HEAD', hash }; } const match = /refs\/([\w-]+)\/(.+)/.exec(ref); if (!match) { throw new errors_1.DownloaderError(`无法解析 ${ref}`); } return { type: match[1] === 'heads' ? 'branch' : match[1] === 'refs' ? 'ref' : match[1], name: match[2], hash }; }); } catch (error) { throw new errors_1.DownloaderError(`拉取 Git 信息失败 ${repo.url}, 原因: ${error.message}`); } } async function getGitHash(repo) { var _a; let refs; try { // 优先从 url 拉取, 但可能没权限 refs = await fetchGitRefs(repo, 'url'); } catch (error) { logger_1.logger.debug(error === null || error === void 0 ? void 0 : error.message); logger_1.logger.debug('将使用 ssh 模式重新拉取 Git 信息'); refs = await fetchGitRefs(repo, 'ssh'); } refs = refs || []; if (repo.ref === 'HEAD') { return (_a = refs.find((ref) => ref.type === 'HEAD')) === null || _a === void 0 ? void 0 : _a.hash; } for (const ref of refs) { if (ref.name === repo.ref) { logger_1.logger.debug('成功匹配到 Git Hash:', repo.ref, '=>', ref.hash); return ref.hash; } } if (repo.ref.length < 8) return; for (const ref of refs) { if (ref.hash.startsWith(repo.ref)) return ref.hash; } } exports.getGitHash = getGitHash; async function downloadByGit(repo, dest) { const gitDir = path_1.default.resolve(dest, '.git'); await execa_1.default.command(`git clone ${repo.ssh} ${dest}`); // 强制指定 --git-dir 避免仓库套仓库时 git 识别错误 const gitOptions = [`-C ${dest}`, `--git-dir ${gitDir}`].join(' '); await execa_1.default.command(`git ${gitOptions} checkout ${repo.ref} --force`); logger_1.logger.debug(`切换仓库 ref 为: ${repo.ref}`); // 尝试获取 ref 对应的 hash 值并保存 try { const { stdout } = await execa_1.default.command(`git ${gitOptions} rev-list -n 1 ${repo.ref}`); const hash = stdout.split('\n').filter(Boolean)[0]; if (hash) repo.hash = hash; logger_1.logger.debug(`获取到当前 ref 对应的 hash 值: ${hash}`); } catch (error) { logger_1.logger.debug('获取 hash 值失败', error); } // 清理 .git 文件夹 await fs_extra_1.default.emptyDir(gitDir); } async function downloadByTar(repo, dest) { var _a; const generateGitTarUrl = (_a = SUPPORTED_SITES.get(repo.site)) === null || _a === void 0 ? void 0 : _a.generateGitTarUrl; if (!generateGitTarUrl) { throw new errors_1.DownloaderError(`Git 站点: ${repo.site} 缺少 generateGitTarUrl 方法支持`); } const tarOptions = tar.parseOptions(await generateGitTarUrl(repo)); await tar.download(tarOptions, dest); } /** * 解析 git 选项或链接 * @param urlOrOptions - git 下载链接或选项 * @returns git 仓库信息 */ function parseOptions(urlOrOptions) { let options; if (typeof urlOrOptions === 'string') { options = parseGitUrl(urlOrOptions); } else { const ref = urlOrOptions.ref || urlOrOptions.commit || urlOrOptions.tag || urlOrOptions.branch || ''; const opts = { ...lodash_1.default.omit(urlOrOptions, ['commit', 'tag', 'branch']), ref }; const url = opts.url; delete opts.url; options = { ...parseGitUrl(url), ...opts }; } return options; } exports.parseOptions = parseOptions; /** * 判断是否支持处理当前链接 * @param url - 链接 * @returns 是否支持该链接 */ function supportProtocol(url) { if (!url) return false; // 去除 git: 的协议头 url = url.replace(GIT_PROTOCOL_PREFIX, ''); const match = GIT_URL_REGEXP.exec(url); if (!match) return false; const siteTypeOrHost = match[1] || match[2] || match[3]; if (siteTypeOrHost) { // 检查是否为支持的站点或站点类型 for (const [host, site] of SUPPORTED_SITES) { if (siteTypeOrHost === host || siteTypeOrHost === site.type) return true; continue; } return false; } return true; } exports.supportProtocol = supportProtocol; /** * 从 git 仓库选项中获取名称 * @param options - git 仓库选项 * @returns 名称 */ function getName(options) { return `${options.user}/${options.name}`; } exports.getName = getName; /** * 下载 git repo 到指定的目录 * @param options - git 选项 * @param dest - 下载目录 * @returns 下载并解压后的目录 */ async function download(options, dest) { await fs_extra_1.default.ensureDir(dest); await fs_extra_1.default.emptyDir(dest); logger_1.logger.debug('Git 仓库信息', options); if (options.mode === 'git') { await downloadByGit(options, dest); } else { try { await downloadByTar(options, dest); } catch (error) { logger_1.logger.debug('通过 tar 方式下载', `${options.user}/${options.name}`, '失败, 原因为:', error === null || error === void 0 ? void 0 : error.message, '尝试使用 git clone 方式下载'); // fallback 后 改写 mode 为 git options.mode = 'git'; await downloadByGit(options, dest); } } } exports.download = download; //# sourceMappingURL=git.js.map