201 lines
4.6 KiB
TypeScript
201 lines
4.6 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
import { BaseUpstream } from '../upstream/classes.baseupstream.js';
|
|
import type {
|
|
IProtocolUpstreamConfig,
|
|
IUpstreamFetchContext,
|
|
IUpstreamRegistryConfig,
|
|
} from '../upstream/interfaces.upstream.js';
|
|
|
|
/**
|
|
* Composer-specific upstream implementation.
|
|
*
|
|
* Handles:
|
|
* - Package metadata fetching (packages.json, provider-includes)
|
|
* - Package version metadata (p2/{vendor}/{package}.json)
|
|
* - Dist file (zip) proxying
|
|
* - Packagist v2 API support
|
|
*/
|
|
export class ComposerUpstream extends BaseUpstream {
|
|
protected readonly protocolName = 'composer';
|
|
|
|
constructor(
|
|
config: IProtocolUpstreamConfig,
|
|
logger?: plugins.smartlog.Smartlog,
|
|
) {
|
|
super(config, logger);
|
|
}
|
|
|
|
/**
|
|
* Fetch the root packages.json from upstream.
|
|
*/
|
|
public async fetchPackagesJson(): Promise<any | null> {
|
|
const context: IUpstreamFetchContext = {
|
|
protocol: 'composer',
|
|
resource: '*',
|
|
resourceType: 'root',
|
|
path: '/packages.json',
|
|
method: 'GET',
|
|
headers: {
|
|
'accept': 'application/json',
|
|
},
|
|
query: {},
|
|
};
|
|
|
|
const result = await this.fetch(context);
|
|
|
|
if (!result || !result.success) {
|
|
return null;
|
|
}
|
|
|
|
if (Buffer.isBuffer(result.body)) {
|
|
return JSON.parse(result.body.toString('utf8'));
|
|
}
|
|
|
|
return result.body;
|
|
}
|
|
|
|
/**
|
|
* Fetch package metadata using v2 API (p2/{vendor}/{package}.json).
|
|
*/
|
|
public async fetchPackageMetadata(vendor: string, packageName: string): Promise<any | null> {
|
|
const fullName = `${vendor}/${packageName}`;
|
|
const path = `/p2/${vendor}/${packageName}.json`;
|
|
|
|
const context: IUpstreamFetchContext = {
|
|
protocol: 'composer',
|
|
resource: fullName,
|
|
resourceType: 'metadata',
|
|
path,
|
|
method: 'GET',
|
|
headers: {
|
|
'accept': 'application/json',
|
|
},
|
|
query: {},
|
|
};
|
|
|
|
const result = await this.fetch(context);
|
|
|
|
if (!result || !result.success) {
|
|
return null;
|
|
}
|
|
|
|
if (Buffer.isBuffer(result.body)) {
|
|
return JSON.parse(result.body.toString('utf8'));
|
|
}
|
|
|
|
return result.body;
|
|
}
|
|
|
|
/**
|
|
* Fetch package metadata with dev versions (p2/{vendor}/{package}~dev.json).
|
|
*/
|
|
public async fetchPackageDevMetadata(vendor: string, packageName: string): Promise<any | null> {
|
|
const fullName = `${vendor}/${packageName}`;
|
|
const path = `/p2/${vendor}/${packageName}~dev.json`;
|
|
|
|
const context: IUpstreamFetchContext = {
|
|
protocol: 'composer',
|
|
resource: fullName,
|
|
resourceType: 'metadata-dev',
|
|
path,
|
|
method: 'GET',
|
|
headers: {
|
|
'accept': 'application/json',
|
|
},
|
|
query: {},
|
|
};
|
|
|
|
const result = await this.fetch(context);
|
|
|
|
if (!result || !result.success) {
|
|
return null;
|
|
}
|
|
|
|
if (Buffer.isBuffer(result.body)) {
|
|
return JSON.parse(result.body.toString('utf8'));
|
|
}
|
|
|
|
return result.body;
|
|
}
|
|
|
|
/**
|
|
* Fetch a provider-includes file.
|
|
*/
|
|
public async fetchProviderIncludes(path: string): Promise<any | null> {
|
|
const context: IUpstreamFetchContext = {
|
|
protocol: 'composer',
|
|
resource: '*',
|
|
resourceType: 'provider',
|
|
path: path.startsWith('/') ? path : `/${path}`,
|
|
method: 'GET',
|
|
headers: {
|
|
'accept': 'application/json',
|
|
},
|
|
query: {},
|
|
};
|
|
|
|
const result = await this.fetch(context);
|
|
|
|
if (!result || !result.success) {
|
|
return null;
|
|
}
|
|
|
|
if (Buffer.isBuffer(result.body)) {
|
|
return JSON.parse(result.body.toString('utf8'));
|
|
}
|
|
|
|
return result.body;
|
|
}
|
|
|
|
/**
|
|
* Fetch a dist file (zip) from upstream.
|
|
*/
|
|
public async fetchDist(url: string): Promise<Buffer | null> {
|
|
// Parse the URL to get the path
|
|
let path: string;
|
|
try {
|
|
const parsed = new URL(url);
|
|
path = parsed.pathname;
|
|
} catch {
|
|
path = url;
|
|
}
|
|
|
|
const context: IUpstreamFetchContext = {
|
|
protocol: 'composer',
|
|
resource: '*',
|
|
resourceType: 'dist',
|
|
path,
|
|
method: 'GET',
|
|
headers: {
|
|
'accept': 'application/zip, application/octet-stream',
|
|
},
|
|
query: {},
|
|
};
|
|
|
|
const result = await this.fetch(context);
|
|
|
|
if (!result || !result.success) {
|
|
return null;
|
|
}
|
|
|
|
return Buffer.isBuffer(result.body) ? result.body : Buffer.from(result.body);
|
|
}
|
|
|
|
/**
|
|
* Override URL building for Composer-specific handling.
|
|
*/
|
|
protected buildUpstreamUrl(
|
|
upstream: IUpstreamRegistryConfig,
|
|
context: IUpstreamFetchContext,
|
|
): string {
|
|
let baseUrl = upstream.url;
|
|
|
|
// Remove trailing slash
|
|
if (baseUrl.endsWith('/')) {
|
|
baseUrl = baseUrl.slice(0, -1);
|
|
}
|
|
|
|
return `${baseUrl}${context.path}`;
|
|
}
|
|
}
|