signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
176 lines • 6.57 kB
JavaScript
;
/*
* Copyright 2026 Signal K contributors
*
* 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
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.enrichEntry = enrichEntry;
const value_1 = require("@sinclair/typebox/value");
const debug_1 = require("../debug");
const cdn_1 = require("./cdn");
const indicators_1 = require("./indicators");
const schemas_1 = require("./schemas");
const debug = (0, debug_1.createDebug)('signalk-server:appstore:enrich');
const MAX_SCREENSHOTS = 6;
const DEPRECATED_KEYWORD = 'signalk-deprecated';
const OFFICIAL_PREFIX = '@signalk/';
function isGithubUrl(url) {
// Require a real boundary in front of github.com so a URL containing
// 'notgithub.com' followed by 'github.com' later in the path can't
// accidentally match. ':\\/' covers https:// and git+ssh://;
// '@' covers the ssh form (git@github.com:...).
return !!url && /(^|:\/\/|@)github\.com[/:]/i.test(url);
}
function extractGithubUrl(pkg) {
const candidates = [];
if (pkg.links?.repository)
candidates.push(pkg.links.repository);
if (typeof pkg.repository === 'string')
candidates.push(pkg.repository);
else if (pkg.repository?.url)
candidates.push(pkg.repository.url);
for (const c of candidates) {
if (c && isGithubUrl(c)) {
return (c
.replace(/^git\+/, '')
.replace(/^git:\/\//, 'https://')
// ssh://git@github.com/... is a valid clone URL but not a
// browser-openable link. Rewrite it the same way we rewrite
// the scp-style git@github.com:... form below.
.replace(/^ssh:\/\/git@github\.com\//, 'https://github.com/')
.replace(/\.git$/, '')
.replace(/^git@github\.com:/, 'https://github.com/'));
}
}
return undefined;
}
function extractIssuesUrl(pkg) {
if (pkg.links?.bugs)
return pkg.links.bugs;
if (typeof pkg.bugs === 'string')
return pkg.bugs;
if (pkg.bugs?.url)
return pkg.bugs.url;
const gh = extractGithubUrl(pkg);
return gh ? `${gh}/issues` : undefined;
}
function parseSignalKMetadata(pkg) {
if (!pkg.signalk || typeof pkg.signalk !== 'object')
return undefined;
if (value_1.Value.Check(schemas_1.SignalKPackageMetadataSchema, pkg.signalk)) {
return pkg.signalk;
}
debug.enabled &&
debug('%s: signalk key did not match schema; falling back to loose parse', pkg.name);
const loose = pkg.signalk;
return {
displayName: typeof loose.displayName === 'string' ? loose.displayName : undefined,
appIcon: typeof loose.appIcon === 'string' ? loose.appIcon : undefined,
screenshots: Array.isArray(loose.screenshots)
? loose.screenshots.filter((s) => typeof s === 'string')
: undefined,
deprecated: typeof loose.deprecated === 'boolean' ? loose.deprecated : undefined,
requires: Array.isArray(loose.requires)
? loose.requires.filter((s) => typeof s === 'string')
: undefined,
recommends: Array.isArray(loose.recommends)
? loose.recommends.filter((s) => typeof s === 'string')
: undefined
};
}
function defaultResolve(pkg, version, declaredPath, lookup) {
const cached = lookup?.(pkg, version, declaredPath);
if (cached)
return cached;
return (0, cdn_1.resolveScreenshotUrl)(pkg, version, declaredPath);
}
function resolveScreenshotList(pkg, meta, lookup) {
const raw = meta?.screenshots;
if (!Array.isArray(raw))
return [];
const out = [];
for (const entry of raw) {
if (typeof entry !== 'string' || entry.trim() === '')
continue;
const resolved = (0, cdn_1.isAbsoluteUrl)(entry)
? entry
: defaultResolve(pkg.name, pkg.version, entry, lookup);
out.push(resolved);
if (out.length >= MAX_SCREENSHOTS)
break;
}
return out;
}
function resolveAppIcon(pkg, meta, lookup) {
const icon = meta?.appIcon;
if (!icon || typeof icon !== 'string' || icon.trim() === '')
return undefined;
return (0, cdn_1.isAbsoluteUrl)(icon)
? icon
: defaultResolve(pkg.name, pkg.version, icon, lookup);
}
function isDeprecated(pkg, meta) {
if (meta?.deprecated === true)
return true;
return (pkg.keywords || []).includes(DEPRECATED_KEYWORD);
}
function isOfficial(pkg) {
return pkg.name.startsWith(OFFICIAL_PREFIX);
}
function normalizeNameList(value) {
if (!Array.isArray(value))
return undefined;
const seen = new Set();
const out = [];
for (const entry of value) {
if (typeof entry !== 'string')
continue;
const trimmed = entry.trim();
if (trimmed === '' || seen.has(trimmed))
continue;
seen.add(trimmed);
out.push(trimmed);
}
return out.length > 0 ? out : undefined;
}
function enrichEntry(pkg, options = {}) {
const meta = parseSignalKMetadata(pkg);
const screenshots = resolveScreenshotList(pkg, meta, options.iconUrlLookup);
const appIcon = resolveAppIcon(pkg, meta, options.iconUrlLookup);
const githubUrl = extractGithubUrl(pkg);
const issuesUrl = extractIssuesUrl(pkg);
const deprecated = isDeprecated(pkg, meta);
const official = isOfficial(pkg);
const result = {
displayName: meta?.displayName,
appIcon,
screenshots: screenshots.length > 0 ? screenshots : undefined,
official,
deprecated,
readmeUrl: (0, cdn_1.readmeUrlFor)(pkg.name, pkg.version),
changelogUrl: (0, cdn_1.changelogUrlFor)(pkg.name, pkg.version),
githubUrl,
issuesUrl,
requires: normalizeNameList(meta?.requires),
recommends: normalizeNameList(meta?.recommends)
};
if (options.includeIndicators) {
result.indicators = (0, indicators_1.computeIndicators)({
hasRepository: !!githubUrl,
githubUrl,
hasScreenshots: screenshots.length > 0,
hasAppIcon: !!appIcon,
description: pkg.description,
readme: pkg.readme,
keywords: pkg.keywords,
...(options.indicatorInputs || {})
});
}
return result;
}
//# sourceMappingURL=enrich.js.map