diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b0443fdf..c3b340e7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,8 +27,11 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: yarn + - run: corepack enable - name: yarn install run: yarn + - name: prepare yarnpnp + run: cd test-data/yarn-pnp && yarn - name: build run: yarn build - name: Unittest diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a7a3cf96..6aafcb0c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -21,6 +21,7 @@ jobs: with: node-version: 18 registry-url: https://registry.npmjs.org + - run: corepack enable - name: Lint and Test if: ${{ steps.release.outputs.release_created }} run: yarn && yarn build && yarn lint diff --git a/.github/workflows/size-limit.yaml b/.github/workflows/size-limit.yaml index 4be381e1..7befd7c1 100644 --- a/.github/workflows/size-limit.yaml +++ b/.github/workflows/size-limit.yaml @@ -24,6 +24,7 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: yarn + - run: corepack enable - name: yarn install run: yarn - name: build diff --git a/src/configuration/fileSchemes.test.ts b/src/configuration/fileSchemes.test.ts new file mode 100644 index 00000000..193e3404 --- /dev/null +++ b/src/configuration/fileSchemes.test.ts @@ -0,0 +1,85 @@ +import { URI } from 'vscode-uri'; +import * as lsp from 'vscode-languageserver'; +import { beforeAll, beforeEach, afterAll, describe, it, expect } from 'vitest'; +import { uri, createServer, position, TestLspServer, openDocumentAndWaitForDiagnostics, readContents, filePath, isWindows } from '../test-utils.js'; +import { ZipfileURI } from '../utils/uri.js'; + +const ZIPFILE_URI = 'zipfile:///dir/foo.zip::path/file.ts'; + +describe('uri handling', () => { + it('parses zipfile:// uri', () => { + const parsed = URI.parse(ZIPFILE_URI); + expect(parsed.scheme).toBe('zipfile'); + expect(parsed.authority).toBe(''); + expect(parsed.path).toBe('/dir/foo.zip::path/file.ts'); + expect(parsed.fsPath).toBe(isWindows ? '\\dir\\foo.zip::path\\file.ts' : '/dir/foo.zip::path/file.ts'); + expect(parsed.query).toBe(''); + expect(parsed.fragment).toBe(''); + }); + + it('stringifies zipfile uri without encoding', () => { + const parsed = URI.parse(ZIPFILE_URI); + expect(parsed.toString(true)).toBe('zipfile:/dir/foo.zip::path/file.ts'); + }); + + it('stringifies zipfile uri with encoding', () => { + const parsed = URI.parse(ZIPFILE_URI); + expect(parsed.toString()).toBe('zipfile:/dir/foo.zip%3A%3Apath/file.ts'); + }); +}); + +describe('zipfileuri handling', () => { + it('parses zipfile:// uri', () => { + const parsed = ZipfileURI.parse(ZIPFILE_URI); + expect(parsed.scheme).toBe('zipfile'); + expect(parsed.authority).toBe(''); + expect(parsed.path).toBe('/dir/foo.zip::path/file.ts'); + expect(parsed.fsPath).toBe(isWindows ? '\\dir\\foo.zip::path\\file.ts' : '/dir/foo.zip::path/file.ts'); + expect(parsed.query).toBe(''); + expect(parsed.fragment).toBe(''); + }); + + it('stringifies zipfile uri with and without encoding', () => { + const parsed = ZipfileURI.parse(ZIPFILE_URI); + expect(parsed.toString(true)).toBe('zipfile:///dir/foo.zip::path/file.ts'); + expect(parsed.toString()).toBe('zipfile:///dir/foo.zip::path/file.ts'); + }); +}); + +describe('neovim zipfile scheme handling with yarn pnp', () => { + let server: TestLspServer; + + beforeAll(async () => { + server = await createServer({ + rootUri: uri('yarn-pnp'), + initializationOptionsOverrides: { + hostInfo: 'neovim', + }, + publishDiagnostics() {}, + }); + }); + + beforeEach(() => { + server.closeAllForTesting(); + }); + + afterAll(() => { + server.closeAllForTesting(); + server.shutdown(); + }); + + it('returns zipfile: uri for definition inside node_modules', async () => { + const doc = { + uri: uri('yarn-pnp', 'testfile.ts'), + languageId: 'typescript', + version: 1, + text: readContents(filePath('yarn-pnp', 'testfile.ts')), + }; + await openDocumentAndWaitForDiagnostics(server, doc); + const pos = position(doc, 'AxiosHeaderValue'); + const results = await server.definition({ textDocument: doc, position: pos }); + const defintion = Array.isArray(results) ? results[0] as lsp.Location : null; + expect(defintion).toBeDefined(); + expect(defintion!.uri).toMatch(/zipfile:\/\/.+.zip::node_modules\/axios\/.+/); + }); +}); diff --git a/src/configuration/fileSchemes.ts b/src/configuration/fileSchemes.ts index 29c1fea6..b9b01479 100644 --- a/src/configuration/fileSchemes.ts +++ b/src/configuration/fileSchemes.ts @@ -14,6 +14,10 @@ export const untitled = 'untitled'; export const git = 'git'; export const github = 'github'; export const azurerepos = 'azurerepos'; +// Equivalent of "untitled" in Sublime Text. +export const buffer = 'buffer'; +// For yarn berry support in neovim. +export const zipfile = 'zipfile'; /** Live share scheme */ export const vsls = 'vsls'; @@ -23,6 +27,17 @@ export const memFs = 'memfs'; export const vscodeVfs = 'vscode-vfs'; export const officeScript = 'office-script'; +export function getSemanticSupportedSchemes(): string[] { + return [ + file, + untitled, + buffer, + // walkThroughSnippet, + // vscodeNotebookCell, + zipfile, + ]; +} + /** * File scheme for which JS/TS language feature should be disabled */ diff --git a/src/diagnostic-queue.ts b/src/diagnostic-queue.ts index 4d8d98b0..eb22d243 100644 --- a/src/diagnostic-queue.ts +++ b/src/diagnostic-queue.ts @@ -87,7 +87,7 @@ export class DiagnosticEventQueue { if (this.ignoredDiagnosticCodes.size) { diagnostics = diagnostics.filter(diagnostic => !this.isDiagnosticIgnored(diagnostic)); } - const uri = this.client.toResource(file).toString(); + const uri = this.client.toResourceUri(file); const diagnosticsForFile = this.diagnostics.get(uri) || new FileDiagnostics(uri, this.publishDiagnostics, this.client, this.features); diagnosticsForFile.update(kind, diagnostics); this.diagnostics.set(uri, diagnosticsForFile); @@ -98,12 +98,12 @@ export class DiagnosticEventQueue { } public getDiagnosticsForFile(file: string): lsp.Diagnostic[] { - const uri = this.client.toResource(file).toString(); + const uri = this.client.toResourceUri(file); return this.diagnostics.get(uri)?.getDiagnostics() || []; } public onDidCloseFile(file: string): void { - const uri = this.client.toResource(file).toString(); + const uri = this.client.toResourceUri(file); const diagnosticsForFile = this.diagnostics.get(uri); diagnosticsForFile?.onDidClose(); this.diagnostics.delete(uri); @@ -113,7 +113,7 @@ export class DiagnosticEventQueue { * A testing function to clear existing file diagnostics, request fresh ones and wait for all to arrive. */ public async waitForDiagnosticsForTesting(file: string): Promise { - const uri = this.client.toResource(file).toString(); + const uri = this.client.toResourceUri(file); let diagnosticsForFile = this.diagnostics.get(uri); if (diagnosticsForFile) { diagnosticsForFile.onDidClose(); diff --git a/src/features/call-hierarchy.ts b/src/features/call-hierarchy.ts index 59d61b03..22988d0f 100644 --- a/src/features/call-hierarchy.ts +++ b/src/features/call-hierarchy.ts @@ -26,7 +26,7 @@ export function fromProtocolCallHierarchyItem(item: ts.server.protocol.CallHiera kind: fromProtocolScriptElementKind(item.kind), name, detail, - uri: client.toResource(item.file).toString(), + uri: client.toResourceUri(item.file), range: Range.fromTextSpan(item.span), selectionRange: Range.fromTextSpan(item.selectionSpan), }; diff --git a/src/features/code-lens/implementationsCodeLens.ts b/src/features/code-lens/implementationsCodeLens.ts index 08f31637..774dc79e 100644 --- a/src/features/code-lens/implementationsCodeLens.ts +++ b/src/features/code-lens/implementationsCodeLens.ts @@ -51,7 +51,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip const locations = response.body .map(reference => // Only take first line on implementation: https://github.com/microsoft/vscode/issues/23924 - Location.create(this.client.toResource(reference.file).toString(), + Location.create(this.client.toResourceUri(reference.file), reference.start.line === reference.end.line ? typeConverters.Range.fromTextSpan(reference) : Range.create( diff --git a/src/features/code-lens/referencesCodeLens.ts b/src/features/code-lens/referencesCodeLens.ts index 9f9e2315..9de4a876 100644 --- a/src/features/code-lens/referencesCodeLens.ts +++ b/src/features/code-lens/referencesCodeLens.ts @@ -47,7 +47,7 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens const locations = response.body.refs .filter(reference => !reference.isDefinition) .map(reference => - typeConverters.Location.fromTextSpan(this.client.toResource(reference.file).toString(), reference)); + typeConverters.Location.fromTextSpan(this.client.toResourceUri(reference.file), reference)); codeLens.command = { title: this.getCodeLensLabel(locations), diff --git a/src/features/source-definition.ts b/src/features/source-definition.ts index 13cce73e..a4ffc5d6 100644 --- a/src/features/source-definition.ts +++ b/src/features/source-definition.ts @@ -46,7 +46,7 @@ export class SourceDefinitionCommand { return; } - const document = client.toOpenDocument(client.toResource(file).toString()); + const document = client.toOpenDocument(client.toResourceUri(file)); if (!document) { lspClient.showErrorMessage('Go to Source Definition failed. File not opened in the editor.'); diff --git a/src/lsp-server.ts b/src/lsp-server.ts index 5165a3f7..ef5907d2 100644 --- a/src/lsp-server.ts +++ b/src/lsp-server.ts @@ -147,6 +147,7 @@ export class LspServer { disableAutomaticTypingAcquisition, maxTsServerMemory, npmLocation, + hostInfo, locale, plugins: plugins || [], onEvent: this.onTsEvent.bind(this), @@ -550,7 +551,7 @@ export class LspServer { async completionResolve(item: lsp.CompletionItem, token?: lsp.CancellationToken): Promise { item.data = item.data?.cacheId !== undefined ? this.completionDataCache.get(item.data.cacheId) : item.data; - const uri = this.tsClient.toResource(item.data.file).toString(); + const uri = this.tsClient.toResourceUri(item.data.file); const document = item.data?.file ? this.tsClient.toOpenDocument(uri) : undefined; if (!document) { return item; @@ -636,7 +637,7 @@ export class LspServer { const changes: lsp.WorkspaceEdit['changes'] = {}; result.locs .forEach((spanGroup) => { - const uri = this.tsClient.toResource(spanGroup.file).toString(); + const uri = this.tsClient.toResourceUri(spanGroup.file); const textEdits = changes[uri] || (changes[uri] = []); spanGroup.locs.forEach((textSpan) => { @@ -868,7 +869,7 @@ export class LspServer { if (renameLocation) { await this.options.lspClient.rename({ textDocument: { - uri: this.tsClient.toResource(args.file).toString(), + uri: this.tsClient.toResourceUri(args.file), }, position: Position.fromLocation(renameLocation), }); @@ -878,7 +879,7 @@ export class LspServer { this.tsClient.configurePlugin(pluginName, configuration); } else if (params.command === Commands.ORGANIZE_IMPORTS && params.arguments) { const file = params.arguments[0] as string; - const uri = this.tsClient.toResource(file).toString(); + const uri = this.tsClient.toResourceUri(file); const document = this.tsClient.toOpenDocument(uri); if (!document) { return; @@ -944,7 +945,7 @@ export class LspServer { } const changes: { [uri: string]: lsp.TextEdit[]; } = {}; for (const edit of edits) { - changes[this.tsClient.toResource(edit.fileName).toString()] = edit.textChanges.map(toTextEdit); + changes[this.tsClient.toResourceUri(edit.fileName)] = edit.textChanges.map(toTextEdit); } const { applied } = await this.options.lspClient.applyWorkspaceEdit({ edit: { changes }, @@ -957,7 +958,7 @@ export class LspServer { for (const rename of params.files) { const codeEdits = await this.getEditsForFileRename(rename.oldUri, rename.newUri, token); for (const codeEdit of codeEdits) { - const uri = this.tsClient.toResource(codeEdit.fileName).toString(); + const uri = this.tsClient.toResourceUri(codeEdit.fileName); const textEdits = changes[uri] || (changes[uri] = []); textEdits.push(...codeEdit.textChanges.map(toTextEdit)); } @@ -1061,7 +1062,7 @@ export class LspServer { return response.body.map(item => { return { location: { - uri: this.tsClient.toResource(item.file).toString(), + uri: this.tsClient.toResourceUri(item.file), range: { start: Position.fromLocation(item.start), end: Position.fromLocation(item.end), diff --git a/src/protocol-translation.ts b/src/protocol-translation.ts index 1b239df3..b1b45360 100644 --- a/src/protocol-translation.ts +++ b/src/protocol-translation.ts @@ -12,9 +12,9 @@ import type { ts } from './ts-protocol.js'; import { Position, Range } from './utils/typeConverters.js'; export function toLocation(fileSpan: ts.server.protocol.FileSpan, client: TsClient): lsp.Location { - const uri = client.toResource(fileSpan.file); + const uri = client.toResourceUri(fileSpan.file); return { - uri: uri.toString(), + uri, range: { start: Position.fromLocation(fileSpan.start), end: Position.fromLocation(fileSpan.end), @@ -125,11 +125,11 @@ export function toTextEdit(edit: ts.server.protocol.CodeEdit): lsp.TextEdit { } export function toTextDocumentEdit(change: ts.server.protocol.FileCodeEdits, client: TsClient): lsp.TextDocumentEdit { - const uri = client.toResource(change.fileName); - const document = client.toOpenDocument(uri.toString()); + const uri = client.toResourceUri(change.fileName); + const document = client.toOpenDocument(uri); return { textDocument: { - uri: uri.toString(), + uri, version: document?.version ?? null, }, edits: change.textChanges.map(c => toTextEdit(c)), diff --git a/src/test-utils.ts b/src/test-utils.ts index d705747b..702ddb6e 100644 --- a/src/test-utils.ts +++ b/src/test-utils.ts @@ -72,6 +72,8 @@ const DEFAULT_TEST_CLIENT_INITIALIZATION_OPTIONS: TypeScriptInitializationOption const DEFAULT_WORKSPACE_SETTINGS: WorkspaceConfiguration = {}; +export const isWindows = process.platform === 'win32'; + export async function openDocumentAndWaitForDiagnostics(server: TestLspServer, textDocument: lsp.TextDocumentItem): Promise { server.didOpenTextDocument({ textDocument }); await server.waitForDiagnosticsForFile(textDocument.uri); @@ -204,6 +206,7 @@ interface TestLspServerOptions { rootUri: string | null; publishDiagnostics: (args: lsp.PublishDiagnosticsParams) => void; clientCapabilitiesOverride?: lsp.ClientCapabilities; + initializationOptionsOverrides?: TypeScriptInitializationOptions; } export async function createServer(options: TestLspServerOptions): Promise { @@ -223,7 +226,7 @@ export async function createServer(options: TestLspServerOptions): Promise void; @@ -158,6 +164,7 @@ export class TsClient implements ITypeScriptServiceClient { private readonly logger: Logger; private readonly tsserverLogger: Logger; private readonly loadingIndicator: ServerInitializingIndicator; + private isNeovimHost: boolean = false; private tracer: Tracer | undefined; private workspaceFolders: WorkspaceFolder[] = []; private readonly documents: LspDocuments; @@ -201,7 +208,7 @@ export class TsClient implements ITypeScriptServiceClient { public toTsFilePath(stringUri: string): string | undefined { // Vim may send `zipfile:` URIs which tsserver with Yarn v2+ hook can handle. Keep as-is. // Example: zipfile:///foo/bar/baz.zip::path/to/module - if (stringUri.startsWith('zipfile:')) { + if (this.isNeovimHost && stringUri.startsWith('zipfile:')) { return stringUri; } @@ -215,7 +222,11 @@ export class TsClient implements ITypeScriptServiceClient { return resource.fsPath; } - return undefined; + return inMemoryResourcePrefix + + '/' + resource.scheme + + '/' + (resource.authority || emptyAuthority) + + (resource.path.startsWith('/') ? resource.path : '/' + resource.path) + + (resource.fragment ? '#' + resource.fragment : ''); } public toOpenDocument(textDocumentUri: DocumentUri, options: { suppressAlertOnFailure?: boolean; } = {}): LspDocument | undefined { @@ -245,14 +256,29 @@ export class TsClient implements ITypeScriptServiceClient { public toResource(filepath: string): URI { // Yarn v2+ hooks tsserver and sends `zipfile:` URIs for Vim. Keep as-is. // Example: zipfile:///foo/bar/baz.zip::path/to/module - if (filepath.startsWith('zipfile:')) { - return URI.parse(filepath); + if (this.isNeovimHost && filepath.startsWith('zipfile:')) { + return ZipfileURI.parse(filepath); + } + + if (filepath.startsWith(inMemoryResourcePrefix)) { + const parts = filepath.match(RE_IN_MEMORY_FILEPATH); + if (parts) { + const resource = URI.parse(parts[1] + '://' + (parts[2] === emptyAuthority ? '' : parts[2]) + '/' + parts[3]); + const tsFilepath = this.toTsFilePath(resource.toString()); + const document = tsFilepath && this.documents.get(tsFilepath); + return document ? document.uri : resource; + } } + const fileUri = URI.file(filepath); const document = this.documents.get(fileUri.fsPath); return document ? document.uri : fileUri; } + public toResourceUri(filepath: string): string { + return this.toResource(filepath).toString(); + } + public getWorkspaceRootForResource(resource: URI): URI | undefined { // For notebook cells, we need to use the notebook document to look up the workspace // if (resource.scheme === Schemes.notebookCell) { @@ -303,7 +329,7 @@ export class TsClient implements ITypeScriptServiceClient { switch (capability) { case ClientCapability.Semantic: { - return ['file', 'untitled'].includes(resource.scheme); + return fileSchemes.getSemanticSupportedSchemes().includes(resource.scheme); } case ClientCapability.Syntax: case ClientCapability.EnhancedSyntax: { @@ -324,6 +350,7 @@ export class TsClient implements ITypeScriptServiceClient { ): boolean { this.apiVersion = options.typescriptVersion.version || API.defaultVersion; this.typescriptVersionSource = options.typescriptVersion.source; + this.isNeovimHost = options.hostInfo === 'neovim'; this.tracer = new Tracer(this.tsserverLogger, options.trace); this.workspaceFolders = workspaceRoot ? [{ uri: URI.file(workspaceRoot) }] : []; this.useSyntaxServer = options.useSyntaxServer; diff --git a/src/typescriptService.ts b/src/typescriptService.ts index 7bfad944..465409da 100644 --- a/src/typescriptService.ts +++ b/src/typescriptService.ts @@ -80,6 +80,7 @@ export interface ITypeScriptServiceClient { * Convert a path to a resource. */ toResource(filepath: string): URI; + toResourceUri(filepath: string): string; /** * Tries to ensure that a document is open on the TS server. diff --git a/src/utils/uri.ts b/src/utils/uri.ts new file mode 100644 index 00000000..2c38f5ef --- /dev/null +++ b/src/utils/uri.ts @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 TypeFox and others. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + */ + +import { URI } from 'vscode-uri'; + +export class ZipfileURI extends URI { + private _originalUri: string; + + private constructor(uri: string, components: URI) { + super(components); + + this._originalUri = uri; + } + + override toString(_skipEncoding: boolean = false): string { + return this._originalUri; + } + + static override parse(value: string, _strict: boolean = false): ZipfileURI { + const uri = URI.parse(value, _strict); + + return new ZipfileURI(value, uri); + } +} diff --git a/test-data/yarn-pnp/.editorconfig b/test-data/yarn-pnp/.editorconfig new file mode 100644 index 00000000..1ed453a3 --- /dev/null +++ b/test-data/yarn-pnp/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.{js,json,yml}] +charset = utf-8 +indent_style = space +indent_size = 2 diff --git a/test-data/yarn-pnp/.gitattributes b/test-data/yarn-pnp/.gitattributes new file mode 100644 index 00000000..af3ad128 --- /dev/null +++ b/test-data/yarn-pnp/.gitattributes @@ -0,0 +1,4 @@ +/.yarn/** linguist-vendored +/.yarn/releases/* binary +/.yarn/plugins/**/* binary +/.pnp.* binary linguist-generated diff --git a/test-data/yarn-pnp/.gitignore b/test-data/yarn-pnp/.gitignore new file mode 100644 index 00000000..870eb6a5 --- /dev/null +++ b/test-data/yarn-pnp/.gitignore @@ -0,0 +1,13 @@ +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Swap the comments on the following lines if you wish to use zero-installs +# In that case, don't forget to run `yarn config set enableGlobalCache false`! +# Documentation here: https://yarnpkg.com/features/caching#zero-installs + +#!.yarn/cache +.pnp.* diff --git a/test-data/yarn-pnp/.vim/coc-settings.json b/test-data/yarn-pnp/.vim/coc-settings.json new file mode 100644 index 00000000..1de47983 --- /dev/null +++ b/test-data/yarn-pnp/.vim/coc-settings.json @@ -0,0 +1,4 @@ +{ + "workspace.workspaceFolderCheckCwd": false, + "tsserver.tsdk": ".yarn/sdks/typescript/lib" +} diff --git a/test-data/yarn-pnp/.yarn/sdks/integrations.yml b/test-data/yarn-pnp/.yarn/sdks/integrations.yml new file mode 100644 index 00000000..231abf42 --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/integrations.yml @@ -0,0 +1,5 @@ +# This file is automatically generated by @yarnpkg/sdks. +# Manual changes might be lost! + +integrations: + - vim diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/bin/tsc b/test-data/yarn-pnp/.yarn/sdks/typescript/bin/tsc new file mode 100755 index 00000000..454b950b --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/bin/tsc @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/bin/tsc + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/bin/tsc your application uses +module.exports = absRequire(`typescript/bin/tsc`); diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/bin/tsserver b/test-data/yarn-pnp/.yarn/sdks/typescript/bin/tsserver new file mode 100755 index 00000000..d7a60568 --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/bin/tsserver @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/bin/tsserver + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/bin/tsserver your application uses +module.exports = absRequire(`typescript/bin/tsserver`); diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsc.js b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsc.js new file mode 100644 index 00000000..2f62fc96 --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsc.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/tsc.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/tsc.js your application uses +module.exports = absRequire(`typescript/lib/tsc.js`); diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsserver.js b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsserver.js new file mode 100644 index 00000000..bbb1e465 --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsserver.js @@ -0,0 +1,225 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +const moduleWrapper = tsserver => { + if (!process.versions.pnp) { + return tsserver; + } + + const {isAbsolute} = require(`path`); + const pnpApi = require(`pnpapi`); + + const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = str => str.startsWith("portal:/"); + const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + + const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { + return `${locator.name}@${locator.reference}`; + })); + + // VSCode sends the zip paths to TS using the "zip://" prefix, that TS + // doesn't understand. This layer makes sure to remove the protocol + // before forwarding it to TS, and to add it back on all returned paths. + + function toEditorPath(str) { + // We add the `zip:` prefix to both `.zip/` paths and virtual paths + if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + // We also take the opportunity to turn virtual paths into physical ones; + // this makes it much easier to work with workspaces that list peer + // dependencies, since otherwise Ctrl+Click would bring us to the virtual + // file instances instead of the real ones. + // + // We only do this to modules owned by the the dependency tree roots. + // This avoids breaking the resolution when jumping inside a vendor + // with peer dep (otherwise jumping into react-dom would show resolution + // errors on react). + // + const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; + if (resolved) { + const locator = pnpApi.findPackageLocator(resolved); + if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + str = resolved; + } + } + + str = normalize(str); + + if (str.match(/\.zip\//)) { + switch (hostInfo) { + // Absolute VSCode `Uri.fsPath`s need to start with a slash. + // VSCode only adds it automatically for supported schemes, + // so we have to do it manually for the `zip` scheme. + // The path needs to start with a caret otherwise VSCode doesn't handle the protocol + // + // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 + // + // 2021-10-08: VSCode changed the format in 1.61. + // Before | ^zip:/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + // 2022-04-06: VSCode changed the format in 1.66. + // Before | ^/zip//c:/foo/bar.zip/package.json + // After | ^/zip/c:/foo/bar.zip/package.json + // + // 2022-05-06: VSCode changed the format in 1.68 + // Before | ^/zip/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + case `vscode <1.61`: { + str = `^zip:${str}`; + } break; + + case `vscode <1.66`: { + str = `^/zip/${str}`; + } break; + + case `vscode <1.68`: { + str = `^/zip${str}`; + } break; + + case `vscode`: { + str = `^/zip/${str}`; + } break; + + // To make "go to definition" work, + // We have to resolve the actual file system path from virtual path + // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) + case `coc-nvim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } break; + + // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) + // We have to resolve the actual file system path from virtual path, + // everything else is up to neovim + case `neovim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } break; + + default: { + str = `zip:${str}`; + } break; + } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); + } + } + + return str; + } + + function fromEditorPath(str) { + switch (hostInfo) { + case `coc-nvim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } break; + + case `neovim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } break; + + case `vscode`: + default: { + return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) + } break; + } + } + + // Force enable 'allowLocalPluginLoads' + // TypeScript tries to resolve plugins using a path relative to itself + // which doesn't work when using the global cache + // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 + // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but + // TypeScript already does local loads and if this code is running the user trusts the workspace + // https://github.com/microsoft/vscode/issues/45856 + const ConfiguredProject = tsserver.server.ConfiguredProject; + const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function() { + this.projectService.allowLocalPluginLoads = true; + return originalEnablePluginsWithOptions.apply(this, arguments); + }; + + // And here is the point where we hijack the VSCode <-> TS communications + // by adding ourselves in the middle. We locate everything that looks + // like an absolute path of ours and normalize it. + + const Session = tsserver.server.Session; + const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + let hostInfo = `unknown`; + + Object.assign(Session.prototype, { + onMessage(/** @type {string | object} */ message) { + const isStringMessage = typeof message === 'string'; + const parsedMessage = isStringMessage ? JSON.parse(message) : message; + + if ( + parsedMessage != null && + typeof parsedMessage === `object` && + parsedMessage.arguments && + typeof parsedMessage.arguments.hostInfo === `string` + ) { + hostInfo = parsedMessage.arguments.hostInfo; + if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { + const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? []).map(Number) + + if (major === 1) { + if (minor < 61) { + hostInfo += ` <1.61`; + } else if (minor < 66) { + hostInfo += ` <1.66`; + } else if (minor < 68) { + hostInfo += ` <1.68`; + } + } + } + } + + const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + }); + + return originalOnMessage.call( + this, + isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + ); + }, + + send(/** @type {any} */ msg) { + return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }))); + } + }); + + return tsserver; +}; + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/tsserver.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/tsserver.js your application uses +module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`)); diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsserverlibrary.js b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsserverlibrary.js new file mode 100644 index 00000000..a68f028f --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/tsserverlibrary.js @@ -0,0 +1,225 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +const moduleWrapper = tsserver => { + if (!process.versions.pnp) { + return tsserver; + } + + const {isAbsolute} = require(`path`); + const pnpApi = require(`pnpapi`); + + const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = str => str.startsWith("portal:/"); + const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + + const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { + return `${locator.name}@${locator.reference}`; + })); + + // VSCode sends the zip paths to TS using the "zip://" prefix, that TS + // doesn't understand. This layer makes sure to remove the protocol + // before forwarding it to TS, and to add it back on all returned paths. + + function toEditorPath(str) { + // We add the `zip:` prefix to both `.zip/` paths and virtual paths + if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + // We also take the opportunity to turn virtual paths into physical ones; + // this makes it much easier to work with workspaces that list peer + // dependencies, since otherwise Ctrl+Click would bring us to the virtual + // file instances instead of the real ones. + // + // We only do this to modules owned by the the dependency tree roots. + // This avoids breaking the resolution when jumping inside a vendor + // with peer dep (otherwise jumping into react-dom would show resolution + // errors on react). + // + const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; + if (resolved) { + const locator = pnpApi.findPackageLocator(resolved); + if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + str = resolved; + } + } + + str = normalize(str); + + if (str.match(/\.zip\//)) { + switch (hostInfo) { + // Absolute VSCode `Uri.fsPath`s need to start with a slash. + // VSCode only adds it automatically for supported schemes, + // so we have to do it manually for the `zip` scheme. + // The path needs to start with a caret otherwise VSCode doesn't handle the protocol + // + // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 + // + // 2021-10-08: VSCode changed the format in 1.61. + // Before | ^zip:/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + // 2022-04-06: VSCode changed the format in 1.66. + // Before | ^/zip//c:/foo/bar.zip/package.json + // After | ^/zip/c:/foo/bar.zip/package.json + // + // 2022-05-06: VSCode changed the format in 1.68 + // Before | ^/zip/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + case `vscode <1.61`: { + str = `^zip:${str}`; + } break; + + case `vscode <1.66`: { + str = `^/zip/${str}`; + } break; + + case `vscode <1.68`: { + str = `^/zip${str}`; + } break; + + case `vscode`: { + str = `^/zip/${str}`; + } break; + + // To make "go to definition" work, + // We have to resolve the actual file system path from virtual path + // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) + case `coc-nvim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } break; + + // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) + // We have to resolve the actual file system path from virtual path, + // everything else is up to neovim + case `neovim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } break; + + default: { + str = `zip:${str}`; + } break; + } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); + } + } + + return str; + } + + function fromEditorPath(str) { + switch (hostInfo) { + case `coc-nvim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } break; + + case `neovim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } break; + + case `vscode`: + default: { + return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) + } break; + } + } + + // Force enable 'allowLocalPluginLoads' + // TypeScript tries to resolve plugins using a path relative to itself + // which doesn't work when using the global cache + // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 + // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but + // TypeScript already does local loads and if this code is running the user trusts the workspace + // https://github.com/microsoft/vscode/issues/45856 + const ConfiguredProject = tsserver.server.ConfiguredProject; + const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function() { + this.projectService.allowLocalPluginLoads = true; + return originalEnablePluginsWithOptions.apply(this, arguments); + }; + + // And here is the point where we hijack the VSCode <-> TS communications + // by adding ourselves in the middle. We locate everything that looks + // like an absolute path of ours and normalize it. + + const Session = tsserver.server.Session; + const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + let hostInfo = `unknown`; + + Object.assign(Session.prototype, { + onMessage(/** @type {string | object} */ message) { + const isStringMessage = typeof message === 'string'; + const parsedMessage = isStringMessage ? JSON.parse(message) : message; + + if ( + parsedMessage != null && + typeof parsedMessage === `object` && + parsedMessage.arguments && + typeof parsedMessage.arguments.hostInfo === `string` + ) { + hostInfo = parsedMessage.arguments.hostInfo; + if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { + const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? []).map(Number) + + if (major === 1) { + if (minor < 61) { + hostInfo += ` <1.61`; + } else if (minor < 66) { + hostInfo += ` <1.66`; + } else if (minor < 68) { + hostInfo += ` <1.68`; + } + } + } + } + + const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + }); + + return originalOnMessage.call( + this, + isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + ); + }, + + send(/** @type {any} */ msg) { + return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }))); + } + }); + + return tsserver; +}; + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/tsserverlibrary.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/tsserverlibrary.js your application uses +module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`)); diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/lib/typescript.js b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/typescript.js new file mode 100644 index 00000000..b5f4db25 --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/lib/typescript.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript your application uses +module.exports = absRequire(`typescript`); diff --git a/test-data/yarn-pnp/.yarn/sdks/typescript/package.json b/test-data/yarn-pnp/.yarn/sdks/typescript/package.json new file mode 100644 index 00000000..eb7dd745 --- /dev/null +++ b/test-data/yarn-pnp/.yarn/sdks/typescript/package.json @@ -0,0 +1,10 @@ +{ + "name": "typescript", + "version": "5.3.3-sdk", + "main": "./lib/typescript.js", + "type": "commonjs", + "bin": { + "tsc": "./bin/tsc", + "tsserver": "./bin/tsserver" + } +} diff --git a/test-data/yarn-pnp/README.md b/test-data/yarn-pnp/README.md new file mode 100644 index 00000000..1da79178 --- /dev/null +++ b/test-data/yarn-pnp/README.md @@ -0,0 +1 @@ +# yarn2 diff --git a/test-data/yarn-pnp/package.json b/test-data/yarn-pnp/package.json new file mode 100644 index 00000000..3588b15f --- /dev/null +++ b/test-data/yarn-pnp/package.json @@ -0,0 +1,8 @@ +{ + "name": "yarn2", + "packageManager": "yarn@4.1.0", + "dependencies": { + "axios": "1.6.7", + "typescript": "^5.3.3" + } +} diff --git a/test-data/yarn-pnp/testfile.ts b/test-data/yarn-pnp/testfile.ts new file mode 100644 index 00000000..f51c9a65 --- /dev/null +++ b/test-data/yarn-pnp/testfile.ts @@ -0,0 +1 @@ +import type { AxiosHeaderValue } from 'axios'; diff --git a/test-data/yarn-pnp/tsconfig.json b/test-data/yarn-pnp/tsconfig.json new file mode 100644 index 00000000..55eb65d8 --- /dev/null +++ b/test-data/yarn-pnp/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "module": "ES2022", + "moduleResolution": "Node16" + }, +} diff --git a/test-data/yarn-pnp/yarn.lock b/test-data/yarn-pnp/yarn.lock new file mode 100644 index 00000000..25f5791c --- /dev/null +++ b/test-data/yarn-pnp/yarn.lock @@ -0,0 +1,113 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"axios@npm:1.6.7": + version: 1.6.7 + resolution: "axios@npm:1.6.7" + dependencies: + follow-redirects: "npm:^1.15.4" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10c0/131bf8e62eee48ca4bd84e6101f211961bf6a21a33b95e5dfb3983d5a2fe50d9fffde0b57668d7ce6f65063d3dc10f2212cbcb554f75cfca99da1c73b210358d + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"follow-redirects@npm:^1.15.4": + version: 1.15.5 + resolution: "follow-redirects@npm:1.15.5" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/418d71688ceaf109dfd6f85f747a0c75de30afe43a294caa211def77f02ef19865b547dfb73fde82b751e1cc507c06c754120b848fe5a7400b0a669766df7615 + languageName: node + linkType: hard + +"form-data@npm:^4.0.0": + version: 4.0.0 + resolution: "form-data@npm:4.0.0" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + mime-types: "npm:^2.1.12" + checksum: 10c0/cb6f3ac49180be03ff07ba3ff125f9eba2ff0b277fb33c7fc47569fc5e616882c5b1c69b9904c4c4187e97dd0419dd03b134174756f296dec62041e6527e2c6e + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b + languageName: node + linkType: hard + +"typescript@npm:^5.3.3": + version: 5.3.3 + resolution: "typescript@npm:5.3.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/e33cef99d82573624fc0f854a2980322714986bc35b9cb4d1ce736ed182aeab78e2cb32b385efa493b2a976ef52c53e20d6c6918312353a91850e2b76f1ea44f + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.3.3#optional!builtin": + version: 5.3.3 + resolution: "typescript@patch:typescript@npm%3A5.3.3#optional!builtin::version=5.3.3&hash=e012d7" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/1d0a5f4ce496c42caa9a30e659c467c5686eae15d54b027ee7866744952547f1be1262f2d40de911618c242b510029d51d43ff605dba8fb740ec85ca2d3f9500 + languageName: node + linkType: hard + +"yarn2@workspace:.": + version: 0.0.0-use.local + resolution: "yarn2@workspace:." + dependencies: + axios: "npm:1.6.7" + typescript: "npm:^5.3.3" + languageName: unknown + linkType: soft pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy