Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@ FROM node:24
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium

# Pin Chromium: the 150.0.7871.46 security update crashes on startup with SIGTRAP
# (Debian bug #1141488). Pin to the last known-good bookworm build until fixed.
ARG CHROMIUM_VERSION=147.0.7727.137-1~deb12u1

RUN apt-get update \
&& apt-get install -y chromium=${CHROMIUM_VERSION} chromium-common=${CHROMIUM_VERSION} \
&& apt-get install -y chromium \
fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
--no-install-recommends
RUN apt-get install -y gosu
Expand Down
3 changes: 3 additions & 0 deletions codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ const config: CodegenConfig = {
config: {
nonOptionalTypename: true,
dedupeFragments: true,
// Codegen v7 defaults unmapped scalars (i.e. Date) to `unknown` rather than
// `any`. Keep the old behaviour so the generated types stay compatible.
defaultScalarType: 'any',
},
schema: './schema.graphql',
generates: {
Expand Down
40,971 changes: 19,142 additions & 21,829 deletions package-lock.json

Large diffs are not rendered by default.

45 changes: 26 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
"build": "npm run gql:build && npm run build:app && npm run build:telemetry"
},
"devDependencies": {
"@grafana/faro-rollup-plugin": "^0.5.1",
"@graphql-codegen/cli": "^5.0.2",
"@graphql-codegen/near-operation-file-preset": "^3.0.0",
"@graphql-codegen/schema-ast": "^4.1.0",
"@graphql-codegen/typescript": "^4.0.7",
"@graphql-codegen/typescript-operations": "^4.5.1",
"@grafana/faro-rollup-plugin": "^0.11.0",
"@graphql-codegen/cli": "^7.2.0",
"@graphql-codegen/near-operation-file-preset": "^5.2.2",
"@graphql-codegen/schema-ast": "^6.1.0",
"@graphql-codegen/typescript": "^6.1.0",
"@graphql-codegen/typescript-operations": "^6.1.6",
"@playwright/test": "^1.56.1",
"@rsksmart/mock-web3-provider": "^1.0.2-beta1",
"@sveltejs/kit": "^2.48.4",
Expand Down Expand Up @@ -90,18 +90,17 @@
"@efstajas/versioned-parser": "^0.1.4",
"@ethereum-attestation-service/eas-sdk": "^2.7.0",
"@gelatocloud/gasless": "^0.0.12",
"@grafana/faro-web-sdk": "^1.18.1",
"@grafana/faro-web-tracing": "^1.18.1",
"@grafana/faro-web-sdk": "^2.9.0",
"@grafana/faro-web-tracing": "^2.9.0",
"@intercom/messenger-js-sdk": "^0.0.18",
"@keystatic/core": "^0.5.50",
"@novu/js": "^3.11.0",
"@octokit/rest": "^21.0.2",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.58.1",
"@opentelemetry/auto-instrumentations-node": "^0.79.0",
"@opentelemetry/sdk-metrics": "^2.0.0",
"@opentelemetry/sdk-node": "^0.208.0",
"@opentelemetry/sdk-node": "^0.221.0",
"@opentelemetry/sdk-trace-node": "^2.0.1",
"@pinata/sdk": "^2.1.0",
"@prelude.so/js-sdk": "^0.6.0",
"@resvg/resvg-js": "^2.6.2",
"@safe-global/safe-apps-provider": "^0.18.3",
Expand Down Expand Up @@ -131,34 +130,42 @@
"graphql": "^16.8.2",
"graphql-request": "^7.1.0",
"isomorphic-fetch": "^3.0.0",
"jimp": "^0.22.12",
"jimp": "^1.6.1",
"jwt-decode": "^4.0.0",
"keystatic-sveltekit": "^1.2.0",
"libphonenumber-js": "^1.12.35",
"lodash": "^4.17.21",
"lodash": "^4.18.1",
"marked": "^15.0.12",
"mdsvex": "^0.11.2",
"meilisearch": "^0.51.0",
"puppeteer": "^23.6.0",
"puppeteer": "^25.8.0",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"redis": "^4.6.14",
"sanitize-html": "^2.13.0",
"sigma": "^3.0.1",
"siwe": "^3.0.0",
"svelte-confetti": "^2.0.0",
"svelte-fast-marquee": "^0.8.0",
"svelte-fast-marquee": "^1.3.0",
"twemoji": "^14.0.2",
"zod": "^4.1.12"
},
"overrides": {
"@web3-onboard/core": {
"svelte": "$svelte"
},
"adm-zip": "^0.6.0",
"cookie": "^0.7.0",
"crypto-es": "^2.1.0",
"elliptic": "^6.6.1",
"nanoid": "^3.3.11",
"serialize-javascript": "^6.0.2",
"tmp": "^0.2.4",
"esbuild": "^0.28.2",
"joi": "^17.13.4",
"nanoid": "^3.3.18",
"serialize-javascript": "^7.1.0",
"tmp": "^0.2.6",
"undici": "^6.28.0",
"uuid": "^9.0.1 || ^11.1.1",
"ws": "^8.18.0"
"ws": "^8.21.0"
},
"type": "module"
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ export const SPLIT_RECEIVERS_TO_LIST_EDITOR_CONFIG_ORCID_RECEIVER_FRAGMENT = gql
accountId
}
orcid
owner {
address
}
orcidMetadata {
givenName
familyName
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/lib/components/splits/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ export const SPLITS_COMPONENT_ORCID_RECEIVER_FRAGMENT = gql`
accountId
}
orcid
owner {
address
}
orcidMetadata {
givenName
familyName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@
areSplitsValid: false,
chain: network.gqlName,
withdrawableBalances: [],
owner: null,
orcidMetadata: null,
};
}

Expand Down
76 changes: 76 additions & 0 deletions src/lib/utils/pinata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Minimal client for the two Pinata pinning endpoints we use.
*
* We used to use `@pinata/sdk` for this, but it ships a webpack bundle with a
* years-old copy of axios baked in, so vulnerabilities in it can't be patched
* from our side. The API surface we need is two REST calls, so we just make
* them ourselves.
*
* Server-side only — these take Pinata API credentials.
*/

const BASE_URL = 'https://api.pinata.cloud';

export interface PinataPinResponse {
IpfsHash: string;
PinSize: number;
Timestamp: string;
}

export interface PinataCredentials {
apiKey: string;
secretApiKey: string;
}

function authHeaders({ apiKey, secretApiKey }: PinataCredentials) {
return {
pinata_api_key: apiKey,
pinata_secret_api_key: secretApiKey,
};
}

async function parseResponse(res: Response, what: string): Promise<PinataPinResponse> {
if (!res.ok) {
throw new Error(`Pinata responded ${res.status} while pinning ${what}: ${await res.text()}`);
}

return res.json();
}

export async function pinJSONToIPFS(
credentials: PinataCredentials,
content: unknown,
options?: { cidVersion?: 0 | 1 },
): Promise<PinataPinResponse> {
const res = await fetch(`${BASE_URL}/pinning/pinJSONToIPFS`, {
method: 'POST',
headers: {
...authHeaders(credentials),
'Content-Type': 'application/json',
},
body: JSON.stringify({
pinataContent: content,
...(options ? { pinataOptions: options } : {}),
}),
});

return parseResponse(res, 'JSON');
}

export async function pinFileToIPFS(
credentials: PinataCredentials,
file: Blob,
{ name }: { name: string },
): Promise<PinataPinResponse> {
const formData = new FormData();
formData.append('file', file, name);
formData.append('pinataMetadata', JSON.stringify({ name }));

const res = await fetch(`${BASE_URL}/pinning/pinFileToIPFS`, {
method: 'POST',
headers: authHeaders(credentials),
body: formData,
});

return parseResponse(res, 'file');
}
8 changes: 4 additions & 4 deletions src/lib/utils/puppeteer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import puppeteer, { type Browser, type PuppeteerNodeLaunchOptions } from 'puppeteer';
import puppeteer, { type Browser, type LaunchOptions } from 'puppeteer';

/**
* Manages a singleton instance of a Puppeteer Browser, preserving
* the last known launch configuration.
*/
export class PuppeteerManager {
static browser: Browser | undefined;
static browserConfig: PuppeteerNodeLaunchOptions | undefined;
static browserConfig: LaunchOptions | undefined;

static #onBrowserExit = () => {
// eslint-disable-next-line no-console
Expand All @@ -23,10 +23,10 @@ export class PuppeteerManager {
* any existing browser instance will be closed and a new one created. Existing instances will
* be relaunched with the same configuration if their underlying process dies.
*
* @param browserConfig {PuppeteerNodeLaunchOptions} Puppeteer browser configuration.
* @param browserConfig {LaunchOptions} Puppeteer browser configuration.
* @returns {Promise<Browser>} A browser instance.
*/
static async launch(browserConfig?: PuppeteerNodeLaunchOptions): Promise<Browser> {
static async launch(browserConfig?: LaunchOptions): Promise<Browser> {
if (browserConfig && this.browser) {
await this.browser.close();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
chain
isClaimed
areSplitsValid
owner {
address
}
orcidMetadata {
givenName
familyName
Expand Down Expand Up @@ -38,7 +41,7 @@
{#if linkedIdentities.length}
<h5>Linked Identities</h5>
<ul>
{#each linkedIdentities as linkedIdentity}
{#each linkedIdentities as linkedIdentity (`${linkedIdentity.chain}-${linkedIdentity.orcid}`)}
<li>
{#if linkedIdentity.__typename === 'OrcidLinkedIdentity'}
<OrcidBadge orcid={linkedIdentity} hideType={false} />
Expand Down
7 changes: 5 additions & 2 deletions src/routes/(pages)/app/(app)/component-showcase/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,16 @@
},
};

const MOCK_PROJECT_2: Project = {
// `satisfies` rather than an annotation, so the inferred type keeps the
// nullable fields the generated fragment types require.
const MOCK_PROJECT_2 = {
__typename: 'Project',
isVisible: true,
chainData: [
{
__typename: 'ClaimedProjectData',
chain: network.gqlName,
description: null,
latestMetadataIpfsHash: '',
totalEarned: [],
withdrawableBalances: [],
Expand Down Expand Up @@ -207,7 +210,7 @@
ownerName: 'efstajas',
url: 'https://github.com/efstajas/svelte-stepper.git',
},
};
} satisfies Project;

const mockSplits: Splits = [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { Attributes } from 'graphology-types';
import type { NodeDisplayData, PartialButFor, EdgeDisplayData } from 'sigma/types';
import type { Settings } from 'sigma/settings';
import type { Project, Source } from '$lib/graphql/__generated__/base-types';
import type { Source } from '$lib/graphql/__generated__/base-types';
import type Sigma from 'sigma';
import type { EdgeLabelDrawingFunction } from 'sigma/rendering';
import { ensureResponseOk } from '$lib/utils/fetch';
import type { ProjectProfileFragment } from '../../../projects/(forges)/components/project-profile/__generated__/gql.generated';

export type LayoutMapping = { [key: string]: { x: number; y: number } };
export type NodeSelectionChangedPayload = { nodeId?: string };
Expand All @@ -16,14 +17,14 @@ export type CustomSource = Omit<Source, 'forge' | '__typename'>;
* @param {String} ownerName The owner name of the project
* @param {String} repoName The name of the project's repository
* @param {String} forge The location of the repository
* @returns {Promise<{ project: Project, description: string }>} Details of the specified project.
* @returns {Promise<{ project: ProjectProfileFragment, description: string }>} Details of the specified project.
*/
export async function fetchProject(
ownerName: string,
repoName: string,
forge: string = 'github',
): Promise<{
project: Project;
project: ProjectProfileFragment;
description: string;
newRepo: CustomSource;
correctCasingRepo: CustomSource;
Expand Down
11 changes: 6 additions & 5 deletions src/routes/api/blog-images/[variant]/[slug].jpg/+server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { error } from '@sveltejs/kit';
import { z } from 'zod';
import type { EntryGenerator } from './$types.js';
import Jimp from 'jimp';
import { Jimp, JimpMime } from 'jimp';
import { getSlug } from '$lib/utils/blog-posts.js';
import { BLOG_COVER_IMAGE_VARIANTS } from '$lib/utils/blog-cover-images';

Expand All @@ -22,11 +22,12 @@ export const GET = async ({ params }) => {

const { coverImage } = z.object({ coverImage: z.string() }).parse(post.metadata);

const jimp = (await Jimp.read(`static${coverImage}`))
.cover(dimensions.width, dimensions.height)
.quality(80);
const jimp = (await Jimp.read(`static${coverImage}`)).cover({
w: dimensions.width,
h: dimensions.height,
});

return new Response((await jimp.getBufferAsync(Jimp.MIME_JPEG)) as BodyInit, {
return new Response((await jimp.getBuffer(JimpMime.jpeg, { quality: 80 })) as BodyInit, {
headers: {
'content-type': 'image/jpeg',
},
Expand Down
8 changes: 4 additions & 4 deletions src/routes/api/custom-avatars/[cid]/+server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fetchIpfs } from '$lib/utils/ipfs.js';
import { error } from '@sveltejs/kit';
import Jimp from 'jimp';
import { Jimp, JimpMime } from 'jimp';

export const GET = async ({ params, fetch, url }) => {
const { cid } = params;
Expand All @@ -23,13 +23,13 @@ export const GET = async ({ params, fetch, url }) => {

const image = await Jimp.read(Buffer.from(blob));

if ([Jimp.MIME_JPEG as string, Jimp.MIME_PNG as string].includes(image.getMIME()) === false) {
if ([JimpMime.jpeg as string, JimpMime.png as string].includes(image.mime ?? '') === false) {
throw new Error('Invalid image format');
}

const resized = image.cover(size, size);
const resized = image.cover({ w: size, h: size });

return new Response((await resized.getBufferAsync(Jimp.MIME_PNG)) as BodyInit, {
return new Response((await resized.getBuffer(JimpMime.png)) as BodyInit, {
headers: {
'content-type': 'image/png',
'cache-control': 'public, max-age=31536000',
Expand Down
Loading
Loading