From f6b70bade0dd8c68bb8f000f00bc290571d181ba Mon Sep 17 00:00:00 2001 From: Huakun Shen Date: Sun, 12 Jan 2025 01:17:15 -0500 Subject: [PATCH] feat: add repo ownership check for org in jsr validation algo --- packages/api/package.json | 1 + .../extensions/jsr/__tests__/ext-pkg.test.ts | 118 ++++---- packages/api/src/extensions/jsr/github.ts | 34 +++ packages/api/src/extensions/jsr/index.ts | 281 ++++++++---------- pnpm-lock.yaml | 150 +++++++++- 5 files changed, 368 insertions(+), 216 deletions(-) create mode 100644 packages/api/src/extensions/jsr/github.ts diff --git a/packages/api/package.json b/packages/api/package.json index 43f4150..f76f28a 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -43,6 +43,7 @@ }, "dependencies": { "@huakunshen/jsr-client": "^0.1.5", + "@octokit/rest": "^21.1.0", "@tauri-apps/api": "^2.2.0", "@tauri-apps/cli": "^2.2.2", "@tauri-apps/plugin-deep-link": "^2.2.0", diff --git a/packages/api/src/extensions/jsr/__tests__/ext-pkg.test.ts b/packages/api/src/extensions/jsr/__tests__/ext-pkg.test.ts index 9a315e9..d1adc85 100644 --- a/packages/api/src/extensions/jsr/__tests__/ext-pkg.test.ts +++ b/packages/api/src/extensions/jsr/__tests__/ext-pkg.test.ts @@ -1,71 +1,75 @@ -import { describe, expect, test } from "bun:test"; -import { validateJsrPackageAsKunkunExtension } from "../index"; +import { describe, expect, test } from "bun:test" +import { validateJsrPackageAsKunkunExtension } from "../index" describe("Validate Jsr package as Kunkun extension", () => { test("Package not signed by GitHub Actions", async () => { expect( - (await validateJsrPackageAsKunkunExtension({ - jsrPackage: { - scope: "kunkun", - name: "api", - version: "0.0.47", - }, - githubUsername: "kunkunsh", - })).error, - ).toBe("JSR package is not signed by GitHub Actions"); - }); + ( + await validateJsrPackageAsKunkunExtension({ + jsrPackage: { + scope: "kunkun", + name: "api", + version: "0.0.47" + }, + githubUsername: "kunkunsh" + }) + ).error + ).toBe("JSR package is not signed by GitHub Actions") + }) test("Non-existent package", async () => { expect( - (await validateJsrPackageAsKunkunExtension({ - jsrPackage: { - scope: "kunkun", - name: "non-existent-package", - version: "0.0.47", - }, - githubUsername: "kunkunsh", - })).error, - ).toBe("JSR package does not exist"); - }); + ( + await validateJsrPackageAsKunkunExtension({ + jsrPackage: { + scope: "kunkun", + name: "non-existent-package", + version: "0.0.47" + }, + githubUsername: "kunkunsh" + }) + ).error + ).toBe("JSR package does not exist") + }) test("Package not linked to a GitHub repository", async () => { expect( - (await validateJsrPackageAsKunkunExtension({ - jsrPackage: { - scope: "hk", - name: "tauri-plugin-network-api", - version: "2.0.3-beta.1", - }, - githubUsername: "kunkunsh", - })).error, - ).toBe("JSR package is not linked to a GitHub repository"); - }); + ( + await validateJsrPackageAsKunkunExtension({ + jsrPackage: { + scope: "hk", + name: "tauri-plugin-network-api", + version: "2.0.3-beta.1" + }, + githubUsername: "kunkunsh" + }) + ).error + ).toBe("JSR package is not linked to a GitHub repository") + }) test("GitHub repository owner does not match JSR package owner", async () => { - expect( - (await validateJsrPackageAsKunkunExtension({ - jsrPackage: { - scope: "kunkun", - name: "ext-image-processing", - version: "0.0.6", - }, - githubUsername: "kunkunsh", // should be HuakunShen - })).error, - ).toBe( - "GitHub repository owner does not match JSR package owner: HuakunShen !== kunkunsh", - ); - }); + const res = await validateJsrPackageAsKunkunExtension({ + jsrPackage: { + scope: "kunkun", + name: "ext-image-processing", + version: "0.0.6" + }, + githubUsername: "Huakun" + }) + expect(res.error).toBe( + "You (Huakun) are not authorized to publish this package. Only kunkunsh or its organization members can publish it." + ) + }) test("A valid extension package", async () => { - expect( - (await validateJsrPackageAsKunkunExtension({ - jsrPackage: { - scope: "kunkun", - name: "ext-image-processing", - version: "0.0.6", - }, - githubUsername: "HuakunShen", - })).data, - ).toBeDefined(); - }); -}); + const res = await await validateJsrPackageAsKunkunExtension({ + jsrPackage: { + scope: "kunkun", + name: "ext-image-processing", + version: "0.0.6" + }, + githubUsername: "HuakunShen" + }) + expect(res.data).toBeDefined() + }) +}) diff --git a/packages/api/src/extensions/jsr/github.ts b/packages/api/src/extensions/jsr/github.ts new file mode 100644 index 0000000..fe45faf --- /dev/null +++ b/packages/api/src/extensions/jsr/github.ts @@ -0,0 +1,34 @@ +/** + * TODO: move this module to another folder + */ +import { Octokit } from "@octokit/rest" + +/** + * Check if a user is a public member of a GitHub organization + * @param orgName - The name of the GitHub organization + * @param username - The username of the user + * @returns A promise that resolves to a boolean indicating if the user is a public member of the organization + */ +export function userIsPublicMemberOfGitHubOrg(orgName: string, username: string): Promise { + const octokit = new Octokit() + return octokit.orgs + .checkPublicMembershipForUser({ org: orgName, username }) + .then(() => true) + .catch(() => false) +} + +/** + * Only works if user grants read:org scope with the org when login + * @param orgName + * @param username + * @param githubToken + */ +export function authenticatedUserIsMemberOfGitHubOrg( + orgName: string, + githubToken: string +): Promise { + const octokit = new Octokit({ auth: githubToken }) + return octokit.orgs.listForAuthenticatedUser().then((res) => { + return res.data.some((org) => org.login === orgName) + }) +} diff --git a/packages/api/src/extensions/jsr/index.ts b/packages/api/src/extensions/jsr/index.ts index fc9da9e..d758991 100644 --- a/packages/api/src/extensions/jsr/index.ts +++ b/packages/api/src/extensions/jsr/index.ts @@ -2,30 +2,31 @@ import { client, getPackage, getPackageVersion, - type GitHubRepository, -} from "@huakunshen/jsr-client/hey-api-client"; -import * as v from "valibot"; -import { ExtPackageJson } from "../../models/manifest"; -import type { JsrPackageMetadata, NpmPkgMetadata } from "./models"; + type GitHubRepository +} from "@huakunshen/jsr-client/hey-api-client" +import * as v from "valibot" +import { ExtPackageJson } from "../../models/manifest" +import { authenticatedUserIsMemberOfGitHubOrg, userIsPublicMemberOfGitHubOrg } from "./github" +import type { JsrPackageMetadata, NpmPkgMetadata } from "./models" + +export * from "./github" client.setConfig({ - baseUrl: "https://api.jsr.io", -}); + baseUrl: "https://api.jsr.io" +}) -export function splitRawJsrPkgName( - packageName: string, -): Promise<{ scope: string; name: string }> { +export function splitRawJsrPkgName(packageName: string): Promise<{ scope: string; name: string }> { return new Promise((resolve, reject) => { // write a regex to match the scope and name - const regex = /^@([^@]+)\/([^@]+)$/; - const match = packageName.match(regex); + const regex = /^@([^@]+)\/([^@]+)$/ + const match = packageName.match(regex) if (!match) { - return reject(new Error("Invalid Jsr package name")); + return reject(new Error("Invalid Jsr package name")) } - const [, rawScope, name] = match; - const scope = rawScope.startsWith("@") ? rawScope.slice(1) : rawScope; - return resolve({ scope, name }); - }); + const [, rawScope, name] = match + const scope = rawScope.startsWith("@") ? rawScope.slice(1) : rawScope + return resolve({ scope, name }) + }) } /** @@ -35,8 +36,7 @@ export function splitRawJsrPkgName( * @param name * @returns */ -export const translateJsrToNpmPkgName = (scope: string, name: string) => - `${scope}__${name}`; +export const translateJsrToNpmPkgName = (scope: string, name: string) => `${scope}__${name}` /** /** @@ -46,19 +46,13 @@ export const translateJsrToNpmPkgName = (scope: string, name: string) => * @param version * @returns */ -export function getJsrPackageHtml( - scope: string, - name: string, - version?: string, -) { - const url = `https://jsr.io/@${scope}/${name}${ - version ? `@${version}` : "" - }`; +export function getJsrPackageHtml(scope: string, name: string, version?: string) { + const url = `https://jsr.io/@${scope}/${name}${version ? `@${version}` : ""}` return fetch(url, { headers: { - "sec-fetch-dest": "document", - }, - }).then((res) => res.text()); + "sec-fetch-dest": "document" + } + }).then((res) => res.text()) } /** @@ -68,29 +62,29 @@ export function getJsrPackageHtml( export async function isSignedByGitHubAction( scope: string, name: string, - version: string, + version: string ): Promise { const pkgVersion = await getPackageVersion({ path: { scope, package: name, - version, - }, - }); - return !!pkgVersion.data?.rekorLogId; + version + } + }) + return !!pkgVersion.data?.rekorLogId } export async function getJsrPackageGitHubRepo( scope: string, - name: string, + name: string ): Promise { const pkg = await getPackage({ path: { scope, - package: name, - }, - }); - return pkg.data?.githubRepository ?? null; + package: name + } + }) + return pkg.data?.githubRepository ?? null } /** @@ -102,12 +96,9 @@ export async function getJsrPackageGitHubRepo( * @param name * @returns */ -export function getJsrPackageMetadata( - scope: string, - name: string, -): Promise { - const url = `https://jsr.io/@${scope}/${name}/meta.json`; - return fetch(url).then((res) => res.json()); +export function getJsrPackageMetadata(scope: string, name: string): Promise { + const url = `https://jsr.io/@${scope}/${name}/meta.json` + return fetch(url).then((res) => res.json()) } /** @@ -122,12 +113,12 @@ export function getJsrPackageSrcFile( scope: string, name: string, version: string, - file: string, + file: string ): Promise { - const url = `https://jsr.io/@${scope}/${name}/${version}/${file}`; + const url = `https://jsr.io/@${scope}/${name}/${version}/${file}` return fetch(url) .then((res) => res.text()) - .catch(() => undefined); + .catch(() => undefined) } /** @@ -136,15 +127,10 @@ export function getJsrPackageSrcFile( * @param name * @returns */ -export function getJsrNpmPkgMetadata( - scope: string, - name: string, -): Promise { +export function getJsrNpmPkgMetadata(scope: string, name: string): Promise { // Sample: https://npm.jsr.io/@jsr/kunkun__api - const url = `https://npm.jsr.io/@jsr/${ - translateJsrToNpmPkgName(scope, name) - }`; - return fetch(url).then((res) => res.json()); + const url = `https://npm.jsr.io/@jsr/${translateJsrToNpmPkgName(scope, name)}` + return fetch(url).then((res) => res.json()) } /** @@ -154,14 +140,10 @@ export function getJsrNpmPkgMetadata( * @param version * @returns */ -export function getJsrNpmPackageVersionMetadata( - scope: string, - name: string, - version: string, -) { +export function getJsrNpmPackageVersionMetadata(scope: string, name: string, version: string) { return getJsrNpmPkgMetadata(scope, name).then((metadata) => { - return metadata.versions[version]; - }); + return metadata.versions[version] + }) } /** @@ -174,16 +156,11 @@ export function getJsrNpmPackageVersionMetadata( export async function getNpmPackageTarballUrl( scope: string, name: string, - version: string, + version: string ): Promise { - const metadata = await getJsrNpmPackageVersionMetadata( - scope, - name, - version, - ); - const tarballUrl: string | undefined = metadata?.dist - .tarball; - return tarballUrl; + const metadata = await getJsrNpmPackageVersionMetadata(scope, name, version) + const tarballUrl: string | undefined = metadata?.dist.tarball + return tarballUrl } /** @@ -192,12 +169,9 @@ export async function getNpmPackageTarballUrl( * @param name * @returns */ -export async function getAllVersionsOfJsrPackage( - scope: string, - name: string, -): Promise { - const metadata = await getJsrNpmPkgMetadata(scope, name); - return Object.keys(metadata.versions); +export async function getAllVersionsOfJsrPackage(scope: string, name: string): Promise { + const metadata = await getJsrNpmPkgMetadata(scope, name) + return Object.keys(metadata.versions) } /** @@ -206,26 +180,22 @@ export async function getAllVersionsOfJsrPackage( * @param name * @returns */ -export function jsrPackageExists( - scope: string, - name: string, - version?: string, -): Promise { +export function jsrPackageExists(scope: string, name: string, version?: string): Promise { if (version) { return getPackageVersion({ path: { scope, package: name, - version, - }, - }).then((res) => res.response.ok && res.response.status === 200); + version + } + }).then((res) => res.response.ok && res.response.status === 200) } return getPackage({ path: { scope, - package: name, - }, - }).then((res) => res.response.ok && res.response.status === 200); + package: name + } + }).then((res) => res.response.ok && res.response.status === 200) } /** @@ -236,10 +206,10 @@ export function jsrPackageExists( export function getTarballSize(url: string): Promise { return fetch(url, { method: "HEAD" }).then((res) => { if (!(res.ok && res.status === 200)) { - throw new Error("Failed to fetch tarball size"); + throw new Error("Failed to fetch tarball size") } - return Number(res.headers.get("Content-Length")); - }); + return Number(res.headers.get("Content-Length")) + }) } /** @@ -254,40 +224,41 @@ export function getTarballSize(url: string): Promise { */ export async function validateJsrPackageAsKunkunExtension(payload: { jsrPackage: { - scope: string; - name: string; - version: string; - }; - githubUsername: string; - tarballSizeLimit?: number; + scope: string + name: string + version: string + } + githubUsername: string + tarballSizeLimit?: number + githubToken?: string }): Promise<{ - error?: string; + error?: string data?: { - pkgJson: ExtPackageJson; - tarballUrl: string; - shasum: string; - apiVersion: string; - tarballSize: number; - }; + pkgJson: ExtPackageJson + tarballUrl: string + shasum: string + apiVersion: string + tarballSize: number + } }> { // check if jsr package exists const jsrExists = await jsrPackageExists( payload.jsrPackage.scope, payload.jsrPackage.name, - payload.jsrPackage.version, - ); + payload.jsrPackage.version + ) if (!jsrExists) { - return { error: "JSR package does not exist" }; + return { error: "JSR package does not exist" } } /* -------------------------------------------------------------------------- */ /* check if jsr pkg is linked to a github repo */ /* -------------------------------------------------------------------------- */ const githubRepo = await getJsrPackageGitHubRepo( payload.jsrPackage.scope, - payload.jsrPackage.name, - ); + payload.jsrPackage.name + ) if (githubRepo === null) { - return { error: "JSR package is not linked to a GitHub repository" }; + return { error: "JSR package is not linked to a GitHub repository" } } /* -------------------------------------------------------------------------- */ /* check if jsr pkg is signed with github action */ @@ -295,21 +266,34 @@ export async function validateJsrPackageAsKunkunExtension(payload: { const signed = await isSignedByGitHubAction( payload.jsrPackage.scope, payload.jsrPackage.name, - payload.jsrPackage.version, - ); + payload.jsrPackage.version + ) if (!signed) { - return { error: "JSR package is not signed by GitHub Actions" }; + return { error: "JSR package is not signed by GitHub Actions" } } /* -------------------------------------------------------------------------- */ /* check if user's github username is the same as repo's owner name */ /* -------------------------------------------------------------------------- */ - if ( - githubRepo.owner?.toLowerCase() !== payload.githubUsername.toLowerCase() - ) { - return { - error: - `GitHub repository owner does not match JSR package owner: ${githubRepo.owner} !== ${payload.githubUsername}`, - }; + if (!githubRepo.owner) { + return { error: "Package's Linked GitHub repository owner is not found." } + } + if (githubRepo.owner.toLowerCase() !== payload.githubUsername.toLowerCase()) { + const isPublicMemeber = await userIsPublicMemberOfGitHubOrg( + githubRepo.owner, + payload.githubUsername + ) + let isOrgMember = false + if (payload.githubToken) { + isOrgMember = await authenticatedUserIsMemberOfGitHubOrg( + githubRepo.owner, + payload.githubToken + ) + } + if (!isPublicMemeber && !isOrgMember) { + return { + error: `You (${payload.githubUsername}) are not authorized to publish this package. Only ${githubRepo.owner} or its organization members can publish it.` + } + } } /* -------------------------------------------------------------------------- */ /* check if jsr.json or deno.json has the same version as package.json */ @@ -318,60 +302,57 @@ export async function validateJsrPackageAsKunkunExtension(payload: { payload.jsrPackage.scope, payload.jsrPackage.name, payload.jsrPackage.version, - "package.json", - ); + "package.json" + ) if (!packageJsonContent) { - return { error: "Could not find package.json in JSR package" }; + return { error: "Could not find package.json in JSR package" } } - let packageJson: any; + let packageJson: any try { - packageJson = JSON.parse(packageJsonContent); + packageJson = JSON.parse(packageJsonContent) } catch (error) { - return { error: "Failed to parse package.json" }; + return { error: "Failed to parse package.json" } } if (packageJson.version !== payload.jsrPackage.version) { // no need to fetch jsr.json or deno.json content, as we already know the version is valid with JSR API return { - error: - "Package version in package.json does not match JSR package version", - }; + error: "Package version in package.json does not match JSR package version" + } } /* -------------------------------------------------------------------------- */ /* validate package.json format against latest schema */ /* -------------------------------------------------------------------------- */ - const parseResult = v.safeParse(ExtPackageJson, packageJson); + const parseResult = v.safeParse(ExtPackageJson, packageJson) if (!parseResult.success) { - return { error: "package.json format not valid" }; + return { error: "package.json format not valid" } } const npmPkgVersionMetadata = await getJsrNpmPackageVersionMetadata( payload.jsrPackage.scope, payload.jsrPackage.name, - payload.jsrPackage.version, - ); - const tarballUrl = npmPkgVersionMetadata.dist.tarball; - const shasum = npmPkgVersionMetadata.dist.shasum; + payload.jsrPackage.version + ) + const tarballUrl = npmPkgVersionMetadata.dist.tarball + const shasum = npmPkgVersionMetadata.dist.shasum if (!tarballUrl) { - return { error: "Could not get tarball URL for JSR package" }; + return { error: "Could not get tarball URL for JSR package" } } - const tarballSize = await getTarballSize(tarballUrl); - const sizeLimit = payload.tarballSizeLimit ?? 50 * 1024 * 1024; // default to 50MB + const tarballSize = await getTarballSize(tarballUrl) + const sizeLimit = payload.tarballSizeLimit ?? 50 * 1024 * 1024 // default to 50MB if (tarballSize > sizeLimit) { return { - error: - `Package tarball size (${tarballSize} bytes) exceeds limit of ${sizeLimit} bytes`, - }; + error: `Package tarball size (${tarballSize} bytes) exceeds limit of ${sizeLimit} bytes` + } } /* -------------------------------------------------------------------------- */ /* get @kksh/api dependency version */ /* -------------------------------------------------------------------------- */ - const apiVersion = parseResult.output.dependencies?.["@kksh/api"]; + const apiVersion = parseResult.output.dependencies?.["@kksh/api"] if (!apiVersion) { return { - error: - `Extension ${packageJson.kunkun.identifier} doesn't not have @kksh/api as a dependency`, - }; + error: `Extension ${packageJson.kunkun.identifier} doesn't not have @kksh/api as a dependency` + } } return { @@ -380,7 +361,7 @@ export async function validateJsrPackageAsKunkunExtension(payload: { tarballUrl, shasum, apiVersion, - tarballSize, - }, - }; + tarballSize + } + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df8253a..5b7d348 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -342,6 +342,9 @@ importers: '@huakunshen/jsr-client': specifier: ^0.1.5 version: 0.1.5(axios@1.7.9)(react@18.3.1)(typescript@5.6.3) + '@octokit/rest': + specifier: ^21.1.0 + version: 21.1.0 '@tauri-apps/api': specifier: ^2.2.0 version: 2.2.0 @@ -2756,6 +2759,58 @@ packages: '@nuxtjs/tailwindcss@6.12.2': resolution: {integrity: sha512-qPJiFH67CkTj/2kBGBzqXihOD1rQXMsbVS4vdQvfBxOBLPfGhU1yw7AATdhPl2BBjO2krjJLuZj39t7dnDYOwg==} + '@octokit/auth-token@5.1.1': + resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} + engines: {node: '>= 18'} + + '@octokit/core@6.1.3': + resolution: {integrity: sha512-z+j7DixNnfpdToYsOutStDgeRzJSMnbj8T1C/oQjB6Aa+kRfNjs/Fn7W6c8bmlt6mfy3FkgeKBRnDjxQow5dow==} + engines: {node: '>= 18'} + + '@octokit/endpoint@10.1.2': + resolution: {integrity: sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA==} + engines: {node: '>= 18'} + + '@octokit/graphql@8.1.2': + resolution: {integrity: sha512-bdlj/CJVjpaz06NBpfHhp4kGJaRZfz7AzC+6EwUImRtrwIw8dIgJ63Xg0OzV9pRn3rIzrt5c2sa++BL0JJ8GLw==} + engines: {node: '>= 18'} + + '@octokit/openapi-types@23.0.1': + resolution: {integrity: sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==} + + '@octokit/plugin-paginate-rest@11.4.0': + resolution: {integrity: sha512-ttpGck5AYWkwMkMazNCZMqxKqIq1fJBNxBfsFwwfyYKTf914jKkLF0POMS3YkPBwp5g1c2Y4L79gDz01GhSr1g==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-request-log@5.3.1': + resolution: {integrity: sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@13.3.0': + resolution: {integrity: sha512-LUm44shlmkp/6VC+qQgHl3W5vzUP99ZM54zH6BuqkJK4DqfFLhegANd+fM4YRLapTvPm4049iG7F3haANKMYvQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/request-error@6.1.6': + resolution: {integrity: sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==} + engines: {node: '>= 18'} + + '@octokit/request@9.1.4': + resolution: {integrity: sha512-tMbOwGm6wDII6vygP3wUVqFTw3Aoo0FnVQyhihh8vVq12uO3P+vQZeo2CKMpWtPSogpACD0yyZAlVlQnjW71DA==} + engines: {node: '>= 18'} + + '@octokit/rest@21.1.0': + resolution: {integrity: sha512-93iLxcKDJboUpmnUyeJ6cRIi7z7cqTZT1K7kRK4LobGxwTwpsa+2tQQbRQNGy7IFDEAmrtkf4F4wBj3D5rVlJQ==} + engines: {node: '>= 18'} + + '@octokit/types@13.7.0': + resolution: {integrity: sha512-BXfRP+3P3IN6fd4uF3SniaHKOO4UXWBfkdR3vA8mIvaoO/wLjGN5qivUtW0QRitBHHMcfC41SLhNVYIZZE+wkA==} + '@parcel/watcher-android-arm64@2.5.0': resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} engines: {node: '>= 10.0.0'} @@ -5551,6 +5606,9 @@ packages: bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -6890,6 +6948,9 @@ packages: resolution: {integrity: sha512-TmpgSeJ2jUV+FNWnSy9iIE9bOV9nCNQ4it+K9BpCNT9JsQOfZYznWGSbMw+Wa4uusEss0IcL/trFVoRxS6IuAA==} engines: {node: '>=8.0.0'} + fast-content-type-parse@2.0.1: + resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -10296,6 +10357,9 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universal-user-agent@7.0.2: + resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -13098,6 +13162,68 @@ snapshots: - supports-color - ts-node + '@octokit/auth-token@5.1.1': {} + + '@octokit/core@6.1.3': + dependencies: + '@octokit/auth-token': 5.1.1 + '@octokit/graphql': 8.1.2 + '@octokit/request': 9.1.4 + '@octokit/request-error': 6.1.6 + '@octokit/types': 13.7.0 + before-after-hook: 3.0.2 + universal-user-agent: 7.0.2 + + '@octokit/endpoint@10.1.2': + dependencies: + '@octokit/types': 13.7.0 + universal-user-agent: 7.0.2 + + '@octokit/graphql@8.1.2': + dependencies: + '@octokit/request': 9.1.4 + '@octokit/types': 13.7.0 + universal-user-agent: 7.0.2 + + '@octokit/openapi-types@23.0.1': {} + + '@octokit/plugin-paginate-rest@11.4.0(@octokit/core@6.1.3)': + dependencies: + '@octokit/core': 6.1.3 + '@octokit/types': 13.7.0 + + '@octokit/plugin-request-log@5.3.1(@octokit/core@6.1.3)': + dependencies: + '@octokit/core': 6.1.3 + + '@octokit/plugin-rest-endpoint-methods@13.3.0(@octokit/core@6.1.3)': + dependencies: + '@octokit/core': 6.1.3 + '@octokit/types': 13.7.0 + + '@octokit/request-error@6.1.6': + dependencies: + '@octokit/types': 13.7.0 + + '@octokit/request@9.1.4': + dependencies: + '@octokit/endpoint': 10.1.2 + '@octokit/request-error': 6.1.6 + '@octokit/types': 13.7.0 + fast-content-type-parse: 2.0.1 + universal-user-agent: 7.0.2 + + '@octokit/rest@21.1.0': + dependencies: + '@octokit/core': 6.1.3 + '@octokit/plugin-paginate-rest': 11.4.0(@octokit/core@6.1.3) + '@octokit/plugin-request-log': 5.3.1(@octokit/core@6.1.3) + '@octokit/plugin-rest-endpoint-methods': 13.3.0(@octokit/core@6.1.3) + + '@octokit/types@13.7.0': + dependencies: + '@octokit/openapi-types': 23.0.1 + '@parcel/watcher-android-arm64@2.5.0': optional: true @@ -16393,6 +16519,8 @@ snapshots: dependencies: tweetnacl: 0.14.5 + before-after-hook@3.0.2: {} + better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 @@ -17601,8 +17729,8 @@ snapshots: '@typescript-eslint/parser': 8.15.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.2(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0(eslint@8.57.1) @@ -17630,37 +17758,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0(supports-color@9.4.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.15.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -17671,7 +17799,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.15.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -17981,6 +18109,8 @@ snapshots: pure-rand: 6.1.0 optional: true + fast-content-type-parse@2.0.1: {} + fast-deep-equal@3.1.3: {} fast-equals@5.0.1: {} @@ -21902,6 +22032,8 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 + universal-user-agent@7.0.2: {} + universalify@0.1.2: {} universalify@2.0.1: {}