feat: multiple changes

Signed-off-by: Avior <git@avior.me>
This commit is contained in:
Florian Bouillon 2024-09-09 14:27:46 +02:00
parent d8f203f434
commit 3e91597dca
31 changed files with 216 additions and 223 deletions

View File

@ -19,7 +19,7 @@ jobs:
run: npm ci
- name: Run BiomeJS
run: npm run lint
run: npx biome ci .
# - uses: mongolyy/reviewdog-action-biome@v1
# with:
# github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,6 +1,6 @@
import node from '@astrojs/node'
import tailwind from '@astrojs/tailwind'
import { defineConfig } from 'astro/config'
import tailwind from "@astrojs/tailwind"
import node from "@astrojs/node"
import routing from './hooks/routing'
// const faviconHook = {
@ -18,7 +18,7 @@ import routing from './hooks/routing'
export default defineConfig({
// Use the NodeJS adapter
adapter: node({
mode: "standalone"
mode: 'standalone'
}),
// some settings to the build output
@ -66,7 +66,5 @@ export default defineConfig({
usePolling: !!(process.env.USE_POLLING ?? process.env.WSL_DISTRO_NAME)
}
}
},
})
}
})

View File

@ -24,7 +24,8 @@
"noStaticOnlyClass": "warn",
"noUselessTypeConstraint": "warn",
"noVoid": "error",
"useSimplifiedLogicExpression": "warn"
"useSimplifiedLogicExpression": "warn",
"noForEach": "off"
},
"performance": {
"noBarrelFile": "error",
@ -46,7 +47,7 @@
"options": {
"strictCase": true,
"requireAscii": true,
"filenameCases": ["camelCase", "export"]
"filenameCases": ["camelCase", "PascalCase", "export"]
}
},
"useForOf": "error",
@ -61,6 +62,9 @@
"useNodeAssertStrict": "warn",
"useNumberNamespace": "warn",
"useSingleCaseStatement": "warn"
},
"suspicious": {
"noEmptyInterface": "off"
}
}
},
@ -74,7 +78,7 @@
"semicolons": "asNeeded",
"quoteStyle": "single",
"trailingComma": "none",
"lineWidth": 200,
"lineWidth": 120,
"bracketSameLine": true
}
}

View File

@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test'
test('has title', async ({ page }) => {
await page.goto('/');
await page.goto('/')
// Expect a title "to contain" a substring.
await expect(page).toHaveTitle(/Dzeio/);
await expect(page).toHaveTitle(/Dzeio/)
})

View File

@ -53,14 +53,14 @@ async function updateRoutes(output: string, routes: Array<string>) {
let file = baseFile
file += `\n\nexport type Routes = ${routes.map((it) => `'${it}'`).join(' | ')}`
file += '\n\nexport default function route(route: Routes, query?: Record<string, string | number>) {'
file +=
'\n\nexport default function route(route: Routes, query?: Record<string, string | number>) {'
file += '\n\treturn formatRoute(route, query)'
file += '\n}\n'
await fs.writeFile(output, file)
}
/**
* format the path back to an url usable by the app
*
@ -141,7 +141,6 @@ const integration: () => AstroIntegration = () => ({
await updateRoutes(outputFile, files)
},
'astro:server:setup': async ({ server }) => {
// get the files list
const files = (await Promise.all([
await getFiles(pagesFolder).then((ev) => ev.map((it) => formatPath(pagesFolder, it))),
@ -158,7 +157,7 @@ const integration: () => AstroIntegration = () => ({
let removeExtension = true
let folder = pagesFolder
if(path.startsWith(publicFolder)) {
if (path.startsWith(publicFolder)) {
removeExtension = false
folder = publicFolder
} else if (!path.startsWith(folder)) {
@ -178,7 +177,7 @@ const integration: () => AstroIntegration = () => ({
path = path.replace(/\\/g, '/')
let removeExtension = true
let folder = pagesFolder
if(path.startsWith(publicFolder)) {
if (path.startsWith(publicFolder)) {
removeExtension = false
folder = publicFolder
}

View File

@ -12,32 +12,39 @@ export default defineConfig({
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined as any,
reporter: process.env.CI ? 'list' : [['html', {
workers: process.env.CI ? 1 : (undefined as any),
reporter: process.env.CI
? 'list'
: [
[
'html',
{
outputFolder: './playwright/report',
open: 'never'
}]],
}
]
],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
trace: 'on-first-retry'
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
use: { ...devices['Desktop Chrome'] }
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
use: { ...devices['Desktop Firefox'] }
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
use: { ...devices['Desktop Safari'] }
}
/* Test against mobile viewports. */
// {
@ -58,5 +65,5 @@ export default defineConfig({
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
]
})

