Compare commits
10 Commits
dcd233fe07
...
e953b2aa44
| Author | SHA1 | Date | |
|---|---|---|---|
| e953b2aa44 | |||
| 4c7a90f4d1 | |||
| dac6ab3a15 | |||
| 403e2ca922 | |||
| 3d2a92d283 | |||
| 775c0ea210 | |||
| 74be5e889a | |||
| 3259927fd1 | |||
| 4b53a8d410 | |||
| 45318dffe3 |
@@ -1,118 +0,0 @@
|
||||
|
||||
name: CVE Security Check - Trivy + OWASP
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
push:
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- '.gitea/workflows/cve-check.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
|
||||
jobs:
|
||||
trivy-scan:
|
||||
name: Trivy Vulnerability Scan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run Trivy vulnerability scan
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
scan-type: 'fs'
|
||||
scan-ref: '.'
|
||||
format: 'sarif'
|
||||
output: 'trivy-results.sarif'
|
||||
severity: 'HIGH,CRITICAL'
|
||||
|
||||
- name: Display Trivy results
|
||||
run: |
|
||||
echo "## 🔍 Trivy Scan Results" >> $GITHUB_STEP_SUMMARY
|
||||
if [ -f trivy-results.sarif ]; then
|
||||
echo "✅ Scan complété" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Upload Trivy SARIF to artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: trivy-results
|
||||
path: trivy-results.sarif
|
||||
|
||||
owasp-dependency-check:
|
||||
name: OWASP Dependency-Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run OWASP Dependency-Check
|
||||
uses: dependency-check/Dependency-Check_Action@main
|
||||
with:
|
||||
project: 'memegoat'
|
||||
path: '.'
|
||||
format: 'JSON'
|
||||
args: >
|
||||
--enableExperimental
|
||||
--suppression ./dependency-check-suppressions.xml
|
||||
|
||||
- name: Generate OWASP HTML Report
|
||||
run: |
|
||||
if [ -d "reports" ]; then
|
||||
echo "📊 Rapport OWASP généré"
|
||||
fi
|
||||
|
||||
- name: Upload OWASP reports
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: dependency-check-reports
|
||||
path: reports/
|
||||
|
||||
- name: Parse OWASP results
|
||||
run: |
|
||||
if [ -f reports/dependency-check-report.json ]; then
|
||||
echo "## 📋 OWASP Dependency-Check Results" >> $GITHUB_STEP_SUMMARY
|
||||
CRITICAL=$(jq '[.reportSchema.vulnerabilities[] | select(.severity=="CRITICAL")] | length' reports/dependency-check-report.json || echo 0)
|
||||
HIGH=$(jq '[.reportSchema.vulnerabilities[] | select(.severity=="HIGH")] | length' reports/dependency-check-report.json || echo 0)
|
||||
MEDIUM=$(jq '[.reportSchema.vulnerabilities[] | select(.severity=="MEDIUM")] | length' reports/dependency-check-report.json || echo 0)
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| 🔴 CRITICAL | $CRITICAL |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| 🟠 HIGH | $HIGH |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| 🟡 MEDIUM | $MEDIUM |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "$CRITICAL" -gt 0 ]; then
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "⚠️ **Vulnérabilités CRITICAL détectées !**" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
security-summary:
|
||||
name: Security Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: [trivy-scan, owasp-dependency-check]
|
||||
if: always()
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
|
||||
- name: Generate final report
|
||||
run: |
|
||||
echo "## 🔐 Security Audit Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ Trivy scan - Completed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ OWASP Dependency-Check - Completed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "📁 [Télécharger les rapports détaillés](artifacts)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Fail if critical vulnerabilities found
|
||||
if: failure()
|
||||
run: |
|
||||
echo "🚨 Des vulnérabilités CRITICAL ont été détectées"
|
||||
exit 1
|
||||
25
.gitea/workflows/lint-backend.yml
Normal file
25
.gitea/workflows/lint-backend.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Backend Lint
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'backend/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'backend/**'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Run lint
|
||||
run: pnpm -F @memegoat/backend lint
|
||||
25
.gitea/workflows/lint-frontend.yml
Normal file
25
.gitea/workflows/lint-frontend.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Frontend Lint
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'frontend/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'frontend/**'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Run lint
|
||||
run: pnpm -F @memegoat/frontend lint
|
||||
50
.gitignore
vendored
Normal file
50
.gitignore
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
*.tsbuildinfo
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
.nyc_output/
|
||||
*.lcov
|
||||
|
||||
# Cache
|
||||
.cache/
|
||||
.parcel-cache/
|
||||
.eslintcache
|
||||
.stylelintcache
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Package manager
|
||||
.pnpm-debug.log
|
||||
.yarn/
|
||||
303
.utlis/gitea_issues.ps1
Normal file
303
.utlis/gitea_issues.ps1
Normal file
@@ -0,0 +1,303 @@
|
||||
<#
|
||||
Gitea Issues Utility (PowerShell)
|
||||
|
||||
Prérequis:
|
||||
- PowerShell 5+ (ou 7+)
|
||||
- Fichier .env à la racine du dépôt avec:
|
||||
GITEA_BASE_URL=https://git.example.com/api/v1
|
||||
GITEA_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
GITEA_DEFAULT_OWNER=owner
|
||||
GITEA_DEFAULT_REPO=repo
|
||||
|
||||
Utilisation rapide:
|
||||
.\gitea_issues.ps1 create -Title "Titre" \
|
||||
[-Body "CorpsMarkdown" | -BodyFromStdin | -BodyFile chemin\\fichier.md | -BodyBase64 "..." ] \
|
||||
-Labels "bug,help wanted" -Assignees "user1,user2" [-Owner o] [-Repo r]
|
||||
.\gitea_issues.ps1 list [-State open|closed|all] [-Page 1] [-Limit 30] [-Owner o] [-Repo r]
|
||||
.\gitea_issues.ps1 get -Index 12 [-Owner o] [-Repo r]
|
||||
.\gitea_issues.ps1 update -Index 12 [-Title x] \
|
||||
[-Body y | -BodyFromStdin | -BodyFile chemin\\fichier.md | -BodyBase64 "..."] \
|
||||
[-Labels csv] [-Assignees csv] [-State open|closed] [-Owner o] [-Repo r]
|
||||
.\gitea_issues.ps1 close -Index 12 [-Owner o] [-Repo r]
|
||||
.\gitea_issues.ps1 open -Index 12 [-Owner o] [-Repo r]
|
||||
.\gitea_issues.ps1 comment -Index 12 \
|
||||
[-Message "TexteMarkdown" | -MessageFromStdin | -MessageFile chemin\\commentaire.md | -MessageBase64 "..."] \
|
||||
[-Owner o] [-Repo r]
|
||||
|
||||
Priorité des sources de contenu (inline prioritaire):
|
||||
Body: -Body > -BodyFromStdin > -BodyFile > -BodyBase64
|
||||
Message: -Message > -MessageFromStdin > -MessageFile > -MessageBase64
|
||||
Astuce: pour éviter les problèmes d'échappement avec les ``` en PowerShell, utilisez un here-string
|
||||
ou passez le contenu via un pipe et -BodyFromStdin / -MessageFromStdin.
|
||||
#>
|
||||
|
||||
[CmdletBinding(DefaultParameterSetName='help')]
|
||||
param(
|
||||
[Parameter(ParameterSetName='create')][switch]$create,
|
||||
[Parameter(ParameterSetName='list')][switch]$list,
|
||||
[Parameter(ParameterSetName='get')][switch]$get,
|
||||
[Parameter(ParameterSetName='update')][switch]$update,
|
||||
[Parameter(ParameterSetName='close')][switch]$close,
|
||||
[Parameter(ParameterSetName='open')][switch]$open,
|
||||
[Parameter(ParameterSetName='comment')][switch]$comment,
|
||||
|
||||
# communs
|
||||
[string]$Owner,
|
||||
[string]$Repo,
|
||||
|
||||
# create/update
|
||||
[string]$Title,
|
||||
[string]$Body,
|
||||
[switch]$BodyFromStdin,
|
||||
[string]$BodyFile,
|
||||
[string]$BodyBase64,
|
||||
[string]$Labels,
|
||||
[string]$Assignees,
|
||||
[ValidateSet('open','closed')][string]$State,
|
||||
|
||||
# list
|
||||
[ValidateSet('open','closed','all')][string]$StateList='open',
|
||||
[int]$Page=1,
|
||||
[int]$Limit=30,
|
||||
|
||||
# get/update/close/open/comment
|
||||
[int]$Index,
|
||||
|
||||
# comment
|
||||
[string]$Message,
|
||||
[switch]$MessageFromStdin,
|
||||
[string]$MessageFile,
|
||||
[string]$MessageBase64
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Capture éventuellement l'entrée du pipeline vers le script
|
||||
# (permet d'utiliser: Get-Content -Raw file | .\gitea_issues.ps1 ... -BodyFromStdin)
|
||||
try {
|
||||
$script:__pipedInput = $null
|
||||
# $input est un énumérateur; si le script reçoit un pipe, on le matérialise en texte
|
||||
$script:__pipedInput = ($input | Out-String)
|
||||
if ([string]::IsNullOrWhiteSpace($script:__pipedInput)) { $script:__pipedInput = $null }
|
||||
} catch { $script:__pipedInput = $null }
|
||||
|
||||
function Load-Env {
|
||||
# Détection robuste du dossier du script (compatibilité pwsh/host)
|
||||
$scriptDir = $null
|
||||
if ($PSScriptRoot) {
|
||||
$scriptDir = $PSScriptRoot
|
||||
} elseif ($PSCommandPath) {
|
||||
$scriptDir = Split-Path -Parent $PSCommandPath
|
||||
} elseif ($MyInvocation -and $MyInvocation.MyCommand -and $MyInvocation.MyCommand.Path) {
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
} else {
|
||||
$scriptDir = (Get-Location).Path
|
||||
}
|
||||
$repoRoot = Resolve-Path (Join-Path $scriptDir '..')
|
||||
$envFile = Join-Path $repoRoot '.env'
|
||||
if (Test-Path $envFile) {
|
||||
Get-Content $envFile | ForEach-Object {
|
||||
if ($_ -match '^[A-Za-z_][A-Za-z0-9_]*=') {
|
||||
$kv = $_ -replace '\r',''
|
||||
$name,$value = $kv.Split('=',2)
|
||||
if ($null -ne $value) {
|
||||
$value = $value.Trim()
|
||||
if ($value.StartsWith('"') -and $value.EndsWith('"') -and $value.Length -ge 2) {
|
||||
$value = $value.Substring(1, $value.Length - 2)
|
||||
}
|
||||
Set-Item -Path ("Env:{0}" -f $name) -Value $value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Ensure-UTF8 {
|
||||
try {
|
||||
# Forcer l'encodage console en UTF-8 (sans BOM)
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||
[Console]::OutputEncoding = $utf8NoBom
|
||||
[Console]::InputEncoding = $utf8NoBom
|
||||
$script:OutputEncoding = $utf8NoBom
|
||||
$global:OutputEncoding = $utf8NoBom
|
||||
# Sur Windows, fixer la page de code à 65001 si possible (silencieux)
|
||||
if ($IsWindows) { cmd /c chcp 65001 > $null 2>&1 }
|
||||
} catch { }
|
||||
}
|
||||
|
||||
function Ensure-Env {
|
||||
if (-not $env:GITEA_BASE_URL) { throw 'GITEA_BASE_URL manquant dans .env' }
|
||||
if (-not $env:GITEA_TOKEN) { throw 'GITEA_TOKEN manquant dans .env' }
|
||||
}
|
||||
|
||||
function Decode-Base64Utf8 {
|
||||
param([string]$b64)
|
||||
if (-not $b64) { return $null }
|
||||
try {
|
||||
$bytes = [Convert]::FromBase64String($b64)
|
||||
return [System.Text.Encoding]::UTF8.GetString($bytes)
|
||||
} catch {
|
||||
throw 'Chaîne Base64 invalide'
|
||||
}
|
||||
}
|
||||
|
||||
function Read-AllStdin {
|
||||
try {
|
||||
# 1) Si l'entrée provient du pipeline (capturée au niveau script)
|
||||
if ($script:__pipedInput) { return $script:__pipedInput }
|
||||
# 2) Sinon, lecture directe de STDIN (pipe vers PowerShell)
|
||||
$content = $null
|
||||
try { $content = [Console]::In.ReadToEnd() } catch { $content = $null }
|
||||
return $content
|
||||
} catch {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Resolve-Content {
|
||||
param(
|
||||
[string]$Inline,
|
||||
[switch]$FromStdin,
|
||||
[string]$FromFile,
|
||||
[string]$FromBase64,
|
||||
[string]$Kind # 'Body' | 'Message'
|
||||
)
|
||||
|
||||
# Avertissements de priorité si plusieurs sources sont présentes
|
||||
$provided = @()
|
||||
if ($Inline) { $provided += 'Inline' }
|
||||
if ($FromStdin) { $provided += 'Stdin' }
|
||||
if ($FromFile) { $provided += 'File' }
|
||||
if ($FromBase64) { $provided += 'Base64' }
|
||||
if ($provided.Count -gt 1) {
|
||||
Write-Warning ("Plusieurs sources pour {0} détectées ({1}); la priorité est Inline > Stdin > File > Base64." -f $Kind, ($provided -join ', '))
|
||||
}
|
||||
|
||||
if ($Inline) { return $Inline }
|
||||
if ($FromStdin) {
|
||||
$stdin = Read-AllStdin
|
||||
if ($stdin) { return $stdin }
|
||||
Write-Warning ("{0} demandé depuis stdin mais aucun contenu lu; on continue sur la source suivante." -f $Kind)
|
||||
}
|
||||
if ($FromFile) {
|
||||
if (-not (Test-Path -Path $FromFile -PathType Leaf)) { throw "Fichier introuvable: $FromFile" }
|
||||
return Get-Content -LiteralPath $FromFile -Raw -Encoding UTF8
|
||||
}
|
||||
if ($FromBase64) { return Decode-Base64Utf8 $FromBase64 }
|
||||
return $null
|
||||
}
|
||||
|
||||
function Resolve-Repo {
|
||||
param([string]$OwnerIn,[string]$RepoIn)
|
||||
$o = if ($OwnerIn) { $OwnerIn } elseif ($env:OWNER) { $env:OWNER } elseif ($env:GITEA_DEFAULT_OWNER) { $env:GITEA_DEFAULT_OWNER } else { '' }
|
||||
$r = if ($RepoIn) { $RepoIn } elseif ($env:REPO) { $env:REPO } elseif ($env:GITEA_DEFAULT_REPO) { $env:GITEA_DEFAULT_REPO } else { '' }
|
||||
if (-not $o -or -not $r) { throw 'OWNER/REPO non définis (utilisez .env ou paramètres -Owner/-Repo)' }
|
||||
return @($o,$r)
|
||||
}
|
||||
|
||||
function Invoke-Gitea {
|
||||
param(
|
||||
[ValidateSet('GET','POST','PATCH','PUT','DELETE')][string]$Method,
|
||||
[string]$Path,
|
||||
$BodyObj
|
||||
)
|
||||
$base = $env:GITEA_BASE_URL.TrimEnd('/')
|
||||
$uri = "$base$Path"
|
||||
$headers = @{ Authorization = "token $($env:GITEA_TOKEN)" }
|
||||
if ($PSBoundParameters.ContainsKey('BodyObj') -and $BodyObj -ne $null) {
|
||||
$json = $BodyObj | ConvertTo-Json -Depth 10
|
||||
# Encodage explicite UTF-8 pour le corps JSON
|
||||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
|
||||
$headers['Content-Type'] = 'application/json; charset=utf-8'
|
||||
return Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers -Body $bytes
|
||||
} else {
|
||||
return Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers
|
||||
}
|
||||
}
|
||||
|
||||
function Write-Help {
|
||||
Get-Content -TotalCount 40 $MyInvocation.MyCommand.Path | ForEach-Object { $_ }
|
||||
}
|
||||
|
||||
try {
|
||||
Ensure-UTF8
|
||||
Load-Env
|
||||
Ensure-Env
|
||||
|
||||
if ($create) {
|
||||
if (-not $Title) { throw 'Paramètre -Title requis' }
|
||||
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
|
||||
$Body = Resolve-Content -Inline $Body -FromStdin:$BodyFromStdin -FromFile $BodyFile -FromBase64 $BodyBase64 -Kind 'Body'
|
||||
$bodyObj = @{ title = $Title }
|
||||
if ($Body) { $bodyObj.body = $Body }
|
||||
if ($Labels) { $bodyObj.labels = ($Labels -split ',') | Where-Object { $_ -ne '' } }
|
||||
if ($Assignees) { $bodyObj.assignees = ($Assignees -split ',') | Where-Object { $_ -ne '' } }
|
||||
$res = Invoke-Gitea -Method POST -Path "/repos/$owner/$repo/issues" -BodyObj $bodyObj
|
||||
$res | ConvertTo-Json -Depth 10 | Write-Output
|
||||
return
|
||||
}
|
||||
|
||||
if ($list) {
|
||||
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
|
||||
$res = Invoke-Gitea -Method GET -Path "/repos/$owner/$repo/issues?state=$StateList&page=$Page&limit=$Limit"
|
||||
$res | ForEach-Object { [PSCustomObject]@{ number = $_.number; title = $_.title; state = $_.state; labels = ($_.labels | ForEach-Object { $_.name }) -join ',' } } | Format-Table -AutoSize
|
||||
return
|
||||
}
|
||||
|
||||
if ($get) {
|
||||
if (-not $Index) { throw 'Paramètre -Index requis' }
|
||||
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
|
||||
$res = Invoke-Gitea -Method GET -Path "/repos/$owner/$repo/issues/$Index"
|
||||
$res | ConvertTo-Json -Depth 10 | Write-Output
|
||||
return
|
||||
}
|
||||
|
||||
if ($update) {
|
||||
if (-not $Index) { throw 'Paramètre -Index requis' }
|
||||
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
|
||||
$Body = Resolve-Content -Inline $Body -FromStdin:$BodyFromStdin -FromFile $BodyFile -FromBase64 $BodyBase64 -Kind 'Body'
|
||||
$bodyObj = @{}
|
||||
if ($Title) { $bodyObj.title = $Title }
|
||||
if ($Body) { $bodyObj.body = $Body }
|
||||
if ($Labels) { $bodyObj.labels = ($Labels -split ',') | Where-Object { $_ -ne '' } }
|
||||
if ($Assignees) { $bodyObj.assignees = ($Assignees -split ',') | Where-Object { $_ -ne '' } }
|
||||
if ($State) { $bodyObj.state = $State }
|
||||
if ($bodyObj.Keys.Count -eq 0) { throw 'Aucun champ à mettre à jour' }
|
||||
$res = Invoke-Gitea -Method PATCH -Path "/repos/$owner/$repo/issues/$Index" -BodyObj $bodyObj
|
||||
$res | ConvertTo-Json -Depth 10 | Write-Output
|
||||
return
|
||||
}
|
||||
|
||||
if ($close) {
|
||||
if (-not $Index) { throw 'Paramètre -Index requis' }
|
||||
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
|
||||
$res = Invoke-Gitea -Method PATCH -Path "/repos/$owner/$repo/issues/$Index" -BodyObj @{ state = 'closed' }
|
||||
$res | ConvertTo-Json -Depth 10 | Write-Output
|
||||
return
|
||||
}
|
||||
|
||||
if ($open) {
|
||||
if (-not $Index) { throw 'Paramètre -Index requis' }
|
||||
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
|
||||
$res = Invoke-Gitea -Method PATCH -Path "/repos/$owner/$repo/issues/$Index" -BodyObj @{ state = 'open' }
|
||||
$res | ConvertTo-Json -Depth 10 | Write-Output
|
||||
return
|
||||
}
|
||||
|
||||
if ($comment) {
|
||||
if (-not $Index) { throw 'Paramètre -Index requis' }
|
||||
$Message = Resolve-Content -Inline $Message -FromStdin:$MessageFromStdin -FromFile $MessageFile -FromBase64 $MessageBase64 -Kind 'Message'
|
||||
if (-not $Message) { throw 'Paramètre -Message (ou -MessageFromStdin / -MessageFile / -MessageBase64) requis' }
|
||||
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
|
||||
$res = Invoke-Gitea -Method POST -Path "/repos/$owner/$repo/issues/$Index/comments" -BodyObj @{ body = $Message }
|
||||
$res | ConvertTo-Json -Depth 10 | Write-Output
|
||||
return
|
||||
}
|
||||
|
||||
Write-Help
|
||||
}
|
||||
catch {
|
||||
Write-Error $_
|
||||
exit 1
|
||||
}
|
||||
339
.utlis/gitea_issues.sh
Normal file
339
.utlis/gitea_issues.sh
Normal file
@@ -0,0 +1,339 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Gitea Issues Utility (Bash)
|
||||
#
|
||||
# Prérequis:
|
||||
# - curl, jq
|
||||
# - Fichier .env à la racine du projet contenant au minimum:
|
||||
# GITEA_BASE_URL=https://git.example.com/api/v1
|
||||
# GITEA_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
# GITEA_DEFAULT_OWNER=owner
|
||||
# GITEA_DEFAULT_REPO=repo
|
||||
#
|
||||
# Utilisation rapide:
|
||||
# ./gitea_issues.sh create -t "Titre" -b "Corps (Markdown)" [-F chemin/fichier.md] [-l label1,label2] [-a user1,user2]
|
||||
# ./gitea_issues.sh list [-s open|closed|all] [-p page] [-P limit]
|
||||
# ./gitea_issues.sh get <index>
|
||||
# ./gitea_issues.sh update <index> [-t titre] [-b corps (Markdown) | -F chemin/fichier.md] [-l labels] [-a assignees]
|
||||
# ./gitea_issues.sh close <index>
|
||||
# ./gitea_issues.sh open <index>
|
||||
# ./gitea_issues.sh comment <index> -m "message (Markdown)" [-M chemin/commentaire.md]
|
||||
#
|
||||
# Notes:
|
||||
# - Les scripts lisent ../.env (depuis ./.utlis/) automatiquement.
|
||||
# - Vous pouvez surcharger owner/repo via variables d'env OWNER/REPO ou flags --owner/--repo.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Assurer un environnement UTF-8 pour les E/S et curl/jq
|
||||
ensure_utf8_locale() {
|
||||
# Si LANG/LC_ALL ne sont pas déjà en UTF-8, essaye de basculer
|
||||
local want="UTF-8"
|
||||
if [[ "${LANG:-}" != *"$want"* || "${LC_ALL:-}" != *"$want"* ]]; then
|
||||
local chosen=""
|
||||
if command -v locale >/dev/null 2>&1; then
|
||||
if locale -a 2>/dev/null | grep -qi '^C\.utf8\|C\.UTF-8$'; then
|
||||
chosen="C.UTF-8"
|
||||
elif locale -a 2>/dev/null | grep -qi '^en_US\.utf8\|en_US\.UTF-8$'; then
|
||||
chosen="en_US.UTF-8"
|
||||
elif locale -a 2>/dev/null | grep -qi '^fr_FR\.utf8\|fr_FR\.UTF-8$'; then
|
||||
chosen="fr_FR.UTF-8"
|
||||
fi
|
||||
fi
|
||||
if [[ -z "$chosen" ]]; then
|
||||
# Fallback raisonnable si locale -a n'est pas dispo
|
||||
chosen="C.UTF-8"
|
||||
fi
|
||||
export LANG="$chosen"
|
||||
export LC_ALL="$chosen"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_utf8_locale
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Charger .env si présent
|
||||
ENV_FILE="$REPO_ROOT/.env"
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
# shellcheck disable=SC2046
|
||||
export $(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$ENV_FILE" | sed 's/#.*//')
|
||||
fi
|
||||
|
||||
BASE_URL="${GITEA_BASE_URL:-}"
|
||||
TOKEN="${GITEA_TOKEN:-}"
|
||||
DEFAULT_OWNER="${GITEA_DEFAULT_OWNER:-}"
|
||||
DEFAULT_REPO="${GITEA_DEFAULT_REPO:-}"
|
||||
|
||||
OWNER_OVERRIDE=""
|
||||
REPO_OVERRIDE=""
|
||||
|
||||
require_tools() {
|
||||
for t in curl jq; do
|
||||
command -v "$t" >/dev/null 2>&1 || { echo "Erreur: outil requis introuvable: $t" >&2; exit 1; }
|
||||
done
|
||||
}
|
||||
|
||||
usage() {
|
||||
sed -n '1,60p' "$0"
|
||||
}
|
||||
|
||||
fail() { echo "Erreur: $*" >&2; exit 1; }
|
||||
|
||||
ensure_env() {
|
||||
[[ -n "$BASE_URL" ]] || fail "GITEA_BASE_URL manquant (.env)"
|
||||
[[ -n "$TOKEN" ]] || fail "GITEA_TOKEN manquant (.env)"
|
||||
}
|
||||
|
||||
resolve_repo() {
|
||||
local owner repo
|
||||
owner="${OWNER_OVERRIDE:-${OWNER:-$DEFAULT_OWNER}}"
|
||||
repo="${REPO_OVERRIDE:-${REPO:-$DEFAULT_REPO}}"
|
||||
[[ -n "$owner" && -n "$repo" ]] || fail "OWNER/REPO non définis (utilisez .env ou --owner/--repo)"
|
||||
echo "$owner" "$repo"
|
||||
}
|
||||
|
||||
api() {
|
||||
local method="$1" path="$2"; shift 2
|
||||
local url="$BASE_URL$path"
|
||||
curl -sS -X "$method" \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Content-Type: application/json; charset=utf-8" \
|
||||
-H "Accept-Charset: utf-8" \
|
||||
"$url" "$@"
|
||||
}
|
||||
|
||||
api_jq_or_status() {
|
||||
# Lit la sortie JSON et vérifie erreurs Gitea
|
||||
local http_code json
|
||||
# Utilise curl avec -w pour capturer le code
|
||||
json=$(curl -sS -w '\n%{http_code}' \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Content-Type: application/json; charset=utf-8" \
|
||||
-H "Accept-Charset: utf-8" \
|
||||
"$@")
|
||||
http_code="${json##*$'\n'}"
|
||||
json="${json%$'\n'*}"
|
||||
if [[ "$http_code" -ge 400 ]]; then
|
||||
echo "$json" | jq . 2>/dev/null || true
|
||||
fail "HTTP $http_code"
|
||||
else
|
||||
echo "$json" | jq .
|
||||
fi
|
||||
}
|
||||
|
||||
parse_common_flags() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--owner) OWNER_OVERRIDE="$2"; shift 2;;
|
||||
--repo) REPO_OVERRIDE="$2"; shift 2;;
|
||||
-h|--help) usage; exit 0;;
|
||||
*) break;;
|
||||
esac
|
||||
done
|
||||
echo "$@"
|
||||
}
|
||||
|
||||
cmd_create() {
|
||||
local title="" body="" body_file="" labels="" assignees=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-t|--title) title="$2"; shift 2;;
|
||||
-b|--body) body="$2"; shift 2;;
|
||||
-F|--body-file) body_file="$2"; shift 2;;
|
||||
-l|--labels) labels="$2"; shift 2;;
|
||||
-a|--assignees) assignees="$2"; shift 2;;
|
||||
*) break;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$title" ]] || fail "Titre requis (-t)"
|
||||
read -r owner repo < <(resolve_repo)
|
||||
local data
|
||||
if [[ -n "$body" ]]; then
|
||||
# Inline prioritaire
|
||||
data=$(jq -n \
|
||||
--arg title "$title" \
|
||||
--arg body "$body" \
|
||||
--arg labels_csv "$labels" \
|
||||
--arg assignees_csv "$assignees" '
|
||||
{
|
||||
title: $title,
|
||||
body: $body
|
||||
}
|
||||
+ ( ($labels_csv|length) > 0
|
||||
then { labels: ($labels_csv | split(",") | map(select(. != ""))) } else {} end)
|
||||
+ ( ($assignees_csv|length) > 0
|
||||
then { assignees: ($assignees_csv | split(",") | map(select(. != ""))) } else {} end)
|
||||
')
|
||||
if [[ -n "$body_file" ]]; then
|
||||
echo "Avertissement: -b et -F fournis; -b (inline) sera prioritaire" >&2
|
||||
fi
|
||||
elif [[ -n "$body_file" ]]; then
|
||||
[[ -f "$body_file" ]] || fail "Fichier introuvable: $body_file"
|
||||
data=$(jq -n \
|
||||
--arg title "$title" \
|
||||
--arg labels_csv "$labels" \
|
||||
--arg assignees_csv "$assignees" \
|
||||
--rawfile body "$body_file" '
|
||||
{
|
||||
title: $title,
|
||||
body: $body
|
||||
}
|
||||
+ ( ($labels_csv|length) > 0
|
||||
then { labels: ($labels_csv | split(",") | map(select(. != ""))) } else {} end)
|
||||
+ ( ($assignees_csv|length) > 0
|
||||
then { assignees: ($assignees_csv | split(",") | map(select(. != ""))) } else {} end)
|
||||
')
|
||||
else
|
||||
data=$(jq -n \
|
||||
--arg title "$title" '
|
||||
{ title: $title }
|
||||
')
|
||||
fi
|
||||
api_jq_or_status -X POST "$BASE_URL/repos/$owner/$repo/issues" --data-binary "$data"
|
||||
}
|
||||
|
||||
cmd_list() {
|
||||
local state="open" page="1" limit="30"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-s|--state) state="$2"; shift 2;;
|
||||
-p|--page) page="$2"; shift 2;;
|
||||
-P|--limit) limit="$2"; shift 2;;
|
||||
*) break;;
|
||||
esac
|
||||
done
|
||||
read -r owner repo < <(resolve_repo)
|
||||
api GET "/repos/$owner/$repo/issues?state=$state&page=$page&limit=$limit" | jq '.[].number as $n | {number: $n, title: .title, state: .state, labels: [.labels[].name]}'
|
||||
}
|
||||
|
||||
cmd_get() {
|
||||
local index="$1"; [[ -n "$index" ]] || fail "Index requis"
|
||||
read -r owner repo < <(resolve_repo)
|
||||
api GET "/repos/$owner/$repo/issues/$index" | jq '{number, title, state, body, labels: [.labels[].name], assignees: [.assignees[].login], created_at, updated_at}'
|
||||
}
|
||||
|
||||
cmd_update() {
|
||||
local index title="" body="" body_file="" labels="" assignees="" state=""
|
||||
index="$1"; shift || true
|
||||
[[ -n "$index" ]] || fail "Index requis"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-t|--title) title="$2"; shift 2;;
|
||||
-b|--body) body="$2"; shift 2;;
|
||||
-F|--body-file) body_file="$2"; shift 2;;
|
||||
-l|--labels) labels="$2"; shift 2;;
|
||||
-a|--assignees) assignees="$2"; shift 2;;
|
||||
-s|--state) state="$2"; shift 2;;
|
||||
*) break;;
|
||||
esac
|
||||
done
|
||||
read -r owner repo < <(resolve_repo)
|
||||
local data
|
||||
if [[ -n "$body" ]]; then
|
||||
data=$(jq -n \
|
||||
--arg title "$title" \
|
||||
--arg body "$body" \
|
||||
--arg labels_csv "$labels" \
|
||||
--arg assignees_csv "$assignees" \
|
||||
--arg state "$state" '
|
||||
{}
|
||||
+ ( ($title|length)>0 then {title:$title} else {} end )
|
||||
+ {body:$body}
|
||||
+ ( ($labels_csv|length)>0 then {labels: ($labels_csv|split(",")|map(select(.!="")))} else {} end )
|
||||
+ ( ($assignees_csv|length)>0 then {assignees: ($assignees_csv|split(",")|map(select(.!="")))} else {} end )
|
||||
+ ( ($state|length)>0 then {state:$state} else {} end )
|
||||
')
|
||||
if [[ -n "$body_file" ]]; then
|
||||
echo "Avertissement: -b et -F fournis; -b (inline) sera prioritaire" >&2
|
||||
fi
|
||||
elif [[ -n "$body_file" ]]; then
|
||||
[[ -f "$body_file" ]] || fail "Fichier introuvable: $body_file"
|
||||
data=$(jq -n \
|
||||
--arg title "$title" \
|
||||
--rawfile body "$body_file" \
|
||||
--arg labels_csv "$labels" \
|
||||
--arg assignees_csv "$assignees" \
|
||||
--arg state "$state" '
|
||||
{}
|
||||
+ ( ($title|length)>0 then {title:$title} else {} end )
|
||||
+ {body:$body}
|
||||
+ ( ($labels_csv|length)>0 then {labels: ($labels_csv|split(",")|map(select(.!="")))} else {} end )
|
||||
+ ( ($assignees_csv|length)>0 then {assignees: ($assignees_csv|split(",")|map(select(.!="")))} else {} end )
|
||||
+ ( ($state|length)>0 then {state:$state} else {} end )
|
||||
')
|
||||
else
|
||||
data=$(jq -n \
|
||||
--arg title "$title" '
|
||||
{}
|
||||
+ ( ($title|length)>0 then {title:$title} else {} end )
|
||||
')
|
||||
fi
|
||||
api_jq_or_status -X PATCH "$BASE_URL/repos/$owner/$repo/issues/$index" --data-binary "$data"
|
||||
}
|
||||
|
||||
cmd_close() {
|
||||
local index="$1"; [[ -n "$index" ]] || fail "Index requis"
|
||||
cmd_update "$index" -s closed
|
||||
}
|
||||
|
||||
cmd_open() {
|
||||
local index="$1"; [[ -n "$index" ]] || fail "Index requis"
|
||||
cmd_update "$index" -s open
|
||||
}
|
||||
|
||||
cmd_comment() {
|
||||
local index message="" message_file=""
|
||||
index="$1"; shift || true
|
||||
[[ -n "$index" ]] || fail "Index requis"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-m|--message) message="$2"; shift 2;;
|
||||
-M|--message-file) message_file="$2"; shift 2;;
|
||||
*) break;;
|
||||
esac
|
||||
done
|
||||
# Inline prioritaire
|
||||
if [[ -n "$message" ]]; then
|
||||
read -r owner repo < <(resolve_repo)
|
||||
local data
|
||||
data=$(jq -n --arg body "$message" '{body:$body}')
|
||||
if [[ -n "$message_file" ]]; then
|
||||
echo "Avertissement: -m et -M fournis; -m (inline) sera prioritaire" >&2
|
||||
fi
|
||||
api_jq_or_status -X POST "$BASE_URL/repos/$owner/$repo/issues/$index/comments" --data-binary "$data"
|
||||
return
|
||||
elif [[ -n "$message_file" ]]; then
|
||||
[[ -f "$message_file" ]] || fail "Fichier introuvable: $message_file"
|
||||
read -r owner repo < <(resolve_repo)
|
||||
local data
|
||||
data=$(jq -n --rawfile body "$message_file" '{body:$body}')
|
||||
api_jq_or_status -X POST "$BASE_URL/repos/$owner/$repo/issues/$index/comments" --data-binary "$data"
|
||||
return
|
||||
else
|
||||
fail "Message requis (-m) ou fichier (-M)"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
require_tools
|
||||
ensure_env
|
||||
local args
|
||||
args=( $(parse_common_flags "$@") ) || true
|
||||
set +u
|
||||
local cmd="${args[0]}"; shift || true
|
||||
set -u
|
||||
case "$cmd" in
|
||||
create) shift; cmd_create "$@" ;;
|
||||
list) shift; cmd_list "$@" ;;
|
||||
get) shift; cmd_get "$1" ;;
|
||||
update) shift; cmd_update "$@" ;;
|
||||
close) shift; cmd_close "$1" ;;
|
||||
open) shift; cmd_open "$1" ;;
|
||||
comment) shift; cmd_comment "$@" ;;
|
||||
-h|--help|help|""|*) usage; exit 1;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -70,9 +70,6 @@ lorem ipsum
|
||||
|
||||
We love contributions! Please read our [contributing guide](CONTRIBUTING.md) before submitting a pull request. If you'd like to self-host, refer to the [self-hosting guide](SELF_HOST.md).
|
||||
|
||||
_It is the sole responsibility of the end users to respect websites' policies when scraping, searching and crawling with Firecrawl. Users are advised to adhere to the applicable privacy policies and terms of use of the websites prior to initiating any scraping activities. By default, Firecrawl respects the directives specified in the websites' robots.txt files when crawling. By utilizing Firecrawl, you expressly agree to comply with these conditions._
|
||||
|
||||
|
||||
## License Disclaimer
|
||||
|
||||
This project is primarily licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), as specified in the LICENSE file in the root directory of this repository. However, certain components of this project are licensed under the MIT License. Refer to the LICENSE files in these specific directories for details.
|
||||
|
||||
56
backend/.gitignore
vendored
Normal file
56
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
/build
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pnpm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/.nyc_output
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# temp directory
|
||||
.temp
|
||||
.tmp
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
98
backend/README.md
Normal file
98
backend/README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
<p align="center">
|
||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
|
||||
</p>
|
||||
|
||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
|
||||
</p>
|
||||
<!--[](https://opencollective.com/nest#backer)
|
||||
[](https://opencollective.com/nest#sponsor)-->
|
||||
|
||||
## Description
|
||||
|
||||
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||
|
||||
## Project setup
|
||||
|
||||
```bash
|
||||
$ pnpm install
|
||||
```
|
||||
|
||||
## Compile and run the project
|
||||
|
||||
```bash
|
||||
# development
|
||||
$ pnpm run start
|
||||
|
||||
# watch mode
|
||||
$ pnpm run start:dev
|
||||
|
||||
# production mode
|
||||
$ pnpm run start:prod
|
||||
```
|
||||
|
||||
## Run tests
|
||||
|
||||
```bash
|
||||
# unit tests
|
||||
$ pnpm run test
|
||||
|
||||
# e2e tests
|
||||
$ pnpm run test:e2e
|
||||
|
||||
# test coverage
|
||||
$ pnpm run test:cov
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
|
||||
|
||||
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
|
||||
|
||||
```bash
|
||||
$ pnpm install -g @nestjs/mau
|
||||
$ mau deploy
|
||||
```
|
||||
|
||||
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
|
||||
|
||||
## Resources
|
||||
|
||||
Check out a few resources that may come in handy when working with NestJS:
|
||||
|
||||
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
|
||||
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
|
||||
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
|
||||
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
|
||||
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
|
||||
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
|
||||
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
|
||||
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
|
||||
|
||||
## Support
|
||||
|
||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||
|
||||
## Stay in touch
|
||||
|
||||
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
|
||||
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||
|
||||
## License
|
||||
|
||||
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
|
||||
40
backend/biome.json
Normal file
40
backend/biome.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": true,
|
||||
"includes": ["**", "!node_modules", "!dist", "!build"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab",
|
||||
"indentWidth": 1
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"suspicious": {
|
||||
"noUnknownAtRules": "off"
|
||||
},
|
||||
"style": {
|
||||
"useImportType": "off"
|
||||
}
|
||||
},
|
||||
"domains": {
|
||||
"next": "recommended",
|
||||
"react": "recommended"
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "on"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
backend/nest-cli.json
Normal file
8
backend/nest-cli.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
||||
65
backend/package.json
Normal file
65
backend/package.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "@memegoat/backend",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"lint": "biome check",
|
||||
"format": "biome format --write",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^11.0.0",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@nestjs/testing": "^11.0.1",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"globals": "^16.0.0",
|
||||
"jest": "^30.0.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
"ts-loader": "^9.5.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.20.0"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
22
backend/src/app.controller.spec.ts
Normal file
22
backend/src/app.controller.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { AppController } from "./app.controller";
|
||||
import { AppService } from "./app.service";
|
||||
|
||||
describe("AppController", () => {
|
||||
let appController: AppController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const app: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe("root", () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.getHello()).toBe("Hello World!");
|
||||
});
|
||||
});
|
||||
});
|
||||
12
backend/src/app.controller.ts
Normal file
12
backend/src/app.controller.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Controller, Get } from "@nestjs/common";
|
||||
import { AppService } from "./app.service";
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
||||
10
backend/src/app.module.ts
Normal file
10
backend/src/app.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { AppController } from "./app.controller";
|
||||
import { AppService } from "./app.service";
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
export class AppModule {}
|
||||
8
backend/src/app.service.ts
Normal file
8
backend/src/app.service.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return "Hello World!";
|
||||
}
|
||||
}
|
||||
8
backend/src/main.ts
Normal file
8
backend/src/main.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import { AppModule } from "./app.module";
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
bootstrap();
|
||||
25
backend/test/app.e2e-spec.ts
Normal file
25
backend/test/app.e2e-spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { App } from "supertest/types";
|
||||
import { AppModule } from "../src/app.module";
|
||||
|
||||
describe("AppController (e2e)", () => {
|
||||
let app: INestApplication<App>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it("/ (GET)", () => {
|
||||
return request(app.getHttpServer())
|
||||
.get("/")
|
||||
.expect(200)
|
||||
.expect("Hello World!");
|
||||
});
|
||||
});
|
||||
9
backend/test/jest-e2e.json
Normal file
9
backend/test/jest-e2e.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"moduleFileExtensions": ["js", "json", "ts"],
|
||||
"rootDir": ".",
|
||||
"testEnvironment": "node",
|
||||
"testRegex": ".e2e-spec.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
}
|
||||
}
|
||||
4
backend/tsconfig.build.json
Normal file
4
backend/tsconfig.build.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
}
|
||||
25
backend/tsconfig.json
Normal file
25
backend/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"resolvePackageJsonExports": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2023",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,42 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": true,
|
||||
"includes": ["**", "!node_modules", "!.next", "!dist", "!build"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab",
|
||||
"indentWidth": 2
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"suspicious": {
|
||||
"noUnknownAtRules": "off"
|
||||
}
|
||||
},
|
||||
"domains": {
|
||||
"next": "recommended",
|
||||
"react": "recommended"
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "on"
|
||||
}
|
||||
}
|
||||
}
|
||||
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": true,
|
||||
"includes": ["**", "!node_modules", "!.next", "!dist", "!build"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab",
|
||||
"indentWidth": 1
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"suspicious": {
|
||||
"noUnknownAtRules": "off"
|
||||
}
|
||||
},
|
||||
"domains": {
|
||||
"next": "recommended",
|
||||
"react": "recommended"
|
||||
}
|
||||
},
|
||||
"css": {
|
||||
"parser": {
|
||||
"tailwindDirectives": true
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "on"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
reactCompiler: true,
|
||||
/* config options here */
|
||||
reactCompiler: true,
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
{
|
||||
"name": "@memegoat/frontend",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "biome check",
|
||||
"format": "biome format --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "16.1.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.2.0",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"babel-plugin-react-compiler": "1.0.0",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
"name": "@memegoat/frontend",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "biome check",
|
||||
"format": "biome format --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "16.1.1",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.3.11",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"babel-plugin-react-compiler": "1.0.0",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import type { Metadata } from "next";
|
||||
import {Geist, Geist_Mono, Ubuntu_Mono, Ubuntu_Sans} from "next/font/google";
|
||||
import { Ubuntu_Mono, Ubuntu_Sans } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const ubuntuSans = Ubuntu_Sans({
|
||||
variable: "--font-ubuntu-sans",
|
||||
subsets: ["latin"],
|
||||
variable: "--font-ubuntu-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const ubuntuMono = Ubuntu_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
weight: ['400', '700'],
|
||||
subsets: ["latin"],
|
||||
variable: "--font-geist-mono",
|
||||
weight: ["400", "700"],
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "MemeGoat",
|
||||
icons: "/memegoat-color.svg"
|
||||
title: "MemeGoat",
|
||||
icons: "/memegoat-color.svg",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${ubuntuSans.variable} ${ubuntuMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${ubuntuSans.variable} ${ubuntuMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<p>Hello world !</p>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<p>Hello world !</p>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
6280
pnpm-lock.yaml
generated
6280
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user