@interopio/desktop-cli
Version:
CLI tool for setting up, building and packaging io.Connect Desktop projects
208 lines • 8.74 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GitHubComponentsStore = void 0;
const error_handler_1 = require("../../../utils/error.handler");
const logger_1 = require("../../../utils/logger");
;
// The github release package name is in format {{component}}-{{version}}
// each release has multiple assets; each asset can be platform specific
// each release has a tag the same as the release name; this can be used to get a specific release
class GitHubComponentsStore {
baseUrl = '';
getByTagUrl = '';
logger = logger_1.Logger.getInstance();
constructor(repo = "interopio/desktop-releases/releases") {
this.baseUrl = `https://api.github.com/repos/${repo}`;
this.getByTagUrl = `${this.baseUrl}/tags/`;
}
async getAll() {
const allReleases = [];
let nextUrl = `${this.baseUrl}?per_page=100&page=1`;
while (nextUrl) {
const response = await fetch(nextUrl);
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const releases = await response.json();
// If no releases returned, we've reached the end
if (releases.length === 0) {
break;
}
allReleases.push(...releases);
// Get next page URL from Link header
const linkHeader = response.headers.get('link');
nextUrl = this.getNextPageUrl(linkHeader);
// Safety check to prevent infinite loops (GitHub typically has reasonable limits)
if (allReleases.length > 10000) {
this.logger.warn('GitHub API pagination: Stopped at 10,000 releases to prevent excessive API calls');
break;
}
}
return this.parseReleases(allReleases);
}
async download(name, version) {
// Find the specific release and asset for this component
const targetRelease = await this.findRelease(name, version);
const asset = this.findAssetForPlatform(targetRelease, process.platform);
if (!asset) {
throw new Error(`No asset found for component in ${name}@${version} on platform '${process.platform}'`);
}
// Download the asset
this.logger.info(`Downloading ${asset.name} from ${asset.browser_download_url}`);
const response = await fetch(asset.browser_download_url);
if (!response.ok) {
throw new Error(`Failed to download asset: ${response.status} ${response.statusText}`);
}
// Get the data as buffer
const buffer = await response.arrayBuffer();
this.logger.info(`Downloaded ${name} v${version || 'latest'} (${asset.name}) successfully!`);
return {
name: name,
data: new Uint8Array(buffer),
filename: asset.name
};
}
getInfo() {
return `GitHub Releases Store: ${this.baseUrl}`;
}
/**
* Find a specific release by component name and version
*/
async findRelease(componentName, version) {
if (version === 'latest') {
// If no version specified, find the latest release for this component
const allReleases = await this.getAllReleases();
const componentReleases = allReleases.filter(release => release.tag_name.startsWith(`${componentName}-v`));
if (componentReleases.length === 0) {
throw new Error(`No releases found for component '${componentName}'`);
}
// Return the most recent release (first in the list)
return componentReleases[0];
}
else {
// If version is specified, look for exact match
const targetTag = `${componentName}-v${version}`;
const response = await fetch(`${this.getByTagUrl}${targetTag}`);
if (response.ok) {
return await response.json();
}
throw new error_handler_1.CLIError(`Release not found for ${componentName} v${version} (was looking for tag: ${targetTag})`, {
suggestions: [
`You are trying to install ${componentName}@${version} - check that the component name and version are correct`,
`Use "desktop-cli components browse" to see available components and versions`
]
});
}
}
/**
* Find the appropriate asset for the current platform
*/
findAssetForPlatform(release, platform) {
this.logger.info(`Finding asset for platform '${platform}' in release '${release.name}. Assets available: ${release.assets.map(a => a.name).join(', ')}'`);
const platformAssets = release.assets.filter(asset => {
const assetName = asset.name.toLowerCase();
switch (platform) {
case 'win32':
return assetName.includes('-win') || assetName.includes('.exe') || assetName.includes('.zip');
case 'darwin':
return assetName.includes('darwin') || assetName.includes('mac') || assetName.includes('.dmg');
default:
return false;
}
});
if (platformAssets.length === 0) {
return null;
}
// Prefer more specific platform matches
const specificAsset = platformAssets.find(asset => {
const assetName = asset.name.toLowerCase();
if (platform === 'darwin') {
// Prefer arm64 on Apple Silicon, otherwise take any darwin asset
return process.arch === 'arm64' ?
assetName.includes('arm64') || assetName.includes('darwin') :
assetName.includes('darwin');
}
return true;
});
return specificAsset || platformAssets[0];
}
/**
* Get all releases without parsing them into components
*/
async getAllReleases() {
const allReleases = [];
let nextUrl = `${this.baseUrl}?per_page=100&page=1`;
while (nextUrl) {
const response = await fetch(nextUrl);
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const releases = await response.json();
if (releases.length === 0) {
break;
}
allReleases.push(...releases);
const linkHeader = response.headers.get('link');
nextUrl = this.getNextPageUrl(linkHeader);
// Safety limit
if (allReleases.length > 1000) {
break;
}
}
return allReleases;
}
/**
* Extract the next page URL from the Link header
* Link header format: <https://api.github.com/repos/owner/repo/releases?page=2>; rel="next", <https://api.github.com/repos/owner/repo/releases?page=5>; rel="last"
*/
getNextPageUrl(linkHeader) {
if (!linkHeader) {
return null;
}
const links = linkHeader.split(',');
for (const link of links) {
const match = link.match(/<([^>]+)>;\s*rel="next"/);
if (match) {
return match[1];
}
}
return null;
}
parseReleases(releases) {
const components = [];
for (const release of releases) {
const componentName = this.getComponentFromName(release.name);
const componentVersion = this.getVersionFromName(release.name);
for (const asset of release.assets) {
components.push({
name: componentName,
version: componentVersion,
platform: this.getPlatformFromAssetURL(asset.browser_download_url),
downloadUrl: asset.browser_download_url
});
}
}
return components;
}
getComponentFromName(name) {
const parts = name.split('-');
return parts.length > 0 ? parts[0] : "unknown";
}
getVersionFromName(name) {
// name is in format {{component}}-{{version}}
const parts = name.split('-');
return parts.length > 1 ? parts[1] : "unknown";
}
getPlatformFromAssetURL(name) {
// asset name is iocd-v10.0.0-202508271503-darwin-arm64.dmg"
if (name.includes('darwin-arm64')) {
return 'darwin-arm64';
}
if (name.includes('darwin')) {
return 'darwin';
}
return 'win32';
}
}
exports.GitHubComponentsStore = GitHubComponentsStore;
//# sourceMappingURL=github.store.js.map