Skip to content
This repository has been archived by the owner on Dec 13, 2024. It is now read-only.

Add rudimentary package resolution via Github #15

Merged
merged 14 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run check-format
npm run pre-commit
908 changes: 631 additions & 277 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"devDependencies": {
"@commitlint/cli": "^18.4.3",
"@commitlint/config-conventional": "^18.4.3",
"@types/adm-zip": "^0.5.5",
"@types/semver": "^7.5.6",
"@types/yargs": "^17.0.32",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
Expand All @@ -40,9 +42,6 @@
"semantic-release": "^22.0.8",
"typescript": "^5.0.2"
},
"bin": {
"lem-link": "out/index.js"
},
"release": {
"branches": [
"stable",
Expand All @@ -58,5 +57,10 @@
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"dependencies": {
"adm-zip": "^0.5.10",
"octokit": "^3.1.2",
"semver": "^7.5.4"
}
}
3 changes: 3 additions & 0 deletions src/packages/github/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { GithubPackage, type GithubPackageReleaseData } from "./package";
export { GithubPackageResolver } from "./resolver";
export { GithubPackageVersion } from "./version";
43 changes: 43 additions & 0 deletions src/packages/github/package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { type Octokit } from "octokit";
import { Package, type PackageIdentifier } from "../package";
import { GithubPackageVersion } from "./version";

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

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

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

public async getLatest(): Promise<GithubPackageVersion | null> {
return GithubPackageVersion.create(
this.client,
this.id,
(
await this.client.rest.repos.getLatestRelease({
...this.id,
})
).data,
);
}
}
15 changes: 15 additions & 0 deletions src/packages/github/resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type Octokit } from "octokit";
import { type PackageIdentifier, PackageResolver } from "../package";
import { GithubPackage } from "./package";

export class GithubPackageResolver extends PackageResolver<GithubPackage> {
public constructor(protected readonly client: Octokit) {
super();
}

public override async resolvePackage(
id: PackageIdentifier,
): Promise<GithubPackage> {
return new GithubPackage(this.client, id);
}
}
49 changes: 49 additions & 0 deletions src/packages/github/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { type Octokit } from "octokit";
import { PackageVersion, type PackageIdentifier } from "../package";
import { type SemVer, parse } from "semver";
import { type GithubPackageReleaseData } from "./package";

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

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

protected getAssetIndex(): number {
return 0;
}

public override async download(): Promise<Buffer | undefined> {
const index = this.getAssetIndex();

const res = await this.client.rest.repos.getReleaseAsset({
repo: this.packId.repo,
owner: this.packId.owner,
asset_id: this.data.assets[index].id,
headers: { accept: "application/octet-stream" },
});

const data = res.data;

if (data instanceof ArrayBuffer) {
return Buffer.from(data);
}
throw new Error(
"github api response was not Array. res status: " + res.status,
);
}
}
66 changes: 66 additions & 0 deletions src/packages/package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { eq as verEquals, type Range, type SemVer } from "semver";

export interface PackageIdentifier {
/**
* does not include @ sign
* @example lemlib
*/
readonly owner: string;
/**
* @example lemlink
*/
readonly repo: string;
}

export abstract class PackageVersion implements PackageIdentifier {
readonly owner: string;
readonly repo: string;
public constructor(
packId: PackageIdentifier,
public readonly version: SemVer,
) {
this.owner = packId.owner;
this.repo = packId.repo;
}
public abstract download(): Promise<Buffer | undefined>;
}

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 getVersion(version: SemVer): Promise<V | undefined> {
return (await this.getVersions()).find((v) =>
verEquals(v.version, version),
);
}

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

public async getLatestInRange(range: Range): Promise<V | undefined> {
return (await this.getVersionsInRange(range))
.sort((a, b) => a.version.compare(b.version))
.pop();
}
}

export abstract class PackageResolver<
P extends Package<PackageVersion, PackageIdentifier>,
> {
public abstract resolvePackage(id: PackageIdentifier): Promise<P | null>;
}
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

/* Language and Environment */
"target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"lib": [
"es2020"
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
Expand Down