@teardown/navigation-metro 2.0.70 → 2.0.71

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.
@@ -34,4 +34,21 @@ export declare function generateLinkingFileContent(routes: RouteNode[], prefixes
34
34
  * Generates the register.d.ts file content
35
35
  */
36
36
  export declare function generateRegisterFileContent(): string;
37
+ export interface RouteTreeEntry {
38
+ importName: string;
39
+ importPath: string;
40
+ path: string;
41
+ isLayout: boolean;
42
+ layoutType: string;
43
+ parentLayoutImportName: string | null;
44
+ }
45
+ /**
46
+ * Builds route tree entries for import generation
47
+ */
48
+ export declare function buildRouteTreeEntries(routes: RouteNode[], routesDir: string, generatedDir: string): RouteTreeEntry[];
49
+ /**
50
+ * Generates the routeTree.generated.ts file content
51
+ * Outputs a hierarchical navigator structure for nested navigation
52
+ */
53
+ export declare function generateRouteTreeFileContent(routes: RouteNode[], routesDir: string, generatedDir: string): string;
37
54
  //# sourceMappingURL=route-generator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"route-generator.d.ts","sourceRoot":"","sources":["../../src/generator/route-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAiB,KAAK,eAAe,EAAE,KAAK,SAAS,EAAuB,MAAM,yBAAyB,CAAC;AAEnH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CA8BpE;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,eAAe,EAAE,CAwBhF;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAgDrE;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAkC1F;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,CAepD"}
1
+ {"version":3,"file":"route-generator.d.ts","sourceRoot":"","sources":["../../src/generator/route-generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAiB,KAAK,eAAe,EAAE,KAAK,SAAS,EAAuB,MAAM,yBAAyB,CAAC;AAEnH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAiCpE;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,eAAe,EAAE,CAwBhF;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAgDrE;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAkC1F;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,CAepD;AA4ED,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED;;GAEG;AAEH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,cAAc,EAAE,CAqDpH;AA+JD;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CA4EjH"}
@@ -9,6 +9,8 @@ exports.buildRouteParamsInterface = buildRouteParamsInterface;
9
9
  exports.generateRoutesFileContent = generateRoutesFileContent;
10
10
  exports.generateLinkingFileContent = generateLinkingFileContent;
11
11
  exports.generateRegisterFileContent = generateRegisterFileContent;
12
+ exports.buildRouteTreeEntries = buildRouteTreeEntries;
13
+ exports.generateRouteTreeFileContent = generateRouteTreeFileContent;
12
14
  const node_fs_1 = require("node:fs");
13
15
  const node_path_1 = require("node:path");
14
16
  const file_scanner_1 = require("../scanner/file-scanner");
@@ -33,6 +35,8 @@ function generateAllRouteFiles(options) {
33
35
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "register.d.ts"), registerContent);
34
36
  const manifestContent = generateManifestContent(routes);
35
37
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "manifest.json"), JSON.stringify(manifestContent, null, 2));
38
+ const routeTreeContent = generateRouteTreeFileContent(routes, routesDir, generatedDir);
39
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(generatedDir, "routeTree.generated.ts"), routeTreeContent);
36
40
  if (verbose) {
37
41
  const count = countRoutes(routes);
38
42
  console.log(`[teardown/navigation] Generated ${count} routes`);
@@ -177,3 +181,275 @@ function generateManifestContent(routes) {
177
181
  function countRoutes(routes) {
178
182
  return (0, file_scanner_1.flattenRoutes)(routes).filter((r) => !r.isLayout).length;
179
183
  }
184
+ /**
185
+ * Converts a relative path to a valid import variable name
186
+ */
187
+ function pathToImportName(relativePath) {
188
+ // Remove extension
189
+ const withoutExt = relativePath.replace(/\.(ts|tsx)$/, "");
190
+ // Convert to valid identifier
191
+ return withoutExt
192
+ .replace(/\//g, "_") // Replace slashes with underscores
193
+ .replace(/\[\.\.\.([^\]]+)\]/g, "CatchAll_$1") // [...slug] -> CatchAll_slug
194
+ .replace(/\[\[([^\]]+)\]\]/g, "Optional_$1") // [[id]] -> Optional_id
195
+ .replace(/\[([^\]]+)\]/g, "Param_$1") // [userId] -> Param_userId
196
+ .replace(/[^a-zA-Z0-9_]/g, "_") // Replace other special chars
197
+ .replace(/^_+/, "") // Remove leading underscores
198
+ .replace(/_+$/g, "") // Remove trailing underscores
199
+ .replace(/_+/g, "_"); // Collapse multiple underscores
200
+ }
201
+ /**
202
+ * Generates the relative import path from generated dir to route file
203
+ */
204
+ function getImportPath(routesDir, generatedDir, routeRelativePath) {
205
+ const routeAbsolutePath = (0, node_path_1.join)(routesDir, routeRelativePath);
206
+ const generatedAbsoluteDir = generatedDir;
207
+ // Get relative path from generated dir to route file
208
+ let importPath = (0, node_path_1.relative)(generatedAbsoluteDir, routeAbsolutePath);
209
+ // Remove extension for import
210
+ importPath = importPath.replace(/\.(ts|tsx)$/, "");
211
+ // Ensure it starts with ./ or ../
212
+ if (!importPath.startsWith(".")) {
213
+ importPath = `./${importPath}`;
214
+ }
215
+ return importPath;
216
+ }
217
+ /**
218
+ * Builds route tree entries for import generation
219
+ */
220
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: route tree traversal and layout assignment
221
+ function buildRouteTreeEntries(routes, routesDir, generatedDir) {
222
+ const allRoutes = (0, file_scanner_1.flattenRoutes)(routes);
223
+ const entries = [];
224
+ // Create a map to find layouts by directory
225
+ const layoutsByDir = new Map();
226
+ for (const route of allRoutes) {
227
+ const importName = pathToImportName(route.relativePath);
228
+ const importPath = getImportPath(routesDir, generatedDir, route.relativePath);
229
+ const entry = {
230
+ importName,
231
+ importPath,
232
+ path: route.path,
233
+ isLayout: route.isLayout,
234
+ layoutType: route.layoutType,
235
+ parentLayoutImportName: null,
236
+ };
237
+ entries.push(entry);
238
+ if (route.isLayout) {
239
+ // Store layout by its directory
240
+ const layoutDir = (0, node_path_1.dirname)(route.relativePath);
241
+ layoutsByDir.set(layoutDir === "." ? "" : layoutDir, entry);
242
+ }
243
+ }
244
+ // Second pass: assign parent layouts
245
+ for (const entry of entries) {
246
+ if (!entry.isLayout) {
247
+ // Find the relative path's directory
248
+ const routePath = allRoutes.find((r) => pathToImportName(r.relativePath) === entry.importName);
249
+ if (routePath) {
250
+ const routeDir = (0, node_path_1.dirname)(routePath.relativePath);
251
+ // Look for layout in same directory or parent directories
252
+ let currentDir = routeDir === "." ? "" : routeDir;
253
+ while (currentDir !== undefined) {
254
+ const layout = layoutsByDir.get(currentDir);
255
+ if (layout) {
256
+ entry.parentLayoutImportName = layout.importName;
257
+ break;
258
+ }
259
+ if (currentDir === "")
260
+ break;
261
+ currentDir = (0, node_path_1.dirname)(currentDir);
262
+ if (currentDir === ".")
263
+ currentDir = "";
264
+ }
265
+ }
266
+ }
267
+ }
268
+ return entries;
269
+ }
270
+ /**
271
+ * Builds a hierarchical navigator structure from route nodes
272
+ */
273
+ function buildNavigatorHierarchy(routes, routesDir, generatedDir) {
274
+ const allImports = [];
275
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: builds nested navigator hierarchy
276
+ function processNode(nodes, parentName) {
277
+ // Find layout in this group
278
+ const layoutNode = nodes.find((n) => n.isLayout);
279
+ // Default to stack if no layout or layout type is "none"
280
+ const layoutType = layoutNode?.layoutType && layoutNode.layoutType !== "none" ? layoutNode.layoutType : "stack";
281
+ const layoutImportName = layoutNode ? pathToImportName(layoutNode.relativePath) : null;
282
+ if (layoutNode) {
283
+ allImports.push({
284
+ name: pathToImportName(layoutNode.relativePath),
285
+ path: getImportPath(routesDir, generatedDir, layoutNode.relativePath),
286
+ });
287
+ }
288
+ const navigatorInfo = {
289
+ name: parentName,
290
+ type: layoutType,
291
+ layoutImportName,
292
+ screens: [],
293
+ nestedNavigators: [],
294
+ };
295
+ // Process children
296
+ for (const node of nodes) {
297
+ if (node.isLayout)
298
+ continue;
299
+ const importName = pathToImportName(node.relativePath);
300
+ const importPath = getImportPath(routesDir, generatedDir, node.relativePath);
301
+ allImports.push({ name: importName, path: importPath });
302
+ // Check if this node has children with a layout (nested navigator)
303
+ const childLayout = node.children.find((c) => c.isLayout);
304
+ if (childLayout) {
305
+ // This is a nested navigator
306
+ const nestedNav = processNode(node.children, importName);
307
+ navigatorInfo.nestedNavigators.push(nestedNav);
308
+ }
309
+ else if (node.children.length > 0) {
310
+ // Has children but no layout - process as screens in current navigator
311
+ navigatorInfo.screens.push({
312
+ name: node.name.split("/").pop() || node.name,
313
+ importName,
314
+ path: node.path,
315
+ });
316
+ // Recursively add children as screens
317
+ const childScreens = collectScreens(node.children, routesDir, generatedDir, allImports);
318
+ navigatorInfo.screens.push(...childScreens);
319
+ }
320
+ else {
321
+ // Regular screen
322
+ navigatorInfo.screens.push({
323
+ name: node.name.split("/").pop() || node.name,
324
+ importName,
325
+ path: node.path,
326
+ });
327
+ }
328
+ }
329
+ return navigatorInfo;
330
+ }
331
+ function collectScreens(nodes, rDir, gDir, imports) {
332
+ const screens = [];
333
+ for (const node of nodes) {
334
+ if (node.isLayout)
335
+ continue;
336
+ const importName = pathToImportName(node.relativePath);
337
+ const importPath = getImportPath(rDir, gDir, node.relativePath);
338
+ imports.push({ name: importName, path: importPath });
339
+ screens.push({
340
+ name: node.name.split("/").pop() || node.name,
341
+ importName,
342
+ path: node.path,
343
+ });
344
+ if (node.children.length > 0) {
345
+ screens.push(...collectScreens(node.children, rDir, gDir, imports));
346
+ }
347
+ }
348
+ return screens;
349
+ }
350
+ const rootNavigator = processNode(routes, "_root");
351
+ return { rootNavigator, allImports };
352
+ }
353
+ /**
354
+ * Generates the navigator tree as TypeScript code
355
+ */
356
+ function generateNavigatorCode(nav, indent) {
357
+ const lines = [];
358
+ lines.push(`${indent}{`);
359
+ lines.push(`${indent}\ttype: "${nav.type}",`);
360
+ if (nav.layoutImportName) {
361
+ lines.push(`${indent}\tlayout: ${nav.layoutImportName},`);
362
+ }
363
+ // Screens
364
+ lines.push(`${indent}\tscreens: {`);
365
+ for (const screen of nav.screens) {
366
+ lines.push(`${indent}\t\t"${screen.name}": {`);
367
+ lines.push(`${indent}\t\t\tscreen: ${screen.importName},`);
368
+ lines.push(`${indent}\t\t\tpath: "${screen.path}",`);
369
+ lines.push(`${indent}\t\t},`);
370
+ }
371
+ lines.push(`${indent}\t},`);
372
+ // Nested navigators
373
+ if (nav.nestedNavigators.length > 0) {
374
+ lines.push(`${indent}\tnavigators: {`);
375
+ for (const nestedNav of nav.nestedNavigators) {
376
+ lines.push(`${indent}\t\t"${nestedNav.name}":`);
377
+ const nestedLines = generateNavigatorCode(nestedNav, `${indent}\t\t`);
378
+ lines.push(...nestedLines.map((l) => l.replace(/^/, "")));
379
+ lines[lines.length - 1] += ",";
380
+ }
381
+ lines.push(`${indent}\t},`);
382
+ }
383
+ lines.push(`${indent}}`);
384
+ return lines;
385
+ }
386
+ /**
387
+ * Generates the routeTree.generated.ts file content
388
+ * Outputs a hierarchical navigator structure for nested navigation
389
+ */
390
+ function generateRouteTreeFileContent(routes, routesDir, generatedDir) {
391
+ const { rootNavigator, allImports } = buildNavigatorHierarchy(routes, routesDir, generatedDir);
392
+ const lines = [
393
+ "/* eslint-disable */",
394
+ "// @ts-nocheck",
395
+ "",
396
+ "// Auto-generated by @teardown/navigation",
397
+ "// Do not edit this file directly",
398
+ `// Generated at: ${new Date().toISOString()}`,
399
+ "",
400
+ 'import type { NavigatorNode } from "@teardown/navigation";',
401
+ "",
402
+ ];
403
+ // Generate unique imports
404
+ const uniqueImports = new Map();
405
+ for (const imp of allImports) {
406
+ if (!uniqueImports.has(imp.name)) {
407
+ uniqueImports.set(imp.name, imp.path);
408
+ }
409
+ }
410
+ for (const [name, path] of uniqueImports) {
411
+ lines.push(`import ${name} from "${path}";`);
412
+ }
413
+ lines.push("");
414
+ // Generate the hierarchical routeTree
415
+ lines.push("export const routeTree: NavigatorNode =");
416
+ const treeLines = generateNavigatorCode(rootNavigator, "");
417
+ lines.push(...treeLines);
418
+ lines[lines.length - 1] += ";";
419
+ lines.push("");
420
+ // Also export flat screens for backwards compatibility
421
+ const entries = buildRouteTreeEntries(routes, routesDir, generatedDir);
422
+ const screenEntries = entries.filter((e) => !e.isLayout);
423
+ lines.push("// Flat screens export for backwards compatibility");
424
+ lines.push("export const screens = {");
425
+ for (const entry of screenEntries) {
426
+ lines.push(`\t"${entry.path}": ${entry.importName},`);
427
+ }
428
+ lines.push("} as const;");
429
+ lines.push("");
430
+ // Export layouts
431
+ const layoutEntries = entries.filter((e) => e.isLayout);
432
+ lines.push("export const layouts = {");
433
+ for (const entry of layoutEntries) {
434
+ const layoutDir = (0, node_path_1.dirname)(entry.importPath.replace(/^\.\//, "").replace(/^\.\.\//, ""));
435
+ const layoutKey = layoutDir === "." ? "_root" : layoutDir.replace(/\//g, "_");
436
+ lines.push(`\t"${layoutKey}": ${entry.importName},`);
437
+ }
438
+ lines.push("} as const;");
439
+ lines.push("");
440
+ // Export types
441
+ lines.push("export type Screens = typeof screens;");
442
+ lines.push("export type Layouts = typeof layouts;");
443
+ lines.push("export type RouteTreeType = typeof routeTree;");
444
+ lines.push("");
445
+ // Export route paths array
446
+ lines.push("export const routePaths = [");
447
+ for (const entry of screenEntries) {
448
+ lines.push(`\t"${entry.path}",`);
449
+ }
450
+ lines.push("] as const;");
451
+ lines.push("");
452
+ lines.push("export type RoutePath = (typeof routePaths)[number];");
453
+ lines.push("");
454
+ return lines.join("\n");
455
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"file-watcher.d.ts","sourceRoot":"","sources":["../../src/watcher/file-watcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,EAAE,KAAK,eAAe,EAAkB,MAAM,8BAA8B,CAAC;AAEpF,MAAM,WAAW,cAAc;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;CAC9C;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,IAAI,CA0GrE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAKvC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
1
+ {"version":3,"file":"file-watcher.d.ts","sourceRoot":"","sources":["../../src/watcher/file-watcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,EAAE,KAAK,eAAe,EAAkB,MAAM,8BAA8B,CAAC;AAEpF,MAAM,WAAW,cAAc;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;CAC9C;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,IAAI,CA+GrE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAKvC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
@@ -73,22 +73,27 @@ function startRouteWatcher(options) {
73
73
  if (verbose) {
74
74
  console.log(`[teardown/navigation] File added: ${relativePath}`);
75
75
  }
76
- // Auto-populate empty files with template content
77
- if (autoTemplate && (0, route_templates_1.isFileEmpty)(filePath)) {
78
- try {
79
- const templateContent = (0, route_templates_1.generateRouteTemplate)(relativePath);
80
- (0, node_fs_1.writeFileSync)(filePath, templateContent);
81
- if (verbose) {
82
- console.log(`[teardown/navigation] Generated template for: ${relativePath}`);
76
+ // Add delay to let filesystem settle before checking if file is empty
77
+ // This prevents race conditions where the file hasn't finished being created
78
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: template generation with error handling
79
+ setTimeout(() => {
80
+ // Auto-populate empty files with template content
81
+ if (autoTemplate && (0, route_templates_1.isFileEmpty)(filePath)) {
82
+ try {
83
+ const templateContent = (0, route_templates_1.generateRouteTemplate)(relativePath);
84
+ (0, node_fs_1.writeFileSync)(filePath, templateContent);
85
+ if (verbose) {
86
+ console.log(`[teardown/navigation] Generated template for: ${relativePath}`);
87
+ }
83
88
  }
84
- }
85
- catch (error) {
86
- if (verbose) {
87
- console.error(`[teardown/navigation] Failed to generate template for ${relativePath}:`, error);
89
+ catch (error) {
90
+ if (verbose) {
91
+ console.error(`[teardown/navigation] Failed to generate template for ${relativePath}:`, error);
92
+ }
88
93
  }
89
94
  }
90
- }
91
- regenerate();
95
+ regenerate();
96
+ }, 50);
92
97
  });
93
98
  watcher.on("unlink", (filePath) => {
94
99
  if (verbose) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teardown/navigation-metro",
3
- "version": "2.0.70",
3
+ "version": "2.0.71",
4
4
  "description": "Metro plugin for @teardown/navigation type generation",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "devDependencies": {
44
44
  "@biomejs/biome": "2.3.11",
45
- "@teardown/tsconfig": "2.0.70",
45
+ "@teardown/tsconfig": "2.0.71",
46
46
  "@types/node": "24.10.1",
47
47
  "typescript": "5.9.3"
48
48
  }