View File

@ -7,12 +7,11 @@ export interface Props extends astroHTML.JSX.AnchorHTMLAttributes {
}
const classes = [
"button",
{outline: Astro.props.outline},
{ghost: Astro.props.ghost},
'button',
{ outline: Astro.props.outline },
{ ghost: Astro.props.ghost },
Astro.props.class
]
---
{'href' in Astro.props && (
<a class:list={classes} {...objectOmit(Astro.props, 'type') as any}>

View File

@ -1,12 +1,9 @@
---
import { getImage } from 'astro:assets'
import AstroUtils from '../../libs/AstroUtils'
import { objectOmit } from '@dzeio/object-util'
import AstroUtils from '../../libs/AstroUtils'
const formats = [
'avif',
'webp'
]
const formats = ['avif', 'webp']
export interface Props extends Omit<astroHTML.JSX.ImgHTMLAttributes, 'src'> {
src: ImageMetadata | string
@ -17,7 +14,7 @@ export interface Props extends Omit<astroHTML.JSX.ImgHTMLAttributes, 'src'> {
type PictureResult = {
format: 'new'
formats: Array<{format: string, img: Awaited<ReturnType<typeof getImage>>}>
formats: Array<{ format: string, img: Awaited<ReturnType<typeof getImage>> }>
src: Awaited<ReturnType<typeof getImage>>
} | {
format: 'raw'
@ -38,14 +35,25 @@ async function resolvePicture(image: ImageMetadata | string): Promise<PictureRes
}
}
const imageFormats: Array<{format: string, img: Awaited<ReturnType<typeof getImage>>}> = await Promise.all(
const imageFormats: Array<{ format: string, img: Awaited<ReturnType<typeof getImage>> }> =
await Promise.all(
formats.map(async (it) => ({
img: await getImage({src: Astro.props.src, format: it, width: Astro.props.width, height: Astro.props.height}),
img: await getImage({
src: Astro.props.src,
format: it,
width: Astro.props.width,
height: Astro.props.height
}),
format: it
}))
)
const orig = await getImage({src: Astro.props.src, format: ext, width: Astro.props.width, height: Astro.props.height})
const orig = await getImage({
src: Astro.props.src,
format: ext,
width: Astro.props.width,
height: Astro.props.height
})
return {
format: 'new',
@ -62,7 +70,6 @@ const res = await AstroUtils.wrap<Result>(async () => {
})
const props = objectOmit(Astro.props, 'src', 'srcDark', 'class')
---
{res.light.format === 'new' && (

View File

@ -6,7 +6,6 @@ interface Props extends Omit<astroHTML.JSX.InputHTMLAttributes, 'type'> {
}
const baseProps = objectOmit(Astro.props, 'label', 'block')
---
<div class:list={[{parent: Astro.props.block}]}>

View File

@ -7,7 +7,7 @@ export interface Props extends Omit<astroHTML.JSX.InputHTMLAttributes, 'type'> {
block?: boolean
suffix?: string
prefix?: string
options: Array<string | number | {title: string | number, description?: string | number | null}>
options: Array<string | number | { title: string | number, description?: string | number | null }>
}
const baseProps = objectOmit(Astro.props, 'label', 'block', 'suffix', 'prefix', 'options')

View File

@ -87,8 +87,8 @@ export function setOnTableClick(table: HTMLTableElement, fn: (row: number, cell:
table.querySelectorAll<HTMLTableCellElement>('td').forEach((it) => {
it.addEventListener('click', () => {
const row = it.parentElement as HTMLTableRowElement
const rowIdx = parseInt(row.dataset.row as string)
const cellIdx = parseInt(it.dataset.cell as string)
const rowIdx = Number.parseInt(row.dataset.row as string)
const cellIdx = Number.parseInt(it.dataset.cell as string)
fn(rowIdx, cellIdx)
})
})

View File

@ -9,10 +9,9 @@ export interface Props {
if (Astro.props.icoPath !== '/favicon.ico') {
console.warn('It is recommanded that the ICO file should be located at /favicon.ico')
}
const appleTouch = await getImage({src: Astro.props.png, width: 180, height: 180})
const appleTouch = await getImage({ src: Astro.props.png, width: 180, height: 180 })
---
<>

View File

@ -1,36 +0,0 @@
import { getImage } from 'astro:assets'
export default class Manifest {
static async create(baseImage: ImageMetadata, options: {
name: string
color?: string
images?: Array<number>
}) {
const [
i192,
i512
] = await Promise.all([
getImage({src: baseImage, format: 'png', width: 192, height: 192}),
getImage({src: baseImage, format: 'png', width: 512, height: 512})
])
return JSON.stringify({
name: options.name,
short_name: options.name,
icons: [
{
src: i192.src,
sizes: "192x192",
type: "image/png"
},
{
src: i512.src,
sizes: "512x512",
type: "image/png"
}
],
theme_color: options.color ?? "#fff",
background_color: options.color ?? "#fff",
display: "standalone"
})
}
}

View File

@ -2,8 +2,8 @@
const year = new Date().getFullYear()
export interface Props {
links?: Array<{href: string, target?: string, display: string}>
socials?: Array<{href: string, target?: string, icon: any}>
links?: Array<{ href: string, target?: string, display: string }>
socials?: Array<{ href: string, target?: string, icon: any }>
}
---

View File

@ -1,8 +1,8 @@
---
import Logo from 'assets/components/layouts/Header/logo.svg'
import Picture from 'components/global/Picture.astro'
import Button from 'components/global/Button.astro'
import { objectMap } from '@dzeio/object-util'
import Logo from 'assets/components/layouts/Header/logo.svg'
import Button from 'components/global/Button.astro'
import Picture from 'components/global/Picture.astro'
export interface Props {
right?: Record<string, string>

View File

@ -4,16 +4,19 @@ import { defineCollection, z } from 'astro:content'
// 2. Define your collection(s)
const projectsCollection = defineCollection({
type: 'content',
schema: ({ image }) => z.object({
schema: ({ image }) =>
z.object({
title: z.string(),
description: z.string().optional(),
image: image().optional(),
link: z.object({
link: z
.object({
href: z.string(),
rel: z.string().optional(),
text: z.string().optional(),
target: z.string().optional()
}).optional(),
})
.optional(),
disabled: z.string().optional(),
created: z.date().optional(),
updated: z.date().optional(),
@ -21,7 +24,6 @@ const projectsCollection = defineCollection({
})
})
// 3. Export a single `collections` object to register your collection(s)
// This key should match your collection directory name in "src/content"
export const collections = {

6
src/env.d.ts vendored
View File

@ -4,14 +4,12 @@
/**
* Environment variables declaration
*/
interface ImportMetaEnv {
}
interface ImportMetaEnv {}
interface ImportMeta {
readonly env: ImportMetaEnv;
readonly env: ImportMetaEnv
}
declare namespace App {
/**
* Middlewares variables

View File

@ -1,7 +1,7 @@
---
import Favicon from 'components/layouts/Favicon/Favicon.astro'
import type IconPNG from 'assets/layouts/Head/favicon.png'
import IconSVG from 'assets/layouts/Head/favicon.svg'
import IconPNG from 'assets/layouts/Head/favicon.png'
import Favicon from 'components/layouts/Favicon/Favicon.astro'
export interface Props {
/**
@ -29,7 +29,7 @@ export interface Props {
*/
twitter?: {
title?: string | undefined
card?: "summary" | "summary_large_image" | "app" | "player" | undefined
card?: 'summary' | 'summary_large_image' | 'app' | 'player' | undefined
site?: string | undefined
creator?: string | undefined
} | undefined
@ -46,9 +46,14 @@ export interface Props {
const props = Astro.props
const image = props.image ? Array.isArray(props.image) ? props.image : [props.image] : undefined
const image = props.image ? (Array.isArray(props.image) ? props.image : [props.image]) : undefined
const canonical = typeof Astro.props.canonical === 'string' ? Astro.props.canonical : Astro.props.canonical === false ? undefined : Astro.url.href
const canonical =
typeof Astro.props.canonical === 'string'
? Astro.props.canonical
: Astro.props.canonical === false
? undefined
: Astro.url.href
---
<!-- Charset -->

View File

@ -1,9 +1,9 @@
---
import Footer from 'components/layouts/Footer.astro'
import Base, { type Props as BaseProps } from './Base.astro'
import Header from 'components/layouts/Header.astro'
import { Mail, Phone } from 'lucide-astro'
import { Github, Linkedin } from 'simple-icons-astro'
import Base, { type Props as BaseProps } from './Base.astro'
export interface Props extends BaseProps {
/**

View File

@ -4,9 +4,7 @@ type Fn<T extends HTMLElement> = (el: Component<T>) => void | Promise<void>
* Component client side initialisation class
*/
export default class Component<T extends HTMLElement> {
private constructor(
public element: T
) {}
private constructor(public element: T) {}
public handled(value: boolean): this
public handled(): boolean

View File

@ -4,7 +4,6 @@
* Following https://developer.mozilla.org/en-US/docs/Web/HTTP/Status an extension of the RFC9110
*/
enum StatusCode {
/****************
* 1xx Requests *
****************/
@ -283,7 +282,7 @@ enum StatusCode {
/**
* Indicates that the client needs to authenticate to gain network access.
*/
NETWORK_AUTHENTIFICATION_REQUIRED,
NETWORK_AUTHENTIFICATION_REQUIRED
}
export default StatusCode

View File

@ -58,9 +58,13 @@ export default interface RFC7807 {
* @param error the error (base items are type, status, title details and instance)
* @returns
*/
export function buildRFC7807(error: RFC7807 & Record<string, any>, response: ResponseBuilder = new ResponseBuilder()): Response {
response.addHeader('Content-Type', 'application/problem+json')
export function buildRFC7807(
error: RFC7807 & Record<string, any>,
response: ResponseBuilder = new ResponseBuilder()
): Response {
return response
.addHeader('Content-Type', 'application/problem+json')
.body(JSON.stringify(error))
.status(error.status ?? 500)
return response.build()
.build()
}

View File

@ -5,7 +5,6 @@ import StatusCode from './HTTP/StatusCode'
* Simple builde to create a new Response object
*/
export default class ResponseBuilder {
public static redirect(location: string, statusCode: number = StatusCode.FOUND) {
const resp = new ResponseBuilder()
resp.addHeader('Location', location)

View File

@ -1,4 +1,4 @@
import { sequence } from "astro/middleware"
import { sequence } from 'astro/middleware'
import logger from './logger'

View File

@ -1,4 +1,4 @@
import { defineMiddleware } from "astro/middleware"
import { defineMiddleware } from 'astro/middleware'
import ResponseBuilder from 'libs/ResponseBuilder'
/**
@ -7,7 +7,10 @@ import ResponseBuilder from 'libs/ResponseBuilder'
export default defineMiddleware(async ({ request, url }, next) => {
const now = new Date()
// Date of request User-Agent 32 first chars request Method
let prefix = `\x1b[2m${now.toISOString()}\x1b[22m ${request.headers.get('user-agent')?.slice(0, 32).padEnd(32)} ${request.method.padEnd(7)}`
let prefix = `\x1b[2m${now.toISOString()}\x1b[22m ${request.headers
.get('user-agent')
?.slice(0, 32)
.padEnd(32)} ${request.method.padEnd(7)}`
const fullURL = url.toString()
const path = fullURL.slice(fullURL.indexOf(url.pathname, fullURL.indexOf(url.host)))
@ -26,14 +29,22 @@ export default defineMiddleware(async ({ request, url }, next) => {
if (import.meta.env.PROD) {
// HTTP Status time to execute path of request
console.log(`${prefix} \x1b[34m[${res.status}]\x1b[0m \x1b[2m${(new Date().getTime() - now.getTime()).toFixed(0).padStart(5, ' ')}ms\x1b[22m ${path}`)
console.log(
`${prefix} \x1b[34m[${res.status}]\x1b[0m \x1b[2m${(new Date().getTime() - now.getTime())
.toFixed(0)
.padStart(5, ' ')}ms\x1b[22m ${path}`
)
}
return res
} catch (e) {
if (import.meta.env.PROD) {
// time to execute path of request
console.log(`${prefix} \x1b[34m[500]\x1b[0m \x1b[2m${(new Date().getTime() - now.getTime()).toFixed(0).padStart(5, ' ')}ms\x1b[22m ${path}`)
console.log(
`${prefix} \x1b[34m[500]\x1b[0m \x1b[2m${(new Date().getTime() - now.getTime())
.toFixed(0)
.padStart(5, ' ')}ms\x1b[22m ${path}`
)
}
// add a full line dash to not miss it

View File

@ -4,7 +4,6 @@
* you MUST call it through the `DaoFactory` file
*/
export default abstract class Dao<Object extends { id: any } = { id: any }> {
/**
* insert a new object into the source
*
@ -19,7 +18,8 @@ export default abstract class Dao<Object extends { id: any } = { id: any }> {
* @param obj the object to create
* @returns the object with it's id filled if create or null otherwise
*/
public insert: Dao<Object>['create'] = (obj: Parameters<Dao<Object>['create']>[0]) => this.create(obj)
public insert: Dao<Object>['create'] = (obj: Parameters<Dao<Object>['create']>[0]) =>
this.create(obj)
/**
* find the list of objects having elements from the query
@ -35,7 +35,8 @@ export default abstract class Dao<Object extends { id: any } = { id: any }> {
* @param query a partial object which filter depending on the elements, if not set it will fetch everything
* @returns an array containing the list of elements that match with the query
*/
public find: Dao<Object>['findAll'] = (query: Parameters<Dao<Object>['findAll']>[0]) => this.findAll(query)
public find: Dao<Object>['findAll'] = (query: Parameters<Dao<Object>['findAll']>[0]) =>
this.findAll(query)
/**
* find an object by it's id
@ -46,7 +47,7 @@ export default abstract class Dao<Object extends { id: any } = { id: any }> {
* @returns
*/
public findById(id: Object['id']): Promise<Object | null> {
return this.findOne({id: id} as Partial<Object>)
return this.findOne({ id: id } as Partial<Object>)
}
/**
@ -91,14 +92,16 @@ export default abstract class Dao<Object extends { id: any } = { id: any }> {
if (!query) {
return null
}
return await this.update({...query, ...changes})
return await this.update({ ...query, ...changes })
}
/**
* update the remote reference of the object or create it if not found
* @param obj the object to update/insert
* @returns the object is updated/inserted or null otherwise
*/
public async upsert(object: Object | Omit<Object, 'id' | 'created' | 'updated'>): Promise<Object | null> {
public async upsert(
object: Object | Omit<Object, 'id' | 'created' | 'updated'>
): Promise<Object | null> {
if ('id' in object) {
return this.update(object)
}

View File

@ -9,8 +9,7 @@
*
* Touch this interface to define which key is linked to which Dao
*/
interface DaoItem {
}
interface DaoItem {}
/**
* Class to get any DAO
@ -48,7 +47,8 @@ export default class DaoFactory {
*/
private static initDao(item: keyof DaoItem): any | undefined {
switch (item) {
default: return undefined
default:
return undefined
}
}
}

View File

@ -1,9 +1,9 @@
---
import MainLayout from 'layouts/MainLayout.astro'
import I404 from 'assets/pages/404/404.svg'
import I404Light from 'assets/pages/404/404.light.svg'
import I404 from 'assets/pages/404/404.svg'
import Button from 'components/global/Button.astro'
import Picture from 'components/global/Picture.astro'
import MainLayout from 'layouts/MainLayout.astro'
---
<MainLayout>

View File

@ -7,7 +7,7 @@ module.exports = {
theme: {
fontFamily: {
// add your default font below
'sans': ['Font Name', ...defaultTheme.fontFamily.sans]
sans: ['Font Name', ...defaultTheme.fontFamily.sans]
},
extend: {
colors: {
@ -21,7 +21,7 @@ module.exports = {
// add a default padding to the container
DEFAULT: '1rem'
}
},
},
}
}
}
}

View File

@ -3,7 +3,7 @@ import { expect, test } from 'vitest'
// Edit an assertion and save to see HMR in action
test('Math.sqrt()', () => {
expect(Math.sqrt(4)).toBe(2);
expect(Math.sqrt(144)).toBe(12);
expect(Math.sqrt(2)).toBe(Math.SQRT2);
});
expect(Math.sqrt(4)).toBe(2)
expect(Math.sqrt(144)).toBe(12)
expect(Math.sqrt(2)).toBe(Math.SQRT2)
})

View File

@ -4,10 +4,8 @@ import { getViteConfig } from 'astro/config'
export default getViteConfig({
test: {
include: [
'./tests/**.ts'
]
include: ['./tests/**.ts']
/* for example, use global to avoid globals imports (describe, test, expect): */
// globals: true,
},
});
}
})