tsgonest 0.7.5 → 0.8.0

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.
Files changed (2) hide show
  1. package/bin/migrate.cjs +283 -78
  2. package/package.json +9 -9
package/bin/migrate.cjs CHANGED
@@ -175950,6 +175950,16 @@ function transformTypiaTags(file, report) {
175950
175950
  count++;
175951
175951
  }
175952
175952
  }
175953
+ if (expr.startsWith("typia.json.assertParse") || expr.startsWith("typia.json.parse")) {
175954
+ const args = node.getArguments();
175955
+ if (args.length === 1) {
175956
+ const argText = args[0].getText();
175957
+ const line = node.getStartLineNumber();
175958
+ node.replaceWithText(`JSON.parse(${argText})`);
175959
+ report.todo(filePath, "typia", "Replaced typia.json.assertParse() with JSON.parse(). Add manual validation if needed.", line);
175960
+ count++;
175961
+ }
175962
+ }
175953
175963
  });
175954
175964
  for (const imp of file.getImportDeclarations()) {
175955
175965
  if (imp.getModuleSpecifierValue() !== "typia") continue;
@@ -175979,19 +175989,41 @@ function ensureTagsImport(file) {
175979
175989
  namedImports: ["tags"]
175980
175990
  });
175981
175991
  }
175992
+ /** Packages whose imports should be pruned after all transforms. */
175993
+ const PRUNE_PACKAGES = new Set([
175994
+ "class-validator",
175995
+ "class-transformer",
175996
+ "@nestjs/swagger"
175997
+ ]);
175982
175998
  /**
175983
175999
  * Remove import declarations that have no named imports, no default import,
175984
176000
  * and no namespace import (i.e., completely empty after transforms).
176001
+ * Also prune individual unused named imports from migration-target packages.
175985
176002
  */
