@beignet/cli 0.0.47 → 0.0.49

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 (144) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +126 -42
  3. package/dist/analysis/contract-factories.d.ts +17 -0
  4. package/dist/analysis/contract-factories.d.ts.map +1 -0
  5. package/dist/analysis/contract-factories.js +176 -0
  6. package/dist/analysis/contract-factories.js.map +1 -0
  7. package/dist/analysis/layers.d.ts +5 -0
  8. package/dist/analysis/layers.d.ts.map +1 -0
  9. package/dist/analysis/layers.js +172 -0
  10. package/dist/analysis/layers.js.map +1 -0
  11. package/dist/analysis/port-wiring.d.ts +23 -0
  12. package/dist/analysis/port-wiring.d.ts.map +1 -0
  13. package/dist/analysis/port-wiring.js +379 -0
  14. package/dist/analysis/port-wiring.js.map +1 -0
  15. package/dist/analysis/source-index.d.ts +5 -0
  16. package/dist/analysis/source-index.d.ts.map +1 -1
  17. package/dist/analysis/source-index.js +171 -42
  18. package/dist/analysis/source-index.js.map +1 -1
  19. package/dist/analysis/workspace.d.ts +10 -4
  20. package/dist/analysis/workspace.d.ts.map +1 -1
  21. package/dist/analysis/workspace.js +66 -6
  22. package/dist/analysis/workspace.js.map +1 -1
  23. package/dist/app-map.d.ts.map +1 -1
  24. package/dist/app-map.js +311 -92
  25. package/dist/app-map.js.map +1 -1
  26. package/dist/check.d.ts +8 -1
  27. package/dist/check.d.ts.map +1 -1
  28. package/dist/check.js +55 -9
  29. package/dist/check.js.map +1 -1
  30. package/dist/db.d.ts +38 -0
  31. package/dist/db.d.ts.map +1 -1
  32. package/dist/db.js +52 -0
  33. package/dist/db.js.map +1 -1
  34. package/dist/explain.js +4 -4
  35. package/dist/explain.js.map +1 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +73 -6
  38. package/dist/index.js.map +1 -1
  39. package/dist/inspect.d.ts.map +1 -1
  40. package/dist/inspect.js +454 -170
  41. package/dist/inspect.js.map +1 -1
  42. package/dist/lib.d.ts +2 -2
  43. package/dist/lib.d.ts.map +1 -1
  44. package/dist/lib.js +1 -1
  45. package/dist/lib.js.map +1 -1
  46. package/dist/lint.d.ts.map +1 -1
  47. package/dist/lint.js +309 -487
  48. package/dist/lint.js.map +1 -1
  49. package/dist/make/inbox.d.ts.map +1 -1
  50. package/dist/make/inbox.js +12 -5
  51. package/dist/make/inbox.js.map +1 -1
  52. package/dist/make/payments.js +3 -3
  53. package/dist/make/payments.js.map +1 -1
  54. package/dist/make/tenancy.js +1 -1
  55. package/dist/make/tenancy.js.map +1 -1
  56. package/dist/make.d.ts.map +1 -1
  57. package/dist/make.js +144 -8
  58. package/dist/make.js.map +1 -1
  59. package/dist/mcp.d.ts +1 -1
  60. package/dist/mcp.d.ts.map +1 -1
  61. package/dist/mcp.js +56 -19
  62. package/dist/mcp.js.map +1 -1
  63. package/dist/preflight.d.ts +11 -3
  64. package/dist/preflight.d.ts.map +1 -1
  65. package/dist/preflight.js +139 -16
  66. package/dist/preflight.js.map +1 -1
  67. package/dist/provider-add.d.ts.map +1 -1
  68. package/dist/provider-add.js +149 -9
  69. package/dist/provider-add.js.map +1 -1
  70. package/dist/provider-audit.d.ts +13 -0
  71. package/dist/provider-audit.d.ts.map +1 -1
  72. package/dist/provider-audit.js +122 -8
  73. package/dist/provider-audit.js.map +1 -1
  74. package/dist/registry-edits.d.ts +7 -0
  75. package/dist/registry-edits.d.ts.map +1 -1
  76. package/dist/registry-edits.js +237 -53
  77. package/dist/registry-edits.js.map +1 -1
  78. package/dist/templates/agents.d.ts.map +1 -1
  79. package/dist/templates/agents.js +29 -7
  80. package/dist/templates/agents.js.map +1 -1
  81. package/dist/templates/base.d.ts +1 -0
  82. package/dist/templates/base.d.ts.map +1 -1
  83. package/dist/templates/base.js +20 -6
  84. package/dist/templates/base.js.map +1 -1
  85. package/dist/templates/db/index.d.ts +1 -0
  86. package/dist/templates/db/index.d.ts.map +1 -1
  87. package/dist/templates/db/index.js.map +1 -1
  88. package/dist/templates/db/migration-status.d.ts +9 -0
  89. package/dist/templates/db/migration-status.d.ts.map +1 -0
  90. package/dist/templates/db/migration-status.js +117 -0
  91. package/dist/templates/db/migration-status.js.map +1 -0
  92. package/dist/templates/db/mysql.d.ts.map +1 -1
  93. package/dist/templates/db/mysql.js +23 -0
  94. package/dist/templates/db/mysql.js.map +1 -1
  95. package/dist/templates/db/postgres.d.ts.map +1 -1
  96. package/dist/templates/db/postgres.js +21 -0
  97. package/dist/templates/db/postgres.js.map +1 -1
  98. package/dist/templates/db/sqlite.d.ts.map +1 -1
  99. package/dist/templates/db/sqlite.js +53 -0
  100. package/dist/templates/db/sqlite.js.map +1 -1
  101. package/dist/templates/index.d.ts.map +1 -1
  102. package/dist/templates/index.js +5 -0
  103. package/dist/templates/index.js.map +1 -1
  104. package/dist/templates/server.d.ts.map +1 -1
  105. package/dist/templates/server.js +2 -0
  106. package/dist/templates/server.js.map +1 -1
  107. package/dist/templates/shared.d.ts +1 -0
  108. package/dist/templates/shared.d.ts.map +1 -1
  109. package/dist/templates/shared.js +4 -2
  110. package/dist/templates/shared.js.map +1 -1
  111. package/package.json +7 -6
  112. package/skills/app-structure/SKILL.md +56 -11
  113. package/src/analysis/contract-factories.ts +256 -0
  114. package/src/analysis/layers.ts +266 -0
  115. package/src/analysis/port-wiring.ts +506 -0
  116. package/src/analysis/source-index.ts +225 -57
  117. package/src/analysis/workspace.ts +115 -6
  118. package/src/app-map.ts +418 -109
  119. package/src/check.ts +86 -10
  120. package/src/db.ts +97 -0
  121. package/src/explain.ts +4 -4
  122. package/src/index.ts +108 -7
  123. package/src/inspect.ts +659 -216
  124. package/src/lib.ts +9 -1
  125. package/src/lint.ts +393 -643
  126. package/src/make/inbox.ts +12 -5
  127. package/src/make/payments.ts +3 -3
  128. package/src/make/tenancy.ts +1 -1
  129. package/src/make.ts +225 -8
  130. package/src/mcp.ts +73 -21
  131. package/src/preflight.ts +195 -16
  132. package/src/provider-add.ts +193 -9
  133. package/src/provider-audit.ts +210 -18
  134. package/src/registry-edits.ts +313 -63
  135. package/src/templates/agents.ts +29 -7
  136. package/src/templates/base.ts +21 -4
  137. package/src/templates/db/index.ts +1 -0
  138. package/src/templates/db/migration-status.ts +126 -0
  139. package/src/templates/db/mysql.ts +23 -0
  140. package/src/templates/db/postgres.ts +21 -0
  141. package/src/templates/db/sqlite.ts +53 -0
  142. package/src/templates/index.ts +5 -0
  143. package/src/templates/server.ts +2 -0
  144. package/src/templates/shared.ts +4 -2
