mirror of
https://github.com/jackwener/OpenCLI.git
synced 2026-09-14 18:25:42 +08:00
498ad3930c
Six new public-API sites — package registries + Docker images + OpenAlex
scholarly works — all unauthenticated, no browser required.
dockerhub search image
rubygems search gem
homebrew formula cask popular
packagist search package
maven search artifact
openalex search work
Conventions held:
- access: 'read' on every command
- typed errors (ArgumentError / EmptyResultError / CommandExecutionError)
instead of generic CliError or silent fallback
- input validators per site (image slugs, gem names, Composer names,
Maven coordinates, OpenAlex work-id / DOI normalization)
- listing rows carry an id-shaped column (image / gem / token / package /
coordinate / id) that round-trips into the corresponding detail command
- HTTP 429 surfaces with retry hint, 404 → EmptyResultError
Audits:
- check:typed-error-lint → no new violations (baseline 196)
- check:silent-column-drop → no new violations (baseline 103)
- advise:listing-id-pairing → unchanged at 13
50 lines
2.6 KiB
JavaScript
50 lines
2.6 KiB
JavaScript
// packagist package — fetch a single Packagist package's metadata.
|
|
//
|
|
// Hits `https://packagist.org/packages/<vendor>/<package>.json`. Returns
|
|
// one row: latest stable version + release time, license, repository,
|
|
// description, lifetime / monthly / daily downloads, github stars, favers.
|
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
import { CommandExecutionError } from '@jackwener/opencli/errors';
|
|
import { PACKAGIST_BASE, packagistFetch, pickStableVersion, requirePackageName, trimDate } from './utils.js';
|
|
|
|
cli({
|
|
site: 'packagist',
|
|
name: 'package',
|
|
access: 'read',
|
|
description: 'Fetch a Packagist package\'s metadata (version, downloads, license, repo, GitHub stars)',
|
|
domain: 'packagist.org',
|
|
strategy: Strategy.PUBLIC,
|
|
browser: false,
|
|
args: [
|
|
{ name: 'name', positional: true, required: true, help: 'Composer package "<vendor>/<package>" (e.g. "symfony/console", "monolog/monolog")' },
|
|
],
|
|
columns: ['package', 'version', 'releasedAt', 'license', 'description', 'repository', 'githubStars', 'favers', 'downloads', 'monthlyDownloads', 'dailyDownloads', 'url'],
|
|
func: async (args) => {
|
|
const { full } = requirePackageName(args.name);
|
|
const url = `${PACKAGIST_BASE}/packages/${full}.json`;
|
|
const body = await packagistFetch(url, 'packagist package');
|
|
const pkg = body?.package;
|
|
if (!pkg || typeof pkg !== 'object') {
|
|
throw new CommandExecutionError(`packagist package returned no "package" object for ${full}.`);
|
|
}
|
|
const versionKey = pickStableVersion(pkg.versions);
|
|
const versionEntry = versionKey ? pkg.versions?.[versionKey] : null;
|
|
const license = Array.isArray(versionEntry?.license) ? versionEntry.license.filter(Boolean).join(', ') : '';
|
|
const downloads = pkg.downloads ?? {};
|
|
return [{
|
|
package: String(pkg.name ?? full).trim(),
|
|
version: versionKey ? String(versionKey) : '',
|
|
releasedAt: trimDate(versionEntry?.time),
|
|
license,
|
|
description: String(pkg.description ?? '').trim(),
|
|
repository: String(pkg.repository ?? '').trim(),
|
|
githubStars: pkg.github_stars != null ? Number(pkg.github_stars) : null,
|
|
favers: pkg.favers != null ? Number(pkg.favers) : null,
|
|
downloads: downloads.total != null ? Number(downloads.total) : null,
|
|
monthlyDownloads: downloads.monthly != null ? Number(downloads.monthly) : null,
|
|
dailyDownloads: downloads.daily != null ? Number(downloads.daily) : null,
|
|
url: `https://packagist.org/packages/${full}`,
|
|
}];
|
|
},
|
|
});
|