Merge branch 'main' into docs

pull/81/head
Sven Sauleau 2023-01-11 21:34:06 +01:00 zatwierdzone przez GitHub
commit 6e519832f4
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
60 zmienionych plików z 1074 dodań i 707 usunięć

Wyświetl plik

@ -16,6 +16,8 @@ module.exports = {
'no-var': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
'@typescript-eslint/no-unused-vars': 'error',
'no-console': 'off',
'no-constant-condition': 'off',
/*
Note: the following rules have been set to off so that linting
can pass with the current code, but we need to gradually
@ -26,13 +28,11 @@ module.exports = {
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/restrict-plus-operands': 'off',
'no-constant-condition': 'off',
'@typescript-eslint/await-thenable': 'off',
'@typescript-eslint/require-await': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
'no-console': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-inferrable-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',

Wyświetl plik

@ -23,7 +23,11 @@ jobs:
- name: Build
run: yarn build
- name: Check formatting
run: yarn pretty
run: yarn pretty
- name: Check backend linting
run: yarn lint:backend
- name: Check functions linting
run: yarn lint:functions
- name: Run API tests
run: yarn test
@ -40,5 +44,7 @@ jobs:
run: yarn && yarn --cwd frontend
- name: Initialize local database
run: yarn database:create-mock
- name: Check frontend linting
run: yarn lint:frontend
- name: Run UI tests
run: yarn test:ui

Wyświetl plik

@ -22,8 +22,8 @@ jobs:
exit 1
fi
env:
CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
CF_DEPLOY_DOMAIN: ${{ secrets.CF_DEPLOY_DOMAIN }}
CF_ZONE_ID: ${{ vars.CF_ZONE_ID }}
CF_DEPLOY_DOMAIN: ${{ vars.CF_DEPLOY_DOMAIN }}
# this is needed to get the lowercase version of the repository_owner name
# TODO: switch to some lowercase function in the future when Actions supports it
@ -109,6 +109,30 @@ jobs:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
if: ${{ env.tfstate_kv != '' }}
- name: download VAPID keys
uses: cloudflare/wrangler-action@2.0.0
with:
command: kv:key get --namespace-id=${{ env.tfstate_kv }} vapid_jwk | jq . > ./tf/vapid_jwk
apiToken: ${{ secrets.CF_API_TOKEN }}
preCommands: |
echo "*** pre commands ***"
apt-get update && apt-get -y install jq
echo "******"
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
continue-on-error: true
if: ${{ env.tfstate_kv != '' }}
- name: generate VAPID keys if needed
run: |
sudo chmod 777 ./tf/vapid_jwk || true
if [ ! -s ./tf/vapid_jwk ]
then
node ./scripts/generate-vapid-keys.mjs > ./tf/vapid_jwk
echo "VAPID keys generated"
fi
- name: Configure
run: terraform plan && terraform apply -auto-approve
continue-on-error: true
@ -116,11 +140,14 @@ jobs:
env:
TF_VAR_cloudflare_account_id: ${{ secrets.CF_ACCOUNT_ID }}
TF_VAR_cloudflare_api_token: ${{ secrets.CF_API_TOKEN }}
TF_VAR_cloudflare_zone_id: ${{ secrets.CF_ZONE_ID }}
TF_VAR_cloudflare_deploy_domain: ${{ secrets.CF_DEPLOY_DOMAIN }}
TF_VAR_cloudflare_zone_id: ${{ vars.CF_ZONE_ID }}
TF_VAR_cloudflare_deploy_domain: ${{ vars.CF_DEPLOY_DOMAIN }}
TF_VAR_gh_username: ${{ env.OWNER_LOWER }}
TF_VAR_d1_id: ${{ env.d1_id }}
TF_VAR_access_auth_domain: ${{ env.auth_domain }}
TF_VAR_wd_instance_title: ${{ vars.INSTANCE_TITLE }}
TF_VAR_wd_admin_email: ${{ vars.ADMIN_EMAIL }}
TF_VAR_wd_instance_description: ${{ vars.INSTANCE_DESCR }}
- name: retrieve Terraform state KV namespace
uses: cloudflare/wrangler-action@2.0.0
@ -134,6 +161,14 @@ jobs:
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
if: ${{ env.tfstate_kv == '' }}
- name: store VAPID keys state
uses: cloudflare/wrangler-action@2.0.0
with:
command: kv:key put --namespace-id=${{ env.tfstate_kv }} vapid_jwk --path=./tf/vapid_jwk
apiToken: ${{ secrets.CF_API_TOKEN }}
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
- name: store Terraform state
uses: cloudflare/wrangler-action@2.0.0

Wyświetl plik

@ -70,7 +70,7 @@ Wildebeest uses [Deploy to Workers](https://deploy.workers.cloudflare.com/) to a
**Click here to start the installation.**
[<img src="https://deploy.workers.cloudflare.com/button"/>](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/wildebeest&authed=true&fields=%7B%22name%22:%22Zone%20tag%22,%22secret%22:%22CF_ZONE_ID%22,%22descr%22:%22Zone%20tag%22%7D&fields=%7B%22name%22:%22Domain%22,%22secret%22:%22CF_DEPLOY_DOMAIN%22,%22descr%22:%22Domain%20on%20which%20your%20instance%20will%20be%20running%22%7D&fields=%7B%22name%22:%22Instance%20title%22,%22secret%22:%22INSTANCE_TITLE%22,%22descr%22:%22Title%20of%20your%20instance%22%7D&fields=%7B%22name%22:%22Administrator%20email%22,%22secret%22:%22ADMIN_EMAIL%22,%22descr%22:%22An%20email%20address%20that%20can%20be%20messaged%20regarding%20inquiries%20or%20issues%22%7D&fields=%7B%22name%22:%22Instance%20description%22,%22secret%22:%22INSTANCE_DESCR%22,%22descr%22:%22A%20short,%20plain-text%20description%20of%20your%20instance%22%7D)
[<img src="https://deploy.workers.cloudflare.com/button"/>](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/wildebeest&authed=true&fields={%22name%22:%22Zone%20ID%22,%22secret%22:%22CF_ZONE_ID%22,%22descr%22:%22Get%20your%20Zone%20ID%20from%20the%20Cloudflare%20Dashboard%22}&fields={%22name%22:%22Domain%22,%22secret%22:%22CF_DEPLOY_DOMAIN%22,%22descr%22:%22Domain%20on%20which%20your%20instance%20will%20be%20running%22}&fields={%22name%22:%22Instance%20title%22,%22secret%22:%22INSTANCE_TITLE%22,%22descr%22:%22Title%20of%20your%20instance%22}&fields={%22name%22:%22Administrator%20Email%22,%22secret%22:%22ADMIN_EMAIL%22,%22descr%22:%22An%20Email%20address%20that%20can%20be%20messaged%20regarding%20inquiries%20or%20issues%22}&fields={%22name%22:%22Instance%20description%22,%22secret%22:%22INSTANCE_DESCR%22,%22descr%22:%22A%20short,%20plain-text%20description%20of%20your%20instance%22})
Please pay attention to all the steps involved in the installation process.

Wyświetl plik

@ -1,4 +1,5 @@
import * as actors from 'wildebeest/backend/src/activitypub/actors'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
import * as objects from 'wildebeest/backend/src/activitypub/objects'
@ -79,7 +80,14 @@ export function makeGetActorAsId(activity: Activity): Function {
}
}
export async function handle(domain: string, activity: Activity, db: D1Database, userKEK: string) {
export async function handle(
domain: string,
activity: Activity,
db: D1Database,
userKEK: string,
adminEmail: string,
vapidKeys: JWK
) {
// The `object` field of the activity is required to be an object, with an
// `id` and a `type` field.
const requireComplexObject = () => {
@ -183,7 +191,7 @@ export async function handle(domain: string, activity: Activity, db: D1Database,
const notifId = await createNotification(db, 'mention', person, fromActor, obj)
await Promise.all([
await addObjectInInbox(db, person, obj),
await sendMentionNotification(db, fromActor, person, notifId),
await sendMentionNotification(db, fromActor, person, notifId, adminEmail, vapidKeys),
])
}
@ -228,7 +236,7 @@ export async function handle(domain: string, activity: Activity, db: D1Database,
// Notify the user
const notifId = await insertFollowNotification(db, receiver, originalActor)
await sendFollowNotification(db, originalActor, receiver, notifId)
await sendFollowNotification(db, originalActor, receiver, notifId, adminEmail, vapidKeys)
} else {
console.warn(`actor ${objectId} not found`)
}
@ -282,7 +290,7 @@ export async function handle(domain: string, activity: Activity, db: D1Database,
// Store the reblog for counting
insertReblog(db, fromActor, obj),
sendReblogNotification(db, fromActor, targetActor, notifId),
sendReblogNotification(db, fromActor, targetActor, notifId, adminEmail, vapidKeys),
])
break
}
@ -312,7 +320,7 @@ export async function handle(domain: string, activity: Activity, db: D1Database,
insertLike(db, fromActor, obj),
])
await sendLikeNotification(db, fromActor, targetActor, notifId)
await sendLikeNotification(db, fromActor, targetActor, notifId, adminEmail, vapidKeys)
break
}

Wyświetl plik

@ -1,53 +1,10 @@
export type InstanceConfig = {
title?: string
email?: string
description?: string
thumbnail?: string
}
import type { Env } from 'wildebeest/backend/src/types/env'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
const DEFAULT_THUMBNAIL =
export const DEFAULT_THUMBNAIL =
'https://imagedelivery.net/NkfPDviynOyTAOI79ar_GQ/b24caf12-5230-48c4-0bf7-2f40063bd400/thumbnail'
export async function configure(db: D1Database, data: InstanceConfig) {
const sql = `
INSERT INTO instance_config
VALUES ('title', ?),
('email', ?),
('thumbnail', ?),
('description', ?);
`
const { success, error } = await db
.prepare(sql)
.bind(data.title, data.email, DEFAULT_THUMBNAIL, data.description)
.run()
if (!success) {
throw new Error('SQL error: ' + error)
}
}
export async function generateVAPIDKeys(db: D1Database) {
const keyPair = (await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, [
'sign',
'verify',
])) as CryptoKeyPair
const jwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey)
const sql = `
INSERT INTO instance_config
VALUES ('vapid_jwk', ?);
`
const { success, error } = await db.prepare(sql).bind(JSON.stringify(jwk)).run()
if (!success) {
throw new Error('SQL error: ' + error)
}
}
export async function get(db: D1Database, name: string): Promise<string> {
const row: { value: string } = await db.prepare('SELECT value FROM instance_config WHERE key = ?').bind(name).first()
if (!row) {
throw new Error(`configuration not found: ${name}`)
}
return row.value
export function getVAPIDKeys(env: Env): JWK {
const value: JWK = JSON.parse(env.VAPID_JWK)
return value
}

Wyświetl plik

@ -29,10 +29,6 @@ export function userConflict(): Response {
return generateErrorResponse(`User already exists or conflicts`, 403)
}
export function timelineMissing(): Response {
return generateErrorResponse(`The timeline is invalid or being regenerated`, 404)
}
export function clientUnknown(): Response {
return generateErrorResponse(`The client is unknown or invalid`, 403)
}

Wyświetl plik

@ -1,4 +1,5 @@
import type { Object } from 'wildebeest/backend/src/activitypub/objects'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import * as actors from 'wildebeest/backend/src/activitypub/actors'
import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
import { loadExternalMastodonAccount } from 'wildebeest/backend/src/mastodon/account'
@ -9,8 +10,6 @@ import { WebPushResult } from 'wildebeest/backend/src/webpush/webpushinfos'
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import type { NotificationType, Notification } from 'wildebeest/backend/src/types/notification'
import { getSubscriptionForAllClients } from 'wildebeest/backend/src/mastodon/subscription'
import { getVAPIDKeys } from 'wildebeest/backend/src/mastodon/subscription'
import * as config from 'wildebeest/backend/src/config'
export async function createNotification(
db: D1Database,
@ -43,9 +42,14 @@ export async function insertFollowNotification(db: D1Database, actor: Actor, fro
return row.id
}
export async function sendFollowNotification(db: D1Database, follower: Actor, actor: Actor, notificationId: string) {
const sub = await config.get(db, 'email')
export async function sendFollowNotification(
db: D1Database,
follower: Actor,
actor: Actor,
notificationId: string,
adminEmail: string,
vapidKeys: JWK
) {
const data = {
preferred_locale: 'en',
notification_type: 'follow',
@ -58,16 +62,21 @@ export async function sendFollowNotification(db: D1Database, follower: Actor, ac
const message: WebPushMessage = {
data: JSON.stringify(data),
urgency: 'normal',
sub,
sub: adminEmail,
ttl: 60 * 24 * 7,
}
return sendNotification(db, actor, message)
return sendNotification(db, actor, message, vapidKeys)
}
export async function sendLikeNotification(db: D1Database, fromActor: Actor, actor: Actor, notificationId: string) {
const sub = await config.get(db, 'email')
export async function sendLikeNotification(
db: D1Database,
fromActor: Actor,
actor: Actor,
notificationId: string,
adminEmail: string,
vapidKeys: JWK
) {
const data = {
preferred_locale: 'en',
notification_type: 'favourite',
@ -80,16 +89,21 @@ export async function sendLikeNotification(db: D1Database, fromActor: Actor, act
const message: WebPushMessage = {
data: JSON.stringify(data),
urgency: 'normal',
sub,
sub: adminEmail,
ttl: 60 * 24 * 7,
}
return sendNotification(db, actor, message)
return sendNotification(db, actor, message, vapidKeys)
}
export async function sendMentionNotification(db: D1Database, fromActor: Actor, actor: Actor, notificationId: string) {
const sub = await config.get(db, 'email')
export async function sendMentionNotification(
db: D1Database,
fromActor: Actor,
actor: Actor,
notificationId: string,
adminEmail: string,
vapidKeys: JWK
) {
const data = {
preferred_locale: 'en',
notification_type: 'mention',
@ -102,16 +116,21 @@ export async function sendMentionNotification(db: D1Database, fromActor: Actor,
const message: WebPushMessage = {
data: JSON.stringify(data),
urgency: 'normal',
sub,
sub: adminEmail,
ttl: 60 * 24 * 7,
}
return sendNotification(db, actor, message)
return sendNotification(db, actor, message, vapidKeys)
}
export async function sendReblogNotification(db: D1Database, fromActor: Actor, actor: Actor, notificationId: string) {
const sub = await config.get(db, 'email')
export async function sendReblogNotification(
db: D1Database,
fromActor: Actor,
actor: Actor,
notificationId: string,
adminEmail: string,
vapidKeys: JWK
) {
const data = {
preferred_locale: 'en',
notification_type: 'reblog',
@ -124,15 +143,14 @@ export async function sendReblogNotification(db: D1Database, fromActor: Actor, a
const message: WebPushMessage = {
data: JSON.stringify(data),
urgency: 'normal',
sub,
sub: adminEmail,
ttl: 60 * 24 * 7,
}
return sendNotification(db, actor, message)
return sendNotification(db, actor, message, vapidKeys)
}
async function sendNotification(db: D1Database, actor: Actor, message: WebPushMessage) {
const vapidKeys = await getVAPIDKeys(db)
async function sendNotification(db: D1Database, actor: Actor, message: WebPushMessage, vapidKeys: JWK) {
const subscriptions = await getSubscriptionForAllClients(db, actor)
const promises = subscriptions.map(async (subscription) => {

Wyświetl plik

@ -9,8 +9,6 @@ import * as media from 'wildebeest/backend/src/media/'
import type { MastodonStatus } from 'wildebeest/backend/src/types'
import { parseHandle } from '../utils/parse'
import { urlToHandle } from '../utils/handle'
import { getLikes } from './like'
import { getReblogs } from './reblog'
export function getMentions(input: string): Array<Handle> {
const mentions: Array<Handle> = []

Wyświetl plik

@ -124,15 +124,6 @@ function subscriptionFromRow(row: any): Subscription {
}
}
export async function getVAPIDKeys(db: D1Database): Promise<JWK> {
const row: any = await db.prepare("SELECT value FROM instance_config WHERE key = 'vapid_jwk'").first()
if (!row) {
throw new Error('missing VAPID keys')
}
const value: JWK = JSON.parse(row.value)
return value
}
export function VAPIDPublicKey(keys: JWK): string {
return b64ToUrlEncoded(exportPublicKeyPair(keys))
}

Wyświetl plik

@ -56,8 +56,6 @@ export async function main(context: EventContext<Env, any, any>) {
url.pathname === '/api/v1/timelines/public' ||
url.pathname === '/api/v1/custom_emojis' ||
url.pathname === '/.well-known/webfinger' ||
url.pathname === '/start-instance' || // Access is required by the handler
url.pathname === '/start-instance-test-access' || // Access is required by the handler
url.pathname === '/api/v1/trends/statuses' ||
url.pathname === '/api/v1/trends/links' ||
url.pathname.startsWith('/ap/') // all ActivityPub endpoints

Wyświetl plik

@ -10,4 +10,10 @@ export interface Env {
DOMAIN: string
ACCESS_AUD: string
ACCESS_AUTH_DOMAIN: string
// Configuration for the instance
INSTANCE_TITLE: string
ADMIN_EMAIL: string
INSTANCE_DESCR: string
VAPID_JWK: string
}

Wyświetl plik

@ -10,7 +10,7 @@ export async function readBody<T>(request: Request): Promise<T> {
return request.json<T>()
} else {
const form = await request.formData()
let out: any = {}
const out: any = {}
for (const [key, value] of form) {
out[key] = value

Wyświetl plik

@ -1,7 +1,7 @@
import { makeDB, isUrlValid } from './utils'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import { addFollowing } from 'wildebeest/backend/src/mastodon/follow'
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
import { configure, generateVAPIDKeys } from 'wildebeest/backend/src/config'
import * as activityHandler from 'wildebeest/backend/src/activitypub/activities/handle'
import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
@ -13,8 +13,10 @@ import * as ap_outbox from 'wildebeest/functions/ap/users/[id]/outbox'
import * as ap_outbox_page from 'wildebeest/functions/ap/users/[id]/outbox/page'
const userKEK = 'test_kek5'
const vapidKeys = {} as JWK
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
const domain = 'cloudflare.com'
const adminEmail = 'admin@example.com'
describe('ActivityPub', () => {
test('fetch non-existant user by id', async () => {
@ -74,7 +76,7 @@ describe('ActivityPub', () => {
},
}
await activityHandler.handle(domain, activity, db, userKEK)
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const row = await db
.prepare(`SELECT target_actor_id, state FROM actor_following WHERE actor_id=?`)
@ -96,7 +98,7 @@ describe('ActivityPub', () => {
object: 'a',
}
await assert.rejects(activityHandler.handle(domain, activity, db, userKEK), {
await assert.rejects(activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys), {
message: '`activity.object` must be of type object',
})
})
@ -114,7 +116,7 @@ describe('ActivityPub', () => {
object: 'a',
}
await assert.rejects(activityHandler.handle(domain, activity, db, userKEK), {
await assert.rejects(activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys), {
message: '`activity.object` must be of type object',
})
})
@ -131,7 +133,7 @@ describe('ActivityPub', () => {
object: 'a',
}
await assert.rejects(activityHandler.handle(domain, activity, db, userKEK), {
await assert.rejects(activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys), {
message: '`activity.object` must be of type object',
})
})
@ -150,7 +152,7 @@ describe('ActivityPub', () => {
},
}
await assert.rejects(activityHandler.handle(domain, activity, db, userKEK), {
await assert.rejects(activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys), {
message: 'object https://example.com/note2 does not exist',
})
})
@ -174,7 +176,7 @@ describe('ActivityPub', () => {
object: object,
}
await assert.rejects(activityHandler.handle(domain, activity, db, userKEK), {
await assert.rejects(activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys), {
message: 'actorid mismatch when updating object',
})
})
@ -204,7 +206,7 @@ describe('ActivityPub', () => {
object: newObject,
}
await activityHandler.handle(domain, activity, db, userKEK)
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const updatedObject = await db.prepare('SELECT * FROM objects WHERE original_object_id=?').bind(object.id).first()
assert(updatedObject)
@ -276,8 +278,6 @@ describe('ActivityPub', () => {
}
const db = await makeDB()
await configure(db, { title: 'title', description: 'a', email: 'email' })
await generateVAPIDKeys(db)
await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const activity: any = {
@ -287,7 +287,7 @@ describe('ActivityPub', () => {
cc: [],
object: objectId,
}
await activityHandler.handle(domain, activity, db, userKEK)
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const object = await db.prepare('SELECT * FROM objects').bind(remoteActorId).first()
assert(object)

Wyświetl plik

@ -1,5 +1,5 @@
import * as activityHandler from 'wildebeest/backend/src/activitypub/activities/handle'
import { configure, generateVAPIDKeys } from 'wildebeest/backend/src/config'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import * as ap_followers_page from 'wildebeest/functions/ap/users/[id]/followers/page'
import * as ap_following_page from 'wildebeest/functions/ap/users/[id]/following/page'
import * as ap_followers from 'wildebeest/functions/ap/users/[id]/followers'
@ -11,6 +11,8 @@ import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
const userKEK = 'test_kek10'
const domain = 'cloudflare.com'
const adminEmail = 'admin@example.com'
const vapidKeys = {} as JWK
describe('ActivityPub', () => {
describe('Follow', () => {
@ -34,8 +36,6 @@ describe('ActivityPub', () => {
test('Receive follow with Accept reply', async () => {
const db = await makeDB()
await configure(db, { title: 'title', description: 'a', email: 'email' })
await generateVAPIDKeys(db)
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const actor2 = await createPerson(domain, db, userKEK, 'sven2@cloudflare.com')
@ -46,7 +46,7 @@ describe('ActivityPub', () => {
object: actor.id.toString(),
}
await activityHandler.handle(domain, activity, db, userKEK)
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const row = await db
.prepare(`SELECT target_actor_id, state FROM actor_following WHERE actor_id=?`)
@ -132,8 +132,6 @@ describe('ActivityPub', () => {
test('creates a notification', async () => {
const db = await makeDB()
await configure(db, { title: 'title', description: 'a', email: 'email' })
await generateVAPIDKeys(db)
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const actor2 = await createPerson(domain, db, userKEK, 'sven2@cloudflare.com')
@ -144,7 +142,7 @@ describe('ActivityPub', () => {
object: actor.id,
}
await activityHandler.handle(domain, activity, db, userKEK)
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM actor_notifications').first()
assert.equal(entry.type, 'follow')

Wyświetl plik

@ -1,5 +1,5 @@
import { makeDB } from '../utils'
import { generateVAPIDKeys, configure } from 'wildebeest/backend/src/config'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import * as objects from 'wildebeest/backend/src/activitypub/objects'
import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
import * as ap_inbox from 'wildebeest/functions/ap/users/[id]/inbox'
@ -8,6 +8,8 @@ import { strict as assert } from 'node:assert/strict'
const userKEK = 'test_kek9'
const domain = 'cloudflare.com'
const adminEmail = 'admin@example.com'
const vapidKeys = {} as JWK
const kv_cache: any = {
async put() {},
@ -20,14 +22,22 @@ describe('ActivityPub', () => {
const db = await makeDB()
const activity: any = {}
const res = await ap_inbox.handleRequest(domain, db, kv_cache, 'sven', activity, userKEK, waitUntil)
const res = await ap_inbox.handleRequest(
domain,
db,
kv_cache,
'sven',
activity,
userKEK,
waitUntil,
adminEmail,
vapidKeys
)
assert.equal(res.status, 404)
})
test('send Note to inbox stores in DB', async () => {
const db = await makeDB()
await configure(db, { title: 'title', description: 'a', email: 'email' })
await generateVAPIDKeys(db)
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const activity: any = {
@ -41,7 +51,17 @@ describe('ActivityPub', () => {
content: 'test note',
},
}
const res = await ap_inbox.handleRequest(domain, db, kv_cache, 'sven', activity, userKEK, waitUntil)
const res = await ap_inbox.handleRequest(
domain,
db,
kv_cache,
'sven',
activity,
userKEK,
waitUntil,
adminEmail,
vapidKeys
)
assert.equal(res.status, 200)
const entry = await db
@ -81,7 +101,17 @@ describe('ActivityPub', () => {
content: 'test note',
},
}
const res = await ap_inbox.handleRequest(domain, db, kv_cache, 'sven', activity, userKEK, waitUntil)
const res = await ap_inbox.handleRequest(
domain,
db,
kv_cache,
'sven',
activity,
userKEK,
waitUntil,
adminEmail,
vapidKeys
)
assert.equal(res.status, 200)
const entry = await db.prepare('SELECT * FROM outbox_objects WHERE actor_id=?').bind(remoteActorId).first()
@ -90,8 +120,6 @@ describe('ActivityPub', () => {
test('local actor sends Note with mention create notification', async () => {
const db = await makeDB()
await configure(db, { title: 'title', description: 'a', email: 'email' })
await generateVAPIDKeys(db)
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
@ -106,7 +134,17 @@ describe('ActivityPub', () => {
content: 'test note',
},
}
const res = await ap_inbox.handleRequest(domain, db, kv_cache, 'a', activity, userKEK, waitUntil)
const res = await ap_inbox.handleRequest(
domain,
db,
kv_cache,
'a',
activity,
userKEK,
waitUntil,
adminEmail,
vapidKeys
)
assert.equal(res.status, 200)
const entry = await db.prepare('SELECT * FROM actor_notifications').first()
@ -132,8 +170,6 @@ describe('ActivityPub', () => {
}
const db = await makeDB()
await configure(db, { title: 'title', description: 'a', email: 'email' })
await generateVAPIDKeys(db)
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const activity: any = {
@ -147,7 +183,17 @@ describe('ActivityPub', () => {
content: 'test note',
},
}
const res = await ap_inbox.handleRequest(domain, db, kv_cache, 'a', activity, userKEK, waitUntil)
const res = await ap_inbox.handleRequest(
domain,
db,
kv_cache,
'a',
activity,
userKEK,
waitUntil,
adminEmail,
vapidKeys
)
assert.equal(res.status, 200)
const entry = await db.prepare('SELECT * FROM actors WHERE id=?').bind(actorB).first()
@ -156,8 +202,6 @@ describe('ActivityPub', () => {
test('send Note records reply', async () => {
const db = await makeDB()
await configure(db, { title: 'title', description: 'a', email: 'email' })
await generateVAPIDKeys(db)
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
{
@ -171,7 +215,17 @@ describe('ActivityPub', () => {
content: 'post',
},
}
const res = await ap_inbox.handleRequest(domain, db, kv_cache, 'sven', activity, userKEK, waitUntil)
const res = await ap_inbox.handleRequest(
domain,
db,
kv_cache,
'sven',
activity,
userKEK,
waitUntil,
adminEmail,
vapidKeys
)
assert.equal(res.status, 200)
}
@ -187,7 +241,17 @@ describe('ActivityPub', () => {
content: 'reply',
},
}
const res = await ap_inbox.handleRequest(domain, db, kv_cache, 'sven', activity, userKEK, waitUntil)
const res = await ap_inbox.handleRequest(
domain,
db,
kv_cache,
'sven',
activity,
userKEK,
waitUntil,
adminEmail,
vapidKeys
)
assert.equal(res.status, 200)
}
@ -206,8 +270,6 @@ describe('ActivityPub', () => {
describe('Announce', () => {
test('records reblog in db', async () => {
const db = await makeDB()
await generateVAPIDKeys(db)
await configure(db, { title: 'title', description: 'a', email: 'email' })
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
@ -218,7 +280,17 @@ describe('ActivityPub', () => {
actor: actorB.id,
object: note.id,
}
const res = await ap_inbox.handleRequest(domain, db, kv_cache, 'a', activity, userKEK, waitUntil)
const res = await ap_inbox.handleRequest(
domain,
db,
kv_cache,
'a',
activity,
userKEK,
waitUntil,
adminEmail,
vapidKeys
)
assert.equal(res.status, 200)
const entry = await db.prepare('SELECT * FROM actor_reblogs').first()
@ -228,8 +300,6 @@ describe('ActivityPub', () => {
test('creates notification', async () => {
const db = await makeDB()
await configure(db, { title: 'title', description: 'a', email: 'email' })
await generateVAPIDKeys(db)
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
@ -240,7 +310,17 @@ describe('ActivityPub', () => {
actor: actorB.id,
object: note.id,
}
const res = await ap_inbox.handleRequest(domain, db, kv_cache, 'a', activity, userKEK, waitUntil)
const res = await ap_inbox.handleRequest(
domain,
db,
kv_cache,
'a',
activity,
userKEK,
waitUntil,
adminEmail,
vapidKeys
)
assert.equal(res.status, 200)
const entry = await db.prepare('SELECT * FROM actor_notifications').first()
@ -254,8 +334,6 @@ describe('ActivityPub', () => {
describe('Like', () => {
test('records like in db', async () => {
const db = await makeDB()
await configure(db, { title: 'title', description: 'a', email: 'email' })
await generateVAPIDKeys(db)
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
@ -266,7 +344,17 @@ describe('ActivityPub', () => {
actor: actorB.id,
object: note.id,
}
const res = await ap_inbox.handleRequest(domain, db, kv_cache, 'a', activity, userKEK, waitUntil)
const res = await ap_inbox.handleRequest(
domain,
db,
kv_cache,
'a',
activity,
userKEK,
waitUntil,
adminEmail,
vapidKeys
)
assert.equal(res.status, 200)
const entry = await db.prepare('SELECT * FROM actor_favourites').first()
@ -276,8 +364,6 @@ describe('ActivityPub', () => {
test('creates notification', async () => {
const db = await makeDB()
await configure(db, { title: 'title', description: 'a', email: 'email' })
await generateVAPIDKeys(db)
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
@ -288,7 +374,17 @@ describe('ActivityPub', () => {
actor: actorB.id,
object: note.id,
}
const res = await ap_inbox.handleRequest(domain, db, kv_cache, 'a', activity, userKEK, waitUntil)
const res = await ap_inbox.handleRequest(
domain,
db,
kv_cache,
'a',
activity,
userKEK,
waitUntil,
adminEmail,
vapidKeys
)
assert.equal(res.status, 200)
const entry = await db.prepare('SELECT * FROM actor_notifications').first()
@ -299,8 +395,6 @@ describe('ActivityPub', () => {
test('records like in db', async () => {
const db = await makeDB()
await configure(db, { title: 'title', description: 'a', email: 'email' })
await generateVAPIDKeys(db)
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
@ -311,7 +405,17 @@ describe('ActivityPub', () => {
actor: actorB.id,
object: note.id,
}
const res = await ap_inbox.handleRequest(domain, db, kv_cache, 'a', activity, userKEK, waitUntil)
const res = await ap_inbox.handleRequest(
domain,
db,
kv_cache,
'a',
activity,
userKEK,
waitUntil,
adminEmail,
vapidKeys
)
assert.equal(res.status, 200)
const entry = await db.prepare('SELECT * FROM actor_favourites').first()

Wyświetl plik

@ -1,4 +1,6 @@
import { strict as assert } from 'node:assert/strict'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import type { Env } from 'wildebeest/backend/src/types/env'
import * as v1_instance from 'wildebeest/functions/api/v1/instance'
import * as v2_instance from 'wildebeest/functions/api/v2/instance'
import * as apps from 'wildebeest/functions/api/v1/apps'
@ -9,24 +11,29 @@ import { makeDB, assertCORS, assertJSON, assertCache, createTestClient } from '.
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
import { createSubscription } from '../src/mastodon/subscription'
import * as subscription from 'wildebeest/functions/api/v1/push/subscription'
import { configure, generateVAPIDKeys } from 'wildebeest/backend/src/config'
const userKEK = 'test_kek'
const domain = 'cloudflare.com'
async function generateVAPIDKeys(): Promise<JWK> {
const keyPair = (await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, [
'sign',
'verify',
])) as CryptoKeyPair
const jwk = (await crypto.subtle.exportKey('jwk', keyPair.privateKey)) as JWK
return jwk
}
describe('Mastodon APIs', () => {
describe('instance', () => {
test('return the instance infos v1', async () => {
const db = await makeDB()
const data = {
title: 'title',
uri: 'uri',
email: 'email',
description: 'description',
}
await configure(db, data)
const env = {
INSTANCE_TITLE: 'a',
ADMIN_EMAIL: 'b',
INSTANCE_DESCR: 'c',
} as Env
const res = await v1_instance.handleRequest(domain, db)
const res = await v1_instance.handleRequest(domain, env)
assert.equal(res.status, 200)
assertCORS(res)
assertJSON(res)
@ -35,55 +42,60 @@ describe('Mastodon APIs', () => {
const data = await res.json<any>()
assert.equal(data.rules.length, 0)
assert.equal(data.uri, domain)
assert.equal(data.title, 'a')
assert.equal(data.email, 'b')
assert.equal(data.description, 'c')
}
})
test('adds a short_description if missing v1', async () => {
const db = await makeDB()
const data = {
title: 'title',
uri: 'uri',
email: 'email',
description: 'description',
}
await configure(db, data)
const env = {
INSTANCE_DESCR: 'c',
} as Env
const res = await v1_instance.handleRequest(domain, db)
const res = await v1_instance.handleRequest(domain, env)
assert.equal(res.status, 200)
{
const data = await res.json<any>()
assert.equal(data.short_description, 'description')
assert.equal(data.short_description, 'c')
}
})
test('return the instance infos v2', async () => {
const db = await makeDB()
const data = {
title: 'title',
uri: 'uri',
email: 'email',
description: 'description',
}
await configure(db, data)
const res = await v2_instance.handleRequest(domain, db)
const env = {
INSTANCE_TITLE: 'a',
ADMIN_EMAIL: 'b',
INSTANCE_DESCR: 'c',
} as Env
const res = await v2_instance.handleRequest(domain, db, env)
assert.equal(res.status, 200)
assertCORS(res)
assertJSON(res)
{
const data = await res.json<any>()
assert.equal(data.rules.length, 0)
assert.equal(data.domain, domain)
assert.equal(data.title, 'a')
assert.equal(data.contact.email, 'b')
assert.equal(data.description, 'c')
}
})
})
describe('apps', () => {
test('return the app infos', async () => {
const db = await makeDB()
await generateVAPIDKeys(db)
const vapidKeys = await generateVAPIDKeys()
const request = new Request('https://example.com', {
method: 'POST',
body: '{"redirect_uris":"mastodon://joinmastodon.org/oauth","website":"https://app.joinmastodon.org/ios","client_name":"Mastodon for iOS","scopes":"read write follow push"}',
})
const res = await apps.handleRequest(db, request)
const res = await apps.handleRequest(db, request, vapidKeys)
assert.equal(res.status, 200)
assertCORS(res)
assertJSON(res)
@ -100,11 +112,14 @@ describe('Mastodon APIs', () => {
})
test('returns 404 for GET request', async () => {
const vapidKeys = await generateVAPIDKeys()
const request = new Request('https://example.com')
const ctx: any = {
next: () => new Response(),
data: null,
env: {},
env: {
VAPID_JWK: JSON.stringify(vapidKeys),
},
request,
}
@ -169,8 +184,8 @@ describe('Mastodon APIs', () => {
test('create subscription', async () => {
const db = await makeDB()
const client = await createTestClient(db)
await generateVAPIDKeys(db)
const connectedActor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const vapidKeys = await generateVAPIDKeys()
const data: any = {
subscription: {
@ -190,7 +205,7 @@ describe('Mastodon APIs', () => {
body: JSON.stringify(data),
})
const res = await subscription.handlePostRequest(db, req, connectedActor, client.id)
const res = await subscription.handlePostRequest(db, req, connectedActor, client.id, vapidKeys)
assert.equal(res.status, 200)
const row: any = await db.prepare('SELECT * FROM subscriptions').first()
@ -202,8 +217,8 @@ describe('Mastodon APIs', () => {
test('create subscriptions only creates one', async () => {
const db = await makeDB()
const client = await createTestClient(db)
await generateVAPIDKeys(db)
const connectedActor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const vapidKeys = await generateVAPIDKeys()
const data: any = {
subscription: {
@ -225,7 +240,7 @@ describe('Mastodon APIs', () => {
body: JSON.stringify(data),
})
const res = await subscription.handlePostRequest(db, req, connectedActor, client.id)
const res = await subscription.handlePostRequest(db, req, connectedActor, client.id, vapidKeys)
assert.equal(res.status, 200)
const { count } = await db.prepare('SELECT count(*) as count FROM subscriptions').first()

Wyświetl plik

@ -1,5 +1,4 @@
import { strict as assert } from 'node:assert/strict'
import { configure, generateVAPIDKeys } from 'wildebeest/backend/src/config'
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
import * as accounts_following from 'wildebeest/functions/api/v1/accounts/[id]/following'
@ -418,8 +417,6 @@ describe('Mastodon APIs', () => {
test('get remote actor statuses', async () => {
const db = await makeDB()
await configure(db, { title: 'title', description: 'a', email: 'email' })
await generateVAPIDKeys(db)
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
@ -508,7 +505,6 @@ describe('Mastodon APIs', () => {
test('get remote actor statuses ignoring object that fail to download', async () => {
const db = await makeDB()
await generateVAPIDKeys(db)
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
await createPublicNote(domain, db, 'my localnote status', actor)

Wyświetl plik

@ -1,4 +1,5 @@
import * as notifications_get from 'wildebeest/functions/api/v1/notifications/[id]'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
import { createNotification, insertFollowNotification } from 'wildebeest/backend/src/mastodon/notification'
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
@ -7,13 +8,13 @@ import { makeDB, assertJSON, createTestClient } from '../utils'
import { strict as assert } from 'node:assert/strict'
import { sendLikeNotification } from 'wildebeest/backend/src/mastodon/notification'
import { createSubscription } from 'wildebeest/backend/src/mastodon/subscription'
import { generateVAPIDKeys, configure } from 'wildebeest/backend/src/config'
import { arrayBufferToBase64 } from 'wildebeest/backend/src/utils/key-ops'
import { getNotifications } from 'wildebeest/backend/src/mastodon/notification'
const userKEK = 'test_kek15'
const domain = 'cloudflare.com'
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
const vapidKeys = {} as JWK
function parseCryptoKey(s: string): any {
const parts = s.split(';')
@ -92,8 +93,6 @@ describe('Mastodon APIs', () => {
test('send like notification', async () => {
const db = await makeDB()
await generateVAPIDKeys(db)
await configure(db, { title: 'title', description: 'a', email: 'email' })
const clientKeys = (await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, [
'sign',
@ -140,7 +139,7 @@ describe('Mastodon APIs', () => {
})
const fromActor = await createPerson(domain, db, userKEK, 'from@cloudflare.com')
await sendLikeNotification(db, fromActor, actor, 'notifid')
await sendLikeNotification(db, fromActor, actor, 'notifid', 'admin@example.com', vapidKeys)
})
})
})

Wyświetl plik

@ -83,6 +83,21 @@ describe('Mastodon APIs', () => {
assert.equal(await data.text(), 'cached data')
})
test('home returns empty if not in cache', async () => {
const db = await makeDB()
const connectedActor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const kv_cache: any = {
async get() {
return null
},
}
const req = new Request('https://' + domain)
const data = await timelines_home.handleRequest(req, kv_cache, connectedActor)
const posts = await data.json<Array<any>>()
assert.equal(posts.length, 0)
})
test('public returns Notes', async () => {
const db = await makeDB()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')

Wyświetl plik

@ -1,48 +0,0 @@
import * as startInstance from 'wildebeest/functions/start-instance'
import { TEST_JWT, ACCESS_CERTS } from './test-data'
import { strict as assert } from 'node:assert/strict'
import { makeDB } from './utils'
const accessDomain = 'access.com'
const accessAud = 'abcd'
describe('Wildebeest', () => {
globalThis.fetch = async (input: RequestInfo) => {
if (input === 'https://' + accessDomain + '/cdn-cgi/access/certs') {
return new Response(JSON.stringify(ACCESS_CERTS))
}
if (input === 'https://' + accessDomain + '/cdn-cgi/access/get-identity') {
return new Response(
JSON.stringify({
email: 'some@cloudflare.com',
})
)
}
throw new Error('unexpected request to ' + input)
}
test('start instance should generate a VAPID key and store a JWK', async () => {
const db = await makeDB()
const body = JSON.stringify({
title: 'title',
description: 'description',
email: 'email',
})
const headers = {
cookie: 'CF_Authorization=' + TEST_JWT,
}
const req = new Request('https://example.com', { method: 'POST', body, headers })
const res = await startInstance.handlePostRequest(req, db, accessDomain, accessAud)
assert.equal(res.status, 201)
const { value } = await db.prepare("SELECT value FROM instance_config WHERE key = 'vapid_jwk'").first()
const jwk = JSON.parse(value)
assert.equal(jwk.key_ops.length, 1)
assert.equal(jwk.key_ops[0], 'sign')
assert.equal(jwk.crv, 'P-256')
})
})

Wyświetl plik

@ -3,20 +3,12 @@ import * as statusesAPI from 'wildebeest/functions/api/v1/statuses'
import { statuses } from 'wildebeest/frontend/src/dummyData'
import type { MastodonStatus } from 'wildebeest/frontend/src/types'
import type { MastodonAccount } from 'wildebeest/backend/src/types'
import { configure } from 'wildebeest/backend/src/config'
const kek = 'test-kek'
/**
* Run helper commands to initialize the database with actors, statuses, etc.
*/
export async function init(domain: string, db: D1Database) {
configure(db, {
title: 'Wildebeest',
email: '',
description: 'Wildebeest dev instance',
thumbnail: '/assets/wildebeest-logo.png',
})
for (const status of statuses as MastodonStatus[]) {
const actor = await getOrCreatePerson(domain, db, status.account.username)
await createStatus(db, actor, status.content)
@ -31,8 +23,12 @@ async function createStatus(db: D1Database, actor: Person, status: string, visib
status,
visibility,
}
const headers = {
'content-type': 'application/json',
}
const req = new Request('https://example.com', {
method: 'POST',
headers,
body: JSON.stringify(body),
})
await statusesAPI.handleRequest(req, db, actor, kek)

Wyświetl plik

@ -1,73 +0,0 @@
import { $, component$, useStore, useClientEffect$, useSignal } from '@builder.io/qwik'
import { DocumentHead } from '@builder.io/qwik-city'
import { WildebeestLogo } from '~/components/MastodonLogo'
import { useDomain } from '~/utils/useDomain'
import Step1 from './step-1'
import { type InstanceConfig, testInstance } from './utils'
export default component$(() => {
const domain = useDomain()
const loading = useSignal(true)
const instanceConfigured = useSignal(false)
const instanceConfig = useStore<InstanceConfig>({
title: `${domain} Wildebeest`,
email: `admin@${domain}`,
description: 'My personal Wildebeest instance (powered by Cloudflare)',
})
useClientEffect$(async () => {
if (await testInstance()) {
instanceConfigured.value = true
}
loading.value = false
})
const getStepToShow = () => {
if (loading.value) return 'loading'
if (!instanceConfigured.value) return 'step-1'
return 'all-good'
}
const stepToShow = getStepToShow()
const setLoading = $((value: boolean) => {
loading.value = value
})
const setInstanceConfigured = $((value: boolean) => {
instanceConfigured.value = value
})
return (
<div class="flex flex-col p-5 items-center max-w-lg mx-auto">
<h1 class="text-center mt-7 mb-9 flex items-center">
<WildebeestLogo size="large" />
</h1>
{stepToShow.startsWith('step-') && (
<div class="text-center">
<p class="mb-1">Welcome to Wildebeest...</p>
<p class="mb-5"> Your instance hasn't been configured yet.</p>
</div>
)}
{stepToShow === 'loading' && <p>Loading...</p>}
{stepToShow === 'step-1' && (
<Step1 instanceConfig={instanceConfig} setLoading={setLoading} setInstanceConfigured={setInstanceConfigured} />
)}
{stepToShow === 'all-good' && <p class="text-center">All good, your instance is ready.</p>}
</div>
)
})
export const head: DocumentHead = () => {
return {
title: 'Wildebeest Start Instance',
meta: [
{
name: 'description',
content: 'Wildebeest Instance Setup page',
},
],
}
}

Wyświetl plik

@ -1,80 +0,0 @@
import { component$, QRL } from '@builder.io/qwik'
import { configure, type InstanceConfig, testInstance } from './utils'
interface Props {
instanceConfig: InstanceConfig
setLoading: QRL<(loading: boolean) => void>
setInstanceConfigured: QRL<(configured: boolean) => void>
}
export default component$<Props>(({ instanceConfig, setLoading, setInstanceConfigured }) => {
return (
<>
<h2 class="mb-5">Configure your instance</h2>
<div class="flex flex-col mb-6 w-full max-w-md">
<label class="mb-2 max-w-max text-semi text-sm" for="start-instance-title">
Title
</label>
<div class="flex justify-center items-center flex-wrap gap-1">
<input
id="start-instance-title"
name="title"
class="bg-black text-white p-3 rounded outline-none border border-black hover:border-wildebeest-vibrant-500 focus:border-wildebeest-vibrant-500 invalid:border-red-400 flex-1 w-full"
value={instanceConfig.title}
onInput$={(ev) => (instanceConfig.title = (ev.target as HTMLInputElement).value)}
/>
</div>
</div>
<div class="flex flex-col mb-6 w-full max-w-md">
<label class="mb-2 max-w-max text-semi text-sm" for="start-instance-email">
Administrator email
</label>
<div class="flex justify-center items-center flex-wrap gap-1">
<input
id="start-instance-email"
name="email"
type="email"
class="bg-black text-white p-3 rounded outline-none border border-black hover:border-wildebeest-vibrant-500 focus:border-wildebeest-vibrant-500 invalid:border-red-400 flex-1 w-full"
value={instanceConfig.email}
onInput$={(ev) => (instanceConfig.email = (ev.target as HTMLInputElement).value)}
/>
</div>
</div>
<div class="flex flex-col mb-6 w-full max-w-md">
<label class="mb-2 max-w-max text-semi text-sm" for="start-instance-description">
Description
</label>
<div class="flex justify-center items-center flex-wrap gap-1">
<input
id="start-instance-description"
name="description"
class="bg-black text-white p-3 rounded outline-none border border-black hover:border-wildebeest-vibrant-500 focus:border-wildebeest-vibrant-500 invalid:border-red-400 flex-1 w-full"
value={instanceConfig.description}
onInput$={(ev) => (instanceConfig.description = (ev.target as HTMLInputElement).value)}
/>
</div>
</div>
<button
type="submit"
class="mb-9 bg-wildebeest-vibrant-500 hover:bg-wildebeest-vibrant-600 p-3 text-white text-uppercase border-wildebeest-vibrant-500 text-lg text-semi outline-none border rounded hover:border-wildebeest-vibrant-600 focus:border-wildebeest-vibrant-600"
preventdefault:click
onClick$={async () => {
setLoading(true)
await configure(instanceConfig)
if (await testInstance()) {
setInstanceConfigured(true)
}
setLoading(false)
}}
>
Configure and start your instance
</button>
</>
)
})

Wyświetl plik

@ -1,21 +0,0 @@
export type InstanceConfig = {
title?: string
email?: string
description?: string
}
export async function configure(data: InstanceConfig) {
const res = await fetch('/start-instance', {
method: 'POST',
body: JSON.stringify(data),
})
if (!res.ok) {
throw new Error('/start-instance returned: ' + res.status)
}
}
export async function testInstance(): Promise<boolean> {
const res = await fetch('/api/v1/instance')
const data = await res.json<{ title?: string }>()
return !!data.title
}

Wyświetl plik

@ -1,25 +0,0 @@
.active {
color: var(--wildebeest-color-200);
position: relative;
&::before,
&::after {
display: block;
content: "";
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 0;
transform: translateX(-50%);
border-color: transparent transparent var(--wildebeest-color-500);
border-style: solid;
border-width: 0 0.7rem 0.7rem;
bottom: -1px;
}
&::after {
bottom: -1px;
border-color: transparent transparent var(--wildebeest-color-600);
}
}

Wyświetl plik

@ -1,7 +1,6 @@
import { component$, Slot, useStylesScoped$ } from '@builder.io/qwik'
import { component$, Slot } from '@builder.io/qwik'
import { Link, useLocation } from '@builder.io/qwik-city'
import StickyHeader from '~/components/StickyHeader/StickyHeader'
import styles from './layout.scss?inline'
type LinkConfig = {
linkText: string
@ -9,14 +8,13 @@ type LinkConfig = {
}
export default component$(() => {
useStylesScoped$(styles)
const location = useLocation()
const renderNavLink = ({ linkText, linkTarget }: LinkConfig) => {
const isActive = location.pathname.replace(/\/$/, '') === linkTarget
return (
<div class={`py-4 ${isActive ? 'active' : ''}`}>
<div class={`py-4 ${isActive ? activeClasses.join(' ') : ''}`}>
<Link href={linkTarget} class="no-underline text-bold text-wildebeest-200 py-4">
{linkText}
</Link>
@ -54,3 +52,35 @@ export default component$(() => {
</div>
)
})
export const activeClasses = [
'relative',
'before:block',
'before:content-[""]',
'before:absolute',
'before:w-0',
'before:h-0',
'before:bottom-[-1px]',
'before:left-1/2',
'before:translate-x-[-50%]',
'before:border-solid',
'before:border-t-0',
'before:border-x-[0.7rem]',
'before:border-b-[0.7rem]',
'before:border-x-transparent',
'before:border-b-wildebeest-500',
'after:block',
'after:content-[""]',
'after:absolute',
'after:w-0',
'after:h-0',
'after:bottom-[-1px]',
'after:left-1/2',
'after:translate-x-[-50%]',
'after:border-solid',
'after:border-t-0',
'after:border-x-[0.7rem]',
'after:border-b-[0.7rem]',
'after:border-x-transparent',
'after:border-b-wildebeest-600',
]

Wyświetl plik

@ -1,4 +1,5 @@
import { component$, Slot, useContextProvider } from '@builder.io/qwik'
import type { Env } from 'wildebeest/backend/src/types/env'
import { DocumentHead, loader$ } from '@builder.io/qwik-city'
import * as instance from 'wildebeest/functions/api/v1/instance'
import type { InstanceConfig } from 'wildebeest/backend/src/types/configs'
@ -8,18 +9,20 @@ import { WildebeestLogo } from '~/components/MastodonLogo'
import { getCommitHash } from '~/utils/getCommitHash'
import { InstanceConfigContext } from '~/utils/instanceConfig'
export const instanceLoader = loader$<{ DATABASE: D1Database }, Promise<InstanceConfig>>(
async ({ platform, redirect }) => {
const response = await instance.handleRequest('', platform.DATABASE)
const results = await response.text()
const json = JSON.parse(results) as InstanceConfig
if (!json.title) {
// If there is no title set then we have not configured the instance
throw redirect(302, '/start-instance')
}
return json
}
)
export const instanceLoader = loader$<
{ DATABASE: D1Database; INSTANCE_TITLE: string; INSTANCE_DESCR: string; ADMIN_EMAIL: string },
Promise<InstanceConfig>
>(async ({ platform }) => {
const env = {
INSTANCE_DESCR: platform.INSTANCE_DESCR,
INSTANCE_TITLE: platform.INSTANCE_TITLE,
ADMIN_EMAIL: platform.ADMIN_EMAIL,
} as Env
const response = await instance.handleRequest('', platform.DATABASE, env)
const results = await response.text()
const json = JSON.parse(results) as InstanceConfig
return json
})
export default component$(() => {
useContextProvider(InstanceConfigContext, instanceLoader.use().value)

Wyświetl plik

@ -1,13 +1,9 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { getFollowers } from 'wildebeest/backend/src/mastodon/follow'
import * as objects from 'wildebeest/backend/src/activitypub/objects'
import type { Activity } from 'wildebeest/backend/src/activitypub/activities'
import { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import type { Env } from 'wildebeest/backend/src/types/env'
import type { Note } from 'wildebeest/backend/src/activitypub/objects/note'
import * as activityCreate from 'wildebeest/backend/src/activitypub/activities/create'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params }) => {
const domain = new URL(request.url).hostname

Wyświetl plik

@ -1,13 +1,9 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { getFollowingId } from 'wildebeest/backend/src/mastodon/follow'
import * as objects from 'wildebeest/backend/src/activitypub/objects'
import type { Activity } from 'wildebeest/backend/src/activitypub/activities'
import { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import type { Env } from 'wildebeest/backend/src/types/env'
import type { Note } from 'wildebeest/backend/src/activitypub/objects/note'
import * as activityCreate from 'wildebeest/backend/src/activitypub/activities/create'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params }) => {
const domain = new URL(request.url).hostname

Wyświetl plik

@ -1,15 +1,16 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import * as activityHandler from 'wildebeest/backend/src/activitypub/activities/handle'
import type { Env } from 'wildebeest/backend/src/types/env'
import * as actors from 'wildebeest/backend/src/activitypub/actors'
import type { Activity } from 'wildebeest/backend/src/activitypub/activities'
import * as activities from 'wildebeest/backend/src/activitypub/activities'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
import { parseRequest } from 'wildebeest/backend/src/utils/httpsigjs/parser'
import { fetchKey, verifySignature } from 'wildebeest/backend/src/utils/httpsigjs/verifier'
import { generateDigestHeader } from 'wildebeest/backend/src/utils/http-signing-cavage'
import * as timeline from 'wildebeest/backend/src/mastodon/timeline'
import * as notification from 'wildebeest/backend/src/mastodon/notification'
import { getVAPIDKeys } from 'wildebeest/backend/src/config'
export const onRequest: PagesFunction<Env, any> = async ({ params, request, env, waitUntil }) => {
const parsedSignature = parseRequest(request)
@ -30,7 +31,17 @@ export const onRequest: PagesFunction<Env, any> = async ({ params, request, env,
const activity: Activity = JSON.parse(body)
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, env.KV_CACHE, params.id as string, activity, env.userKEK, waitUntil)
return handleRequest(
domain,
env.DATABASE,
env.KV_CACHE,
params.id as string,
activity,
env.userKEK,
waitUntil,
env.ADMIN_EMAIL,
getVAPIDKeys(env)
)
}
export async function handleRequest(
@ -40,7 +51,9 @@ export async function handleRequest(
id: string,
activity: Activity,
userKEK: string,
waitUntil: (p: Promise<any>) => void
waitUntil: (p: Promise<any>) => void,
adminEmail: string,
vapidKeys: JWK
): Promise<Response> {
const handle = parseHandle(id)
@ -54,7 +67,7 @@ export async function handleRequest(
return new Response('', { status: 404 })
}
await activityHandler.handle(domain, activity, db, userKEK)
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
// Assuming we received new posts or a like, pregenerate the user's timelines
// and notifications.

Wyświetl plik

@ -1,12 +1,8 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import * as objects from 'wildebeest/backend/src/activitypub/objects'
import type { Activity } from 'wildebeest/backend/src/activitypub/activities'
import { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import type { Env } from 'wildebeest/backend/src/types/env'
import type { Note } from 'wildebeest/backend/src/activitypub/objects/note'
import * as activityCreate from 'wildebeest/backend/src/activitypub/activities/create'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params }) => {
const domain = new URL(request.url).hostname
@ -17,6 +13,7 @@ const headers = {
'content-type': 'application/json; charset=utf-8',
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO: use userKEK
export async function handleRequest(domain: string, db: D1Database, id: string, userKEK: string): Promise<Response> {
const handle = parseHandle(id)

Wyświetl plik

@ -21,6 +21,7 @@ const headers = {
const DEFAULT_LIMIT = 20
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO: use userKEK
export async function handleRequest(domain: string, db: D1Database, id: string, userKEK: string): Promise<Response> {
const handle = parseHandle(id)

Wyświetl plik

@ -4,7 +4,6 @@ import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
import { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import type { Env } from 'wildebeest/backend/src/types/env'
import type { MastodonAccount } from 'wildebeest/backend/src/types/account'
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import type { Handle } from 'wildebeest/backend/src/utils/parse'
import { queryAcct } from 'wildebeest/backend/src/webfinger/index'

Wyświetl plik

@ -2,13 +2,12 @@ import type { Env } from 'wildebeest/backend/src/types/env'
import type { Activity } from 'wildebeest/backend/src/activitypub/activities'
import type { Note } from 'wildebeest/backend/src/activitypub/objects/note'
import { loadExternalMastodonAccount } from 'wildebeest/backend/src/mastodon/account'
import type { Object } from 'wildebeest/backend/src/activitypub/objects'
import { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
import { makeGetActorAsId, makeGetObjectAsId } from 'wildebeest/backend/src/activitypub/activities/handle'
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import type { Handle } from 'wildebeest/backend/src/utils/parse'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import type { MastodonAccount, MastodonStatus } from 'wildebeest/backend/src/types'
import type { MastodonStatus } from 'wildebeest/backend/src/types'
import { toMastodonStatusFromObject } from 'wildebeest/backend/src/mastodon/status'
import * as objects from 'wildebeest/backend/src/activitypub/objects'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
@ -41,6 +40,7 @@ export async function handleRequest(request: Request, db: D1Database, id: string
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO: use userKEK
async function getRemoteStatuses(request: Request, handle: Handle, db: D1Database, userKEK: string): Promise<Response> {
const url = new URL(request.url)
const domain = url.hostname
@ -61,6 +61,7 @@ async function getRemoteStatuses(request: Request, handle: Handle, db: D1Databas
const activities = await outbox.get(actor)
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO: use account
const account = await loadExternalMastodonAccount(acct, actor)
const promises = activities.items.map(async (activity: Activity) => {

Wyświetl plik

@ -5,7 +5,7 @@ import type { Env } from 'wildebeest/backend/src/types/env'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import { getFollowingAcct, getFollowingRequestedAcct } from 'wildebeest/backend/src/mastodon/follow'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params, data }) => {
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, data }) => {
return handleRequest(request, env.DATABASE, data.connectedActor)
}

Wyświetl plik

@ -9,7 +9,7 @@ import * as images from 'wildebeest/backend/src/media/image'
import type { Env } from 'wildebeest/backend/src/types/env'
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import { updateActorProperty } from 'wildebeest/backend/src/activitypub/actors'
import type { CredentialAccount, MastodonAccount } from 'wildebeest/backend/src/types/account'
import type { CredentialAccount } from 'wildebeest/backend/src/types/account'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import { loadLocalMastodonAccount } from 'wildebeest/backend/src/mastodon/account'

Wyświetl plik

@ -1,9 +1,9 @@
import { ContextData } from 'wildebeest/backend/src/types/context'
import { b64ToUrlEncoded, exportPublicKeyPair } from 'wildebeest/backend/src/webpush/util'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import { Env } from 'wildebeest/backend/src/types/env'
import { createClient } from 'wildebeest/backend/src/mastodon/client'
import { getVAPIDKeys, VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'
import { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'
import { getVAPIDKeys } from 'wildebeest/backend/src/config'
type AppsPost = {
redirect_uris: string
@ -12,11 +12,11 @@ type AppsPost = {
scopes: string
}
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, data }) => {
return handleRequest(env.DATABASE, request)
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env }) => {
return handleRequest(env.DATABASE, request, getVAPIDKeys(env))
}
export async function handleRequest(db: D1Database, request: Request) {
export async function handleRequest(db: D1Database, request: Request, vapidKeys: JWK) {
if (request.method !== 'POST') {
return new Response('', { status: 400 })
}
@ -24,7 +24,7 @@ export async function handleRequest(db: D1Database, request: Request) {
const body = await request.json<AppsPost>()
const client = await createClient(db, body.client_name, body.redirect_uris, body.website, body.scopes)
const vapidKey = VAPIDPublicKey(await getVAPIDKeys(db))
const vapidKey = VAPIDPublicKey(vapidKeys)
const res = {
name: body.client_name,

Wyświetl plik

@ -1,46 +1,37 @@
import type { Env } from 'wildebeest/backend/src/types/env'
import { DEFAULT_THUMBNAIL } from 'wildebeest/backend/src/config'
const INSTANCE_VERSION = '4.0.2'
export const onRequest: PagesFunction<Env, any> = async ({ env, request }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE)
return handleRequest(domain, env)
}
export async function handleRequest(domain: string, db: D1Database) {
export async function handleRequest(domain: string, env: Env) {
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'content-type, authorization',
'content-type': 'application/json; charset=utf-8',
}
const query = `
SELECT * FROM instance_config WHERE key IN ('title', 'description', 'email', 'short_description', 'thumbnail')
`
const { results, error, success } = await db.prepare(query).all()
if (!success) {
throw new Error('SQL error: ' + error)
}
const res: any = {}
if (results) {
for (let i = 0, len = results.length; i < len; i++) {
const row: any = results[i]
res[row.key] = row.value
}
}
res.thumbnail = DEFAULT_THUMBNAIL
// Registration is disabled because unsupported by Wildebeest. Users
// should go through the login flow and authenticate with Access.
// The documentation is incorrect and registrations is a boolean.
res.registrations = false
res.version = INSTANCE_VERSION
res.rules = []
res.uri = domain
res.title = env.INSTANCE_TITLE
res.email = env.ADMIN_EMAIL
res.description = env.INSTANCE_DESCR
if (!res.short_description) {
res.short_description = res.description
}
res.short_description = res.description
return new Response(JSON.stringify(res), { headers })
}

Wyświetl plik

@ -1,11 +1,8 @@
// https://docs.joinmastodon.org/methods/notifications/#get
import type { Env } from 'wildebeest/backend/src/types/env'
import * as objects from 'wildebeest/backend/src/activitypub/objects'
import type { Person } from 'wildebeest/backend/src/activitypub/actors'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import { getNotifications } from 'wildebeest/backend/src/mastodon/notification'
import type { MastodonStatus } from 'wildebeest/backend/src/types'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, data }) => {
return handleRequest(request, env.KV_CACHE, data.connectedActor)

Wyświetl plik

@ -4,7 +4,6 @@ import type { Notification } from 'wildebeest/backend/src/types/notification'
import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
import { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
import { loadExternalMastodonAccount } from 'wildebeest/backend/src/mastodon/account'
import * as objects from 'wildebeest/backend/src/activitypub/objects'
import type { Person } from 'wildebeest/backend/src/activitypub/actors'
import type { Env } from 'wildebeest/backend/src/types/env'
import type { ContextData } from 'wildebeest/backend/src/types/context'

Wyświetl plik

@ -1,22 +1,20 @@
import { getClientById } from 'wildebeest/backend/src/mastodon/client'
import { getVAPIDKeys } from 'wildebeest/backend/src/config'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import { createSubscription, getSubscription } from 'wildebeest/backend/src/mastodon/subscription'
import type { CreateRequest } from 'wildebeest/backend/src/mastodon/subscription'
import { ContextData } from 'wildebeest/backend/src/types/context'
import { Env } from 'wildebeest/backend/src/types/env'
import * as errors from 'wildebeest/backend/src/errors'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import type { WebPushInfos, WebPushMessage, WebPushResult } from 'wildebeest/backend/src/webpush/webpushinfos'
import { b64ToUrlEncoded, exportPublicKeyPair } from 'wildebeest/backend/src/webpush/util'
import { generateWebPushMessage } from 'wildebeest/backend/src/webpush'
import { getVAPIDKeys, VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'
import { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'
export const onRequestGet: PagesFunction<Env, any, ContextData> = async ({ request, env, data }) => {
return handleGetRequest(env.DATABASE, request, data.connectedActor, data.clientId)
}
export const onRequestPost: PagesFunction<Env, any, ContextData> = async ({ request, env, data }) => {
return handlePostRequest(env.DATABASE, request, data.connectedActor, data.clientId)
return handlePostRequest(env.DATABASE, request, data.connectedActor, data.clientId, getVAPIDKeys(env))
}
const headers = {
@ -56,7 +54,13 @@ export async function handleGetRequest(db: D1Database, request: Request, connect
return new Response(JSON.stringify(res), { headers })
}
export async function handlePostRequest(db: D1Database, request: Request, connectedActor: Actor, clientId: string) {
export async function handlePostRequest(
db: D1Database,
request: Request,
connectedActor: Actor,
clientId: string,
vapidKeys: JWK
) {
const client = await getClientById(db, clientId)
if (client === null) {
return errors.clientUnknown()
@ -70,7 +74,7 @@ export async function handlePostRequest(db: D1Database, request: Request, connec
subscription = await createSubscription(db, connectedActor, client, data)
}
const vapidKey = VAPIDPublicKey(await getVAPIDKeys(db))
const vapidKey = VAPIDPublicKey(vapidKeys)
const res = {
id: 4,

Wyświetl plik

@ -1,7 +1,6 @@
// https://docs.joinmastodon.org/methods/statuses/#create
import { loadLocalMastodonAccount } from 'wildebeest/backend/src/mastodon/account'
import type { MediaAttachment } from 'wildebeest/backend/src/types/media'
import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
import type { Document } from 'wildebeest/backend/src/activitypub/objects'
import { getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/objects'
@ -9,8 +8,6 @@ import { getMentions } from 'wildebeest/backend/src/mastodon/status'
import * as activities from 'wildebeest/backend/src/activitypub/activities/create'
import type { Env } from 'wildebeest/backend/src/types/env'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import type { MastodonAccount } from 'wildebeest/backend/src/types/account'
import type { MastodonStatus } from 'wildebeest/backend/src/types/status'
import { queryAcct } from 'wildebeest/backend/src/webfinger'
import { deliverFollowers, deliverToActor } from 'wildebeest/backend/src/activitypub/deliver'
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
@ -50,7 +47,7 @@ export async function handleRequest(
return new Response('', { status: 400 })
}
let mediaAttachments: Array<Document> = []
const mediaAttachments: Array<Document> = []
if (body.media_ids && body.media_ids.length > 0) {
for (let i = 0, len = body.media_ids.length; i < len; i++) {
const id = body.media_ids[i]

Wyświetl plik

@ -1,15 +1,11 @@
// https://docs.joinmastodon.org/methods/statuses/#get
import type { UUID } from 'wildebeest/backend/src/types'
import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import type { Person } from 'wildebeest/backend/src/activitypub/actors'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import { getFollowers } from 'wildebeest/backend/src/mastodon/follow'
import { getMastodonStatusById } from 'wildebeest/backend/src/mastodon/status'
import type { Env } from 'wildebeest/backend/src/types/env'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ params, request, env, data }) => {
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ params, env }) => {
return handleRequest(env.DATABASE, params.id as UUID)
}

Wyświetl plik

@ -1,7 +1,6 @@
// https://docs.joinmastodon.org/methods/statuses/#favourite
import type { Env } from 'wildebeest/backend/src/types/env'
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { insertLike } from 'wildebeest/backend/src/mastodon/like'
import { getSigningKey } from 'wildebeest/backend/src/mastodon/account'
import { deliverToActor } from 'wildebeest/backend/src/activitypub/deliver'
@ -11,10 +10,9 @@ import * as like from 'wildebeest/backend/src/activitypub/activities/like'
import { getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/objects'
import type { Note } from 'wildebeest/backend/src/activitypub/objects/note'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import { queryAcct } from 'wildebeest/backend/src/webfinger'
import { toMastodonStatusFromObject } from 'wildebeest/backend/src/mastodon/status'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, data, params }) => {
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ env, data, params }) => {
return handleRequest(env.DATABASE, params.id as string, data.connectedActor, env.userKEK)
}

Wyświetl plik

@ -1,7 +1,6 @@
// https://docs.joinmastodon.org/methods/statuses/#boost
import type { Env } from 'wildebeest/backend/src/types/env'
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
import { insertReblog } from 'wildebeest/backend/src/mastodon/reblog'
import { getSigningKey } from 'wildebeest/backend/src/mastodon/account'
@ -12,10 +11,9 @@ import * as announce from 'wildebeest/backend/src/activitypub/activities/announc
import { getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/objects'
import type { Note } from 'wildebeest/backend/src/activitypub/objects/note'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import { queryAcct } from 'wildebeest/backend/src/webfinger'
import { toMastodonStatusFromObject } from 'wildebeest/backend/src/mastodon/status'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, data, params }) => {
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ env, data, params }) => {
return handleRequest(env.DATABASE, params.id as string, data.connectedActor, env.userKEK)
}

Wyświetl plik

@ -1,12 +1,6 @@
import type { Env } from 'wildebeest/backend/src/types/env'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import type { Actor } from 'wildebeest/backend/src/activitypub/actors/'
import * as objects from 'wildebeest/backend/src/activitypub/objects/'
import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
import { getHomeTimeline } from 'wildebeest/backend/src/mastodon/timeline'
import { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
import type { MastodonAccount, MastodonStatus } from 'wildebeest/backend/src/types/'
import * as errors from 'wildebeest/backend/src/errors'
const headers = {
'Access-Control-Allow-Origin': '*',
@ -14,7 +8,7 @@ const headers = {
'content-type': 'application/json; charset=utf-8',
}
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params, data }) => {
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, data }) => {
return handleRequest(request, env.KV_CACHE, data.connectedActor)
}
@ -29,7 +23,7 @@ export async function handleRequest(request: Request, cache: KVNamespace, actor:
const timeline = await cache.get(actor.id + '/timeline/home')
if (timeline === null) {
return errors.timelineMissing()
return new Response(JSON.stringify([]), { headers })
}
return new Response(timeline, { headers })
}

Wyświetl plik

@ -8,7 +8,7 @@ const headers = {
'content-type': 'application/json; charset=utf-8',
}
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params, data }) => {
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env }) => {
const { searchParams } = new URL(request.url)
const local = searchParams.get('local') === 'true'
const remote = searchParams.get('remote') === 'true'
@ -21,6 +21,7 @@ export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request,
export async function handleRequest(
domain: string,
db: D1Database,
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO: use only_media
{ local = false, remote = false, only_media = false, offset = 0 } = {}
): Promise<Response> {
let localParam = LocalPreference.NotSet

Wyświetl plik

@ -1,44 +1,29 @@
import type { Env } from 'wildebeest/backend/src/types/env'
import { DEFAULT_THUMBNAIL } from 'wildebeest/backend/src/config'
import type { InstanceConfigV2 } from 'wildebeest/backend/src/types/configs'
const INSTANCE_VERSION = '4.0.2'
export const onRequest: PagesFunction<Env, any> = async ({ env, request }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE)
return handleRequest(domain, env.DATABASE, env)
}
export async function handleRequest(domain: string, db: D1Database) {
export async function handleRequest(domain: string, db: D1Database, env: Env) {
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'content-type, authorization',
'content-type': 'application/json; charset=utf-8',
}
const query = `
SELECT * FROM instance_config WHERE key IN ('title', 'description', 'email', 'short_description', 'thumbnail')
`
const { results, error, success } = await db.prepare(query).all()
if (!success) {
throw new Error('SQL error: ' + error)
}
const config: any = {}
if (results) {
for (let i = 0, len = results.length; i < len; i++) {
const row: any = results[i]
config[row.key] = row.value
}
}
const res: InstanceConfigV2 = {
domain,
title: config.title,
title: env.INSTANCE_TITLE,
version: INSTANCE_VERSION,
source_url: 'https://github.com/cloudflare/wildebeest',
description: config.description,
description: env.INSTANCE_DESCR,
thumbnail: {
url: config.thumbnail,
url: DEFAULT_THUMBNAIL,
},
languages: ['en'],
registrations: {
@ -47,7 +32,7 @@ export async function handleRequest(domain: string, db: D1Database) {
enabled: false,
},
contact: {
email: config.email,
email: env.ADMIN_EMAIL,
},
rules: [],
}

Wyświetl plik

@ -2,11 +2,9 @@
// especially)
import type { Env } from 'wildebeest/backend/src/types/env'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import type { Person } from 'wildebeest/backend/src/activitypub/actors'
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
import * as errors from 'wildebeest/backend/src/errors'
export const onRequestPost: PagesFunction<Env, any, ContextData> = async ({ request, env, data }) => {
export const onRequestPost: PagesFunction<Env, any, ContextData> = async ({ request, env }) => {
return handlePostRequest(request, env.DATABASE, env.userKEK)
}

Wyświetl plik

@ -10,7 +10,7 @@ import { getPersonByEmail } from 'wildebeest/backend/src/activitypub/actors'
// Extract the JWT token sent by Access (running before us).
const extractJWTFromRequest = (request: Request) => request.headers.get('Cf-Access-Jwt-Assertion') || ''
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ data, request, env }) => {
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env }) => {
return handleRequest(request, env.DATABASE, env.userKEK, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD)
}
@ -58,8 +58,6 @@ export async function handleRequest(
return new Response('', { status: 403 })
}
const scope = url.searchParams.get('scope') || ''
const jwt = extractJWTFromRequest(request)
const validate = access.generateValidator({ jwt, domain: accessDomain, aud: accessAud })
await validate(request)

Wyświetl plik

@ -9,7 +9,7 @@ type Body = {
code: string | null
}
export const onRequest: PagesFunction<Env, any> = async ({ params, request, env }) => {
export const onRequest: PagesFunction<Env, any> = async ({ request, env }) => {
return handleRequest(env.DATABASE, request)
}

Wyświetl plik

@ -1,48 +0,0 @@
import type { Env } from 'wildebeest/backend/src/types/env'
import * as access from 'wildebeest/backend/src/access'
import * as errors from 'wildebeest/backend/src/errors'
import { parse } from 'cookie'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import type { InstanceConfig } from 'wildebeest/backend/src/config'
import { configure } from 'wildebeest/backend/src/config'
export const onRequestGet: PagesFunction<Env, any, ContextData> = async ({ request, data, env }) => {
return handleGetRequest(env.DATABASE, request)
}
// Route to test if Access has been configured properly
export async function handleGetRequest(db: D1Database, request: Request): Promise<Response> {
const query = `
SELECT * FROM instance_config WHERE key IN ('accessDomain', 'accessAud')
`
const { results, error, success } = await db.prepare(query).all()
if (!success) {
throw new Error('SQL error: ' + error)
}
const data: any = {}
if (results) {
for (let i = 0, len = results.length; i < len; i++) {
const row: any = results[i]
data[row.key] = row.value
}
}
const cookie = parse(request.headers.get('Cookie') || '')
const jwt = cookie['CF_Authorization']
if (!jwt) {
return errors.notAuthorized('missing authorization')
}
const domain = env.ACCESS_AUTH_DOMAIN
const validator = access.generateValidator({ jwt, domain, aud: env.ACCESS_AUD })
const { payload } = await validator(request)
const identity = await access.getIdentity({ jwt, domain })
if (!identity) {
return errors.notAuthorized('failed to load identity')
}
return new Response(JSON.stringify({ email: identity.email }))
}

Wyświetl plik

@ -1,58 +0,0 @@
// First screen to configure and start the instance
import type { Env } from 'wildebeest/backend/src/types/env'
import * as errors from 'wildebeest/backend/src/errors'
import * as access from 'wildebeest/backend/src/access'
import { parse } from 'cookie'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import type { InstanceConfig } from 'wildebeest/backend/src/config'
import * as config from 'wildebeest/backend/src/config'
export const onRequestPost: PagesFunction<Env, any> = async ({ request, env }) => {
return handlePostRequest(request, env.DATABASE, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD)
}
export const onRequestGet: PagesFunction<Env, any> = async (ctx) => {
const { request, env, next } = ctx
const cookie = parse(request.headers.get('Cookie') || '')
const jwt = cookie['CF_Authorization']
if (!jwt) {
const { hostname } = new URL(request.url)
const url = access.generateLoginURL({
redirectURL: new URL('/start-instance', 'https://' + env.DOMAIN),
domain: env.ACCESS_AUTH_DOMAIN,
aud: env.ACCESS_AUD,
})
return Response.redirect(url)
}
const frontend = await import('../frontend/server/entry.cloudflare-pages')
return frontend.onRequest(ctx)
}
export async function handlePostRequest(
request: Request,
db: D1Database,
accessDomain: string,
accessAud: string
): Promise<Response> {
const data = await request.json<InstanceConfig>()
const cookie = parse(request.headers.get('Cookie') || '')
const jwt = cookie['CF_Authorization']
if (!jwt) {
return new Response('', { status: 401 })
}
const validator = access.generateValidator({ jwt, domain: accessDomain, aud: accessAud })
const { payload } = await validator(request)
const identity = await access.getIdentity({ jwt, domain: accessDomain })
if (!identity) {
return errors.notAuthorized('failed to load identity')
}
await config.configure(db, data)
await config.generateVAPIDKeys(db)
return new Response('', { status: 201 })
}

Wyświetl plik

@ -139,11 +139,6 @@ CREATE TABLE IF NOT EXISTS subscriptions (
FOREIGN KEY(client_id) REFERENCES clients(id)
);
CREATE TABLE IF NOT EXISTS instance_config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE VIRTUAL TABLE IF NOT EXISTS search_fts USING fts5 (
type,
name,

Wyświetl plik

@ -17,6 +17,7 @@
"eslint": "^8.29.0",
"jest": "^29.3.1",
"jest-environment-miniflare": "^2.11.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.1",
"ts-jest": "^29.0.3",
"typescript": "^4.9.4",
@ -26,8 +27,10 @@
"scripts": {
"pretty": "npx prettier --list-different './**/*.ts'",
"test": "NODE_OPTIONS=--experimental-vm-modules yarn jest",
"lint": "npx eslint .",
"lint:be": "npx eslint backend",
"lint": "run-s lint:* --print-label",
"lint:frontend": "npm --prefix frontend run lint",
"lint:backend": "npx eslint backend",
"lint:functions": "npx eslint functions",
"build": "yarn --cwd frontend install && yarn --cwd frontend build",
"d1": "NO_D1_WARNING=true wrangler d1",
"pages": "NO_D1_WARNING=true wrangler pages",

Wyświetl plik

@ -0,0 +1,35 @@
const PROJECT_URL = 'https://github.com/cloudflare/wildebeest'
const ONE_CLICK_BASE_URL = 'https://deploy.workers.cloudflare.com'
const FIELDS = [
{
name: 'Zone ID',
secret: 'CF_ZONE_ID',
descr: 'Get your Zone ID from the Cloudflare Dashboard',
},
{
name: 'Domain',
secret: 'CF_DEPLOY_DOMAIN',
descr: 'Domain on which your instance will be running',
},
{
name: 'Instance title',
secret: 'INSTANCE_TITLE',
descr: 'Title of your instance',
},
{
name: 'Administrator Email',
secret: 'ADMIN_EMAIL',
descr: 'An Email address that can be messaged regarding inquiries or issues',
},
{
name: 'Instance description',
secret: 'INSTANCE_DESCR',
descr: 'A short, plain-text description of your instance',
},
]
const fields = FIELDS.map((x) => JSON.stringify(x))
.map((v) => `fields=${v}`)
.join('&')
const url = new URL(`/?url=${PROJECT_URL}&authed=true&${fields}`, ONE_CLICK_BASE_URL)
console.log(url.href)

Wyświetl plik

@ -0,0 +1,6 @@
import { webcrypto } from 'node:crypto'
const key = await webcrypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]);
const serverKey = await webcrypto.subtle.exportKey("jwk", key.privateKey);
console.log(JSON.stringify(serverKey));

Wyświetl plik

@ -32,6 +32,19 @@ variable "access_auth_domain" {
sensitive = true
}
variable "wd_instance_title" {
type = string
sensitive = true
}
variable "wd_admin_email" {
type = string
sensitive = true
}
variable "wd_instance_description" {
type = string
sensitive = true
}
terraform {
required_providers {
cloudflare = {
@ -81,6 +94,11 @@ resource "cloudflare_pages_project" "wildebeest_pages_project" {
DOMAIN = sensitive(trimspace(var.cloudflare_deploy_domain))
ACCESS_AUD = sensitive(cloudflare_access_application.wildebeest_access.aud)
ACCESS_AUTH_DOMAIN = sensitive(var.access_auth_domain)
INSTANCE_TITLE = var.wd_instance_title
ADMIN_EMAIL = var.wd_admin_email
INSTANCE_DESCR = var.wd_instance_description
VAPID_JWK = sensitive(file("${path.module}/vapid_jwk"))
}
kv_namespaces = {
KV_CACHE = sensitive(cloudflare_workers_kv_namespace.wildebeest_cache.id)
@ -120,15 +138,3 @@ resource "cloudflare_access_application" "wildebeest_access" {
session_duration = "168h"
auto_redirect_to_identity = false
}
resource "cloudflare_access_policy" "policy" {
application_id = cloudflare_access_application.wildebeest_access.id
account_id = var.cloudflare_account_id
name = "policy"
precedence = "1"
decision = "allow"
include {
email = ["CHANGEME@example.com"]
}
}

555
yarn.lock
Wyświetl plik

@ -1127,6 +1127,11 @@ array-union@^2.1.0:
resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
available-typed-arrays@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
babel-jest@^29.3.1:
version "29.3.1"
resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz"
@ -1297,6 +1302,14 @@ busboy@^1.6.0:
dependencies:
streamsearch "^1.1.0"
call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
dependencies:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
@ -1317,7 +1330,7 @@ caniuse-lite@^1.0.30001400:
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001436.tgz"
integrity sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg==
chalk@^2.0.0:
chalk@^2.0.0, chalk@^2.4.1:
version "2.4.2"
resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@ -1457,6 +1470,17 @@ cron-schedule@^3.0.4:
resolved "https://registry.npmjs.org/cron-schedule/-/cron-schedule-3.0.6.tgz"
integrity sha512-izfGgKyzzIyLaeb1EtZ3KbglkS6AKp9cv7LxmiyoOu+fXfol1tQDC0Cof0enVZGNtudTHW+3lfuW9ZkLQss4Wg==
cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
dependencies:
nice-try "^1.0.4"
path-key "^2.0.1"
semver "^5.5.0"
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
@ -1505,6 +1529,14 @@ deepmerge@^4.2.2:
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz"
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
define-properties@^1.1.3, define-properties@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1"
integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==
dependencies:
has-property-descriptors "^1.0.0"
object-keys "^1.1.1"
detect-libc@^2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz"
@ -1568,6 +1600,62 @@ error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"
es-abstract@^1.19.0, es-abstract@^1.20.4:
version "1.21.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.0.tgz#dd1b69ea5bfc3c27199c9753efd4de015102c252"
integrity sha512-GUGtW7eXQay0c+PRq0sGIKSdaBorfVqsCMhGHo4elP7YVqZu9nCZS4UkK4gv71gOWNMra/PaSKD3ao1oWExO0g==
dependencies:
call-bind "^1.0.2"
es-set-tostringtag "^2.0.0"
es-to-primitive "^1.2.1"
function-bind "^1.1.1"
function.prototype.name "^1.1.5"
get-intrinsic "^1.1.3"
get-symbol-description "^1.0.0"
globalthis "^1.0.3"
gopd "^1.0.1"
has "^1.0.3"
has-property-descriptors "^1.0.0"
has-proto "^1.0.1"
has-symbols "^1.0.3"
internal-slot "^1.0.4"
is-array-buffer "^3.0.0"
is-callable "^1.2.7"
is-negative-zero "^2.0.2"
is-regex "^1.1.4"
is-shared-array-buffer "^1.0.2"
is-string "^1.0.7"
is-typed-array "^1.1.10"
is-weakref "^1.0.2"
object-inspect "^1.12.2"
object-keys "^1.1.1"
object.assign "^4.1.4"
regexp.prototype.flags "^1.4.3"
safe-regex-test "^1.0.0"
string.prototype.trimend "^1.0.6"
string.prototype.trimstart "^1.0.6"
typed-array-length "^1.0.4"
unbox-primitive "^1.0.2"
which-typed-array "^1.1.9"
es-set-tostringtag@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8"
integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==
dependencies:
get-intrinsic "^1.1.3"
has "^1.0.3"
has-tostringtag "^1.0.0"
es-to-primitive@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
dependencies:
is-callable "^1.1.4"
is-date-object "^1.0.1"
is-symbol "^1.0.2"
esbuild-android-64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.51.tgz#414a087cb0de8db1e347ecca6c8320513de433db"
@ -1979,6 +2067,13 @@ flatted@^3.1.0:
resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
for-each@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
dependencies:
is-callable "^1.1.3"
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz"
@ -1999,6 +2094,21 @@ function-bind@^1.1.1:
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
function.prototype.name@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621"
integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.3"
es-abstract "^1.19.0"
functions-have-names "^1.2.2"
functions-have-names@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz"
@ -2009,6 +2119,15 @@ get-caller-file@^2.0.5:
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385"
integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==
dependencies:
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.3"
get-package-type@^0.1.0:
version "0.1.0"
resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz"
@ -2019,6 +2138,14 @@ get-stream@^6.0.0, get-stream@^6.0.1:
resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
get-symbol-description@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==
dependencies:
call-bind "^1.0.2"
get-intrinsic "^1.1.1"
github-from-package@0.0.0:
version "0.0.0"
resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz"
@ -2062,6 +2189,13 @@ globals@^13.15.0:
dependencies:
type-fest "^0.20.2"
globalthis@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf"
integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==
dependencies:
define-properties "^1.1.3"
globby@^11.1.0:
version "11.1.0"
resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz"
@ -2074,7 +2208,14 @@ globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
graceful-fs@^4.2.9:
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
dependencies:
get-intrinsic "^1.1.3"
graceful-fs@^4.1.2, graceful-fs@^4.2.9:
version "4.2.10"
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
@ -2084,6 +2225,11 @@ grapheme-splitter@^1.0.4:
resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz"
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
has-bigints@^1.0.1, has-bigints@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz"
@ -2094,6 +2240,30 @@ has-flag@^4.0.0:
resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-property-descriptors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861"
integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==
dependencies:
get-intrinsic "^1.1.1"
has-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
has-symbols@^1.0.2, has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
has-tostringtag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
dependencies:
has-symbols "^1.0.2"
has@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz"
@ -2101,6 +2271,11 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
html-escaper@^2.0.0:
version "2.0.2"
resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz"
@ -2180,11 +2355,36 @@ ini@~1.3.0:
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
internal-slot@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.4.tgz#8551e7baf74a7a6ba5f749cfb16aa60722f0d6f3"
integrity sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==
dependencies:
get-intrinsic "^1.1.3"
has "^1.0.3"
side-channel "^1.0.4"
is-array-buffer@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.1.tgz#deb1db4fcae48308d54ef2442706c0393997052a"
integrity sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==
dependencies:
call-bind "^1.0.2"
get-intrinsic "^1.1.3"
is-typed-array "^1.1.10"
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz"
integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
is-bigint@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==
dependencies:
has-bigints "^1.0.1"
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz"
@ -2192,6 +2392,19 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
is-boolean-object@^1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==
dependencies:
call-bind "^1.0.2"
has-tostringtag "^1.0.0"
is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
is-core-module@^2.9.0:
version "2.11.0"
resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz"
@ -2199,6 +2412,13 @@ is-core-module@^2.9.0:
dependencies:
has "^1.0.3"
is-date-object@^1.0.1:
version "1.0.5"
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
dependencies:
has-tostringtag "^1.0.0"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
@ -2221,6 +2441,18 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
is-negative-zero@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150"
integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==
is-number-object@^1.0.4:
version "1.0.7"
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc"
integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==
dependencies:
has-tostringtag "^1.0.0"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
@ -2231,6 +2463,21 @@ is-path-inside@^3.0.3:
resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz"
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
is-regex@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
dependencies:
call-bind "^1.0.2"
has-tostringtag "^1.0.0"
is-shared-array-buffer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79"
integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==
dependencies:
call-bind "^1.0.2"
is-stream@^2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz"
@ -2241,6 +2488,38 @@ is-stream@^3.0.0:
resolved "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz"
integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==
is-string@^1.0.5, is-string@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd"
integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==
dependencies:
has-tostringtag "^1.0.0"
is-symbol@^1.0.2, is-symbol@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
dependencies:
has-symbols "^1.0.2"
is-typed-array@^1.1.10, is-typed-array@^1.1.9:
version "1.1.10"
resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f"
integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==
dependencies:
available-typed-arrays "^1.0.5"
call-bind "^1.0.2"
for-each "^0.3.3"
gopd "^1.0.1"
has-tostringtag "^1.0.0"
is-weakref@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==
dependencies:
call-bind "^1.0.2"
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
@ -2694,6 +2973,11 @@ jsesc@^2.5.1:
resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
json-parse-better-errors@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
json-parse-even-better-errors@^2.3.0:
version "2.3.1"
resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz"
@ -2742,6 +3026,16 @@ lines-and-columns@^1.1.6:
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
load-json-file@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==
dependencies:
graceful-fs "^4.1.2"
parse-json "^4.0.0"
pify "^3.0.0"
strip-bom "^3.0.0"
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz"
@ -2804,6 +3098,11 @@ makeerror@1.0.12:
dependencies:
tmpl "1.0.5"
memorystream@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz"
@ -2916,6 +3215,11 @@ natural-compare@^1.4.0:
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-abi@^3.3.0:
version "3.30.0"
resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.30.0.tgz"
@ -2938,11 +3242,36 @@ node-releases@^2.0.6:
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz"
integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==
normalize-package-data@^2.3.2:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
dependencies:
hosted-git-info "^2.1.4"
resolve "^1.10.0"
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
npm-run-all@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba"
integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==
dependencies:
ansi-styles "^3.2.1"
chalk "^2.4.1"
cross-spawn "^6.0.5"
memorystream "^0.3.1"
minimatch "^3.0.4"
pidtree "^0.3.0"
read-pkg "^3.0.0"
shell-quote "^1.6.1"
string.prototype.padend "^3.0.0"
npm-run-path@^4.0.1:
version "4.0.1"
resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz"
@ -2967,6 +3296,26 @@ npx-import@^1.1.3:
semver "^7.3.7"
validate-npm-package-name "^4.0.0"
object-inspect@^1.12.2, object-inspect@^1.9.0:
version "1.12.2"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea"
integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==
object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
object.assign@^4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.4"
has-symbols "^1.0.3"
object-keys "^1.1.1"
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
@ -3040,6 +3389,14 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
parse-json@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==
dependencies:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
parse-json@^5.2.0:
version "5.2.0"
resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz"
@ -3065,6 +3422,11 @@ path-is-absolute@^1.0.0:
resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
path-key@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==
path-key@^3.0.0, path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
@ -3085,6 +3447,13 @@ path-to-regexp@^6.2.0:
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz"
integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==
path-type@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==
dependencies:
pify "^3.0.0"
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz"
@ -3100,6 +3469,16 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
pidtree@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a"
integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
pirates@^4.0.4:
version "4.0.5"
resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz"
@ -3190,6 +3569,15 @@ react-is@^18.0.0:
resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
read-pkg@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
integrity sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==
dependencies:
load-json-file "^4.0.0"
normalize-package-data "^2.3.2"
path-type "^3.0.0"
readable-stream@^3.1.1, readable-stream@^3.4.0:
version "3.6.0"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz"
@ -3206,6 +3594,15 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
regexp.prototype.flags@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"
integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.3"
functions-have-names "^1.2.2"
regexpp@^3.2.0:
version "3.2.0"
resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz"
@ -3238,7 +3635,7 @@ resolve.exports@^1.1.0:
resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz"
integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==
resolve@^1.20.0:
resolve@^1.10.0, resolve@^1.20.0:
version "1.22.1"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
@ -3301,6 +3698,15 @@ safe-buffer@^5.0.1, safe-buffer@~5.2.0:
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-regex-test@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295"
integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==
dependencies:
call-bind "^1.0.2"
get-intrinsic "^1.1.3"
is-regex "^1.1.4"
selfsigned@^2.0.0, selfsigned@^2.0.1:
version "2.1.1"
resolved "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz"
@ -3313,6 +3719,11 @@ semiver@^1.1.0:
resolved "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz"
integrity sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==
"semver@2 || 3 || 4 || 5", semver@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
semver@7.x, semver@^7.0.0, semver@^7.3.5, semver@^7.3.7:
version "7.3.8"
resolved "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz"
@ -3330,6 +3741,13 @@ set-cookie-parser@^2.4.8:
resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz"
integrity sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==
dependencies:
shebang-regex "^1.0.0"
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
@ -3337,16 +3755,30 @@ shebang-command@^2.0.0:
dependencies:
shebang-regex "^3.0.0"
shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==
shebang-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shell-quote@^1.7.3:
shell-quote@^1.6.1, shell-quote@^1.7.3:
version "1.7.4"
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.4.tgz#33fe15dee71ab2a81fcbd3a52106c5cfb9fb75d8"
integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==
side-channel@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
dependencies:
call-bind "^1.0.0"
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
signal-exit@^3.0.3, signal-exit@^3.0.7:
version "3.0.7"
resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz"
@ -3412,6 +3844,32 @@ spawn-command@^0.0.2-1:
resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
integrity sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==
spdx-correct@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==
dependencies:
spdx-expression-parse "^3.0.0"
spdx-license-ids "^3.0.0"
spdx-exceptions@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
spdx-expression-parse@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
dependencies:
spdx-exceptions "^2.1.0"
spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0:
version "3.0.12"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz#69077835abe2710b65f03969898b6637b505a779"
integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
@ -3451,6 +3909,33 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string.prototype.padend@^3.0.0:
version "3.1.4"
resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz#2c43bb3a89eb54b6750de5942c123d6c98dd65b6"
integrity sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.4"
es-abstract "^1.20.4"
string.prototype.trimend@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533"
integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.4"
es-abstract "^1.20.4"
string.prototype.trimstart@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4"
integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.4"
es-abstract "^1.20.4"
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
@ -3465,6 +3950,11 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1:
dependencies:
ansi-regex "^5.0.1"
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
strip-bom@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz"
@ -3633,11 +4123,30 @@ type-fest@^0.21.3:
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz"
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
typed-array-length@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb"
integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==
dependencies:
call-bind "^1.0.2"
for-each "^0.3.3"
is-typed-array "^1.1.9"
typescript@^4.9.4:
version "4.9.4"
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz"
integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==
unbox-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==
dependencies:
call-bind "^1.0.2"
has-bigints "^1.0.2"
has-symbols "^1.0.3"
which-boxed-primitive "^1.0.2"
undici@5.9.1:
version "5.9.1"
resolved "https://registry.npmjs.org/undici/-/undici-5.9.1.tgz"
@ -3677,6 +4186,14 @@ v8-to-istanbul@^9.0.1:
"@types/istanbul-lib-coverage" "^2.0.1"
convert-source-map "^1.6.0"
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
dependencies:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
validate-npm-package-name@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz"
@ -3691,6 +4208,36 @@ walker@^1.0.8:
dependencies:
makeerror "1.0.12"
which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
dependencies:
is-bigint "^1.0.1"
is-boolean-object "^1.1.0"
is-number-object "^1.0.4"
is-string "^1.0.5"
is-symbol "^1.0.3"
which-typed-array@^1.1.9:
version "1.1.9"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6"
integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==
dependencies:
available-typed-arrays "^1.0.5"
call-bind "^1.0.2"
for-each "^0.3.3"
gopd "^1.0.1"
has-tostringtag "^1.0.0"
is-typed-array "^1.1.10"
which@^1.2.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
dependencies:
isexe "^2.0.0"
which@^2.0.1:
version "2.0.2"
resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"