signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
276 lines (274 loc) • 10.6 kB
JavaScript
;
/* 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
};