astro-contributors 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,53 @@
1
+ # astro-contributors
2
+
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#70](https://github.com/trueberryless-org/astro-contributors/pull/70) [`3788107`](https://github.com/trueberryless-org/astro-contributors/commit/37881073106047c17d1a17a87776244f322e7f40) Thanks [@trueberryless](https://github.com/trueberryless)! - Rename package from starlight-contributor-list to astro-contributors
8
+
9
+ ## 0.4.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`0d1841f`](https://github.com/trueberryless-org/astro-contributors/commit/0d1841f3df224c12523384487a4f88fdc7950f45) Thanks [@trueberryless](https://github.com/trueberryless)! - Remove unnecessary dependencies, peer dependencies and dev dependencies
14
+
15
+ ### Patch Changes
16
+
17
+ - [#66](https://github.com/trueberryless-org/astro-contributors/pull/66) [`5014fc1`](https://github.com/trueberryless-org/astro-contributors/commit/5014fc1506028ebbc6f6ea129c7d0a9965cb90ee) Thanks [@trueberryless](https://github.com/trueberryless)! - Fine tune package metadata
18
+
19
+ ## 0.3.2
20
+
21
+ ### Patch Changes
22
+
23
+ - [#60](https://github.com/trueberryless-org/astro-contributors/pull/60) [`ad2c493`](https://github.com/trueberryless-org/astro-contributors/commit/ad2c4933a3a049916d17d5fabba5f204fa8ac376) Thanks [@trueberryless](https://github.com/trueberryless)! - Fix keyboard focus accessibility issues.
24
+
25
+ ## 0.3.1
26
+
27
+ ### Patch Changes
28
+
29
+ - [#51](https://github.com/trueberryless-org/astro-contributors/pull/51) [`57dc151`](https://github.com/trueberryless-org/astro-contributors/commit/57dc15123e9f0132b29ce9e47e0fb8dc91f2be49) Thanks [@trueberryless-org](https://github.com/apps/trueberryless-org)! - Setups trusted publishing using OpenID Connect (OIDC) authentication — no code changes.
30
+
31
+ ## 0.3.0
32
+
33
+ ### Minor Changes
34
+
35
+ - [#24](https://github.com/trueberryless-org/astro-contributors/pull/24) [`368e54d`](https://github.com/trueberryless-org/astro-contributors/commit/368e54d14c5277bbfce227f835715532bd5067c8) Thanks [@trueberryless](https://github.com/trueberryless)! - Add `ignore` parameter option
36
+
37
+ ## 0.2.1
38
+
39
+ ### Patch Changes
40
+
41
+ - [#19](https://github.com/trueberryless-org/astro-contributors/pull/19) [`4f439a3`](https://github.com/trueberryless-org/astro-contributors/commit/4f439a38bed4c3bbdde1891c65c37066fa033363) Thanks [@trueberryless](https://github.com/trueberryless)! - Update to Starlight 0.32
42
+
43
+ ## 0.2.0
44
+
45
+ ### Minor Changes
46
+
47
+ - [#9](https://github.com/trueberryless-org/astro-contributors/pull/9) [`128f46c`](https://github.com/trueberryless-org/astro-contributors/commit/128f46cca2b8c98abade03e5a3dee64970b367ae) Thanks [@trueberryless](https://github.com/trueberryless)! - Adds support for Astro v5, drops support for Astro v4.
48
+
49
+ ⚠️ **BREAKING CHANGE:** The minimum supported version of Starlight is now `0.30.0`.
50
+
51
+ Please follow the [upgrade guide](https://github.com/withastro/starlight/releases/tag/%40astrojs/starlight%400.30.0) to update your project.
52
+
53
+ Note that the [`legacy.collections` flag](https://docs.astro.build/en/reference/legacy-flags/#collections) is not supported by this plugin and you should update your collections to use Astro's new Content Layer API.
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # `astro-contributors`
2
+
3
+ Display a list of all contributors to your project
4
+
5
+ ## Documentation
6
+
7
+ Want to get started immediately?
8
+
9
+ Check out the `astro-contributors` getting started guide.
10
+
11
+ ## License
12
+
13
+ Licensed under the MIT license, Copyright © trueberryless.
14
+
15
+ See [LICENSE](/LICENSE) for more information.
@@ -0,0 +1,15 @@
1
+ ---
2
+ import { getAllContributors } from "../libs/getContributors";
3
+ import FacePile from "./FacePile.astro";
4
+
5
+ export interface Props {
6
+ githubRepo?: `${string}/${string}`;
7
+ ignore?: string[];
8
+ }
9
+
10
+ const { githubRepo = "withastro/docs", ignore } = Astro.props as Props;
11
+
12
+ const contributors = await getAllContributors(githubRepo, ignore);
13
+ ---
14
+
15
+ <FacePile {contributors} />
@@ -0,0 +1,87 @@
1
+ ---
2
+ import type { Contributor } from "../libs/getContributors";
3
+
4
+ export interface Props {
5
+ contributors: Contributor[];
6
+ }
7
+
8
+ const { contributors } = Astro.props as Props;
9
+ ---
10
+
11
+ <!-- Thanks to @5t3ph for https://smolcss.dev/#smol-avatar-list! -->
12
+ <div>
13
+ <ul class="avatar-list">
14
+ {
15
+ contributors.map((item) => (
16
+ <li>
17
+ <a href={`https://github.com/${item.login}`}>
18
+ <img
19
+ alt={item.login}
20
+ title={item.login}
21
+ width="3rem"
22
+ height="3rem"
23
+ src={`https://avatars.githubusercontent.com/u/${item.id}?s=64`}
24
+ loading="lazy"
25
+ />
26
+ </a>
27
+ </li>
28
+ ))
29
+ }
30
+ </ul>
31
+ </div>
32
+
33
+ <style>
34
+ .avatar-list {
35
+ --avatar-size: 3rem;
36
+ --avatar-overlap: -0.125em;
37
+ --avatar-row-spacing: 0.125em;
38
+ --avatar-outline-width: 1px;
39
+ --avatar-outline-offset: 0.08em;
40
+
41
+ display: flex;
42
+ flex-wrap: wrap;
43
+ list-style: none;
44
+ padding: 0rem;
45
+ font-size: var(--avatar-size);
46
+ }
47
+
48
+ .avatar-list li {
49
+ position: relative;
50
+ --avatar-row-margin: calc(
51
+ var(--avatar-outline-offset) + var(--avatar-outline-width) +
52
+ var(--avatar-row-spacing) / 2
53
+ );
54
+ margin: var(--avatar-row-margin) var(--avatar-overlap)
55
+ var(--avatar-row-margin) 0;
56
+ }
57
+
58
+ .avatar-list li:has(:focus-visible) {
59
+ z-index: 10;
60
+ transform: scale(1.1);
61
+ }
62
+
63
+ .avatar-list img,
64
+ .avatar-list a {
65
+ display: block;
66
+ border-radius: 50%;
67
+ width: var(--avatar-size);
68
+ height: var(--avatar-size);
69
+ /* Hide alt/title if Avatar image fails to load. */
70
+ text-decoration: none;
71
+ color: transparent;
72
+ }
73
+
74
+ .avatar-list img {
75
+ width: 100%;
76
+ height: 100%;
77
+ object-fit: cover;
78
+ background-color: var(--sl-color-accent);
79
+ /* Indicates the contributor image boundaries for forced colors users, transparent is changed to a solid color */
80
+ outline: 1px solid transparent;
81
+ }
82
+
83
+ .avatar-list a:focus-visible {
84
+ outline: 2px solid var(--sl-color-accent);
85
+ outline-offset: var(--avatar-outline-offset);
86
+ }
87
+ </style>
package/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { default as ContributorList } from "./components/ContributorList.astro";
@@ -0,0 +1,105 @@
1
+ import { cachedFetch } from "./util-server";
2
+
3
+ export interface Contributor {
4
+ login: string;
5
+ id: number;
6
+ contributions: number;
7
+ }
8
+
9
+ interface Commit {
10
+ author: {
11
+ login: string;
12
+ id: number;
13
+ };
14
+ commit: {
15
+ message: string;
16
+ };
17
+ }
18
+
19
+ const printError = (e: Error) =>
20
+ console.warn(
21
+ `[error] /src/util/getContributors.ts\n ${e?.message ?? e}`
22
+ );
23
+
24
+ async function recursiveFetch(endpoint: string, page = 1) {
25
+ try {
26
+ const queryParam = endpoint.includes("?") ? "&" : "?";
27
+ const pageSize = 100;
28
+ const url = `https://api.github.com/${endpoint}${queryParam}per_page=${pageSize}&page=${page}`;
29
+
30
+ const token = import.meta.env.PUBLIC_GITHUB_TOKEN;
31
+
32
+ const res = await cachedFetch(
33
+ url,
34
+ {
35
+ method: "GET",
36
+ headers: {
37
+ Authorization:
38
+ token && `Basic ${Buffer.from(token, "binary").toString("base64")}`,
39
+ "User-Agent": "astro-docs/1.0",
40
+ },
41
+ },
42
+ { duration: "15m" }
43
+ );
44
+
45
+ const data = await res.json();
46
+
47
+ if (!res.ok) {
48
+ throw new Error(
49
+ `Request to fetch endpoint failed. Reason: ${res.statusText}
50
+ Message: ${data?.message}`
51
+ );
52
+ }
53
+
54
+ // Fetch more data recursively if there are more than GitHub’s per-page response limit.
55
+ if (data.length === pageSize) {
56
+ const rest = await recursiveFetch(endpoint, page + 1);
57
+ data.push(...rest);
58
+ }
59
+
60
+ return data;
61
+ } catch (e) {
62
+ printError(e as Error);
63
+ return [];
64
+ }
65
+ }
66
+
67
+ export async function getAllContributors(repo: string, ignore: string[] = []) {
68
+ const endpoint = `repos/${repo}/contributors`;
69
+ const contributors: Contributor[] = await recursiveFetch(endpoint);
70
+
71
+ return contributors.filter(({ login }) => !ignore.includes(login));
72
+ }
73
+
74
+ export async function getTranslationContributors(
75
+ paths: string[],
76
+ repo: string
77
+ ) {
78
+ const ignoredCommitKeywords =
79
+ /(en-only|typo|broken link|i18nReady|i18nIgnore)/i;
80
+ const contributors: Contributor[] = [];
81
+
82
+ for (const path of paths) {
83
+ const endpoint = `repos/${repo}/commits?path=${path}`;
84
+ const commits: Commit[] = await recursiveFetch(endpoint);
85
+
86
+ for (const { author, commit } of commits) {
87
+ if (ignoredCommitKeywords.test(commit.message) || !author) continue;
88
+ const contributor = contributors.find(
89
+ (contributor) => contributor.id === author.id
90
+ );
91
+
92
+ if (!contributor) {
93
+ contributors.push({
94
+ id: author.id,
95
+ login: author.login,
96
+ contributions: 1,
97
+ });
98
+ continue;
99
+ }
100
+ contributor.contributions += 1;
101
+ }
102
+ }
103
+
104
+ return contributors.sort((a, b) => b.contributions - a.contributions);
105
+ }
@@ -0,0 +1,45 @@
1
+ /*
2
+ This file contains server-side utilities using features that don't work in a browser.
3
+ Do not import this file from a hydrated client-side component.
4
+ */
5
+
6
+ export type CachedFetchOptions = {
7
+ duration?: string;
8
+ verbose?: boolean;
9
+ };
10
+
11
+ export async function cachedFetch(
12
+ url: string,
13
+ fetchOptions = {},
14
+ { duration = "5m", verbose = false }: CachedFetchOptions = {}
15
+ ) {
16
+ let status = 200;
17
+ let statusText = "OK";
18
+ let buffer: Buffer | undefined;
19
+
20
+ try {
21
+ const response = await fetch(url, fetchOptions);
22
+ status = response.status;
23
+ statusText = response.statusText;
24
+ if (!response.ok) {
25
+ throw new Error(`Bad response for ${url} (${status}): ${statusText}`);
26
+ }
27
+ const arrayBuffer = await response.arrayBuffer();
28
+ buffer = Buffer.from(arrayBuffer);
29
+ } catch (e: unknown) {
30
+ const error = e as Error;
31
+ const msg: string = error?.message || error.toString();
32
+ const matches = msg.match(/^Bad response for (.*) \(.*?\): (.*)$/);
33
+ status = parseInt(matches?.[2] || "") || 404;
34
+ statusText = matches?.[3] || msg;
35
+ }
36
+
37
+ return {
38
+ ok: status >= 200 && status <= 299,
39
+ status,
40
+ statusText,
41
+ body: buffer,
42
+ json: () => buffer && JSON.parse(buffer.toString()),
43
+ text: () => buffer?.toString(),
44
+ };
45
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "astro-contributors",
3
+ "version": "0.5.0",
4
+ "description": "Display a list of all contributors to your project",
5
+ "keywords": [
6
+ "starlight",
7
+ "docs",
8
+ "contributors",
9
+ "component"
10
+ ],
11
+ "homepage": "https://astro-contributors.trueberryless.org/",
12
+ "bugs": {
13
+ "url": "https://github.com/trueberryless-org/astro-contributors/issues"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/trueberryless-org/astro-contributors.git",
18
+ "directory": "packages/astro-contributors"
19
+ },
20
+ "license": "MIT",
21
+ "author": "trueberryless <trueberryless@gmail.com> (https://trueberryless.org)",
22
+ "type": "module",
23
+ "exports": {
24
+ ".": "./index.ts",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "peerDependencies": {
28
+ "astro": "^5.0.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=22.12.0"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ }
36
+ }