Skip to content

Commit

Permalink
refactor: 🚧 detach package resolution from github
Browse files Browse the repository at this point in the history
  • Loading branch information
meisZWFLZ committed Dec 14, 2023
1 parent 3bc547c commit cf076dc
Showing 1 changed file with 117 additions and 41 deletions.
158 changes: 117 additions & 41 deletions src/packages/package.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { type Octokit } from "octokit";
import { SemVer, parse } from "semver";
import { createWriteStream } from "fs";
import { Octokit } from "octokit";
import { type Range, SemVer, parse, maxSatisfying } from "semver";
import { type IncomingMessage } from "http";
import { get as httpsGet } from "https";
import { get as httpsGet, type RequestOptions } from "https";

export interface PackageIdentifier {
/**
Expand All @@ -11,74 +10,128 @@ export interface PackageIdentifier {
*/
readonly owner: string;
/**
* @example lemlib
* @example lemlink
*/
readonly repo: string;
}
export type PackageReleaseData = Awaited<

export type GithubPackageReleaseData = Awaited<
ReturnType<Octokit["rest"]["repos"]["listReleases"]>
>["data"][number];

export class PackageVersion extends SemVer {
export abstract class PackageVersion
extends SemVer
implements PackageIdentifier
{
readonly owner: string;
readonly repo: string;
public constructor(packId: PackageIdentifier, version: SemVer) {
super(version);

this.owner = packId.owner;
this.repo = packId.repo;
}
public abstract download(): Promise<NodeJS.ReadableStream | undefined>;
}

export class GithubPackageVersion extends PackageVersion {
protected constructor(
protected readonly client: Octokit,
public readonly packId: PackageIdentifier,
public readonly data: PackageReleaseData,
public readonly data: GithubPackageReleaseData,
version: SemVer,
) {
super(version);
super(packId, version);
}

public static create(
client: Octokit,
packId: PackageIdentifier,
data: PackageReleaseData,
): PackageVersion | null {
data: GithubPackageReleaseData,
): GithubPackageVersion | null {
const version = parse(data.tag_name);
if (version == null) return null;
return new PackageVersion(client, packId, data, version);
return new GithubPackageVersion(client, packId, data, version);
}

protected getAssetIndex(): number {
return 0;
}

public override async download(): Promise<NodeJS.ReadableStream | undefined> {
const index = this.getAssetIndex();
const auth = await this.client.auth();
const opts: RequestOptions = {};

if (
typeof auth === "object" &&
auth != null &&
"token" in auth &&
typeof auth.token === "string"
)
opts.headers = {
Authorization: `Bearer ${auth.token}`,
};

const response = await new Promise<IncomingMessage>((resolve) =>
httpsGet(this.data.assets[index].url, opts, resolve),
);

return response;
}
}

export abstract class Package<
V extends PackageVersion,
ID extends PackageIdentifier,
> implements PackageIdentifier
{
public readonly owner: string;
public readonly repo: string;

constructor(id: ID) {
this.owner = id.owner;
this.repo = id.repo;
}

public abstract getVersions(): Promise<V[]>;
public abstract getLatest(): Promise<V | null>;

public async getVersionsInRange(range: Range): Promise<V[]> {
const versions = await this.getVersions();
return versions.filter((v) => range.test(v));
}

public async download(
path: Path,
asset: { index: number } | { id: number } = { index: 0 },
): Promise<boolean> {
try {
let index: number;
if ("index" in asset) index = asset.index;
else index = this.data.assets.findIndex((a) => a.id === asset.id);

const response = await new Promise<IncomingMessage>((resolve) =>
httpsGet(this.data.assets[index].url, resolve),
);
const fileStream = createWriteStream(path);

response.pipe(fileStream);
return true;
} catch (err) {
return false;
}
public async getLatestInRange(range: Range): Promise<V | null> {
return maxSatisfying(await this.getVersionsInRange(range), range);
}
}

export class Package {
export class GithubPackage extends Package<
GithubPackageVersion,
PackageIdentifier
> {
constructor(
protected readonly id: PackageIdentifier,
protected readonly client: Octokit,
) {}
protected readonly id: PackageIdentifier,
) {
super(id);
}

public async getVersions(): Promise<PackageVersion[]> {
public override async getVersions(): Promise<GithubPackageVersion[]> {
return (
await this.client.rest.repos.listReleases({
...this.id,
})
).data
.map((release) => PackageVersion.create(this.client, this.id, release))
.filter((release): release is PackageVersion => release != null);
.map((release) =>
GithubPackageVersion.create(this.client, this.id, release),
)
.filter((release): release is GithubPackageVersion => release != null);
}

public async getLatest(): Promise<PackageVersion | null> {
return PackageVersion.create(
public async getLatest(): Promise<GithubPackageVersion | null> {
return GithubPackageVersion.create(
this.client,
this.id,
(
Expand All @@ -90,5 +143,28 @@ export class Package {
}
}

/** placeholder */
type Path = string & { __type: "path" };
export abstract class PackageResolver<
P extends Package<PackageVersion, PackageIdentifier>,
> {
public abstract resolvePackage(id: PackageIdentifier): Promise<P | null>;
}

export class GithubPackageResolver extends PackageResolver<GithubPackage> {
private static readonly client: Octokit = new Octokit();
private static readonly singleton: GithubPackageResolver =
new GithubPackageResolver(GithubPackageResolver.client);

protected constructor(protected readonly client: Octokit) {
super();
}

public static get(): GithubPackageResolver {
return GithubPackageResolver.singleton;
}

public override async resolvePackage(
id: PackageIdentifier,
): Promise<GithubPackage> {
return new GithubPackage(this.client, id);
}
}

0 comments on commit cf076dc

Please sign in to comment.