9b395f5cc3
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
80 lines
3.3 KiB
JavaScript
80 lines
3.3 KiB
JavaScript
// pypi package — fetch a single PyPI package's metadata.
|
|
//
|
|
// Hits `https://pypi.org/pypi/<pkg>/json`. Returns the most agent-useful
|
|
// projection: name, latest version, summary, author, license, homepage,
|
|
// project URLs, requires-python, last-modified time. Download stats are
|
|
// intentionally separate (see `pypi downloads`).
|
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
import { EmptyResultError } from '@jackwener/opencli/errors';
|
|
import { PYPI_BASE, pypiFetch, requirePackageName } from './utils.js';
|
|
|
|
function pickHomepage(info) {
|
|
if (info.home_page) return String(info.home_page);
|
|
const proj = info.project_urls;
|
|
if (proj && typeof proj === 'object') {
|
|
return String(proj.Homepage || proj.homepage || proj.Documentation || proj.Source || proj['Source Code'] || '');
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function pickRepository(info) {
|
|
const proj = info.project_urls;
|
|
if (proj && typeof proj === 'object') {
|
|
return String(proj.Source || proj['Source Code'] || proj.Repository || proj.repository || '');
|
|
}
|
|
return '';
|
|
}
|
|
|
|
cli({
|
|
site: 'pypi',
|
|
name: 'package',
|
|
access: 'read',
|
|
description: 'Single PyPI package metadata (latest version, license, homepage, classifiers)',
|
|
domain: 'pypi.org',
|
|
strategy: Strategy.PUBLIC,
|
|
browser: false,
|
|
args: [
|
|
{ name: 'name', positional: true, required: true, help: 'PyPI package name (e.g. "requests", "pandas")' },
|
|
],
|
|
columns: [
|
|
'name', 'latestVersion', 'summary', 'author', 'license', 'homepage', 'repository',
|
|
'requiresPython', 'keywords', 'releases', 'firstReleased', 'lastReleased', 'url',
|
|
],
|
|
func: async (args) => {
|
|
const name = requirePackageName(args.name);
|
|
const body = await pypiFetch(`${PYPI_BASE}/pypi/${encodeURIComponent(name)}/json`, `pypi package ${name}`);
|
|
const info = body?.info;
|
|
if (!info || !info.name) {
|
|
throw new EmptyResultError('pypi package', `PyPI returned no metadata for "${name}".`);
|
|
}
|
|
const releases = body?.releases ?? {};
|
|
const releaseVersions = Object.keys(releases).filter((v) => Array.isArray(releases[v]) && releases[v].length > 0);
|
|
// earliest / latest release timestamps from the upload_time fields
|
|
let firstReleased = '';
|
|
let lastReleased = '';
|
|
for (const v of releaseVersions) {
|
|
for (const file of releases[v]) {
|
|
const t = String(file?.upload_time ?? '').slice(0, 10);
|
|
if (!t) continue;
|
|
if (!firstReleased || t < firstReleased) firstReleased = t;
|
|
if (!lastReleased || t > lastReleased) lastReleased = t;
|
|
}
|
|
}
|
|
return [{
|
|
name: String(info.name),
|
|
latestVersion: String(info.version ?? ''),
|
|
summary: String(info.summary ?? ''),
|
|
author: String(info.author ?? info.author_email ?? ''),
|
|
license: String(info.license_expression ?? info.license ?? ''),
|
|
homepage: pickHomepage(info),
|
|
repository: pickRepository(info),
|
|
requiresPython: String(info.requires_python ?? ''),
|
|
keywords: String(info.keywords ?? ''),
|
|
releases: releaseVersions.length,
|
|
firstReleased,
|
|
lastReleased,
|
|
url: String(info.package_url ?? `${PYPI_BASE}/project/${info.name}/`),
|
|
}];
|
|
},
|
|
});
|