@@ -27,12 +27,19 @@ export type LocalImportReference = {
27
27
  column: number;
28
28
  };
29
29
 
30
+ export type ModuleReference = LocalImportReference & {
31
+ syntax: "dynamic-import" | "export" | "import" | "import-equals" | "require";
32
+ staticallyResolvable: boolean;
33
+ };
34
+
30
35
  type ImportBinding =
31
36
  | { kind: "named"; file: string; exportName: string }
32
37
  | { kind: "namespace"; file: string };
33
38
 
34
39
  type FileIndex = {
35
40
  declarations: Map<string, ts.Declaration>;
41
+ localExports: Map<string, string>;
42
+ locals: Map<string, ts.Declaration>;
36
43
  imports: Map<string, ImportBinding>;
37
44
  reexports: Map<string, { file: string; exportName: string }>;
38
45
  exportStars: string[];
@@ -68,6 +75,8 @@ export function createSourceIndex(workspace: AnalysisWorkspace): SourceIndex {
68
75
  const sourceFile = await workspace.readSourceFile(file);
69
76
  const index: FileIndex = {
70
77
  declarations: new Map(),
78
+ localExports: new Map(),
79
+ locals: new Map(),
71
80
  imports: new Map(),
72
81
  reexports: new Map(),
73
82
  exportStars: [],
@@ -115,6 +124,22 @@ export function createSourceIndex(workspace: AnalysisWorkspace): SourceIndex {
115
124
  const target = source
116
125
  ? resolveLocalSourceFile(workspace, file, source)
117
126
  : undefined;
127
+
128
+ if (!statement.moduleSpecifier) {
129
+ if (
130
+ statement.exportClause &&
131
+ ts.isNamedExports(statement.exportClause)
132
+ ) {
133
+ for (const element of statement.exportClause.elements) {
134
+ if (statement.isTypeOnly || element.isTypeOnly) continue;
135
+ index.localExports.set(
136
+ element.name.text,
137
+ element.propertyName?.text ?? element.name.text,
138
+ );
139
+ }
140
+ }
141
+ continue;
142
+ }
118
143
  if (!target) continue;
119
144
 
120
145
  if (!statement.exportClause) {
@@ -133,10 +158,13 @@ export function createSourceIndex(workspace: AnalysisWorkspace): SourceIndex {
133
158
  continue;
134
159
  }
135
160
 
136
- if (ts.isVariableStatement(statement) && hasExportModifier(statement)) {
161
+ if (ts.isVariableStatement(statement)) {
137
162
  for (const declaration of statement.declarationList.declarations) {
138
163
  if (ts.isIdentifier(declaration.name)) {
139
- index.declarations.set(declaration.name.text, declaration);
164
+ index.locals.set(declaration.name.text, declaration);
165
+ if (hasExportModifier(statement)) {
166
+ index.declarations.set(declaration.name.text, declaration);
167
+ }
140
168
  }
141
169
  }
142
170
  continue;
@@ -148,10 +176,12 @@ export function createSourceIndex(workspace: AnalysisWorkspace): SourceIndex {
148
176
  ts.isInterfaceDeclaration(statement) ||
149
177
  ts.isTypeAliasDeclaration(statement) ||
150
178
  ts.isEnumDeclaration(statement)) &&
151
- hasExportModifier(statement) &&
152
179
  statement.name
153
180
  ) {
154
- index.declarations.set(statement.name.text, statement);
181
+ index.locals.set(statement.name.text, statement);
182
+ if (hasExportModifier(statement)) {
183
+ index.declarations.set(statement.name.text, statement);
184
+ }
155
185
  }
156
186
  }
157
187
 
@@ -171,6 +201,14 @@ export function createSourceIndex(workspace: AnalysisWorkspace): SourceIndex {
171
201
  const declaration = index.declarations.get(exportName);
172
202
  if (declaration) return { file, exportName, declaration };
173
203
 
204
+ const localName = index.localExports.get(exportName);
205
+ const localDeclaration = localName
206
+ ? index.locals.get(localName)
207
+ : undefined;
208
+ if (localDeclaration) {
209
+ return { file, exportName, declaration: localDeclaration };
210
+ }
211
+
174
212
  const reexport = index.reexports.get(exportName);
175
213
  if (reexport) {
176
214
  return (
@@ -192,7 +230,7 @@ export function createSourceIndex(workspace: AnalysisWorkspace): SourceIndex {
192
230
  return {
193
231
  async resolveName(file, name) {
194
232
  const index = await readIndex(file);
195
- const local = index.declarations.get(name);
233
+ const local = index.locals.get(name);
196
234
  if (local) {
197
235
  return { file, exportName: name, declaration: local };
198
236
  }
@@ -267,20 +305,48 @@ export function exportedVariableDeclarations(
267
305
  declaration: ts.VariableDeclaration;
268
306
  }> = [];
269
307
 
308
+ const locals = new Map<string, ts.VariableDeclaration>();
309
+ const seen = new Set<string>();
310
+ const addDeclaration = (
311
+ exportName: string,
312
+ declaration: ts.VariableDeclaration,
313
+ ) => {
314
+ const key = `${exportName}:${declaration.pos}`;
315
+ if (seen.has(key)) return;
316
+ seen.add(key);
317
+ declarations.push({ exportName, declaration });
318
+ };
319
+
270
320
  for (const statement of sourceFile.statements) {
271
- if (!ts.isVariableStatement(statement) || !hasExportModifier(statement)) {
272
- continue;
273
- }
321
+ if (!ts.isVariableStatement(statement)) continue;
274
322
  for (const declaration of statement.declarationList.declarations) {
275
323
  if (ts.isIdentifier(declaration.name)) {
276
- declarations.push({
277
- exportName: declaration.name.text,
278
- declaration,
279
- });
324
+ locals.set(declaration.name.text, declaration);
325
+ if (hasExportModifier(statement)) {
326
+ addDeclaration(declaration.name.text, declaration);
327
+ }
280
328
  }
281
329
  }
282
330
  }
283
331
 
332
+ for (const statement of sourceFile.statements) {
333
+ if (
334
+ !ts.isExportDeclaration(statement) ||
335
+ statement.moduleSpecifier ||
336
+ statement.isTypeOnly ||
337
+ !statement.exportClause ||
338
+ !ts.isNamedExports(statement.exportClause)
339
+ ) {
340
+ continue;
341
+ }
342
+ for (const element of statement.exportClause.elements) {
343
+ if (element.isTypeOnly) continue;
344
+ const localName = element.propertyName?.text ?? element.name.text;
345
+ const declaration = locals.get(localName);
346
+ if (declaration) addDeclaration(element.name.text, declaration);
347
+ }
348
+ }
349
+
284
350
  return declarations;
285
351
  }
286
352
 
@@ -321,64 +387,166 @@ export async function localImportReferences(
321
387
  workspace: AnalysisWorkspace,
322
388
  file: string,
323
389
  ): Promise<LocalImportReference[]> {
324
- const sourceFile = await workspace.readSourceFile(file);
325
- const references: LocalImportReference[] = [];
326
-
327
- for (const statement of sourceFile.statements) {
328
- if (
329
- !ts.isImportDeclaration(statement) &&
330
- !ts.isExportDeclaration(statement)
331
- ) {
332
- continue;
333
- }
334
- if (!statement.moduleSpecifier) continue;
335
-
336
- const importPath = moduleText(statement.moduleSpecifier);
337
- const resolvedFile = importPath
338
- ? resolveLocalSourceFile(workspace, file, importPath)
339
- : undefined;
340
- if (
341
- !importPath ||
342
- (!resolvedFile && !isLocalModuleSpecifier(workspace, file, importPath))
343
- ) {
344
- continue;
345
- }
390
+ const references = await moduleReferences(workspace, file);
391
+
392
+ return references
393
+ .filter(
394
+ (reference) =>
395
+ reference.staticallyResolvable &&
396
+ (reference.resolvedFile ||
397
+ isLocalModuleSpecifier(workspace, file, reference.importPath)),
398
+ )
399
+ .map(
400
+ ({ syntax: _syntax, staticallyResolvable: _static, ...reference }) =>
401
+ reference,
402
+ );
403
+ }
346
404
 
405
+ export async function moduleReferences(
406
+ workspace: AnalysisWorkspace,
407
+ file: string,
408
+ ): Promise<ModuleReference[]> {
409
+ const sourceFile = await workspace.readSourceFile(file);
410
+ const references: ModuleReference[] = [];
411
+
412
+ const addReference = ({
413
+ importPath,
414
+ kind,
415
+ node,
416
+ staticallyResolvable,
417
+ syntax,
418
+ }: {
419
+ importPath: string;
420
+ kind: "type" | "value";
421
+ node: ts.Node;
422
+ staticallyResolvable: boolean;
423
+ syntax: ModuleReference["syntax"];
424
+ }) => {
347
425
  const position = sourceFile.getLineAndCharacterOfPosition(
348
- statement.getStart(sourceFile),
426
+ node.getStart(sourceFile),
349
427
  );
350
- const typeOnly = ts.isImportDeclaration(statement)
351
- ? Boolean(
352
- statement.importClause?.isTypeOnly ||
353
- (statement.importClause?.namedBindings &&
354
- ts.isNamedImports(statement.importClause.namedBindings) &&
355
- statement.importClause.namedBindings.elements.length > 0 &&
356
- statement.importClause.namedBindings.elements.every(
357
- (element) => element.isTypeOnly,
358
- )),
359
- )
360
- : statement.isTypeOnly ||
361
- (statement.exportClause &&
362
- ts.isNamedExports(statement.exportClause) &&
363
- statement.exportClause.elements.length > 0 &&
364
- statement.exportClause.elements.every(
365
- (element) => element.isTypeOnly,
366
- ));
367
428
  references.push({
368
429
  file,
369
430
  importPath,
370
- resolvedFile,
371
- kind: typeOnly ? "type" : "value",
431
+ ...(staticallyResolvable
432
+ ? { resolvedFile: resolveLocalSourceFile(workspace, file, importPath) }
433
+ : {}),
434
+ kind,
372
435
  line: position.line + 1,
373
436
  column: position.character + 1,
437
+ syntax,
438
+ staticallyResolvable,
374
439
  });
375
- }
440
+ };
441
+
442
+ const visit = (node: ts.Node) => {
443
+ if (ts.isImportDeclaration(node) && node.moduleSpecifier) {
444
+ const importPath = moduleText(node.moduleSpecifier);
445
+ if (importPath) {
446
+ addReference({
447
+ importPath,
448
+ kind: importDeclarationKind(node),
449
+ node,
450
+ staticallyResolvable: true,
451
+ syntax: "import",
452
+ });
453
+ }
454
+ } else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
455
+ const importPath = moduleText(node.moduleSpecifier);
456
+ if (importPath) {
457
+ addReference({
458
+ importPath,
459
+ kind: exportDeclarationKind(node),
460
+ node,
461
+ staticallyResolvable: true,
462
+ syntax: "export",
463
+ });
464
+ }
465
+ } else if (
466
+ ts.isImportEqualsDeclaration(node) &&
467
+ ts.isExternalModuleReference(node.moduleReference) &&
468
+ node.moduleReference.expression
469
+ ) {
470
+ const importPath = moduleText(node.moduleReference.expression);
471
+ if (importPath) {
472
+ addReference({
473
+ importPath,
474
+ kind: node.isTypeOnly ? "type" : "value",
475
+ node,
476
+ staticallyResolvable: true,
477
+ syntax: "import-equals",
478
+ });
479
+ }
480
+ } else if (ts.isCallExpression(node)) {
481
+ const syntax =
482
+ node.expression.kind === ts.SyntaxKind.ImportKeyword
483
+ ? "dynamic-import"
484
+ : ts.isIdentifier(node.expression) &&
485
+ node.expression.text === "require"
486
+ ? "require"
487
+ : undefined;
488
+ if (syntax) {
489
+ const argument = node.arguments[0];
490
+ const importPath = moduleText(argument);
491
+ addReference({
492
+ importPath:
493
+ importPath ?? argument?.getText(sourceFile) ?? "<missing>",
494
+ kind: "value",
495
+ node,
496
+ staticallyResolvable: importPath !== undefined,
497
+ syntax,
498
+ });
499
+ }
500
+ }
501
+
502
+ ts.forEachChild(node, visit);
503
+ };
504
+
505
+ visit(sourceFile);
376
506
 
377
507
  return references;
378
508
  }
379
509
 
380
- function moduleText(node: ts.Expression): string | undefined {
381
- return ts.isStringLiteral(node) ? node.text : undefined;
510
+ function importDeclarationKind(
511
+ declaration: ts.ImportDeclaration,
512
+ ): "type" | "value" {
513
+ const clause = declaration.importClause;
514
+ if (!clause) return "value";
515
+ if (clause.isTypeOnly) return "type";
516
+ if (clause.name) return "value";
517
+
518
+ const bindings = clause.namedBindings;
519
+ if (
520
+ bindings &&
521
+ ts.isNamedImports(bindings) &&
522
+ bindings.elements.length > 0 &&
523
+ bindings.elements.every((element) => element.isTypeOnly)
524
+ ) {
525
+ return "type";
526
+ }
527
+ return "value";
528
+ }
529
+
530
+ function exportDeclarationKind(
531
+ declaration: ts.ExportDeclaration,
532
+ ): "type" | "value" {
533
+ if (declaration.isTypeOnly) return "type";
534
+ if (
535
+ declaration.exportClause &&
536
+ ts.isNamedExports(declaration.exportClause) &&
537
+ declaration.exportClause.elements.length > 0 &&
538
+ declaration.exportClause.elements.every((element) => element.isTypeOnly)
539
+ ) {
540
+ return "type";
541
+ }
542
+ return "value";
543
+ }
544
+
545
+ function moduleText(node: ts.Expression | undefined): string | undefined {
546
+ return node &&
547
+ (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
548
+ ? node.text
549
+ : undefined;
382
550
  }
383
551
 
384
552
  function hasExportModifier(node: ts.Node): boolean {
@@ -2,9 +2,11 @@ import { readdir, readFile, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import ts from "typescript";
4
4
  import {
5
+ type BeignetConfig,
5
6
  loadBeignetConfig,
6
7
  normalizePath,
7
8
  type ResolvedBeignetConfig,
9
+ resolveConfig,
8
10
  } from "../config.js";
9
11
 
10
12
  const ignoredDirectoryNames = new Set([
@@ -22,19 +24,34 @@ export type AnalysisWorkspace = {
22
24
  fileSet: Set<string>;
23
25
  config: ResolvedBeignetConfig;
24
26
  compilerOptions: ts.CompilerOptions;
27
+ moduleResolutionCache: ts.ModuleResolutionCache;
25
28
  readSource(file: string): Promise<string>;
26
29
  readSourceFile(file: string): Promise<ts.SourceFile>;
27
30
  };
28
31
 
32
+ type AnalysisWorkspaceOptions = {
33
+ config?: BeignetConfig;
34
+ };
35
+
29
36
  export async function createAnalysisWorkspace(
30
37
  cwd = process.cwd(),
38
+ options: AnalysisWorkspaceOptions = {},
31
39
  ): Promise<AnalysisWorkspace> {
32
40
  const targetDir = path.resolve(cwd);
33
41
  await assertDirectory(targetDir);
34
42
  const files = await listProjectFiles(targetDir);
35
43
  const fileSet = new Set(files);
36
- const config = await loadBeignetConfig(targetDir, files);
44
+ const config = options.config
45
+ ? resolveConfig(options.config)
46
+ : await loadBeignetConfig(targetDir, files);
37
47
  const compilerOptions = resolveCompilerOptions(targetDir);
48
+ const moduleResolutionCache = ts.createModuleResolutionCache(
49
+ targetDir,
50
+ ts.sys.useCaseSensitiveFileNames
51
+ ? (fileName) => fileName
52
+ : (fileName) => fileName.toLowerCase(),
53
+ compilerOptions,
54
+ );
38
55
  const sources = new Map<string, string>();
39
56
  const sourceFiles = new Map<string, ts.SourceFile>();
40
57
 
@@ -53,6 +70,7 @@ export async function createAnalysisWorkspace(
53
70
  fileSet,
54
71
  config,
55
72
  compilerOptions,
73
+ moduleResolutionCache,
56
74
  readSource,
57
75
  async readSourceFile(file) {
58
76
  const cached = sourceFiles.get(file);
@@ -111,7 +129,7 @@ export async function listProjectFiles(targetDir: string): Promise<string[]> {
111
129
  export function resolveLocalSourceFile(
112
130
  workspace: Pick<
113
131
  AnalysisWorkspace,
114
- "compilerOptions" | "fileSet" | "targetDir"
132
+ "compilerOptions" | "fileSet" | "moduleResolutionCache" | "targetDir"
115
133
  >,
116
134
  importerFile: string,
117
135
  importPath: string,
@@ -126,10 +144,62 @@ export function resolveLocalSourceFile(
126
144
  return workspace.fileSet.has(file) ? file : undefined;
127
145
  }
128
146
 
147
+ export function resolveLocalModulePath(
148
+ workspace: Pick<
149
+ AnalysisWorkspace,
150
+ "compilerOptions" | "fileSet" | "moduleResolutionCache" | "targetDir"
151
+ >,
152
+ importerFile: string,
153
+ importPath: string,
154
+ ): string | undefined {
155
+ const resolvedFile = resolveLocalSourceFile(
156
+ workspace,
157
+ importerFile,
158
+ importPath,
159
+ );
160
+ if (resolvedFile) return resolvedFile;
161
+
162
+ if (!isLocalModuleSpecifier(workspace, importerFile, importPath)) {
163
+ return undefined;
164
+ }
165
+
166
+ if (importPath.startsWith(".")) {
167
+ return normalizePath(path.join(path.dirname(importerFile), importPath));
168
+ }
169
+
170
+ const baseUrl = compilerPathsBase(
171
+ workspace.compilerOptions,
172
+ workspace.targetDir,
173
+ );
174
+ for (const [pattern, targets] of Object.entries(
175
+ workspace.compilerOptions.paths ?? {},
176
+ )) {
177
+ if (pattern === "*") continue;
178
+ const wildcardValue = modulePatternWildcard(pattern, importPath);
179
+ if (wildcardValue === undefined) continue;
180
+
181
+ for (const target of targets) {
182
+ const substituted = target.replace("*", wildcardValue);
183
+ const absolute = path.resolve(baseUrl, substituted);
184
+ const relative = normalizePath(
185
+ path.relative(workspace.targetDir, absolute),
186
+ );
187
+ if (relative !== ".." && !relative.startsWith("../")) return relative;
188
+ }
189
+ }
190
+
191
+ const relative = normalizePath(
192
+ path.relative(workspace.targetDir, path.resolve(baseUrl, importPath)),
193
+ );
194
+ return relative !== ".." && !relative.startsWith("../")
195
+ ? relative
196
+ : undefined;
197
+ }
198
+
129
199
  export function isLocalModuleSpecifier(
130
200
  workspace: Pick<
131
201
  AnalysisWorkspace,
132
- "compilerOptions" | "fileSet" | "targetDir"
202
+ "compilerOptions" | "fileSet" | "moduleResolutionCache" | "targetDir"
133
203
  >,
134
204
  importerFile: string,
135
205
  importPath: string,
@@ -180,7 +250,10 @@ export function isLocalModuleSpecifier(
180
250
  }
181
251
 
182
252
  function resolveModule(
183
- workspace: Pick<AnalysisWorkspace, "compilerOptions" | "targetDir">,
253
+ workspace: Pick<
254
+ AnalysisWorkspace,
255
+ "compilerOptions" | "moduleResolutionCache" | "targetDir"
256
+ >,
184
257
  importerFile: string,
185
258
  importPath: string,
186
259
  ): ts.ResolvedModuleFull | undefined {
@@ -189,6 +262,7 @@ function resolveModule(
189
262
  path.join(workspace.targetDir, importerFile),
190
263
  workspace.compilerOptions,
191
264
  ts.sys,
265
+ workspace.moduleResolutionCache,
192
266
  ).resolvedModule;
193
267
  }
194
268
 
@@ -201,8 +275,23 @@ function modulePatternMatches(pattern: string, importPath: string): boolean {
201
275
  );
202
276
  }
203
277
 
278
+ function modulePatternWildcard(
279
+ pattern: string,
280
+ importPath: string,
281
+ ): string | undefined {
282
+ const wildcard = pattern.indexOf("*");
283
+ if (wildcard === -1) return pattern === importPath ? "" : undefined;
284
+
285
+ const prefix = pattern.slice(0, wildcard);
286
+ const suffix = pattern.slice(wildcard + 1);
287
+ if (!importPath.startsWith(prefix) || !importPath.endsWith(suffix)) {
288
+ return undefined;
289
+ }
290
+ return importPath.slice(prefix.length, importPath.length - suffix.length);
291
+ }
292
+
204
293
  function resolveCompilerOptions(targetDir: string): ts.CompilerOptions {
205
- return (
294
+ const compilerOptions =
206
295
  ts.getParsedCommandLineOfConfigFile(
207
296
  path.join(targetDir, "tsconfig.json"),
208
297
  {},
@@ -210,7 +299,27 @@ function resolveCompilerOptions(targetDir: string): ts.CompilerOptions {
210
299
  ...ts.sys,
211
300
  onUnRecoverableConfigFileDiagnostic: () => {},
212
301
  },
213
- )?.options ?? {}
302
+ )?.options ?? {};
303
+
304
+ if (compilerOptions.paths) return compilerOptions;
305
+ return {
306
+ ...compilerOptions,
307
+ baseUrl: compilerOptions.baseUrl ?? targetDir,
308
+ paths: {
309
+ "@/*": ["*"],
310
+ },
311
+ };
312
+ }
313
+
314
+ function compilerPathsBase(
315
+ compilerOptions: ts.CompilerOptions,
316
+ targetDir: string,
317
+ ): string {
318
+ const optionsWithPathBase = compilerOptions as ts.CompilerOptions & {
319
+ pathsBasePath?: string;
320
+ };
321
+ return (
322
+ compilerOptions.baseUrl ?? optionsWithPathBase.pathsBasePath ?? targetDir
214
323
  );
215
324
  }
216
325