175986
176003
  function cleanupImports(file) {
175987
176004
  const imports = file.getImportDeclarations();
175988
- for (const imp of imports) {
176005
+ const fullText = file.getFullText();
176006
+ for (const imp of [...imports]) {
175989
176007
  const hasDefault = imp.getDefaultImport() !== void 0;
175990
176008
  const hasNamespace = imp.getNamespaceImport() !== void 0;
175991
176009
  const hasNamed = imp.getNamedImports().length > 0;
175992
- imp.getModuleSpecifierValue();
175993
- if (!hasDefault && !hasNamespace && !hasNamed && imp.getText().includes("{")) imp.remove();
176010
+ const moduleSpecifier = imp.getModuleSpecifierValue();
176011
+ if (!hasDefault && !hasNamespace && !hasNamed && imp.getText().includes("{")) {
176012
+ imp.remove();
176013
+ continue;
176014
+ }
176015
+ if (PRUNE_PACKAGES.has(moduleSpecifier) && hasNamed) {
176016
+ const importText = imp.getText();
176017
+ const withoutImport = fullText.replace(importText, "");
176018
+ const namedImports = imp.getNamedImports();
176019
+ const unused = namedImports.filter((n) => !withoutImport.includes(n.getName()));
176020
+ if (unused.length === namedImports.length) imp.remove();
176021
+ else if (unused.length > 0) for (const n of unused.reverse()) n.remove();
176022
+ }
175994
176023
  }
176024
+ const text = file.getFullText();
176025
+ const collapsed = text.replace(/\n([ \t]*\n){2,}/g, "\n\n");
176026
+ if (collapsed !== text) file.replaceWithText(collapsed);
175995
176027
  }
175996
176028
 
175997
176029
  //#endregion
@@ -176051,46 +176083,48 @@ const NON_CLASS_VALIDATOR_DECORATORS = new Set([
176051
176083
  "Default",
176052
176084
  "ForeignKey",
176053
176085
  "BelongsTo",
176054
- "HasMany"
176086
+ "HasMany",
176087
+ "Prop",
176088
+ "prop"
176055
176089
  ]);
176056
- /** Map class-validator decorator → branded type expression (or null to just remove). */
176090
+ /** Map class-validator decorator → branded type expressions (or empty to just remove). */
176057
176091
  function decoratorToBrandedType(decorator) {
176058
176092
  const name = decorator.getName();
176059
176093
  const args = decorator.getArguments().map((a) => a.getText());
176060
176094
  switch (name) {
176061
176095
  case "Min": return {
176062
- tag: args[0] ? `tags.Minimum<${args[0]}>` : null,
176096
+ tags: args[0] ? [`tags.Minimum<${args[0]}>`] : [],
176063
176097
  makeOptional: false,
176064
176098
  known: true
176065
176099
  };
176066
176100
  case "Max": return {
176067
- tag: args[0] ? `tags.Maximum<${args[0]}>` : null,
176101
+ tags: args[0] ? [`tags.Maximum<${args[0]}>`] : [],
176068
176102
  makeOptional: false,
176069
176103
  known: true
176070
176104
  };
176071
176105
  case "MinLength": return {
176072
- tag: args[0] ? `tags.MinLength<${args[0]}>` : null,
176106
+ tags: args[0] ? [`tags.MinLength<${args[0]}>`] : [],
176073
176107
  makeOptional: false,
176074
176108
  known: true
176075
176109
  };
176076
176110
  case "MaxLength": return {
176077
- tag: args[0] ? `tags.MaxLength<${args[0]}>` : null,
176111
+ tags: args[0] ? [`tags.MaxLength<${args[0]}>`] : [],
176078
176112
  makeOptional: false,
176079
176113
  known: true
176080
176114
  };
176081
176115
  case "IsEmail": return {
176082
- tag: "tags.Email",
176116
+ tags: ["tags.Email"],
176083
176117
  makeOptional: false,
176084
176118
  known: true
176085
176119
  };
176086
176120
  case "IsUUID": return {
176087
- tag: "tags.Uuid",
176121
+ tags: ["tags.Uuid"],
176088
176122
  makeOptional: false,
176089
176123
  known: true
176090
176124
  };
176091
176125
  case "IsUrl":
176092
176126
  case "IsURL": return {
176093
- tag: "tags.Url",
176127
+ tags: ["tags.Url"],
176094
176128
  makeOptional: false,
176095
176129
  known: true
176096
176130
  };
@@ -176098,193 +176132,198 @@ function decoratorToBrandedType(decorator) {
176098
176132
  if (args[0]) {
176099
176133
  const pattern = args[0].replace(/^\/|\/[gimsuy]*$/g, "");
176100
176134
  return {
176101
- tag: `tags.Pattern<${JSON.stringify(pattern)}>`,
176135
+ tags: [`tags.Pattern<${JSON.stringify(pattern)}>`],
176102
176136
  makeOptional: false,
176103
176137
  known: true
176104
176138
  };
176105
176139
  }
176106
176140
  return {
176107
- tag: null,
176141
+ tags: [],
176108
176142
  makeOptional: false,
176109
176143
  known: true
176110
176144
  };
176111
176145
  case "IsDateString": return {
176112
- tag: `tags.Format<"date-time">`,
176146
+ tags: [`tags.Format<"date-time">`],
176113
176147
  makeOptional: false,
176114
176148
  known: true
176115
176149
  };
176116
176150
  case "IsNotEmpty": return {
176117
- tag: "tags.MinLength<1>",
176151
+ tags: ["tags.MinLength<1>"],
176118
176152
  makeOptional: false,
176119
176153
  known: true
176120
176154
  };
176121
176155
  case "IsPositive": return {
176122
- tag: "tags.Positive",
176156
+ tags: ["tags.Positive"],
176123
176157
  makeOptional: false,
176124
176158
  known: true
176125
176159
  };
176126
176160
  case "IsNegative": return {
176127
- tag: "tags.Negative",
176161
+ tags: ["tags.Negative"],
176128
176162
  makeOptional: false,
176129
176163
  known: true
176130
176164
  };
176131
176165
  case "IsInt": return {
176132
- tag: "tags.Int",
176166
+ tags: ["tags.Int"],
176133
176167
  makeOptional: false,
176134
176168
  known: true
176135
176169
  };
176136
176170
  case "ArrayMinSize": return {
176137
- tag: args[0] ? `tags.MinItems<${args[0]}>` : null,
176171
+ tags: args[0] ? [`tags.MinItems<${args[0]}>`] : [],
176138
176172
  makeOptional: false,
176139
176173
  known: true
176140
176174
  };
176141
176175
  case "ArrayMaxSize": return {
176142
- tag: args[0] ? `tags.MaxItems<${args[0]}>` : null,
176176
+ tags: args[0] ? [`tags.MaxItems<${args[0]}>`] : [],
176143
176177
  makeOptional: false,
176144
176178
  known: true
176145
176179
  };
176146
176180
  case "ArrayNotEmpty": return {
176147
- tag: "tags.MinItems<1>",
176181
+ tags: ["tags.MinItems<1>"],
176148
176182
  makeOptional: false,
176149
176183
  known: true
176150
176184
  };
176151
176185
  case "IsDate": return {
176152
- tag: `tags.Format<"date-time">`,
176186
+ tags: [`tags.Format<"date-time">`],
176153
176187
  makeOptional: false,
176154
176188
  known: true
176155
176189
  };
176156
176190
  case "IsISO8601": return {
176157
- tag: `tags.Format<"date-time">`,
176191
+ tags: [`tags.Format<"date-time">`],
176158
176192
  makeOptional: false,
176159
176193
  known: true
176160
176194
  };
176161
176195
  case "IsJSON": return {
176162
- tag: null,
176196
+ tags: [],
176163
176197
  makeOptional: false,
176164
176198
  known: true
176165
176199
  };
176166
176200
  case "IsIn": return {
176167
- tag: null,
176201
+ tags: [],
176168
176202
  makeOptional: false,
176169
176203
  known: false
176170
176204
  };
176171
176205
  case "IsTimeZone": return {
176172
- tag: null,
176206
+ tags: [],
176173
176207
  makeOptional: false,
176174
176208
  known: true
176175
176209
  };
176176
176210
  case "IsIP": return {
176177
- tag: `tags.Format<"ipv4">`,
176211
+ tags: [`tags.Format<"ipv4">`],
176178
176212
  makeOptional: false,
176179
176213
  known: true
176180
176214
  };
176181
176215
  case "IsCreditCard": return {
176182
- tag: null,
176216
+ tags: [],
176183
176217
  makeOptional: false,
176184
176218
  known: true
176185
176219
  };
176186
176220
  case "IsPhoneNumber": return {
176187
- tag: null,
176221
+ tags: [],
176188
176222
  makeOptional: false,
176189
176223
  known: true
176190
176224
  };
176191
176225
  case "IsHexColor": return {
176192
- tag: null,
176226
+ tags: [],
176193
176227
  makeOptional: false,
176194
176228
  known: true
176195
176229
  };
176196
176230
  case "IsMACAddress": return {
176197
- tag: null,
176231
+ tags: [],
176198
176232
  makeOptional: false,
176199
176233
  known: true
176200
176234
  };
176201
176235
  case "IsPort": return {
176202
- tag: null,
176236
+ tags: [],
176203
176237
  makeOptional: false,
176204
176238
  known: true
176205
176239
  };
176206
176240
  case "IsMimeType": return {
176207
- tag: null,
176241
+ tags: [],
176208
176242
  makeOptional: false,
176209
176243
  known: true
176210
176244
  };
176211
176245
  case "IsSemVer": return {
176212
- tag: null,
176246
+ tags: [],
176213
176247
  makeOptional: false,
176214
176248
  known: true
176215
176249
  };
176216
176250
  case "IsAlpha": return {
176217
- tag: null,
176251
+ tags: [],
176218
176252
  makeOptional: false,
176219
176253
  known: true
176220
176254
  };
176221
176255
  case "IsAlphanumeric": return {
176222
- tag: null,
176256
+ tags: [],
176223
176257
  makeOptional: false,
176224
176258
  known: true
176225
176259
  };
176226
176260
  case "IsNumberString": return {
176227
- tag: null,
176261
+ tags: [],
176228
176262
  makeOptional: false,
176229
176263
  known: true
176230
176264
  };
176231
176265
  case "IsBase64": return {
176232
- tag: null,
176266
+ tags: [],
176233
176267
  makeOptional: false,
176234
176268
  known: true
176235
176269
  };
176236
176270
  case "IsMongoId": return {
176237
- tag: null,
176271
+ tags: [],
176238
176272
  makeOptional: false,
176239
176273
  known: true
176240
176274
  };
176241
176275
  case "IsCurrency": return {
176242
- tag: null,
176276
+ tags: [],
176243
176277
  makeOptional: false,
176244
176278
  known: true
176245
176279
  };
176246
176280
  case "Contains": return {
176247
- tag: null,
176281
+ tags: [],
176248
176282
  makeOptional: false,
176249
176283
  known: true
176250
176284
  };
176251
176285
  case "NotContains": return {
176252
- tag: null,
176286
+ tags: [],
176253
176287
  makeOptional: false,
176254
176288
  known: true
176255
176289
  };
176256
176290
  case "IsHash": return {
176257
- tag: null,
176291
+ tags: [],
176258
176292
  makeOptional: false,
176259
176293
  known: true
176260
176294
  };
176261
176295
  case "IsLatitude": return {
176262
- tag: null,
176296
+ tags: [],
176263
176297
  makeOptional: false,
176264
176298
  known: true
176265
176299
  };
176266
176300
  case "IsLongitude": return {
176267
- tag: null,
176301
+ tags: [],
176268
176302
  makeOptional: false,
176269
176303
  known: true
176270
176304
  };
176271
176305
  case "IsLatLong": return {
176272
- tag: null,
176273
- makeOptional: false,
176274
- known: true
176275
- };
176276
- case "Length": return {
176277
- tag: args[0] ? `tags.MinLength<${args[0]}>` : null,
176306
+ tags: [],
176278
176307
  makeOptional: false,
176279
176308
  known: true
176280
176309
  };
176310
+ case "Length": {
176311
+ const result = [];
176312
+ if (args[0]) result.push(`tags.MinLength<${args[0]}>`);
176313
+ if (args[1]) result.push(`tags.MaxLength<${args[1]}>`);
176314
+ return {
176315
+ tags: result,
176316
+ makeOptional: false,
176317
+ known: true
176318
+ };
176319
+ }
176281
176320
  case "IsOptional": return {
176282
- tag: null,
176321
+ tags: [],
176283
176322
  makeOptional: true,
176284
176323
  known: true
176285
176324
  };
176286
176325
  case "Allow": return {
176287
- tag: null,
176326
+ tags: [],
176288
176327
  makeOptional: false,
176289
176328
  known: true
176290
176329
  };
@@ -176303,12 +176342,12 @@ function decoratorToBrandedType(decorator) {
176303
176342
  case "Equals":
176304
176343
  case "NotEquals":
176305
176344
  case "IsInstance": return {
176306
- tag: null,
176345
+ tags: [],
176307
176346
  makeOptional: false,
176308
176347
  known: true
176309
176348
  };
176310
176349
  default: return {
176311
- tag: null,
176350
+ tags: [],
176312
176351
  makeOptional: false,
176313
176352
  known: false
176314
176353
  };
@@ -176316,16 +176355,22 @@ function decoratorToBrandedType(decorator) {
176316
176355
  }
176317
176356
  /**
176318
176357
  * Transform class-validator DTOs to interfaces with branded types.
176319
- * Only processes *.dto.ts files.
176358
+ * Processes any file that imports from class-validator. Skips NestJS framework
176359
+ * classes and entity/ORM classes via decorator detection.
176320
176360
  */
176321
176361
  function transformClassValidator(file, report) {
176322
- if (!file.getFilePath().endsWith(".dto.ts")) return 0;
176323
176362
  const cvImport = file.getImportDeclaration((d) => d.getModuleSpecifierValue() === "class-validator");
176324
176363
  if (!cvImport) return 0;
176325
176364
  let count = 0;
176326
176365
  const filePath = file.getFilePath();
176327
176366
  let needsTagsImport = false;
176328
176367
  const unknownDecorators = /* @__PURE__ */ new Set();
176368
+ const MAPPED_TYPE_HELPERS = new Set([
176369
+ "OmitType",
176370
+ "PickType",
176371
+ "PartialType",
176372
+ "IntersectionType"
176373
+ ]);
176329
176374
  const classes = file.getClasses();
176330
176375
  for (const cls of classes) {
176331
176376
  if (cls.getDecorators().some((d) => [
@@ -176335,8 +176380,55 @@ function transformClassValidator(file, report) {
176335
176380
  "Guard",
176336
176381
  "Interceptor",
176337
176382
  "Pipe",
176338
- "Filter"
176383
+ "Filter",
176384
+ "Entity",
176385
+ "Schema",
176386
+ "Table",
176387
+ "Document",
176388
+ "Model",
176389
+ "ViewEntity",
176390
+ "ChildEntity",
176391
+ "Embeddable"
176339
176392
  ].includes(d.getName()))) continue;
176393
+ const extendsExpr = cls.getExtends();
176394
+ if (extendsExpr && MAPPED_TYPE_HELPERS.has(extendsExpr.getExpression().getText().split("(")[0].trim())) {
176395
+ for (const prop of cls.getProperties()) {
176396
+ const brandedTypes = [];
176397
+ let isOptional = prop.hasQuestionToken();
176398
+ const decoratorsToRemove = [];
176399
+ for (const dec of prop.getDecorators()) {
176400
+ const decName = dec.getName();
176401
+ if (decName.startsWith("Api")) continue;
176402
+ if (NON_CLASS_VALIDATOR_DECORATORS.has(decName)) continue;
176403
+ const result = decoratorToBrandedType(dec);
176404
+ if (result.tags.length > 0) {
176405
+ brandedTypes.push(...result.tags);
176406
+ needsTagsImport = true;
176407
+ }
176408
+ if (result.makeOptional) isOptional = true;
176409
+ if (!result.known) {
176410
+ unknownDecorators.add(decName);
176411
+ continue;
176412
+ }
176413
+ decoratorsToRemove.push(dec);
176414
+ }
176415
+ for (const dec of decoratorsToRemove.reverse()) {
176416
+ dec.remove();
176417
+ count++;
176418
+ }
176419
+ if (brandedTypes.length > 0) {
176420
+ const currentType = prop.getTypeNode()?.getText() ?? "any";
176421
+ prop.setType(`${currentType} & ${brandedTypes.join(" & ")}`);
176422
+ }
176423
+ if (isOptional && !prop.hasQuestionToken()) prop.setHasQuestionToken(true);
176424
+ }
176425
+ const className = cls.getName();
176426
+ if (className) {
176427
+ report.info(filePath, "class-validator", `Stripped decorators from class ${className} (kept as class — extends mapped type)`);
176428
+ count++;
176429
+ }
176430
+ continue;
176431
+ }
176340
176432
  const properties = cls.getProperties();
176341
176433
  const interfaceProps = [];
176342
176434
  for (const prop of properties) {
@@ -176350,8 +176442,8 @@ function transformClassValidator(file, report) {
176350
176442
  if (decName.startsWith("Api")) continue;
176351
176443
  if (NON_CLASS_VALIDATOR_DECORATORS.has(decName)) continue;
176352
176444
  const result = decoratorToBrandedType(dec);
176353
- if (result.tag) {
176354
- brandedTypes.push(result.tag);
176445
+ if (result.tags.length > 0) {
176446
+ brandedTypes.push(...result.tags);
176355
176447
  needsTagsImport = true;
176356
176448
  }
176357
176449
  if (result.makeOptional) isOptional = true;
@@ -176374,7 +176466,6 @@ function transformClassValidator(file, report) {
176374
176466
  if (isExported && isDefault) interfaceText = `export default interface ${className}`;
176375
176467
  else if (isExported) interfaceText = `export interface ${className}`;
176376
176468
  else interfaceText = `interface ${className}`;
176377
- const extendsExpr = cls.getExtends();
176378
176469
  if (extendsExpr) interfaceText += ` extends ${extendsExpr.getText()}`;
176379
176470
  interfaceText += ` {\n${interfaceProps.join("\n")}\n}`;
176380
176471
  cls.replaceWithText(interfaceText);
@@ -176572,28 +176663,101 @@ function transformSwagger(file, report) {
176572
176663
  const fullText = file.getFullText();
176573
176664
  if (fullText.includes("SwaggerModule") || fullText.includes("DocumentBuilder")) report.todo(filePath, "swagger", "Remove SwaggerModule.setup() and DocumentBuilder — tsgonest generates OpenAPI at build time. Configure in tsgonest.config.ts → openapi.");
176574
176665
  if (fullText.includes("NestiaSwaggerComposer")) report.todo(filePath, "swagger", "Remove NestiaSwaggerComposer — tsgonest generates OpenAPI at build time. Configure in tsgonest.config.ts → openapi.");
176666
+ const decoratorsToRemove = [];
176575
176667
  file.forEachDescendant((node) => {
176576
176668
  if (!import_ts_morph.Node.isDecorator(node)) return;
176577
176669
  const name = node.getName();
176578
176670
  if (REMOVABLE_DECORATORS.has(name)) {
176579
- node.remove();
176580
- count++;
176671
+ decoratorsToRemove.push(node);
176581
176672
  return;
176582
176673
  }
176583
176674
  if (name in SECURITY_DECORATORS) {
176584
176675
  const schemeInfo = SECURITY_DECORATORS[name];
176585
176676
  report.detectedSecuritySchemes.set(schemeInfo.name, schemeInfo.config);
176586
176677
  report.info(filePath, "swagger", `Auto-detected ${name} → securitySchemes.${schemeInfo.name} (will be added to tsgonest.config.ts)`, node.getStartLineNumber());
176587
- node.remove();
176588
- count++;
176678
+ decoratorsToRemove.push(node);
176589
176679
  }
176590
176680
  });
176591
- if (count > 0) {
176681
+ for (const node of decoratorsToRemove.reverse()) {
176682
+ node.remove();
176683
+ count++;
176684
+ }
176685
+ const MAPPED_TYPE_HELPERS = new Set([
176686
+ "OmitType",
176687
+ "PickType",
176688
+ "PartialType",
176689
+ "IntersectionType"
176690
+ ]);
176691
+ {
176592
176692
  const remainingText = file.getFullText();
176593
- const namedImports = swaggerImport.getNamedImports().map((n) => n.getName());
176594
176693
  const importText = swaggerImport.getText();
176595
176694
  const withoutImport = remainingText.replace(importText, "");
176596
- if (!namedImports.some((name) => withoutImport.includes(name))) swaggerImport.remove();
176695
+ const namedImports = swaggerImport.getNamedImports();
176696
+ const toRemove = namedImports.filter((n) => !withoutImport.includes(n.getName()));
176697
+ if (toRemove.length === namedImports.length) {
176698
+ swaggerImport.remove();
176699
+ count++;
176700
+ } else if (toRemove.length > 0) {
176701
+ for (const imp of toRemove.reverse()) {
176702
+ imp.remove();
176703
+ count++;
176704
+ }
176705
+ const remaining = swaggerImport.getNamedImports();
176706
+ if (remaining.length > 0 && remaining.every((n) => MAPPED_TYPE_HELPERS.has(n.getName()))) {
176707
+ swaggerImport.setModuleSpecifier("@nestjs/mapped-types");
176708
+ report.info(filePath, "swagger", `Rewrote mapped-type import from @nestjs/swagger → @nestjs/mapped-types`);
176709
+ count++;
176710
+ }
176711
+ }
176712
+ }
176713
+ return count;
176714
+ }
176715
+
176716
+ //#endregion
176717
+ //#region src/migrate/transforms/baseurl-imports.ts
176718
+ const TS_EXTENSIONS = [
176719
+ ".ts",
176720
+ ".tsx",
176721
+ "/index.ts",
176722
+ "/index.tsx"
176723
+ ];
176724
+ /**
176725
+ * Rewrite baseUrl-relative imports to relative paths.
176726
+ * Must run BEFORE other transforms — operates on raw import specifiers.
176727
+ *
176728
+ * @param file The source file to transform
176729
+ * @param baseDir The absolute directory that baseUrl resolves to (e.g., project root)
176730
+ * @param report Migration report for tracking changes
176731
+ * @param fileExists Optional file existence checker (defaults to fs.existsSync, injectable for tests)
176732
+ * @returns Number of rewrites applied
176733
+ */
176734
+ function rewriteBaseUrlImports(file, baseDir, report, fileExists = fs.existsSync) {
176735
+ let count = 0;
176736
+ const filePath = file.getFilePath();
176737
+ const fileDir = (0, path.dirname)(filePath);
176738
+ for (const imp of file.getImportDeclarations()) {
176739
+ const specifier = imp.getModuleSpecifierValue();
176740
+ if (specifier.startsWith(".")) continue;
176741
+ if (specifier.startsWith("@")) continue;
176742
+ const candidate = (0, path.resolve)(baseDir, specifier);
176743
+ let resolvedPath = null;
176744
+ for (const ext of TS_EXTENSIONS) {
176745
+ const tryPath = candidate + ext;
176746
+ if (fileExists(tryPath)) {
176747
+ resolvedPath = tryPath;
176748
+ break;
176749
+ }
176750
+ }
176751
+ if (!resolvedPath && fileExists(candidate)) resolvedPath = candidate;
176752
+ if (!resolvedPath) continue;
176753
+ let relativePath = (0, path.relative)(fileDir, resolvedPath);
176754
+ relativePath = relativePath.split(path.sep).join("/");
176755
+ relativePath = relativePath.replace(/\.tsx?$/, "");
176756
+ relativePath = relativePath.replace(/\/index$/, "");
176757
+ if (!relativePath.startsWith(".")) relativePath = "./" + relativePath;
176758
+ imp.setModuleSpecifier(relativePath);
176759
+ count++;
176760
+ report.info(filePath, "general", `Rewrote baseUrl import '${specifier}' → '${relativePath}'`);
176597
176761
  }
176598
176762
  return count;
176599
176763
  }
@@ -176624,7 +176788,8 @@ var MigrateReport = class {
176624
176788
  typia: 0,
176625
176789
  classValidator: 0,
176626
176790
  classTransformer: 0,
176627
- swagger: 0
176791
+ swagger: 0,
176792
+ baseUrlRewrites: 0
176628
176793
  };
176629
176794
  filesChanged = /* @__PURE__ */ new Set();
176630
176795
  packageJsonChanges = {
@@ -176670,7 +176835,7 @@ var MigrateReport = class {
176670
176835
  });
176671
176836
  }
176672
176837
  get totalTransforms() {
176673
- return this.stats.nestia + this.stats.typia + this.stats.classValidator + this.stats.classTransformer + this.stats.swagger;
176838
+ return this.stats.nestia + this.stats.typia + this.stats.classValidator + this.stats.classTransformer + this.stats.swagger + this.stats.baseUrlRewrites;
176674
176839
  }
176675
176840
  get todos() {
176676
176841
  return this.items.filter((i) => i.severity === "todo");
@@ -176687,6 +176852,7 @@ var MigrateReport = class {
176687
176852
  if (this.stats.classValidator > 0) console.log(` class-validator → tsgonest: ${this.stats.classValidator}`);
176688
176853
  if (this.stats.classTransformer > 0) console.log(` class-transformer → tsgonest: ${this.stats.classTransformer}`);
176689
176854
  if (this.stats.swagger > 0) console.log(` @nestjs/swagger cleanup: ${this.stats.swagger}`);
176855
+ if (this.stats.baseUrlRewrites > 0) console.log(` baseUrl import rewrites: ${this.stats.baseUrlRewrites}`);
176690
176856
  console.log(` total: ${this.totalTransforms}`);
176691
176857
  console.log(` files changed: ${this.filesChanged.size}`);
176692
176858
  if (this.configFileGenerated) console.log(`\n Generated: tsgonest.config.ts`);
@@ -176726,6 +176892,7 @@ var MigrateReport = class {
176726
176892
  if (this.stats.classValidator > 0) lines.push(`| class-validator → tsgonest | ${this.stats.classValidator} |`);
176727
176893
  if (this.stats.classTransformer > 0) lines.push(`| class-transformer → tsgonest | ${this.stats.classTransformer} |`);
176728
176894
  if (this.stats.swagger > 0) lines.push(`| @nestjs/swagger cleanup | ${this.stats.swagger} |`);
176895
+ if (this.stats.baseUrlRewrites > 0) lines.push(`| baseUrl import rewrites | ${this.stats.baseUrlRewrites} |`);
176729
176896
  lines.push(`| **Total** | **${this.totalTransforms}** |`);
176730
176897
  lines.push(`| Files changed | ${this.filesChanged.size} |`);
176731
176898
  lines.push("");
@@ -177350,13 +177517,51 @@ async function main() {
177350
177517
  const snapshots = /* @__PURE__ */ new Map();
177351
177518
  for (const file of files) snapshots.set(file, file.getFullText());
177352
177519
  const report = new MigrateReport();
177353
- if (plan.transformSources && sources.length > 0) for (const file of files) {
177354
- if (detected.hasNestia) report.stats.nestia += transformNestia(file, report);
177355
- if (detected.hasTypia) report.stats.typia += transformTypiaTags(file, report);
177356
- if (detected.hasClassValidator) report.stats.classValidator += transformClassValidator(file, report);
177357
- if (detected.hasClassTransformer) report.stats.classTransformer += transformClassTransformer(file, report);
177358
- if (detected.hasSwagger) report.stats.swagger += transformSwagger(file, report);
177359
- cleanupImports(file);
177520
+ if (plan.transformSources && sources.length > 0) {
177521
+ const tsconfigForBaseUrl = (0, path.resolve)(cwd, opts.tsconfig);
177522
+ let baseUrl;
177523
+ try {
177524
+ const tsc = (0, jsonc_parser.parse)((0, fs.readFileSync)(tsconfigForBaseUrl, "utf-8"), [], { allowTrailingComma: true });
177525
+ if (tsc?.compilerOptions?.baseUrl) baseUrl = tsc.compilerOptions.baseUrl;
177526
+ } catch {}
177527
+ if (baseUrl) {
177528
+ const baseDir = (0, path.resolve)((0, path.dirname)(tsconfigForBaseUrl), baseUrl);
177529
+ for (const file of files) report.stats.baseUrlRewrites += rewriteBaseUrlImports(file, baseDir, report);
177530
+ }
177531
+ for (const file of files) {
177532
+ if (detected.hasNestia) report.stats.nestia += transformNestia(file, report);
177533
+ if (detected.hasTypia) report.stats.typia += transformTypiaTags(file, report);
177534
+ if (detected.hasClassValidator) report.stats.classValidator += transformClassValidator(file, report);
177535
+ if (detected.hasClassTransformer) report.stats.classTransformer += transformClassTransformer(file, report);
177536
+ if (detected.hasSwagger) report.stats.swagger += transformSwagger(file, report);
177537
+ cleanupImports(file);
177538
+ }
177539
+ }
177540
+ const stillImported = /* @__PURE__ */ new Set();
177541
+ for (const file of files) for (const imp of file.getImportDeclarations()) stillImported.add(imp.getModuleSpecifierValue());
177542
+ if (plan.removeClassDeps) {
177543
+ const stillUsed = pkgInfo.classDeps.filter((d) => stillImported.has(d));
177544
+ if (stillUsed.length > 0) {
177545
+ pkgInfo.classDeps = pkgInfo.classDeps.filter((d) => !stillUsed.includes(d));
177546
+ plan.removeClassDeps = pkgInfo.classDeps.length > 0;
177547
+ report.warn("package.json", "general", `Kept ${stillUsed.join(", ")} in package.json — still imported in source files.`);
177548
+ }
177549
+ }
177550
+ if (plan.removeSwaggerDeps) {
177551
+ const stillUsed = pkgInfo.swaggerDeps.filter((d) => stillImported.has(d));
177552
+ if (stillUsed.length > 0) {
177553
+ pkgInfo.swaggerDeps = pkgInfo.swaggerDeps.filter((d) => !stillUsed.includes(d));
177554
+ plan.removeSwaggerDeps = pkgInfo.swaggerDeps.length > 0;
177555
+ report.warn("package.json", "general", `Kept ${stillUsed.join(", ")} in package.json — still imported in source files (e.g., custom decorators).`);
177556
+ }
177557
+ }
177558
+ if (plan.removeNestiaDeps) {
177559
+ const stillUsed = pkgInfo.nestiaDeps.filter((d) => stillImported.has(d));
177560
+ if (stillUsed.length > 0) {
177561
+ pkgInfo.nestiaDeps = pkgInfo.nestiaDeps.filter((d) => !stillUsed.includes(d));
177562
+ plan.removeNestiaDeps = pkgInfo.nestiaDeps.length > 0;
177563
+ report.warn("package.json", "general", `Kept ${stillUsed.join(", ")} in package.json — still imported in source files.`);
177564
+ }
177360
177565
  }
177361
177566
  for (const file of files) {
177362
177567
  const original = snapshots.get(file);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsgonest",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "description": "TypeScript compiler with runtime validation, serialization, and OpenAPI generation for NestJS",
5
5
  "keywords": [
6
6
  "typescript",
@@ -32,8 +32,8 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "jsonc-parser": "^3.3.1",
35
- "@tsgonest/types": "0.7.5",
36
- "@tsgonest/runtime": "0.7.5"
35
+ "@tsgonest/runtime": "0.8.0",
36
+ "@tsgonest/types": "0.8.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "ts-morph": "^25.0.0",
@@ -42,12 +42,12 @@
42
42
  "vitest": "^4.0.0"
43
43
  },
44
44
  "optionalDependencies": {
45
- "@tsgonest/cli-darwin-arm64": "0.7.5",
46
- "@tsgonest/cli-darwin-x64": "0.7.5",
47
- "@tsgonest/cli-linux-x64": "0.7.5",
48
- "@tsgonest/cli-linux-arm64": "0.7.5",
49
- "@tsgonest/cli-win32-arm64": "0.7.5",
50
- "@tsgonest/cli-win32-x64": "0.7.5"
45
+ "@tsgonest/cli-darwin-x64": "0.8.0",
46
+ "@tsgonest/cli-darwin-arm64": "0.8.0",
47
+ "@tsgonest/cli-linux-arm64": "0.8.0",
48
+ "@tsgonest/cli-win32-arm64": "0.8.0",
49
+ "@tsgonest/cli-linux-x64": "0.8.0",
50
+ "@tsgonest/cli-win32-x64": "0.8.0"
51
51
  },
52
52
  "scripts": {
53
53
  "build": "tsdown",