Files
twenty/packages/twenty-server/src/utils/image.ts
T
Félix MalfaitandGitHub dc93cf4c59 feat: add TypeScript Go (tsgo) for faster type checking (#17211)
## Summary

- Add `@typescript/native-preview` (tsgo) for dramatically faster type
checking on frontend projects
- Configure tsgo as default for frontend projects (twenty-front,
twenty-ui, twenty-shared, etc.)
- Keep tsc for twenty-server (faster for NestJS decorator-heavy code)
- Fix type imports for tsgo compatibility (DOMPurify, AxiosInstance)
- Remove deprecated `baseUrl` from tsconfigs where safe

## Performance Results

| Project | tsgo | tsc -b | Speedup |
|---------|------|--------|---------|
| **twenty-front** | 1.4s | 60.7s | **43x faster** |
| **twenty-server** | 2m42s | 1m10s | tsc is faster (decorators) |

tsgo excels at modern React/JSX codebases but struggles with
decorator-heavy NestJS backends, so we use the optimal checker for each.

## Usage

```bash
# Default (tsgo for frontend, tsc for backend)
nx typecheck twenty-front
nx typecheck twenty-server

# Force tsc fallback if needed
nx typecheck twenty-front --configuration=tsc

# Force tsgo on backend (slower, not recommended)
nx typecheck twenty-server --configuration=tsgo
```

## Test plan

- [x] `nx typecheck twenty-front` passes with tsgo
- [x] `nx typecheck twenty-server` passes with tsc
- [x] `nx run-many -t typecheck --exclude=fireflies` passes
- [ ] CI tests pass
2026-01-19 12:46:34 +01:00

94 lines
2.5 KiB
TypeScript

import { type AxiosError, type AxiosInstance } from 'axios';
const cropRegex = /([w|h])([0-9]+)/;
export type ShortCropSize = `${'w' | 'h'}${number}` | 'original';
export interface CropSize {
type: 'width' | 'height';
value: number;
}
export const getCropSize = (value: ShortCropSize): CropSize | null => {
const match = value.match(cropRegex);
if (value === 'original' || match === null) {
return null;
}
return {
type: match[1] === 'w' ? 'width' : 'height',
value: +match[2],
};
};
export const getImageBufferFromUrl = async (
url: string,
axiosInstance: AxiosInstance,
): Promise<Buffer> => {
if (!url || typeof url !== 'string' || url.trim().length === 0) {
throw new Error('Invalid URL provided: URL must be a non-empty string');
}
try {
const response = await axiosInstance.get(url, {
responseType: 'arraybuffer',
validateStatus: (status) => status >= 200 && status < 300,
maxRedirects: 5,
timeout: 30000,
});
if (!response.data) {
throw new Error('Received empty response from image URL');
}
const bufferLength = Buffer.isBuffer(response.data)
? response.data.length
: response.data.byteLength;
if (bufferLength === 0) {
throw new Error('Received empty response from image URL');
}
const contentType = response.headers['content-type'];
if (contentType && !contentType.startsWith('image/')) {
throw new Error(
`Invalid content type: expected image/*, got ${contentType}`,
);
}
return Buffer.from(response.data, 'binary');
} catch (error) {
const axiosError = error as AxiosError;
const axiosResponse = axiosError.response;
if (axiosResponse) {
throw new Error(
`Failed to fetch image: HTTP ${axiosResponse.status} from ${url}`,
);
}
if (error instanceof Error) {
if (
axiosError.code === 'ECONNABORTED' ||
error.message.includes('timeout')
) {
throw new Error(
`Request timeout while fetching image from URL: ${url}`,
);
}
if (
axiosError.code === 'ENOTFOUND' ||
axiosError.code === 'ECONNREFUSED' ||
error.message.includes('ENOTFOUND') ||
error.message.includes('ECONNREFUSED')
) {
throw new Error(`Failed to connect to image URL: ${url}`);
}
throw new Error(`Failed to fetch image from URL: ${error.message}`);
}
throw new Error(`Failed to fetch image from URL: ${url}`);
}
};