feat: add repo ownership check for org in jsr validation algo

This commit is contained in:
Huakun Shen 2025-01-12 01:17:15 -05:00
parent 0af6ef2d0a
commit f6b70bade0
5 changed files with 368 additions and 216 deletions

View File

@ -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",

View File

@ -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()
})
})

View File

@ -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<boolean> {
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<boolean> {
const octokit = new Octokit({ auth: githubToken })
return octokit.orgs.listForAuthenticatedUser().then((res) => {
return res.data.some((org) => org.login === orgName)
})
}

View File

@ -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<boolean> {
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<GitHubRepository | null> {
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<JsrPackageMetadata> {
const url = `https://jsr.io/@${scope}/${name}/meta.json`;
return fetch(url).then((res) => res.json());
export function getJsrPackageMetadata(scope: string, name: string): Promise<JsrPackageMetadata> {
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<string | undefined> {
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<NpmPkgMetadata> {
export function getJsrNpmPkgMetadata(scope: string, name: string): Promise<NpmPkgMetadata> {
// 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<string | undefined> {
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<string[]> {
const metadata = await getJsrNpmPkgMetadata(scope, name);
return Object.keys(metadata.versions);
export async function getAllVersionsOfJsrPackage(scope: string, name: string): Promise<string[]> {
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<boolean> {
export function jsrPackageExists(scope: string, name: string, version?: string): Promise<boolean> {
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<number> {
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<number> {
*/
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
}
}
}

150
pnpm-lock.yaml generated
View File

@ -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: {}