UNPKG

@serpent/common-cli

Version:

通用的 cli 相关的函数

481 lines (473 loc) 21.7 kB
import path from 'path'; import { b as findupPackage, o as ospath, k as existsDir } from './context-96dcd180.mjs'; import { D as Dir, J as JsonFile } from './Dir-b98fddc7.mjs'; import 'module'; import 'assert'; import { r as readJsonFile } from './readJsonFile-255f65c0.mjs'; import fs from 'fs'; import { silentRunOutput, run } from './run.mjs'; export { c as createPackage } from './createPackage-cfe91bd7.mjs'; import { _ as __awaiter, a as __generator, b as __spreadArray, e as __rest, c as __assign } from './tslib.es6-636c4f06.mjs'; import { t as tryGetProjectRootDir, i as isProjectRootDir } from './project-0299066e.mjs'; import { i as isCmdExists } from './isCommandExists-0f8fbb8f.mjs'; import { compareVersion, satisfiesVersion, sortVersions, filterVersions } from './version.mjs'; import { logger } from './logger.mjs'; import http from 'http'; import https from 'https'; import './_commonjsHelpers-7d1333e8.mjs'; import './exists-d3d14d46.mjs'; import 'os'; import 'node:buffer'; import 'node:path'; import 'node:child_process'; import 'node:process'; import 'child_process'; import 'node:url'; import 'node:os'; import './index-3b2c9171.mjs'; import 'events'; import 'buffer'; import 'stream'; import 'util'; import 'url'; import './info-fd7bb244.mjs'; import './warn-1994a28d.mjs'; import './supportColor-2e661db8.mjs'; import './helper-1331d234.mjs'; import 'crypto'; import './satisfies-144b0f35.mjs'; import './format-e8a6577a.mjs'; function getDurkaRegistry() { return process.env.DURKA_REGISTRY || 'https://registry.npmmirror.com/'; } /** 获取当前环境下的 registry */ function getNpmClientRegistry(npmClient) { if (npmClient === void 0) { npmClient = 'npm'; } return silentRunOutput("".concat(npmClient, " config get registry")); } function getDurkaConfig() { var res = {}; try { res = readJsonFile(findupPackage()).durka || {}; } catch (e) { } return res; } function getDurkaHome() { return process.env.DURKA_HOME || path.join(ospath.home(), '.durka'); } function getDurkaInstallPrefix(mod, version) { if (mod === void 0) { mod = 'node'; } if (version === void 0) { version = process.version; } return path.join(getDurkaHome(), 'packages', mod, version.split('.')[0]); } /** * 安装脚本到 durka/scripts 目录下 */ function installDurkaScript(options) { var name = options.name, subname = options.subname, content = options.content, devOnly = options.devOnly, installToPackage = options.installToPackage, installMode = options.installMode; var root = tryGetProjectRootDir(); if (!root) throw new Error("Not a valid node project, can not install durka script."); var dist = new Dir(path.join(root, 'durka', 'scripts')); var config = dist.tryReadJson('config.json', {}); if (!config[name]) config[name] = []; // 安装过,就不需要安装了 if (!config[name].includes(subname)) { config[name].push(subname); dist.writeContent('config.json', JSON.stringify(config, null, 2)); } dist.writeContent("".concat(name, "-").concat(subname), content, { mode: 511 }); if (installToPackage) { var pkgFile = path.join(root, 'package.json'); var jf = new JsonFile(pkgFile); var scriptsPath = "scripts.".concat(name); var prevValue = jf.get(scriptsPath); var appendCmd1 = "durka --devOnly run ".concat(name); var appendCmd2 = "durka run ".concat(name); var needAppendedValue = devOnly ? appendCmd1 : appendCmd2; var notNeedAppendValue = devOnly ? appendCmd2 : appendCmd1; if (!prevValue) { jf.set(scriptsPath, needAppendedValue); jf.save(); } else if (!prevValue.includes(needAppendedValue) && prevValue.includes(notNeedAppendValue)) { throw new Error("\u5DF2\u7ECF\u5B58\u5728\u547D\u4EE4 ".concat(notNeedAppendValue, "\uFF0C\u65E0\u6CD5\u518D\u6DFB\u52A0\u547D\u4EE4 ").concat(needAppendedValue)); } else if (!prevValue.includes(needAppendedValue)) { if (installMode === 'after') { jf.set(scriptsPath, "".concat(prevValue, " && ").concat(needAppendedValue)); } else { jf.set(scriptsPath, "".concat(needAppendedValue, " && ").concat(prevValue)); } jf.save(); } } } /** * 运行 durka/scripts 中的脚本 */ function runDurkaScript(options) { return __awaiter(this, void 0, void 0, function () { var name, _a, args, onlyRunSubname, root, dist, config, subnameList, _i, subnameList_1, subname, file; return __generator(this, function (_b) { switch (_b.label) { case 0: name = options.name, _a = options.args, args = _a === void 0 ? [] : _a, onlyRunSubname = options.subname; root = tryGetProjectRootDir(); if (!root) throw new Error("".concat(process.cwd(), " is not a valid node project, can not run durka script.")); dist = new Dir(path.join(root, 'durka', 'scripts')); config = dist.tryReadJson('config.json', {}); subnameList = config[name] || []; if (!onlyRunSubname) return [3 /*break*/, 4]; if (!subnameList.includes(onlyRunSubname)) return [3 /*break*/, 2]; return [4 /*yield*/, run([dist.abs("".concat(name, "-").concat(onlyRunSubname))])]; case 1: _b.sent(); return [3 /*break*/, 3]; case 2: throw new Error("Not found ".concat(name, ":").concat(onlyRunSubname, " script in durka/scripts/config.json")); case 3: return [3 /*break*/, 8]; case 4: _i = 0, subnameList_1 = subnameList; _b.label = 5; case 5: if (!(_i < subnameList_1.length)) return [3 /*break*/, 8]; subname = subnameList_1[_i]; file = dist.abs("".concat(name, "-").concat(subname)); return [4 /*yield*/, run(__spreadArray([file], args, true))]; case 6: _b.sent(); _b.label = 7; case 7: _i++; return [3 /*break*/, 5]; case 8: return [2 /*return*/]; } }); }); } /** * 请求指定链接上的内容,默认会解析成 json,超过 5s 未返回会报错 * @param url * @param options */ function fetch(url, options) { var _a; if (options === void 0) { options = {}; } return __awaiter(this, void 0, void 0, function () { var signal, _b, timeout, data; return __generator(this, function (_c) { switch (_c.label) { case 0: signal = { timeout: false }; return [4 /*yield*/, wait(simpleFetch(url, signal, options.json), signal, (_a = options.timeout) !== null && _a !== void 0 ? _a : 5000)]; case 1: _b = _c.sent(), timeout = _b.timeout, data = _b.data; if (timeout) { throw new Error("request ".concat(url, " timeout")); } return [2 /*return*/, data]; } }); }); } /** * 一个简单的获取指定链接上的 json 数据的函数 */ function simpleFetch(url, signal, json) { if (json === void 0) { json = true; } return new Promise(function (resolve, reject) { var isHTTPS = url.startsWith('https:'); var client; if (isHTTPS) { client = https.get(url, { timeout: 5000 }, fetchCallback); } else { client = http.get(url, { timeout: 5000 }, fetchCallback); } client.on('error', function (e) { reject(e); }); function fetchCallback(res) { var _data = ''; var status = res.statusCode; var message = res.statusMessage; res.on('close', function () { res.removeAllListeners(); var data; if (_data) { if (json) { try { data = JSON.parse(_data); } catch (e) { reject(new Error("".concat(url, " response is not a valid json: ").concat(_data))); } } else { data = _data; } } if (!status || (status >= 200 && status < 300)) { resolve(data); } else if (status === 404) { // 特殊错误码,方便后续业务处理 reject(new Error("NOT FOUND ".concat(url))); } else { reject(new Error("fetch ".concat(url, " error: ").concat(status, " ").concat(message))); } }); res.on('data', function (chunk) { if (signal.timeout) { res.removeAllListeners(); res.destroy(); } else { if (typeof chunk === 'string') _data += chunk; if (Buffer.isBuffer(chunk)) _data += chunk.toString(); } }); } }); } /** * 给异步函数设置一个超时时间,如果超过指定的时间还没返回,则直接 resolve 掉 */ function wait(promise, signal, maxWaitTime) { return Promise.race([ promise.then(function (res) { return ({ timeout: false, data: res }); }), sleep(maxWaitTime).then(function () { signal.timeout = true; return { timeout: true }; }), ]); } function sleep(ms) { return new Promise(function (resolve) { setTimeout(function () { return resolve(); }, ms); }); } /** * 获取 npm 模块的详细信息(如果远程获取失败则自动切换到本地 * @param name npm 模块名称 * @param registry npm registry */ function getPackageDetail(name, registry) { return __awaiter(this, void 0, void 0, function () { var e_1, detail; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 4]); return [4 /*yield*/, getRemotePackageDetail(name, registry)]; case 1: return [2 /*return*/, _a.sent()]; case 2: e_1 = _a.sent(); return [4 /*yield*/, getLocalPackageDetail(name)]; case 3: detail = _a.sent(); if (!Object.keys(detail.versions).length) throw e_1; // 本地也没有对应的模块 logger.warn(" fetch \"".concat(name, "\" error: ").concat(e_1.message)); logger.warn(" fallback to offline mode"); return [2 /*return*/, detail]; case 4: return [2 /*return*/]; } }); }); } /** 获取在线模块信息 */ function getRemotePackageDetail(name, registry) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, _getRemotePackageDetail(name, registry)]; }); }); } function _getRemotePackageDetail(name, registry, retryCount) { if (retryCount === void 0) { retryCount = 0; } return __awaiter(this, void 0, void 0, function () { var pkgUrl, fields, jq, raw, _a, _b, versions, _c, deps_1, rest, e_3; return __generator(this, function (_d) { switch (_d.label) { case 0: pkgUrl = new URL(name, registry || getDurkaRegistry()).href; if (!(isCmdExists('curl') && isCmdExists('jq'))) return [3 /*break*/, 4]; _d.label = 1; case 1: _d.trys.push([1, 3, , 4]); fields = ['description', 'time', 'dist-tags', 'homepage', 'license', 'repository', 'readme', 'author', 'maintainers']; jq = "jq '{ versions: .versions | keys_unsorted, deps: [.versions[].dependencies], ".concat(fields.map(function (f) { return "\"".concat(f, "\": .\"").concat(f, "\""); }), " }'"); return [4 /*yield*/, silentRunOutput("curl --connect-timeout 5 --max-time 16 -s '".concat(pkgUrl, "' | ").concat(jq), { shell: true })]; case 2: raw = _d.sent(); _a = JSON.parse(raw), _b = _a.versions, versions = _b === void 0 ? [] : _b, _c = _a.deps, deps_1 = _c === void 0 ? [] : _c, rest = __rest(_a, ["versions", "deps"]); return [2 /*return*/, __assign(__assign({ name: name }, rest), { versions: versions.reduce(function (res, version, i) { res[version] = { name: name, version: version, dependencies: deps_1[i] }; return res; }, {}) })]; case 3: _d.sent(); // TODO: 有可能是网络问题,暂时不处理了,后面识别到了再一个个添加 throw new Error("Not found package \"".concat(name, "\" in ").concat(pkgUrl)); // 下游有消费,保持一致 case 4: _d.trys.push([4, 6, , 9]); return [4 /*yield*/, fetch(pkgUrl)]; case 5: return [2 /*return*/, _d.sent()]; case 6: e_3 = _d.sent(); if (!(e_3.error === 'ECONNRESET' && retryCount <= 2)) return [3 /*break*/, 8]; return [4 /*yield*/, _getRemotePackageDetail(name, registry, retryCount + 1)]; case 7: return [2 /*return*/, _d.sent()]; case 8: // TODO: 有可能是网络问题,暂时不处理了,后面识别到了再一个个添加 // if (e.message === `NOT FOUND ${pkgUrl}`) throw new Error("Not found package \"".concat(name, "\" in ").concat(pkgUrl)); // 下游有消费,保持一致 case 9: return [2 /*return*/]; } }); }); } /** 获取本地模块信息 */ function getLocalPackageDetail(name) { return __awaiter(this, void 0, void 0, function () { var baseDir, detail, localVersions, maxVersion, versions; return __generator(this, function (_a) { baseDir = path.join.apply(path, __spreadArray([getDurkaInstallPrefix()], name.split('/'), false)); detail = { name: name, versions: {} }; if (existsDir(baseDir)) { localVersions = fs.readdirSync(baseDir); localVersions.forEach(function (v) { var rootDir = path.join(baseDir, v, 'lib', 'node_modules', name); if (isProjectRootDir(rootDir)) { var pkg_1 = readJsonFile(path.join(rootDir, 'package.json')); detail.versions[v] = Object.keys(pkg_1).reduce(function (res, k) { // _ 开头的字段是 npm 在发布或安装的时候自动生成的字段 if (!k.startsWith('_')) res[k] = pkg_1[k]; return res; }, {}); // 记录当前版本所在的文件目录 detail.versions[v].durkaLocalFolder = rootDir; } }); localVersions = Object.keys(detail.versions); if (localVersions.length) { maxVersion = localVersions.sort(compareVersion)[localVersions.length - 1]; versions = detail.versions; Object.assign(detail, detail.versions[maxVersion], { name: name, versions: versions }); } } return [2 /*return*/, detail]; }); }); } /** 获取远程模块的版本号 */ function getRemoteVersions(name, options) { if (options === void 0) { options = {}; } return getVersions(getPackageDetail(name, options.registry), options); } /** 获取本地模块的版本号 */ function getLocalVersions(name, options) { return getVersions(getLocalPackageDetail(name), options); } /** 获取远程所有 tag 及其关联的版本号 */ function getRemoteTags(name, registry) { return getPackageDetail(name, registry).then(function (d) { return d['dist-tags'] || {}; }); } /** 获取远程中指定 tag 关联的版本号,有可能不存在 */ function getRemoteTagVersion(name, tag, registry) { return getRemoteTags(name, registry).then(function (d) { return d[tag]; }); } /** 获取远程模块的最新版本号 */ function getRemoteLatestVersion(name, options) { if (options === void 0) { options = {}; } return getLatest(getRemoteVersions, name, options); } /** 获取本地模块的最新版本号 */ function getLocalLatestVersion(name, options) { if (options === void 0) { options = {}; } return getLatest(getLocalVersions, name, options); } function getLatest(fetch, name, options) { if (options === void 0) { options = {}; } var includePrerelease = options.includePrerelease, range = options.range, tag = options.tag, registry = options.registry; return fetch(name, { mode: includePrerelease ? 'pre&major' : 'major', range: range, tag: tag, order: 'desc', registry: registry, }).then(function (versions) { return versions.shift(); }); } function getVersions(prom, options) { if (options === void 0) { options = {}; } var _a = options.order, order = _a === void 0 ? 'desc' : _a, _b = options.mode, mode = _b === void 0 ? 'all' : _b, range = options.range, tag = options.tag; return prom.then(function (detail) { // 服务器报错,可能会返回一个 html 内容 if (!detail || typeof detail !== 'object') { throw new Error("Fetch package versions failed: ".concat(detail)); } var versions = []; if (tag) { var tagVersion = (detail['dist-tags'] || {})[tag]; if (tagVersion) versions.push(tagVersion); } else { versions = Object.keys(detail.versions); } if (range) versions = versions.filter(function (v) { return satisfiesVersion(v, range); }); if (mode === 'all') return sortVersions(versions, order); return sortVersions(filterVersions(versions, mode), order); }); } /** * 安装模块 * @param name 要安装的模块名 * @param version 要安装的模块版本,必须是一个具体是版本,不能是 tag 或 "^"、"~" 开头的版本号 * @param options * @returns 返回安装后的模块根目录 */ function npmInstall(name, version, options) { if (options === void 0) { options = {}; } return __awaiter(this, void 0, void 0, function () { var _a, prefix, _b, registry, _c, dependencies, _d, flags, nameParts, realPrefix, expectResultDir, installs; return __generator(this, function (_e) { switch (_e.label) { case 0: _a = options.prefix, prefix = _a === void 0 ? getDurkaInstallPrefix() : _a, _b = options.registry, registry = _b === void 0 ? getDurkaRegistry() : _b, _c = options.dependencies, dependencies = _c === void 0 ? [] : _c, _d = options.flags, flags = _d === void 0 ? [] : _d; nameParts = name.split('/'); realPrefix = path.join.apply(path, __spreadArray(__spreadArray([prefix], nameParts, false), [version], false)); expectResultDir = path.join.apply(path, __spreadArray([realPrefix, 'lib', 'node_modules'], nameParts, false)); if (!(!isProjectRootDir(expectResultDir) || options.force)) return [3 /*break*/, 2]; console.log(); installs = __spreadArray(["".concat(name, "@").concat(version)], dependencies, true); return [4 /*yield*/, run(__spreadArray(__spreadArray(__spreadArray(['npm', 'install', '--global'], flags, true), ['--prefix', realPrefix, '--registry', registry], false), installs, true))]; case 1: _e.sent(); console.log(); _e.label = 2; case 2: return [2 /*return*/, expectResultDir]; } }); }); } /** 获取 npmInstall 命令安装项目之后的根目录 */ function getNpmInstallRootPath(name, version, options) { if (options === void 0) { options = {}; } var _a = options.prefix, prefix = _a === void 0 ? getDurkaInstallPrefix() : _a; var ns = name.split('/'); var p = path.join.apply(path, __spreadArray(__spreadArray([prefix], ns, false), [version], false)); return path.join.apply(path, __spreadArray([p, 'lib', 'node_modules'], ns, false)); } export { fetch, getDurkaConfig, getDurkaHome, getDurkaInstallPrefix, getDurkaRegistry, getLocalLatestVersion, getLocalPackageDetail, getLocalVersions, getNpmClientRegistry, getNpmInstallRootPath, getPackageDetail, getRemoteLatestVersion, getRemotePackageDetail, getRemoteTagVersion, getRemoteTags, getRemoteVersions, installDurkaScript, npmInstall, runDurkaScript };