Skip to content

Commit

Permalink
chore(ci): introduce benchmarks on JS/TS projects to track skott perf…
Browse files Browse the repository at this point in the history
…ormance over time (#145)

* feat(remote-tarball-fetcher): add github fetcher

* bench: add vitest benchmark with few js and ts projects

* fix: properly extract tar and zip artifacts

* refactor: extract util for web streams to nodejs streams

* feat: use GITHUB_TOKEN to avoid rate limiting in CI

* refactor: customize benchmark output

* ci: run benchmark for skott

* chore: scope benchmark only for ubuntu runners

* chore(ci): run the benchmark with a publish of the result only in the context of a feat branch

* get rid of benchmarks in ci

* chore: add changeset

* docs: update pull_request_template

* ci: enable bench

* benchmark: publish results

* Add node version to benchmark

* benchmark: publish results

* Add node version to benchmark

* benchmark: publish benchmark results from node_20.x

* benchmark: publish benchmark results from node_18.x

* ci: add github sha and ref

* benchmark: publish benchmark results from node_20.x

* benchmark: publish benchmark results from node_20.x

* benchmark: publish benchmark results from node_20.x

* benchmark: publish benchmark results from node_20.x

* benchmark: publish benchmark results from node_20.x

* fetch origin before

* benchmark: publish benchmark results from node_20.x

* use checkout v4

* benchmark: publish benchmark results from node_20.x

* use checkout v4

* benchmark: publish benchmark results from node_20.x

* use checkout v4

* benchmark: publish benchmark results from node_20.x

* use checkout v4

* benchmark: publish benchmark results from node_20.x

* use checkout v4

* benchmark: publish benchmark results from node_20.x

* benchmark: publish benchmark results from node_18.x

* benchmark: publish benchmark results from node_20.x

* benchmark: publish benchmark results from node_18.x

* benchmark: publish benchmark results from node_18.x

* benchmark: publish benchmark results from node_20.x

* benchmark: publish benchmark results from node_18.x

* debug env

* benchmark: publish benchmark results from node_20.x

* benchmark: publish benchmark results from node_18.x

* debug env

* benchmark: publish benchmark results from node_20.x

* benchmark: publish benchmark results from node_18.x

* ci: constrain benchmark run

* benchmark: publish benchmark results from node_20.x

* benchmark: publish benchmark results from node_18.x

---------

Co-authored-by: skott_benchmark <skott.devtool@gmail.com>
  • Loading branch information
antoine-coulon and skott_benchmark committed Mar 15, 2024
1 parent e0e3ba1 commit 2c00723
Show file tree
Hide file tree
Showing 25 changed files with 1,262 additions and 458 deletions.
7 changes: 7 additions & 0 deletions .changeset/silent-frogs-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"remote-tarball-fetcher": minor
---

Add GitHub tarball fetcher to be able to pull public repositories as `.zip` (used in skott's benchmark).

Switch to an [Effect](https://github.com/Effect-TS)-based API.
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
integration tests?
--------------------------------------------------------------------------->

- [ ] Unit tests were added to cover the new feature or bug fix (+ eventually integration tests)
- [ ] Unit tests were added to cover the new feature or bug fix (+ eventually integration tests, but unit should be preferred whenever its possible).

## Impacted documentation

Expand Down
33 changes: 32 additions & 1 deletion .github/workflows/skott.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
fail-fast: false

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
Expand Down Expand Up @@ -55,3 +55,34 @@ jobs:
run: pnpm -r test:integration
env:
CI: true

- name: Run benchmarks
if: matrix.os == 'ubuntu-latest' && !contains(github.ref, 'main') && !contains(github.ref, 'release')
run: |
git config --local user.name "skott_bot"
git config --local user.email "skott.devtool@gmail.com"
git fetch origin
git pull origin ${{ github.head_ref }} --rebase
pnpm -r benchmark
git status
git add .
git commit -m "benchmark: publish benchmark results from node_${{ matrix.node-version }}"
git pull origin ${{ github.head_ref }} --rebase
if git diff --quiet; then
echo "No conflicts, proceeding with the push."
else
git checkout --theirs .
git add .
git rebase --continue
fi
git push origin HEAD:${{ github.head_ref }}
env:
CI: true
NODE_VERSION: ${{ matrix.node-version }}



1 change: 0 additions & 1 deletion packages/remote-tarball-fetcher/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
}
],
"max-nested-callbacks": "off",
"@typescript-eslint/explicit-function-return-type": ["error"],
"@typescript-eslint/no-unused-vars": [
"error",
{ "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }
Expand Down
Binary file added packages/remote-tarball-fetcher/fixture/project.zip
Binary file not shown.
11 changes: 8 additions & 3 deletions packages/remote-tarball-fetcher/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,23 @@
"lint": "eslint ."
},
"dependencies": {
"node-fetch": "^3.3.1",
"@effect/schema": "0.63.2",
"effect": "2.4.1",
"semver": "^7.5.3",
"tar": "^6.1.15"
"tar": "^6.1.15",
"unzipper": "^0.10.14"
},
"devDependencies": {
"@nodesecure/eslint-config": "^1.7.0",
"@skottorg/config": "workspace:*",
"@types/adm-zip": "^0.5.5",
"@types/chai": "^4.3.5",
"@types/mocha": "^9.1.1",
"@types/node": "^16.18.36",
"@types/node": "^20.11.24",
"@types/semver": "^7.5.0",
"@types/tar": "^6.1.5",
"@types/tar-stream": "^3.1.3",
"@types/unzipper": "^0.10.9",
"chai": "^4.3.7",
"eslint": "^8.43.0",
"eslint-config-prettier": "^8.8.0",
Expand Down
11 changes: 0 additions & 11 deletions packages/remote-tarball-fetcher/src/fetcher/common.ts

This file was deleted.

27 changes: 27 additions & 0 deletions packages/remote-tarball-fetcher/src/fetcher/definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { ReadableStream } from "node:stream/web";

import { Effect, Option } from "effect";

export interface PackageInformation {
id: string;
tarballUrl: string;
}

export class FetchPackageInformationError {
readonly _tag = "FetchPackageInformationError";
}

export interface Fetcher<AdditionalInformation = Record<string, string>> {
fetchPackageInformation: (
libraryName: string
) => Effect.Effect<
PackageInformation & AdditionalInformation,
FetchPackageInformationError
>;
downloadTarball: (tarballUrl: string) => Effect.Effect<
Option.Option<{
stream: ReadableStream;
format: "zip" | "tar";
}>
>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ReadableStream } from "node:stream/web";

import { expect } from "chai";
import { Effect, Option, pipe } from "effect";

import { githubFetcher } from "./github.js";

function itEffect<A, E>(
description: string,
effect: Effect.Effect<A, E, never>
) {
it(description, () => effect.pipe(Effect.runPromise));
}

describe("GitHub integration", () => {
describe("When fetching skott tarball", () => {
itEffect(
"Should fetch the tarball from the main branch",
Effect.gen(function* gen(_) {
const fetcher = githubFetcher;

const { id, defaultBranch, tarballUrl } = yield* _(
fetcher.fetchPackageInformation("antoine-coulon/skott")
);

expect(id).to.be.a("string");
expect({ defaultBranch, tarballUrl }).to.deep.equal({
defaultBranch: "main",
tarballUrl: "https://github.com/antoine-coulon/skott/archive/main.zip"
});

const { format, stream } = yield* _(
pipe(
Effect.map(
fetcher.downloadTarball(
"https://github.com/antoine-coulon/skott/archive/main.zip"
),
Option.getOrThrow
)
)
);

expect(format).to.equal("zip");
expect(stream).to.be.an.instanceOf(ReadableStream);
})
);
});
});
56 changes: 56 additions & 0 deletions packages/remote-tarball-fetcher/src/fetcher/github.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as S from "@effect/schema/Schema";
import { Effect, Option, flow, pipe } from "effect";

import { FetchPackageInformationError, type Fetcher } from "./definition.js";

interface GitHubRepositoryInformation {
defaultBranch: string;
}

const GitHubSchema = S.struct({
default_branch: S.string,
id: S.number
});

function makeTarballUrl(branch: string, repositoryFullName: string) {
return `https://github.com/${repositoryFullName}/archive/${branch}.zip`;
}

// eslint-disable-next-line func-style, @typescript-eslint/explicit-function-return-type
export const githubFetcher: Fetcher<GitHubRepositoryInformation> = {
fetchPackageInformation: (repositoryName) =>
pipe(
Effect.tryPromise({
try: () => {
const headers = process.env.GITHUB_TOKEN
? {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
}
: {};

return fetch(`https://api.github.com/repos/${repositoryName}`, {
headers
} as any).then((response) => response.json());
},
catch: () => new FetchPackageInformationError()
}),
Effect.flatMap(flow(S.decodeUnknown(GitHubSchema), Effect.orDie)),
Effect.map(({ default_branch, id }) => {
return {
id: id.toString(),
defaultBranch: default_branch,
tarballUrl: makeTarballUrl(default_branch, repositoryName)
};
})
),
downloadTarball: (tarballUrl) =>
Effect.promise(() =>
fetch(tarballUrl).then((response) => Option.fromNullable(response.body))
).pipe(
Effect.map(
Option.map((body) => {
return { stream: body, format: "zip" };
})
)
)
};
3 changes: 2 additions & 1 deletion packages/remote-tarball-fetcher/src/fetcher/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./common.js";
export * from "./definition.js";
export * from "./npm.js";
export * from "./github.js";
92 changes: 58 additions & 34 deletions packages/remote-tarball-fetcher/src/fetcher/npm.integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,76 @@
import { Readable } from "node:stream";
import { ReadableStream } from "node:stream/web";

import { expect } from "chai";
import { Effect, Option } from "effect";

import { npmFetcher } from "./npm.js";

function itEffect<A, E>(
description: string,
effect: Effect.Effect<A, E, never>
) {
it(description, () => effect.pipe(Effect.runPromise));
}

describe("npm tarball fetcher", () => {
describe("When fetching package information from the npm registry", () => {
describe("When no version is provided", () => {
it("should fetch both the latest version and the tarball of the latest version", async () => {
const fetcher = npmFetcher;
itEffect(
"should fetch both the latest version and the tarball of the latest version",
Effect.gen(function* gen(_) {
const fetcher = npmFetcher;

const packageInformation = await fetcher.fetchPackageInformation(
"openforker"
);
const packageInformation = yield* _(
fetcher.fetchPackageInformation("openforker")
);

expect(packageInformation).to.deep.equal({
latestVersion: "1.0.7",
tarballUrl:
"https://registry.npmjs.org/openforker/-/openforker-1.0.7.tgz"
});
expect(packageInformation).to.deep.equal({
id: "1.0.7",
latestVersion: "1.0.7",
tarballUrl:
"https://registry.npmjs.org/openforker/-/openforker-1.0.7.tgz"
});

const tarball = await fetcher.downloadTarball(
packageInformation.tarballUrl
);
const { stream } = yield* _(
Effect.map(
fetcher.downloadTarball(packageInformation.tarballUrl),
Option.getOrThrow
)
);

expect(tarball).to.be.an.instanceOf(Readable);
});
expect(stream).to.be.an.instanceOf(ReadableStream);
})
);
});

describe("When a version is provided", () => {
it("should fetch both the latest version and the tarball from the specified version", async () => {
const fetcher = npmFetcher;

const packageInformation = await fetcher.fetchPackageInformation(
"openforker@1.0.5"
);

expect(packageInformation).to.deep.equal({
latestVersion: "1.0.7",
tarballUrl:
"https://registry.npmjs.org/openforker/-/openforker-1.0.5.tgz"
});

const tarball = await fetcher.downloadTarball(
packageInformation.tarballUrl
);
expect(tarball).to.be.an.instanceOf(Readable);
});
itEffect(
"should fetch both the latest version and the tarball from the specified version",
Effect.gen(function* gen(_) {
const fetcher = npmFetcher;

const packageInformation = yield* _(
fetcher.fetchPackageInformation("openforker@1.0.5")
);

expect(packageInformation).to.deep.equal({
id: "1.0.5",
latestVersion: "1.0.7",
tarballUrl:
"https://registry.npmjs.org/openforker/-/openforker-1.0.5.tgz"
});

const { format, stream } = yield* _(
Effect.map(
fetcher.downloadTarball(packageInformation.tarballUrl),
Option.getOrThrow
)
);

expect(format).to.equal("tar");
expect(stream).to.be.an.instanceOf(ReadableStream);
})
);
});
});
});
Loading

0 comments on commit 2c00723

Please sign in to comment.