UNPKG

signalk-server

Version:

An implementation of a [Signal K](http://signalk.org) server for boats.

276 lines (274 loc) 10.6 kB
"use strict"; /* eslint-disable @typescript-eslint/no-explicit-any */ /* * Copyright 2017 Teppo Kurki <teppo.kurki@iki.fi> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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.modulesWithKeyword = modulesWithKeyword; exports.restoreModules = restoreModules; exports.checkForNewServerVersion = checkForNewServerVersion; exports.getAuthor = getAuthor; exports.getKeywords = getKeywords; const child_process_1 = require("child_process"); const fs_1 = __importDefault(require("fs")); const lodash_1 = __importDefault(require("lodash")); const node_fetch_1 = __importDefault(require("node-fetch")); const path_1 = __importDefault(require("path")); const semver_1 = __importStar(require("semver")); const debug_1 = require("./debug"); const process_1 = require("process"); const debug = (0, debug_1.createDebug)('signalk:modules'); const npmDebug = (0, debug_1.createDebug)('signalk:modules:npm'); function findModulesInDir(dir, keyword) { // If no directory by name return empty array. if (!fs_1.default.existsSync(dir)) { return []; } debug('findModulesInDir: ' + dir); return fs_1.default .readdirSync(dir) .filter((name) => name !== '.bin') .reduce((result, filename) => { if (filename.indexOf('@') === 0) { return result.concat(findModulesInDir(dir + filename + '/', keyword).map((entry) => { return { module: entry.module, metadata: entry.metadata, location: dir }; })); } else { let metadata; try { // eslint-disable-next-line @typescript-eslint/no-require-imports metadata = require(path_1.default.join(dir, filename, 'package.json')); } catch (err) { debug(err); } if (metadata && metadata.keywords && metadata.keywords.includes(keyword)) { result.push({ module: metadata.name, metadata, location: dir }); } } return result; }, []); } // Extract unique directory paths from app object. function getModulePaths(config) { // appPath is the app working directory. const { appPath, configPath } = config; return (appPath === configPath ? [appPath] : [configPath, appPath]).map((pathOption) => path_1.default.join(pathOption, 'node_modules/')); } const getModuleSortName = (x) => (x.module || '').replace('@signalk', ' '); // Sort handler that puts strings with '@signalk' first. const priorityPrefix = (a, b) => getModuleSortName(a).localeCompare(getModuleSortName(b)); // Searches for installed modules that contain `keyword`. function modulesWithKeyword(config, keyword) { return lodash_1.default.uniqBy( // _.flatten since values are inside an array. [[modules...], [modules...]] lodash_1.default.flatten(getModulePaths(config).map((pathOption) => findModulesInDir(pathOption, keyword))), (moduleData) => moduleData.module).sort(priorityPrefix); } function installModule(config, name, version, onData, onErr, onClose) { runNpm(config, name, version, 'install', onData, onErr, onClose); } function removeModule(config, name, version, onData, onErr, onClose) { runNpm(config, name, null, 'remove', onData, onErr, onClose); } function restoreModules(config, onData, onErr, onClose) { runNpm(config, null, null, 'remove', onData, onErr, onClose); } function runNpm(config, name, version, command, onData, onErr, onClose) { let npm; const opts = {}; let packageString; if (name) { packageString = version ? `${name}@${version}` : name; } else { packageString = ''; } debug(`${command}: ${packageString}`); if (isTheServerModule(name, config)) { if (process.platform === 'win32') { npm = (0, child_process_1.spawn)('cmd', ['/c', `npm ${command} -g ${packageString} `], opts); } else { npm = (0, child_process_1.spawn)('sudo', ['npm', command, '-g', packageString], opts); } } else { opts.cwd = config.configPath; if (process.platform === 'win32') { npm = (0, child_process_1.spawn)('cmd', ['/c', `npm --save ${command} ${packageString}`], opts); } else { npm = (0, child_process_1.spawn)('npm', ['--save', command, packageString], opts); } } npm.stdout.on('data', onData); npm.stderr.on('data', onErr); npm.on('close', onClose); npm.on('error', (err) => { onErr(err); onClose(-1); }); } function isTheServerModule(moduleName, config) { return moduleName === config.name; } const modulesByKeyword = {}; function findModulesWithKeyword(keyword) { return new Promise((resolve, reject) => { if (modulesByKeyword[keyword] && Date.now() - modulesByKeyword[keyword].time < 60 * 1000) { resolve(modulesByKeyword[keyword].packages); return; } searchByKeyword(keyword) .then((moduleData) => { npmDebug(`npm search returned ${moduleData.length} modules with keyword ${keyword}`); const result = moduleData.reduce((acc, module) => { const name = module.package.name; if (!acc[name] || semver_1.default.gt(module.package.version, acc[name].package.version)) { acc[name] = module; } return acc; }, {}); const packages = lodash_1.default.values(result); modulesByKeyword[keyword] = { time: Date.now(), packages }; resolve(packages); }) .catch((e) => { reject(e); }); }); } function searchByKeyword(keyword) { return new Promise((resolve, reject) => { let fetchedCount = 0; let toFetchCount = 1; let moduleData = []; const npmFetch = () => { npmDebug(`searching ${keyword} from ${fetchedCount + 1} of ${toFetchCount}`); (0, node_fetch_1.default)(`https://registry.npmjs.org/-/v1/search?size=250&from=${fetchedCount > 0 ? fetchedCount : 0}&text=keywords:${keyword}`) .then((r) => r.json()) .then((parsed) => { moduleData = moduleData.concat(parsed.objects); fetchedCount += parsed.objects.length; toFetchCount = parsed.total; if (fetchedCount < toFetchCount) { (0, process_1.nextTick)(() => npmFetch()); } else { resolve(moduleData); } }) .catch(reject); }; npmFetch(); }); } function doFetchDistTags() { return (0, node_fetch_1.default)('https://registry.npmjs.org/-/package/signalk-server/dist-tags'); } function getLatestServerVersion(currentVersion, distTags = doFetchDistTags) { return new Promise((resolve, reject) => { distTags() .then((npmjsResults) => npmjsResults.json()) .then((npmjsParsed) => { const prereleaseData = semver_1.default.prerelease(currentVersion); if (prereleaseData) { if (semver_1.default.satisfies(npmjsParsed.latest, `>${currentVersion}`)) { resolve(npmjsParsed.latest); } else { resolve(npmjsParsed[prereleaseData[0]]); } } else { resolve(npmjsParsed.latest); } }) .catch(reject); }); } function checkForNewServerVersion(currentVersion, serverUpgradeIsAvailable, getLatestServerVersionP = getLatestServerVersion) { getLatestServerVersionP(currentVersion) .then((version) => { if (semver_1.default.satisfies(new semver_1.SemVer(version), `>${currentVersion}`)) { serverUpgradeIsAvailable(undefined, version); } }) .catch((err) => { serverUpgradeIsAvailable(`unable to check for new server version: ${err}`); }); } function getAuthor(thePackage) { var _a; return `${(_a = thePackage.publisher) === null || _a === void 0 ? void 0 : _a.username}${thePackage.name.startsWith('@signalk/') ? ' (Signal K team)' : ''}`; } function getKeywords(thePackage) { const keywords = thePackage.keywords; debug('%s keywords: %j', thePackage.name, keywords); return keywords; } module.exports = { modulesWithKeyword, installModule, removeModule, isTheServerModule, findModulesWithKeyword, getLatestServerVersion, checkForNewServerVersion, getAuthor, getKeywords, restoreModules };