@tricoteuses/senat 2.20.21 → 2.20.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,17 +4,12 @@ import fs from "fs-extra";
4
4
  import { DateTime } from "luxon";
5
5
  import path from "path";
6
6
  import { DATA_ORIGINAL_FOLDER, DATA_TRANSFORMED_FOLDER, iterLoadSenatDossiersLegislatifsRapportUrls, iterLoadSenatDossiersLegislatifsTexteUrls, RAPPORT_FOLDER, TEXTE_FOLDER, } from "../loaders";
7
- import { parseExposeDesMotifs, parseTexte, parseTexteFromFile } from "../model/texte";
7
+ import { parseExposeDesMotifs, parseTexte, parseTexteFromFile } from "../parsers/texte";
8
8
  import { getSessionsFromStart, UNDEFINED_SESSION } from "../types/sessions";
9
9
  import { commonOptions } from "./shared/cli_helpers";
10
10
  import { ensureAndClearDir, fetchWithRetry, isOptionEmptyOrHasValue } from "./shared/util";
11
11
  const optionsDefinitions = [
12
12
  ...commonOptions,
13
- {
14
- help: "parse and convert documents into JSON (textes only for now, requires format xml)",
15
- name: "parseDocuments",
16
- type: Boolean,
17
- },
18
13
  {
19
14
  alias: "F",
20
15
  help: "formats of documents to retrieve (xml/html/pdf for textes, html/pdf for rapports); leave empty for all",
@@ -38,205 +33,142 @@ const options = commandLineArgs(optionsDefinitions);
38
33
  const textDecoder = new TextDecoder("utf8");
39
34
  const today = DateTime.now();
40
35
  function isDocumentRecent(documentDate, daysThreshold) {
41
- if (!documentDate) {
36
+ if (!documentDate)
42
37
  return false;
43
- }
44
38
  const docDate = DateTime.fromISO(documentDate);
45
- if (!docDate.isValid) {
46
- return false;
47
- }
48
- const daysDiff = today.diff(docDate, "days").days;
49
- return daysDiff <= daysThreshold;
39
+ return docDate.isValid && today.diff(docDate, "days").days <= daysThreshold;
50
40
  }
51
- async function retrieveTextes(dataDir, sessions) {
52
- const textesDir = path.join(dataDir, TEXTE_FOLDER);
53
- fs.ensureDirSync(textesDir);
54
- const originalTextesDir = path.join(textesDir, DATA_ORIGINAL_FOLDER);
55
- const transformedTextesDir = path.join(textesDir, DATA_TRANSFORMED_FOLDER);
56
- if (options["parseDocuments"]) {
57
- ensureAndClearDir(transformedTextesDir);
41
+ function shouldDownload(filePath, docDate, options) {
42
+ if (options.force)
43
+ return true;
44
+ if (!fs.existsSync(filePath))
45
+ return true;
46
+ if (options.onlyRecent !== undefined) {
47
+ return isDocumentRecent(docDate, options.onlyRecent);
48
+ }
49
+ return false;
50
+ }
51
+ async function downloadDocument(documentUrl, verbose) {
52
+ if (verbose) {
53
+ console.log(`Downloading document ${documentUrl}…`);
58
54
  }
59
- let retrievedTextesCount = 0;
60
- const texteUrlsNotFoundOrError = [];
61
- const texteUrlsParseError = [];
62
- for (const session of sessions) {
63
- for (const { item: texteMetadata } of iterLoadSenatDossiersLegislatifsTexteUrls(dataDir, session)) {
64
- const texteDir = path.join(originalTextesDir, `${texteMetadata.session ?? UNDEFINED_SESSION}`, texteMetadata.name);
65
- fs.ensureDirSync(texteDir);
66
- let exposeDesMotifsContent = null;
67
- if (texteMetadata.url_expose_des_motifs) {
68
- exposeDesMotifsContent = await downloadExposeDesMotifs(texteDir, texteMetadata.name, String(texteMetadata.url_expose_des_motifs));
69
- }
70
- if (isOptionEmptyOrHasValue(options["formats"], "xml")) {
71
- const textePath = path.join(texteDir, `${texteMetadata.name}.xml`);
72
- let texteBuffer = null;
73
- // Check if document should be skipped based on onlyRecent option
74
- const shouldSkip = !options["force"] &&
75
- fs.existsSync(textePath) &&
76
- (options["only-recent"] === undefined || !isDocumentRecent(texteMetadata.date, options["only-recent"]));
77
- if (shouldSkip) {
78
- if (!options["silent"]) {
79
- console.info(`Already downloaded texte ${textePath}…`);
80
- }
81
- }
82
- else {
83
- texteBuffer = await downloadDocument(texteMetadata.url_xml.toString());
84
- if (!texteBuffer) {
85
- texteUrlsNotFoundOrError.push(texteMetadata.url_xml);
86
- continue;
87
- }
88
- fs.writeFileSync(textePath, Buffer.from(texteBuffer));
89
- retrievedTextesCount++;
90
- }
91
- if (options["parseDocuments"]) {
92
- const parsedTexte = await parseDocument(texteMetadata.session, transformedTextesDir, textePath, texteMetadata.name, texteBuffer, exposeDesMotifsContent);
93
- if (!parsedTexte) {
94
- texteUrlsParseError.push(texteMetadata.url_xml);
95
- }
96
- }
97
- }
98
- if (isOptionEmptyOrHasValue(options["formats"], "html")) {
99
- const textePath = path.join(texteDir, `${texteMetadata.name}.html`);
100
- // Check if document should be skipped based on onlyRecent option
101
- const shouldSkip = !options["force"] &&
102
- fs.existsSync(textePath) &&
103
- (options["only-recent"] === undefined || !isDocumentRecent(texteMetadata.date, options["only-recent"]));
104
- if (shouldSkip) {
105
- if (!options["silent"]) {
106
- console.info(`Already downloaded texte ${textePath}…`);
107
- }
108
- }
109
- else {
110
- const texteBuffer = await downloadDocument(texteMetadata.url_html.toString());
111
- if (!texteBuffer) {
112
- texteUrlsNotFoundOrError.push(texteMetadata.url_html);
113
- continue;
114
- }
115
- fs.writeFileSync(textePath, Buffer.from(texteBuffer));
116
- retrievedTextesCount++;
55
+ try {
56
+ const response = await fetchWithRetry(documentUrl);
57
+ if (!response.ok) {
58
+ if (response.status === 404) {
59
+ if (verbose) {
60
+ console.warn(`Document ${documentUrl} not found`);
117
61
  }
118
62
  }
119
- if (isOptionEmptyOrHasValue(options["formats"], "pdf")) {
120
- const textePath = path.join(texteDir, `${texteMetadata.name}.pdf`);
121
- // Check if document should be skipped based on onlyRecent option
122
- const shouldSkip = !options["force"] &&
123
- fs.existsSync(textePath) &&
124
- (options["only-recent"] === undefined || !isDocumentRecent(texteMetadata.date, options["only-recent"]));
125
- if (shouldSkip) {
126
- if (!options["silent"]) {
127
- console.info(`Already downloaded texte ${textePath}…`);
128
- }
129
- }
130
- else {
131
- const texteBuffer = await downloadDocument(texteMetadata.url_pdf.toString());
132
- if (!texteBuffer) {
133
- texteUrlsNotFoundOrError.push(texteMetadata.url_pdf);
134
- continue;
135
- }
136
- fs.writeFileSync(textePath, Buffer.from(texteBuffer));
137
- retrievedTextesCount++;
63
+ else {
64
+ if (verbose) {
65
+ console.error(`An error occurred while retrieving document ${documentUrl}: ${response.status}`);
138
66
  }
139
67
  }
68
+ return null;
140
69
  }
70
+ return response.arrayBuffer();
141
71
  }
142
- if (options["verbose"]) {
143
- console.log(`${retrievedTextesCount} textes retrieved`);
144
- console.log(`${texteUrlsNotFoundOrError.length} textes failed to be retrieved with URLs ${texteUrlsNotFoundOrError.join(", ")}`);
145
- if (options["parseDocuments"]) {
146
- console.log(`${texteUrlsParseError.length} textes failed to be parsed with URLs ${texteUrlsParseError.join(", ")}`);
147
- }
72
+ catch (error) {
73
+ console.error(error.message);
74
+ return null;
148
75
  }
149
76
  }
150
- async function retrieveRapports(dataDir, sessions) {
151
- const rapportsDir = path.join(dataDir, RAPPORT_FOLDER);
152
- fs.ensureDirSync(rapportsDir);
153
- const originalRapportsDir = path.join(rapportsDir, DATA_ORIGINAL_FOLDER);
154
- let retrievedRapportsCount = 0;
155
- const rapportUrlsNotFoundOrError = [];
156
- for (const session of sessions) {
157
- for (const { item: rapportMetadata } of iterLoadSenatDossiersLegislatifsRapportUrls(dataDir, session)) {
158
- const rapportDir = path.join(originalRapportsDir, `${rapportMetadata.session ?? UNDEFINED_SESSION}`, rapportMetadata.name);
159
- fs.ensureDirSync(rapportDir);
160
- if (isOptionEmptyOrHasValue(options["formats"], "html")) {
161
- const rapportPath = path.join(rapportDir, `${rapportMetadata.name}.html`);
162
- // Check if document should be skipped based on onlyRecent option
163
- const shouldSkip = !options["force"] &&
164
- fs.existsSync(rapportPath) &&
165
- (options["only-recent"] === undefined || !isDocumentRecent(rapportMetadata.date, options["only-recent"]));
166
- if (shouldSkip) {
167
- if (!options["silent"]) {
168
- console.info(`Already downloaded rapport ${rapportPath}…`);
169
- }
170
- continue;
171
- }
172
- const rapportBuffer = await downloadDocument(rapportMetadata.url_html.toString());
173
- if (!rapportBuffer) {
174
- rapportUrlsNotFoundOrError.push(rapportMetadata.url_html);
175
- continue;
176
- }
177
- fs.writeFileSync(rapportPath, Buffer.from(rapportBuffer));
178
- retrievedRapportsCount++;
179
- }
180
- if (isOptionEmptyOrHasValue(options["formats"], "pdf")) {
181
- const rapportPath = path.join(rapportDir, `${rapportMetadata.name}.pdf`);
182
- // Check if document should be skipped based on onlyRecent option
183
- const shouldSkip = !options["force"] &&
184
- fs.existsSync(rapportPath) &&
185
- (options["only-recent"] === undefined || !isDocumentRecent(rapportMetadata.date, options["only-recent"]));
186
- if (shouldSkip) {
187
- if (!options["silent"]) {
188
- console.info(`Already downloaded rapport ${rapportPath}…`);
189
- }
190
- continue;
191
- }
192
- const rapportBuffer = await downloadDocument(rapportMetadata.url_pdf.toString());
193
- if (!rapportBuffer) {
194
- rapportUrlsNotFoundOrError.push(rapportMetadata.url_pdf);
195
- continue;
196
- }
197
- fs.writeFileSync(rapportPath, Buffer.from(rapportBuffer));
198
- retrievedRapportsCount++;
77
+ async function processDocument(url, destPath, docDate, options) {
78
+ if (!shouldDownload(destPath, docDate, options)) {
79
+ if (options.verbose)
80
+ console.info(`Already downloaded ${destPath}…`);
81
+ return { success: true, skipped: true, buffer: null };
82
+ }
83
+ const arrayBuffer = await downloadDocument(url, options.verbose);
84
+ if (!arrayBuffer) {
85
+ return { success: false, skipped: false, buffer: null };
86
+ }
87
+ const buffer = Buffer.from(arrayBuffer);
88
+ await fs.outputFile(destPath, buffer);
89
+ return { success: true, skipped: false, buffer };
90
+ }
91
+ export async function processTexte(texteMetadata, originalTextesDir, transformedTextesDir, options) {
92
+ const texteDir = path.join(originalTextesDir, `${texteMetadata.session ?? UNDEFINED_SESSION}`, texteMetadata.name);
93
+ let exposeDesMotifsContent = null;
94
+ if (texteMetadata.url_expose_des_motifs) {
95
+ const exposePath = path.join(texteDir, `${texteMetadata.name}-expose.html`);
96
+ const res = await processDocument(texteMetadata.url_expose_des_motifs.toString(), exposePath, texteMetadata.date, options);
97
+ if (res.buffer) {
98
+ exposeDesMotifsContent = res.buffer;
99
+ }
100
+ else if (res.skipped && options.parseDocuments) {
101
+ if (await fs.pathExists(exposePath)) {
102
+ exposeDesMotifsContent = await fs.readFile(exposePath);
199
103
  }
200
104
  }
201
105
  }
202
- if (options["verbose"]) {
203
- console.log(`${retrievedRapportsCount} rapports retrieved`);
204
- console.log(`${rapportUrlsNotFoundOrError.length} rapports failed with URLs ${rapportUrlsNotFoundOrError.join(", ")}`);
106
+ const formats = [
107
+ { type: "xml", url: texteMetadata.url_xml, isParseTarget: true },
108
+ { type: "html", url: texteMetadata.url_html, isParseTarget: false },
109
+ { type: "pdf", url: texteMetadata.url_pdf, isParseTarget: false },
110
+ ];
111
+ for (const format of formats) {
112
+ if (!isOptionEmptyOrHasValue(options.formats, format.type))
113
+ continue;
114
+ const destPath = path.join(texteDir, `${texteMetadata.name}.${format.type}`);
115
+ const result = await processDocument(format.url.toString(), destPath, texteMetadata.date, options);
116
+ // Specific logic: Parsing (Only applies to XML)
117
+ if (format.isParseTarget && options.parseDocuments) {
118
+ await parseDocument(texteMetadata.session, transformedTextesDir, destPath, texteMetadata.name, result.buffer, exposeDesMotifsContent, options);
119
+ }
205
120
  }
206
121
  }
207
- async function downloadExposeDesMotifs(texteDir, texteName, url) {
208
- const content = await downloadDocument(url);
209
- if (!content) {
210
- return null;
122
+ export async function processRapport(rapportMetadata, originalRapportsDir, options) {
123
+ const rapportDir = path.join(originalRapportsDir, `${rapportMetadata.session ?? UNDEFINED_SESSION}`, rapportMetadata.name);
124
+ const formats = [
125
+ { type: "html", url: rapportMetadata.url_html },
126
+ { type: "pdf", url: rapportMetadata.url_pdf },
127
+ ];
128
+ for (const format of formats) {
129
+ if (!isOptionEmptyOrHasValue(options["formats"], format.type))
130
+ continue;
131
+ const destPath = path.join(rapportDir, `${rapportMetadata.name}.${format.type}`);
132
+ await processDocument(format.url.toString(), destPath, rapportMetadata.date, options);
211
133
  }
212
- const exposeDesMotifsPath = path.join(texteDir, `${texteName}-expose.html`);
213
- fs.writeFileSync(exposeDesMotifsPath, Buffer.from(content));
214
- return content;
215
134
  }
216
- async function downloadDocument(documentUrl) {
217
- if (!options["silent"]) {
218
- console.log(`Downloading document ${documentUrl}…`);
135
+ async function retrieveTextes(dataDir, sessions) {
136
+ const originalTextesDir = path.join(dataDir, TEXTE_FOLDER, DATA_ORIGINAL_FOLDER);
137
+ const transformedTextesDir = path.join(dataDir, TEXTE_FOLDER, DATA_TRANSFORMED_FOLDER);
138
+ if (options["parseDocuments"]) {
139
+ ensureAndClearDir(transformedTextesDir);
219
140
  }
220
- try {
221
- const response = await fetchWithRetry(documentUrl);
222
- if (!response.ok) {
223
- if (response.status === 404) {
224
- console.warn(`Texte ${documentUrl} not found`);
225
- }
226
- else {
227
- console.error(`An error occurred while retrieving document ${documentUrl}: ${response.status}`);
228
- }
229
- return null;
141
+ const dlOptions = {
142
+ force: options["force"],
143
+ silent: options["silent"],
144
+ verbose: options["verbose"],
145
+ onlyRecent: options["only-recent"],
146
+ formats: options["formats"],
147
+ parseDocuments: options["parseDocuments"],
148
+ };
149
+ for (const session of sessions) {
150
+ for (const { item: texteMetadata } of iterLoadSenatDossiersLegislatifsTexteUrls(dataDir, session)) {
151
+ await processTexte(texteMetadata, originalTextesDir, transformedTextesDir, dlOptions);
230
152
  }
231
- return response.arrayBuffer();
232
153
  }
233
- catch (error) {
234
- console.error(error.message);
235
- return null;
154
+ }
155
+ async function retrieveRapports(dataDir, sessions) {
156
+ const originalRapportsDir = path.join(dataDir, RAPPORT_FOLDER, DATA_ORIGINAL_FOLDER);
157
+ const dlOptions = {
158
+ force: options["force"],
159
+ silent: options["silent"],
160
+ verbose: options["verbose"],
161
+ onlyRecent: options["only-recent"],
162
+ formats: options["formats"],
163
+ };
164
+ for (const session of sessions) {
165
+ for (const { item: rapportMetadata } of iterLoadSenatDossiersLegislatifsRapportUrls(dataDir, session)) {
166
+ await processRapport(rapportMetadata, originalRapportsDir, dlOptions);
167
+ }
236
168
  }
237
169
  }
238
- async function parseDocument(session, transformedTextesDir, textePath, texteName, texteBuffer, exposeDesMotifs = null) {
239
- if (!options["silent"]) {
170
+ async function parseDocument(session, transformedTextesDir, textePath, texteName, texteBuffer, exposeDesMotifs = null, options = {}) {
171
+ if (options.verbose) {
240
172
  console.log(`Parsing texte ${textePath}…`);
241
173
  }
242
174
  let parsedTexte;
@@ -247,19 +179,17 @@ async function parseDocument(session, transformedTextesDir, textePath, texteName
247
179
  else {
248
180
  parsedTexte = await parseTexteFromFile(textePath);
249
181
  }
250
- if (!parsedTexte) {
182
+ if (!parsedTexte)
251
183
  return null;
252
- }
253
184
  if (exposeDesMotifs) {
254
- if (!options["silent"]) {
185
+ if (options.verbose) {
255
186
  console.log("Parsing exposé des motifs…");
256
187
  }
257
188
  const exposeDesMotifsHtml = textDecoder.decode(exposeDesMotifs);
258
189
  parsedTexte.exposeDesMotifs = parseExposeDesMotifs(exposeDesMotifsHtml);
259
190
  }
260
191
  const transformedTexteDir = path.join(transformedTextesDir, `${session ?? UNDEFINED_SESSION}`, texteName);
261
- fs.ensureDirSync(transformedTexteDir);
262
- fs.writeJSONSync(path.join(transformedTexteDir, `${texteName}.json`), parsedTexte, { spaces: 2 });
192
+ await fs.outputJSON(path.join(transformedTexteDir, `${texteName}.json`), parsedTexte, { spaces: 2 });
263
193
  return parsedTexte;
264
194
  }
265
195
  async function main() {
@@ -277,9 +207,11 @@ async function main() {
277
207
  console.timeEnd("documents processing time");
278
208
  }
279
209
  }
280
- main()
281
- .then(() => process.exit(0))
282
- .catch((error) => {
283
- console.log(error);
284
- process.exit(1);
285
- });
210
+ if (process.argv[1].endsWith("retrieve_documents.ts")) {
211
+ main()
212
+ .then(() => process.exit(0))
213
+ .catch((error) => {
214
+ console.log(error);
215
+ process.exit(1);
216
+ });
217
+ }
@@ -64,6 +64,16 @@ export declare const pullOption: {
64
64
  name: string;
65
65
  type: BooleanConstructor;
66
66
  };
67
+ export declare const fetchDocumentsOption: {
68
+ help: string;
69
+ name: string;
70
+ type: BooleanConstructor;
71
+ };
72
+ export declare const parseDocumentsOption: {
73
+ help: string;
74
+ name: string;
75
+ type: BooleanConstructor;
76
+ };
67
77
  export declare const commonOptions: ({
68
78
  defaultOption: boolean;
69
79
  help: string;
@@ -64,6 +64,16 @@ export const pullOption = {
64
64
  name: "pull",
65
65
  type: Boolean,
66
66
  };
67
+ export const fetchDocumentsOption = {
68
+ help: "download documents",
69
+ name: "fetchDocuments",
70
+ type: Boolean,
71
+ };
72
+ export const parseDocumentsOption = {
73
+ help: "parse documents",
74
+ name: "parseDocuments",
75
+ type: Boolean,
76
+ };
67
77
  export const commonOptions = [
68
78
  categoriesOption,
69
79
  dataDirDefaultOption,
@@ -76,4 +86,6 @@ export const commonOptions = [
76
86
  commitOption,
77
87
  remoteOption,
78
88
  pullOption,
89
+ fetchDocumentsOption,
90
+ parseDocumentsOption,
79
91
  ];
@@ -1,29 +1,18 @@
1
- import { iterLoadSenatScrutins } from "../loaders";
1
+ import { iterLoadSenatAmendements, iterLoadSenatDossiersLegislatifs } from "../loaders";
2
2
  import commandLineArgs from "command-line-args";
3
3
  import { dataDirDefaultOption } from "./shared/cli_helpers";
4
4
  const optionsDefinitions = [dataDirDefaultOption];
5
5
  const options = commandLineArgs(optionsDefinitions);
6
- const noValidation = false;
7
6
  const session = 2024;
8
- const s = new Set();
9
- for (const { item: scrutin } of iterLoadSenatScrutins(options["dataDir"], session, { noValidation: noValidation })) {
10
- s.add(scrutin["lecture_libelle"]);
7
+ const sinceCommit = undefined;
8
+ for (const { item: amendement, filePathFromDataset } of iterLoadSenatAmendements(options["dataDir"], session, {
9
+ log: true,
10
+ sinceCommit: sinceCommit,
11
+ })) {
12
+ console.log(amendement["numero"]);
11
13
  }
12
- console.log(s);
13
- /*
14
- for (const { item: amendement } of iterLoadSenatAmendements(
15
- options["dataDir"],
16
- session,
17
- { noValidation: noValidation },
18
- )) {
19
- console.log(amendement["numero"])
14
+ for (const { item: dossierLegislatif } of iterLoadSenatDossiersLegislatifs(options["dataDir"], session, {
15
+ sinceCommit: sinceCommit,
16
+ })) {
17
+ console.log(dossierLegislatif["numero"]);
20
18
  }
21
-
22
- for (const { item: dossierLegislatif } of iterLoadSenatDossiersLegislatifs(
23
- options["dataDir"],
24
- session,
25
- { noValidation: noValidation },
26
- )) {
27
- console.log(dossierLegislatif["numero"])
28
- }
29
- */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tricoteuses/senat",
3
- "version": "2.20.21",
3
+ "version": "2.20.23",
4
4
  "description": "Handle French Sénat's open data",
5
5
  "keywords": [
6
6
  "France",
@@ -52,7 +52,6 @@
52
52
  "data:retrieve_open_data": "tsx src/scripts/retrieve_open_data.ts --all",
53
53
  "data:retrieve_senateurs_photos": "tsx src/scripts/retrieve_senateurs_photos.ts --fetch",
54
54
  "data:retrieve_videos": "tsx src/scripts/retrieve_videos.ts",
55
- "data:parse_textes_lois": "tsx src/scripts/parse_textes.ts",
56
55
  "prepare": "npm run build",
57
56
  "prepublishOnly": "npm run build",
58
57
  "prettier": "prettier --write 'src/**/*.ts' 'tests/**/*.test.ts'",
@@ -65,17 +64,17 @@
65
64
  "cheerio": "^1.1.2",
66
65
  "command-line-args": "^6.0.1",
67
66
  "dotenv": "^17.2.3",
68
- "fast-xml-parser": "^5.3.2",
69
- "fs-extra": "^11.3.2",
67
+ "fast-xml-parser": "^5.3.3",
68
+ "fs-extra": "^11.3.3",
70
69
  "jsdom": "^27.2.0",
71
- "kysely": "^0.28.8",
70
+ "kysely": "^0.28.9",
72
71
  "luxon": "^3.7.2",
73
72
  "node-stream-zip": "^1.8.2",
74
73
  "p-limit": "^7.2.0",
75
74
  "pg": "^8.13.1",
76
75
  "pg-cursor": "^2.12.1",
77
76
  "slug": "^11.0.0",
78
- "tsx": "^4.20.6",
77
+ "tsx": "^4.21.0",
79
78
  "windows-1252": "^3.0.4"
80
79
  },
81
80
  "devDependencies": {
@@ -93,7 +92,6 @@
93
92
  "@typescript-eslint/parser": "^8.46.0",
94
93
  "cross-env": "^10.1.0",
95
94
  "eslint": "^8.57.1",
96
- "iconv-lite": "^0.7.0",
97
95
  "kysely-codegen": "^0.19.0",
98
96
  "prettier": "^3.5.3",
99
97
  "tslib": "^2.1.0",