feature: migration dvf en TypeScript

parse.js/sync.js/lib/dotenv.js -> .ts (run via tsx), ajout tsconfig.json,
eslint config TS-enabled (parser tseslint + resolver typescript), devDeps
typescript/typescript-eslint/tsx/eslint-import-resolver-typescript,
scripts package.json en tsx + typecheck, .lintstagedrc *.ts, README en tsx.
Fix securite: escapeId sur les identifiants de colonnes dans sync.ts (au lieu
de backticks bruts) pour ne pas casser/injecter via un en-tete CSV distant.
Suppression du todo.txt audit (finding escapeId traite).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-23 10:18:08 +02:00
parent 1f34ea5d2f
commit 10af5a50e5
10 changed files with 941 additions and 70 deletions
+1
View File
@@ -1,5 +1,6 @@
{ {
"*.js": ["eslint --fix", "prettier --write"], "*.js": ["eslint --fix", "prettier --write"],
"*.ts": ["eslint --fix", "prettier --write"],
"*.css": ["prettier --write"], "*.css": ["prettier --write"],
"*.jsx": ["prettier --write"], "*.jsx": ["prettier --write"],
"*.html.twig": ["twig-cs-fixer lint --fix"], "*.html.twig": ["twig-cs-fixer lint --fix"],
+3 -3
View File
@@ -36,14 +36,14 @@ Voila un sample des données parsées [geodvf/sample.json](geodvf/sample.json)
Détecte les années manquantes en base, télécharge les csv.gz si besoin, parse et insère directement. Détecte les années manquantes en base, télécharge les csv.gz si besoin, parse et insère directement.
node sync.js # importe les années manquantes tsx sync.ts # importe les années manquantes
node sync.js 2025 # force le re-import d'une année tsx sync.ts 2025 # force le re-import d'une année
# parse (manuel) # parse (manuel)
Génère du SQL sur stdout à partir d'un csv.gz (ancien workflow). Génère du SQL sur stdout à partir d'un csv.gz (ancien workflow).
node parse.js geodvf/2025.csv.gz | gzip > geodvf/2025.sql.gz tsx parse.ts geodvf/2025.csv.gz | gzip > geodvf/2025.sql.gz
pv geodvf/2025.sql.gz | gunzip | mysql -u user -ppassword -h host database pv geodvf/2025.sql.gz | gunzip | mysql -u user -ppassword -h host database
``` ```
+36 -1
View File
@@ -5,6 +5,7 @@ import n from 'eslint-plugin-n';
import raflint from 'eslint-plugin-raflint'; import raflint from 'eslint-plugin-raflint';
import pluginimport from 'eslint-plugin-import-x'; import pluginimport from 'eslint-plugin-import-x';
import babelParser from '@babel/eslint-parser'; import babelParser from '@babel/eslint-parser';
import tseslint from 'typescript-eslint';
import js from '@eslint/js'; import js from '@eslint/js';
import globals from 'globals'; import globals from 'globals';
@@ -31,6 +32,7 @@ export default [
ignores: ['legacy/', 'dist/', 'node_modules/', 'web/', 'public/', 'coverage/', 'data/', 'vendor/', 'react/', 'js/', 'assets/', '**/old/'], ignores: ['legacy/', 'dist/', 'node_modules/', 'web/', 'public/', 'coverage/', 'data/', 'vendor/', 'react/', 'js/', 'assets/', '**/old/'],
}, },
{ {
files: ['**/*.{js,mjs,cjs,ts}'],
languageOptions: { languageOptions: {
globals: { globals: {
...globals.browser, ...globals.browser,
@@ -47,8 +49,19 @@ export default [
plugins: { plugins: {
raflint, raflint,
}, },
settings: {
'import-x/resolver': {
typescript: {
alwaysTryTypes: true,
noWarnOnMultipleProjects: true,
project: ['tsconfig.json'],
},
node: true,
},
},
rules: { rules: {
'sonarjs/no-empty-function': 0, 'sonarjs/no-empty-function': 0,
'unicorn/no-this-outside-of-class': 0,
'sonarjs/no-unused-expressions': 0, 'sonarjs/no-unused-expressions': 0,
'sonarjs/no-unsafe-unzip': 0, 'sonarjs/no-unsafe-unzip': 0,
'sonarjs/os-command': 0, 'sonarjs/os-command': 0,
@@ -133,6 +146,7 @@ export default [
'unicorn/prefer-ternary': 0, 'unicorn/prefer-ternary': 0,
'unicorn/prefer-top-level-await': 0, 'unicorn/prefer-top-level-await': 0,
'unicorn/prevent-abbreviations': 0, 'unicorn/prevent-abbreviations': 0,
'unicorn/name-replacements': 0,
'unicorn/consistent-compound-words': 0, 'unicorn/consistent-compound-words': 0,
// ne pas retirer `undefined` passe en argument (souvent requis par la signature -> casse tsc) // ne pas retirer `undefined` passe en argument (souvent requis par la signature -> casse tsc)
'unicorn/no-useless-undefined': ['error', { checkArguments: false }], 'unicorn/no-useless-undefined': ['error', { checkArguments: false }],
@@ -218,7 +232,6 @@ export default [
'wrap-iife': ['error', 'inside'], 'wrap-iife': ['error', 'inside'],
yoda: 'error', yoda: 'error',
'vars-on-top': 'error', 'vars-on-top': 'error',
radix: ['error', 'as-needed'],
'prefer-regex-literals': 'error', 'prefer-regex-literals': 'error',
'prefer-promise-reject-errors': 'error', 'prefer-promise-reject-errors': 'error',
'promise/catch-or-return': ['error', { terminationMethod: ['catch', 'finally'] }], 'promise/catch-or-return': ['error', { terminationMethod: ['catch', 'finally'] }],
@@ -285,4 +298,26 @@ export default [
'prefer-const': ['error', { destructuring: 'all' }], 'prefer-const': ['error', { destructuring: 'all' }],
}, },
}, },
{
files: ['**/*.ts'],
languageOptions: {
parser: tseslint.parser,
},
plugins: {
'@typescript-eslint': tseslint.plugin,
},
rules: {
// le compilo TS gère les globals/types : évite les faux positifs no-undef (Express, RequestInit)
'no-undef': 'off',
// la règle core ne comprend pas `import type`
'no-duplicate-imports': 'off',
},
},
{
// fichiers de déclaration générés (vite-env.d.ts) : nom imposé par la convention Vite
files: ['**/*.d.ts'],
rules: {
'unicorn/filename-case': 'off',
},
},
]; ];
View File
+843 -1
View File
@@ -19,6 +19,7 @@
"@babel/eslint-parser": "^8.0.1", "@babel/eslint-parser": "^8.0.1",
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"eslint": "^10.5.0", "eslint": "^10.5.0",
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-import-x": "^4.16.2", "eslint-plugin-import-x": "^4.16.2",
"eslint-plugin-n": "^18.1.0", "eslint-plugin-n": "^18.1.0",
"eslint-plugin-promise": "^7.3.0", "eslint-plugin-promise": "^7.3.0",
@@ -29,7 +30,10 @@
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^17.0.7", "lint-staged": "^17.0.7",
"prettier": "^3.8.4", "prettier": "^3.8.4",
"ts-api-utils": "^2.5.0" "ts-api-utils": "^2.5.0",
"tsx": "^4.22.4",
"typescript": "^6.0.3",
"typescript-eslint": "^8.61.1"
}, },
"engines": { "engines": {
"node": ">=21.0.0" "node": ">=21.0.0"
@@ -297,6 +301,448 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
"integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
"integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
"integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
"integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
"integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
"integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
"integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
"integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
"integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
"integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
"integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
"integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
"integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
"integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
"integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
"integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
"integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
"integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
"integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
"integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
"integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
"integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
"integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
"integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
"integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
"integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.9.1", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
@@ -612,6 +1058,152 @@
"undici-types": ">=7.24.0 <7.24.7" "undici-types": ">=7.24.0 <7.24.7"
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.61.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz",
"integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.61.1",
"@typescript-eslint/type-utils": "8.61.1",
"@typescript-eslint/utils": "8.61.1",
"@typescript-eslint/visitor-keys": "8.61.1",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.61.1",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.61.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz",
"integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.61.1",
"@typescript-eslint/types": "8.61.1",
"@typescript-eslint/typescript-estree": "8.61.1",
"@typescript-eslint/visitor-keys": "8.61.1",
"debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.61.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz",
"integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.61.1",
"@typescript-eslint/types": "^8.61.1",
"debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.61.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz",
"integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.61.1",
"@typescript-eslint/visitor-keys": "8.61.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.61.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz",
"integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.61.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz",
"integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.61.1",
"@typescript-eslint/typescript-estree": "8.61.1",
"@typescript-eslint/utils": "8.61.1",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.61.1", "version": "8.61.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz",
@@ -626,6 +1218,76 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.61.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz",
"integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.61.1",
"@typescript-eslint/tsconfig-utils": "8.61.1",
"@typescript-eslint/types": "8.61.1",
"@typescript-eslint/visitor-keys": "8.61.1",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.61.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz",
"integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.61.1",
"@typescript-eslint/types": "8.61.1",
"@typescript-eslint/typescript-estree": "8.61.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.61.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz",
"integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.61.1",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@unrs/resolver-binding-android-arm-eabi": { "node_modules/@unrs/resolver-binding-android-arm-eabi": {
"version": "1.12.2", "version": "1.12.2",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz",
@@ -1392,6 +2054,48 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/esbuild": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
"integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.28.1",
"@esbuild/android-arm": "0.28.1",
"@esbuild/android-arm64": "0.28.1",
"@esbuild/android-x64": "0.28.1",
"@esbuild/darwin-arm64": "0.28.1",
"@esbuild/darwin-x64": "0.28.1",
"@esbuild/freebsd-arm64": "0.28.1",
"@esbuild/freebsd-x64": "0.28.1",
"@esbuild/linux-arm": "0.28.1",
"@esbuild/linux-arm64": "0.28.1",
"@esbuild/linux-ia32": "0.28.1",
"@esbuild/linux-loong64": "0.28.1",
"@esbuild/linux-mips64el": "0.28.1",
"@esbuild/linux-ppc64": "0.28.1",
"@esbuild/linux-riscv64": "0.28.1",
"@esbuild/linux-s390x": "0.28.1",
"@esbuild/linux-x64": "0.28.1",
"@esbuild/netbsd-arm64": "0.28.1",
"@esbuild/netbsd-x64": "0.28.1",
"@esbuild/openbsd-arm64": "0.28.1",
"@esbuild/openbsd-x64": "0.28.1",
"@esbuild/openharmony-arm64": "0.28.1",
"@esbuild/sunos-x64": "0.28.1",
"@esbuild/win32-arm64": "0.28.1",
"@esbuild/win32-ia32": "0.28.1",
"@esbuild/win32-x64": "0.28.1"
}
},
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -1515,6 +2219,41 @@
} }
} }
}, },
"node_modules/eslint-import-resolver-typescript": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.5.tgz",
"integrity": "sha512-nbE5XLph6TLtGYcu/U6e6ZVXyKBhbDWK5cLGk76eJ7NdZpwf1P9EFkpt1Z01mNZNrrilsAYWKH6zUkL4reoXbw==",
"dev": true,
"license": "ISC",
"dependencies": {
"debug": "^4.4.1",
"eslint-import-context": "^0.1.8",
"get-tsconfig": "^4.10.1",
"is-bun-module": "^2.0.0",
"stable-hash-x": "^0.2.0",
"tinyglobby": "^0.2.14",
"unrs-resolver": "^1.7.11"
},
"engines": {
"node": "^16.17.0 || >=18.6.0"
},
"funding": {
"url": "https://opencollective.com/eslint-import-resolver-typescript"
},
"peerDependencies": {
"eslint": "*",
"eslint-plugin-import": "*",
"eslint-plugin-import-x": "*"
},
"peerDependenciesMeta": {
"eslint-plugin-import": {
"optional": true
},
"eslint-plugin-import-x": {
"optional": true
}
}
},
"node_modules/eslint-plugin-es-x": { "node_modules/eslint-plugin-es-x": {
"version": "7.8.0", "version": "7.8.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz",
@@ -1929,6 +2668,24 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/file-entry-cache": { "node_modules/file-entry-cache": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -1992,6 +2749,21 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/functional-red-black-tree": { "node_modules/functional-red-black-tree": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
@@ -2189,6 +2961,16 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/is-bun-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz",
"integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.7.1"
}
},
"node_modules/is-extglob": { "node_modules/is-extglob": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -3030,6 +3812,23 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "2.5.0", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
@@ -3051,6 +3850,25 @@
"license": "0BSD", "license": "0BSD",
"optional": true "optional": true
}, },
"node_modules/tsx": {
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz",
"integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.28.0"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -3078,6 +3896,30 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/typescript-eslint": {
"version": "8.61.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.1.tgz",
"integrity": "sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.61.1",
"@typescript-eslint/parser": "8.61.1",
"@typescript-eslint/typescript-estree": "8.61.1",
"@typescript-eslint/utils": "8.61.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "7.24.6", "version": "7.24.6",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
+10 -5
View File
@@ -10,11 +10,11 @@
"license": "ISC", "license": "ISC",
"author": "Raphael Piccolo", "author": "Raphael Piccolo",
"type": "module", "type": "module",
"main": "server.js", "main": "sync.ts",
"scripts": { "scripts": {
"cov": "c8 npm run test",
"prepare": "husky", "prepare": "husky",
"start": "node server.js" "start": "tsx sync.ts",
"typecheck": "tsc -b"
}, },
"dependencies": { "dependencies": {
"csv-parse": "^7.0.0", "csv-parse": "^7.0.0",
@@ -27,6 +27,7 @@
"@babel/eslint-parser": "^8.0.1", "@babel/eslint-parser": "^8.0.1",
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"eslint": "^10.5.0", "eslint": "^10.5.0",
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-import-x": "^4.16.2", "eslint-plugin-import-x": "^4.16.2",
"eslint-plugin-n": "^18.1.0", "eslint-plugin-n": "^18.1.0",
"eslint-plugin-promise": "^7.3.0", "eslint-plugin-promise": "^7.3.0",
@@ -37,12 +38,16 @@
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^17.0.7", "lint-staged": "^17.0.7",
"prettier": "^3.8.4", "prettier": "^3.8.4",
"ts-api-utils": "^2.5.0" "ts-api-utils": "^2.5.0",
"tsx": "^4.22.4",
"typescript": "^6.0.3",
"typescript-eslint": "^8.61.1"
}, },
"engines": { "engines": {
"node": ">=21.0.0" "node": ">=21.0.0"
}, },
"allowScripts": { "allowScripts": {
"unrs-resolver": true "unrs-resolver": true,
"esbuild@0.28.1": true
} }
} }
+7 -6
View File
@@ -2,7 +2,7 @@ import { parse } from 'csv-parse';
import fs from 'node:fs'; import fs from 'node:fs';
import zlib from 'node:zlib'; import zlib from 'node:zlib';
import mysql from 'mysql2'; import mysql from 'mysql2';
import './lib/dotenv.js'; import './lib/dotenv.ts';
if (process.argv.length != 3) { if (process.argv.length != 3) {
throw new Error('You should give a project dir'); throw new Error('You should give a project dir');
@@ -25,8 +25,8 @@ if (!connectionString) {
const connection = mysql.createConnection(process.env.MYSQL); const connection = mysql.createConnection(process.env.MYSQL);
// generate sql // generate sql
let columns = []; const columns: string[] = [];
const flushBatch = (batch) => { const flushBatch = (batch: unknown[][]) => {
if (batch.length === 0) return; if (batch.length === 0) return;
const values = batch.map(row => `(${row.map(val => connection.escape(val)).join(', ')})`).join(', '); const values = batch.map(row => `(${row.map(val => connection.escape(val)).join(', ')})`).join(', ');
@@ -37,18 +37,19 @@ const flushBatch = (batch) => {
// quand quelques ligne de csv sont parsées on les assemble puis on genere le sql // quand quelques ligne de csv sont parsées on les assemble puis on genere le sql
parser.on('readable', () =>{ parser.on('readable', () =>{
let record = null;
const batch = []; const batch = [];
while ((record = parser.read()) !== null) { let record = parser.read();
while (record !== null) {
// console.log(record); // console.log(record);
// get columns and values to insert and escape them for sql // get columns and values to insert and escape them for sql
if (columns.length === 0) { if (columns.length === 0) {
columns = Object.keys(record).map(col => connection.escapeId(col)); columns.push(...Object.keys(record).map(col => connection.escapeId(col)));
} }
const values = Object.values(record); const values = Object.values(record);
batch.push(values); batch.push(values);
record = parser.read();
} }
flushBatch(batch); flushBatch(batch);
+16 -13
View File
@@ -4,21 +4,22 @@ import zlib from 'node:zlib';
import { pipeline } from 'node:stream/promises'; import { pipeline } from 'node:stream/promises';
import { Writable } from 'node:stream'; import { Writable } from 'node:stream';
import mysql from 'mysql2/promise'; import mysql from 'mysql2/promise';
import './lib/dotenv.js'; import type { Connection } from 'mysql2/promise';
import './lib/dotenv.ts';
const BASE_URL = 'https://files.data.gouv.fr/geo-dvf/latest/csv';
const GEODVF_DIR = 'geodvf';
const connectionString = process.env.MYSQL; const connectionString = process.env.MYSQL;
if (!connectionString) { if (!connectionString) {
throw new Error('MYSQL environment variable not set'); throw new Error('MYSQL environment variable not set');
} }
const BASE_URL = 'https://files.data.gouv.fr/geo-dvf/latest/csv';
const GEODVF_DIR = 'geodvf';
// parse mysql connection string // parse mysql connection string
const url = new URL(connectionString); const url = new URL(connectionString);
const dbConfig = { const dbConfig = {
host: url.hostname, host: url.hostname,
port: url.port || 3306, port: url.port ? Number(url.port) : 3306,
user: url.username, user: url.username,
password: decodeURIComponent(url.password), password: decodeURIComponent(url.password),
database: url.pathname.slice(1), database: url.pathname.slice(1),
@@ -29,12 +30,12 @@ async function getYearsOnServer() {
const res = await fetch(`${BASE_URL}/`); const res = await fetch(`${BASE_URL}/`);
const html = await res.text(); const html = await res.text();
const years = Array.from(html.matchAll(/href="(\d{4})\/"/g), match => Number(match[1])); const years = Array.from(html.matchAll(/href="(\d{4})\/"/g), match => Number(match[1]));
return years.toSorted(); return years.toSorted((a, b) => a - b);
} }
async function getYearsInDb(connection) { async function getYearsInDb(connection: Connection) {
const [rows] = await connection.query('SELECT DISTINCT YEAR(date_mutation) as annee FROM dvf ORDER BY annee'); const [rows] = await connection.query('SELECT DISTINCT YEAR(date_mutation) as annee FROM dvf ORDER BY annee');
return rows.map(r => r.annee); return (rows as { annee: number }[]).map(r => r.annee);
} }
async function downloadYear(year) { async function downloadYear(year) {
@@ -55,13 +56,13 @@ async function downloadYear(year) {
return file; return file;
} }
async function importYear(connection, year, file) { async function importYear(connection: Connection, year: number, file: string) {
console.log(` Parsing et insertion de ${file} ...`); console.log(` Parsing et insertion de ${file} ...`);
let columns = null; let columns: string[] | null = null;
let inserted = 0; let inserted = 0;
const BATCH_SIZE = 5000; const BATCH_SIZE = 5000;
let batch = []; let batch: unknown[][] = [];
const flush = async () => { const flush = async () => {
if (batch.length === 0) return; if (batch.length === 0) return;
@@ -80,10 +81,12 @@ async function importYear(connection, year, file) {
const writer = new Writable({ const writer = new Writable({
objectMode: true, objectMode: true,
async write(record, _encoding, callback) { async write(record: Record<string, string>, _encoding, callback) {
try { try {
if (!columns) { if (!columns) {
columns = Object.keys(record).map(col => `\`${col}\``); // escapeId (et non un simple backtick) pour ne pas casser l'identifiant
// si un en-tête CSV distant contient un backtick — cf. parse.ts
columns = Object.keys(record).map(col => connection.escapeId(col));
} }
const values = Object.values(record).map(v => (v === '' ? null : v)); const values = Object.values(record).map(v => (v === '' ? null : v));
batch.push(values); batch.push(values);
-39
View File
@@ -1,39 +0,0 @@
# AUDIT SÉCURITÉ — dvf — 2026-06-21
# Stack: service Node (CLI one-shot d'import — pas de serveur HTTP, pas de controller, pas de service Docker)
# Méthode: revue de code statique. Findings réels uniquement (conventions stack acceptées exclues).
# Note: outil CLI lancé à la main (sync.js / parse.js). Pas de Dockerfile, pas de .drone.yml,
# absent du docker-compose.yml racine => aucune surface réseau exposée.
# .env présent sur disque MAIS git-ignored (.gitignore: .env + .env*) et NON git-tracké
# (git ls-files .env => vide). Donc pas de secret committé. Le .env contient néanmoins
# le mot de passe root MySQL de la prod flatbay.fr (mysql://root:...@flatbay.fr/dvf).
## 🔴 CRITIQUE (exploitable à distance / fuite de données / RCE)
RAS
## 🟠 ÉLEVÉ
RAS
## 🟡 MOYEN
[ ] Identifiant de colonne non échappé dans l'INSERT (injection SQL via en-tête CSV) — sync.js:69 (+85-87)
Risque: `columns` vient des clés du CSV distant, simplement entouré de backticks
(`\`${col}\``) au lieu de `connection.escapeId(col)`. Un nom de colonne contenant un
backtick casse l'identifiant et permettrait d'injecter du SQL dans `INSERT INTO dvf (...)`.
Les VALEURS sont en placeholders `?` (sûres) — seul l'en-tête est concerné. Source =
data.gouv.fr (de confiance), import CLI non exposé : exploitabilité réelle faible, mais
le projet possède déjà la bonne primitive (parse.js:48 utilise escapeId). À aligner.
Reco: `columns = Object.keys(record).map(col => connection.escapeId(col))` dans sync.js,
et valider que les colonnes attendues correspondent au schéma `dvf` (whitelist) avant insert.
## 🔵 DURCISSEMENT (faible)
[ ] .env en clair contient le root MySQL de la prod flatbay.fr — .env:1
Risque: `mysql://root:Y3458mo_gppp@flatbay.fr/dvf`. Non committé (bien), mais c'est le
compte root prod posé en clair sur le host de dev. Compromission du host = accès root BDD prod.
Reco: utiliser un user MySQL dédié `dvf` à privilèges restreints (INSERT/SELECT sur la base
`dvf` uniquement), pas root. Confirmer que ce mot de passe n'a jamais transité par un commit
(historique git du repo Gitea) et le faire tourner s'il a fuité ailleurs.
[ ] Téléchargement distant sans vérification d'intégrité — sync.js:48 / downloadYear
Risque: `fetch(url)` sur files.data.gouv.fr puis insertion directe en BDD prod, sans
contrôle de hash/taille. Source publique de confiance (URL en dur, pas d'input user => pas de
SSRF), mais un MITM/empoisonnement DNS empoisonnerait les données importées. Risque théorique.
Reco: vérifier un checksum publié si disponible, ou au minimum la taille/cohérence du fichier.
# Audit: pas de RCE/SSRF/command-injection/route exposée. Surface = un script d'import CLI.
+23
View File
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2024",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"allowJs": true,
"checkJs": false,
"noEmit": true,
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": false,
"isolatedModules": true,
"verbatimModuleSyntax": false,
"esModuleInterop": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": false,
"noImplicitAny": false,
"strictNullChecks": false,
"forceConsistentCasingInFileNames": true
},
"include": ["*.ts", "lib/**/*.ts"],
"exclude": ["node_modules", "dvf", "geodvf", "coverage", "dist"]
}