Skip to content

Conversation

chase
Copy link

@chase chase commented Oct 18, 2025

Extends the symlink support in GetEachFileNameOfModule to properly resolve module specifiers across symlinked packages and workspaces.

Key changes:

  • Move knownsymlinks from compiler to dedicated symlinks package
  • Implement active resolution via ResolveModuleName to populate cache
  • Add dependency resolution from package.json to detect symlinks early
  • Improve ignored path handling (node_modules/., .git, .# emacs locks)
  • Add comprehensive test coverage for symlink resolution
  • Fix declaration emit to prefer original paths over symlink paths

@chase
Copy link
Author

chase commented Oct 18, 2025

My intention is to help fix #1034, which is the only thing left blocking my team from adopting tsgo.

I'm not very experienced with Go, but I wanted to expand upon @shinichy's work here: https://github.com/shinichy/typescript-go/tree/symlinks

@neo773
Copy link

neo773 commented Oct 18, 2025

We have the same issue in our team that's stopping us from adopting tsgo

So I compiled your branch and ran a test, it doesn't seem to make any difference in our code base. Just wanted to let you know.

neo@neos-MacBook-Pro twenty-server % /Users/neo/Desktop/typescript-go/built/local/tsgo          
src/engine/api/mcp/services/tools/mcp-metadata-tools.service.ts:90:9 - error TS2742: The inferred type of 'send' cannot be named without a reference to '../../../../../../../../node_modules/axios/index.d.cts'. This is likely not portable. A type annotation is necessary.

90   async send(requestContext: RequestContext, data: Query) {
           ~~~~

src/engine/api/rest/metadata/rest-api-metadata.service.ts:24:9 - error TS2742: The inferred type of 'get' cannot be named without a reference to '../../../../../../../node_modules/axios/index.d.cts'. This is likely not portable. A type annotation is necessary.

24   async get(request: Request) {
           ~~~

src/engine/api/rest/metadata/rest-api-metadata.service.ts:38:9 - error TS2742: The inferred type of 'create' cannot be named without a reference to '../../../../../../../node_modules/axios/index.d.cts'. This is likely not portable. A type annotation is necessary.

38   async create(request: Request) {
           ~~~~~~

src/engine/api/rest/metadata/rest-api-metadata.service.ts:52:9 - error TS2742: The inferred type of 'update' cannot be named without a reference to '../../../../../../../node_modules/axios/index.d.cts'. This is likely not portable. A type annotation is necessary.

52   async update(request: Request) {
           ~~~~~~

src/engine/api/rest/metadata/rest-api-metadata.service.ts:66:9 - error TS2742: The inferred type of 'delete' cannot be named without a reference to '../../../../../../../node_modules/axios/index.d.cts'. This is likely not portable. A type annotation is necessary.

66   async delete(request: Request) {
           ~~~~~~

src/engine/metadata-modules/constants/search-vector-field.constants.ts:3:14 - error TS2742: The inferred type of 'SEARCH_VECTOR_FIELD' cannot be named without a reference to '../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

3 export const SEARCH_VECTOR_FIELD = {
               ~~~~~~~~~~~~~~~~~~~

src/engine/twenty-orm/relation-nested-queries/utils/formatConnectRecordNotFoundErrorMessage.util.ts:5:14 - error TS2742: The inferred type of 'formatConnectRecordNotFoundErrorMessage' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

5 export const formatConnectRecordNotFoundErrorMessage = (
               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/prefill-views.ts:21:14 - error TS2742: The inferred type of 'prefillViews' cannot be named without a reference to '../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

21 export const prefillViews = async (
                ~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view.ts:12:14 - error TS2742: The inferred type of 'companiesAllView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

12 export const companiesAllView = (
                ~~~~~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/custom-all.view.ts:6:14 - error TS2742: The inferred type of 'customAllView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

6 export const customAllView = (
               ~~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/dashboards-all.view.ts:11:14 - error TS2742: The inferred type of 'dashboardsAllView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

11 export const dashboardsAllView = (
                ~~~~~~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view.ts:10:14 - error TS2742: The inferred type of 'notesAllView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

10 export const notesAllView = (
                ~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/opportunities-all.view.ts:8:14 - error TS2742: The inferred type of 'opportunitiesAllView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

8 export const opportunitiesAllView = (
               ~~~~~~~~~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-by-stage.view.ts:8:14 - error TS2742: The inferred type of 'opportunitiesByStageView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

8 export const opportunitiesByStageView = (
               ~~~~~~~~~~~~~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-table-by-stage.view.ts:8:14 - error TS2742: The inferred type of 'opportunitiesTableByStageView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

8 export const opportunitiesTableByStageView = (
               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/people-all.view.ts:11:14 - error TS2742: The inferred type of 'peopleAllView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

11 export const peopleAllView = (
                ~~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view.ts:10:14 - error TS2742: The inferred type of 'tasksAllView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

10 export const tasksAllView = (
                ~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-assigned-to-me.ts:11:14 - error TS2742: The inferred type of 'tasksAssignedToMeView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

11 export const tasksAssignedToMeView = (
                ~~~~~~~~~~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view.ts:10:14 - error TS2742: The inferred type of 'tasksByStatusView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

10 export const tasksByStatusView = (
                ~~~~~~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view.ts:8:14 - error TS2742: The inferred type of 'workflowRunsAllView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

8 export const workflowRunsAllView = (
               ~~~~~~~~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view.ts:11:14 - error TS2742: The inferred type of 'workflowVersionsAllView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

11 export const workflowVersionsAllView = (
                ~~~~~~~~~~~~~~~~~~~~~~~

src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts:11:14 - error TS2742: The inferred type of 'workflowsAllView' cannot be named without a reference to '../../../../../../../node_modules/@lingui/core/dist/index.d.cts'. This is likely not portable. A type annotation is necessary.

11 export const workflowsAllView = (
                ~~~~~~~~~~~~~~~~

src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts:14:28 - error TS2503: Cannot find namespace 'DOMPurify'.

14   private readonly purify: DOMPurify.DOMPurify;
                              ~~~~~~~~~

src/utils/image.ts:27:18 - error TS2749: 'Axios' refers to a value, but is being used as a type here. Did you mean 'typeof Axios'?

27   axiosInstance: Axios,
                    ~~~~~

test/integration/metadata/suites/field-metadata/morph-relation/failing-field-metadata-morph-relation-creation.integration-spec.ts:1:23 - error TS2307: Cannot find module '@faker-js/faker/.' or its corresponding type declarations.

1 import { faker } from '@faker-js/faker/.';
                        ~~~~~~~~~~~~~~~~~~~


Found 25 errors in 22 files.

Errors  Files
     1  src/engine/api/mcp/services/tools/mcp-metadata-tools.service.ts:90
     4  src/engine/api/rest/metadata/rest-api-metadata.service.ts:24
     1  src/engine/metadata-modules/constants/search-vector-field.constants.ts:3
     1  src/engine/twenty-orm/relation-nested-queries/utils/formatConnectRecordNotFoundErrorMessage.util.ts:5
     1  src/engine/workspace-manager/standard-objects-prefill-data/prefill-views.ts:21
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view.ts:12
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/custom-all.view.ts:6
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/dashboards-all.view.ts:11
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view.ts:10
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/opportunities-all.view.ts:8
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-by-stage.view.ts:8
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-table-by-stage.view.ts:8
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/people-all.view.ts:11
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view.ts:10
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-assigned-to-me.ts:11
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view.ts:10
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view.ts:8
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view.ts:11
     1  src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts:11
     1  src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts:14
     1  src/utils/image.ts:27
     1  test/integration/metadata/suites/field-metadata/morph-relation/failing-field-metadata-morph-relation-creation.integration-spec.ts:1

neo@neos-MacBook-Pro twenty-server % 

chase added 3 commits October 18, 2025 20:19
Extends the symlink support in GetEachFileNameOfModule to properly
resolve module specifiers across symlinked packages and workspaces.

Key changes:
- Move knownsymlinks from compiler to dedicated symlinks package
- Implement active resolution via ResolveModuleName to populate cache
- Add dependency resolution from package.json to detect symlinks early
- Improve ignored path handling (node_modules/., .git, .# emacs locks)
- Add comprehensive test coverage for symlink resolution
- Fix declaration emit to prefer original paths over symlink paths

This aligns with upstream TypeScript's symlink resolution behavior,
ensuring correct module specifiers in declaration files for monorepos
and symlinked dependencies.

Fixes baseline mismatches in:
- declarationEmitReexportedSymlinkReference2/3
- symlinkedWorkspaceDependencies* tests
- nodeModuleReexportFromDottedPath
Optimizes populateSymlinkCacheFromResolutions to avoid redundant
dependency resolution. Previously, every module specifier generation
would re-resolve all package.json dependencies. Now uses package-level
caching to resolve once and reuse results.

Performance improvements (measured with benchmarks):
- Speed: 9.28x faster (89.2% reduction: 509µs → 55µs per operation)
- Memory: 8.64x less (88.4% reduction: 597KB → 69KB)
- Allocations: 9.22x fewer (89.2% reduction: 12,177 → 1,321)

Key changes:
- Add package-level cache tracking in KnownSymlinks
- Eliminate intermediate slice allocations
- Reduce redundant ToPath() calls
- Add comprehensive benchmarks for symlink operations

For a project with 50 dependencies and 100 files, this saves multiple
seconds of compilation time by avoiding 5,000+ redundant resolutions.
@chase
Copy link
Author

chase commented Oct 18, 2025

We have the same issue in our team that's stopping us from adopting tsgo

So I compiled your branch and ran a test, it doesn't seem to make any difference in our code base. Just wanted to let you know.

Interesting. This is what I get in Linux after setting up your repo and replacing tsc in node_modules/.bin so that nx uses it instead:

src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts(14,28): error TS2503: Cannot find namespace 'DOMPurify'.
src/utils/image.ts(27,18): error TS2749: 'Axios' refers to a value, but is being used as a type here. Did you mean 'typeof Axios'?
test/integration/metadata/suites/field-metadata/morph-relation/failing-field-metadata-morph-relation-creation.integration-spec.ts(1,23): error TS2307: Cannot find module '@faker-js/faker/.' or its corresponding type declarations.

Seems like this might need some fixes for macOS.

@chase
Copy link
Author

chase commented Oct 19, 2025

@neo773 should be fixed with the latest commit, the above errors seem like they might be actual errors or at least ones that can be fixed trivially:

Patch

diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts
index f81f151..335fe77 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts
@@ -8,10 +8,12 @@ import * as planer from 'planer';
 
 import { safeDecodeURIComponent } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/safe-decode-uri-component.util';
 
+type DOMPurifyInstance = ReturnType<typeof DOMPurify>;
+
 @Injectable()
 export class ImapMessageTextExtractorService {
   private readonly jsdomInstance: JSDOM;
-  private readonly purify: DOMPurify.DOMPurify;
+  private readonly purify: DOMPurifyInstance;
 
   constructor() {
     this.jsdomInstance = new JSDOM('');
diff --git a/packages/twenty-server/src/utils/image.ts b/packages/twenty-server/src/utils/image.ts
index 8e98729..ad78167 100644
--- a/packages/twenty-server/src/utils/image.ts
+++ b/packages/twenty-server/src/utils/image.ts
@@ -1,4 +1,4 @@
-import { type Axios } from 'axios';
+import { type AxiosInstance } from 'axios';
 
 const cropRegex = /([w|h])([0-9]+)/;
 
@@ -24,7 +24,7 @@ export const getCropSize = (value: ShortCropSize): CropSize | null => {
 
 export const getImageBufferFromUrl = async (
   url: string,
-  axiosInstance: Axios,
+  axiosInstance: AxiosInstance,
 ): Promise<Buffer> => {
   const response = await axiosInstance.get(url, {
     responseType: 'arraybuffer',
diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-field-metadata-morph-relation-creation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-field-metadata-morph-relation-creation.integration-spec.ts
index 0aa4bb7..2981c77 100644
--- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-field-metadata-morph-relation-creation.integration-spec.ts
+++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-field-metadata-morph-relation-creation.integration-spec.ts
@@ -1,4 +1,4 @@
-import { faker } from '@faker-js/faker/.';
+import { faker } from '@faker-js/faker';
 import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
 import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
 import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';

@neo773
Copy link

neo773 commented Oct 19, 2025

@chase
With the new commit it finally works, as for the remaining type errors they don't seem to affect Strada version, but it doesn't matters.

Thank you for your work on this.

@tmm1
Copy link
Contributor

tmm1 commented Oct 19, 2025

Thanks for working on this!

Fixes #1034 (comment) also

@chase chase changed the title Improve symlink resolution in module specifier generation Fix #1034: Improve symlink resolution in module specifier generation Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants