signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
128 lines • 4.86 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.buildPluginDetail = buildPluginDetail;
exports.readDetailFromCache = readDetailFromCache;
const categories_1 = require("../categories");
const debug_1 = require("../debug");
const cdn_1 = require("./cdn");
const github_releases_1 = require("./github-releases");
const indicators_1 = require("./indicators");
const debug = (0, debug_1.createDebug)('signalk-server:appstore:detail');
const DEFAULT_TIMEOUT_MS = 20_000;
async function fetchText(url, options = {}) {
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
try {
const res = await fetch(url, {
signal: AbortSignal.timeout(timeoutMs)
});
if (!res.ok) {
debug.enabled && debug('fetchText %s returned %d', url, res.status);
// 5xx is a CDN/origin failure, not a definitive 'asset missing'
// signal — the asset may still exist once the outage clears.
if (res.status >= 500 && res.status < 600)
return { kind: 'error' };
return { kind: 'missing' };
}
return { kind: 'ok', text: await res.text() };
}
catch (err) {
debug.enabled && debug('fetchText %s failed: %O', url, err);
return { kind: 'error' };
}
}
function resolveDependencyList(names, resolver) {
if (!names || names.length === 0)
return [];
return names.map((name) => {
const lookup = resolver?.(name);
return {
name,
displayName: lookup?.displayName,
appIcon: lookup?.appIcon,
installed: lookup?.installed ?? false
};
});
}
async function buildPluginDetail(summary, resolver) {
let readmeText = '';
if (summary.npmReadme && summary.npmReadme.trim().length > 0) {
readmeText = summary.npmReadme;
}
else {
const fetched = await fetchText((0, cdn_1.readmeUrlFor)(summary.name, summary.version));
if (fetched.kind === 'ok')
readmeText = fetched.text;
}
// Prefer GitHub Releases as the changelog source — that's where most
// plugin authors put real release notes. Fall back to a CHANGELOG.md in
// the published tarball for packages not hosted on GitHub. Rendered to
// Markdown via unified/rehype-remark so the existing Changelog tab
// renders release notes directly.
let changelog = '';
let changelogFormat = 'synthesized';
const slug = (0, github_releases_1.parseGithubSlug)(summary.githubUrl);
if (slug) {
const releases = await (0, github_releases_1.fetchReleasesMarkdown)(slug.owner, slug.repo);
if (releases && releases.trim()) {
changelog = releases;
changelogFormat = 'markdown';
}
}
if (!changelog) {
const fetched = await fetchText((0, cdn_1.changelogUrlFor)(summary.name, summary.version));
if (fetched.kind === 'ok' && fetched.text.trim().length > 0) {
changelog = fetched.text;
changelogFormat = 'markdown';
}
}
const indicators = (0, indicators_1.computeIndicators)({
hasRepository: !!summary.githubUrl,
githubUrl: summary.githubUrl,
hasScreenshots: summary.screenshots.length > 0,
hasAppIcon: !!summary.appIcon,
description: summary.description,
readme: readmeText,
keywords: summary.keywords,
lastReleaseDate: summary.lastReleaseDate
});
return {
name: summary.name,
version: summary.version,
displayName: summary.displayName,
appIcon: summary.appIcon,
screenshots: summary.screenshots,
official: summary.official,
deprecated: summary.deprecated,
description: summary.description,
author: summary.author,
categories: summary.categories,
githubUrl: summary.githubUrl,
npmUrl: summary.npmUrl,
isPlugin: summary.isPlugin,
isWebapp: summary.isWebapp,
readme: readmeText,
changelog,
indicators,
requires: resolveDependencyList(summary.requires, resolver),
recommends: resolveDependencyList(summary.recommends, resolver),
recent: (0, categories_1.isRecent)(summary.lastReleaseDate),
readmeFormat: 'markdown',
changelogFormat,
fetchedAt: Date.now(),
fromCache: false
};
}
function readDetailFromCache(cache, name) {
const cached = cache.readPluginDetail(name);
return cached?.payload;
}
//# sourceMappingURL=detail.js.map