diff --git a/docs/esm-migration-plan.md b/docs/esm-migration-plan.md index d2be92e1..72570c72 100644 --- a/docs/esm-migration-plan.md +++ b/docs/esm-migration-plan.md @@ -4,6 +4,27 @@ This document outlines the plan to migrate from TypeScript's deprecated `"moduleResolution": "node"` (node10) to `"moduleResolution": "node16"` or `"nodenext"`. This change is necessary because the published ESM packages have extensionless imports that don't work correctly in modern ESM environments. +## TL;DR - Remaining Work + +- [x] expressions - Migrated ✅ +- [x] workflow-parser - Migrated ✅ +- [x] languageservice - Migrated ✅ +- [x] languageserver - Add `.js` extensions to imports ✅ +- [ ] languageserver - Update `tsconfig.build.json` to `moduleResolution: "node16"` (blocked by vscode-languageserver) +- [ ] languageserver - Upgrade `vscode-languageserver` to stable v10+ when released + +**Blocker:** `vscode-languageserver@8.0.2` lacks ESM exports. Stable v10 with `exports` field needed. + +### ⚠️ Important: `skipLibCheck: true` Required + +All migrated packages use `skipLibCheck: true` in their `tsconfig.build.json`. This works around a TS2386 "Overload signatures must all be optional or required" error in `@types/node/module.d.ts`. + +**Why can't we just fix the error?** The error is in `@types/node`, a third-party package maintained by DefinitelyTyped. We can't modify `node_modules`, and upstream fixes take time. + +**Is `skipLibCheck` safe?** Yes. It only skips type checking of `.d.ts` files (declaration files from dependencies). Our own `.ts` source files are still fully type-checked. This is a common and recommended workaround for issues in third-party type definitions. + +--- + ## Issues Fixed This migration will resolve the following issues: @@ -199,14 +220,13 @@ src/connection.ts(1,43): error TS2307: Cannot find module 'vscode-languageserver With `moduleResolution: "node16"`, TypeScript follows Node.js ESM resolution rules which require explicit `exports` for subpath imports like `vscode-languageserver/browser` and `vscode-languageserver/node`. -**Status:** Verified December 2025. Version 9.0.1 is available but ESM export support is not confirmed. +**Status:** Partial - `.js` extensions added, waiting for stable `vscode-languageserver` release with ESM exports to complete migration. -**Current Decision:** The languageserver package is **deferred** from this migration until the upstream `vscode-languageserver` package adds proper ESM exports. It will continue using the old `moduleResolution: "node"` configuration. +**Completed:** All relative imports in languageserver source files have been updated to use `.js` extensions. This is compatible with the current `moduleResolution: "node"` and will enable a seamless migration once a stable vscode-languageserver version with ESM exports is available. **Options to resolve:** -- Wait for vscode-languageserver to add ESM exports -- Try upgrading to vscode-languageserver v9.x to see if exports were added -- Use a bundler to work around the module resolution +- Wait for stable vscode-languageserver v10+ with ESM exports +- Use pre-release `vscode-languageserver@10.0.0-next.16` (has proper exports but is unstable) - Fork or patch the dependency --- @@ -218,7 +238,7 @@ With `moduleResolution: "node16"`, TypeScript follows Node.js ESM resolution rul | expressions | 1068 | ✅ Migrated | | workflow-parser | 292 | ✅ Migrated | | languageservice | 452 | ✅ Migrated | -| languageserver | 6 files | ⏸️ Deferred (vscode-languageserver lacks ESM exports) | +| languageserver | 31 | 🔶 Partial (`.js` extensions added, awaiting stable vscode-languageserver) | --- diff --git a/languageserver/src/connection.ts b/languageserver/src/connection.ts index 35de5f60..8424980c 100644 --- a/languageserver/src/connection.ts +++ b/languageserver/src/connection.ts @@ -20,18 +20,18 @@ import { TextDocumentSyncKind } from "vscode-languageserver"; import {TextDocument} from "vscode-languageserver-textdocument"; -import {getClient} from "./client"; -import {Commands} from "./commands"; -import {contextProviders} from "./context-providers"; -import {descriptionProvider} from "./description-provider"; -import {getFileProvider} from "./file-provider"; -import {InitializationOptions, RepositoryContext} from "./initializationOptions"; -import {onCompletion} from "./on-completion"; -import {ReadFileRequest, Requests} from "./request"; -import {getActionsMetadataProvider} from "./utils/action-metadata"; -import {TTLCache} from "./utils/cache"; -import {timeOperation} from "./utils/timer"; -import {valueProviders} from "./value-providers"; +import {getClient} from "./client.js"; +import {Commands} from "./commands.js"; +import {contextProviders} from "./context-providers.js"; +import {descriptionProvider} from "./description-provider.js"; +import {getFileProvider} from "./file-provider.js"; +import {InitializationOptions, RepositoryContext} from "./initializationOptions.js"; +import {onCompletion} from "./on-completion.js"; +import {ReadFileRequest, Requests} from "./request.js"; +import {getActionsMetadataProvider} from "./utils/action-metadata.js"; +import {TTLCache} from "./utils/cache.js"; +import {timeOperation} from "./utils/timer.js"; +import {valueProviders} from "./value-providers.js"; export function initConnection(connection: Connection) { const documents: TextDocuments = new TextDocuments(TextDocument); diff --git a/languageserver/src/context-providers.test.ts b/languageserver/src/context-providers.test.ts index 59f948e5..ddefe791 100644 --- a/languageserver/src/context-providers.test.ts +++ b/languageserver/src/context-providers.test.ts @@ -1,9 +1,9 @@ import {data, DescriptionDictionary} from "@actions/expressions"; import {WorkflowContext} from "@actions/languageservice/context/workflow-context"; import {Mode} from "@actions/languageservice/context-providers/default"; -import {contextProviders} from "./context-providers"; -import {RepositoryContext} from "./initializationOptions"; -import {TTLCache} from "./utils/cache"; +import {contextProviders} from "./context-providers.js"; +import {RepositoryContext} from "./initializationOptions.js"; +import {TTLCache} from "./utils/cache.js"; describe("contextProviders", () => { const mockCache = new TTLCache(); diff --git a/languageserver/src/context-providers.ts b/languageserver/src/context-providers.ts index 4ae90ef2..7e5f9bd2 100644 --- a/languageserver/src/context-providers.ts +++ b/languageserver/src/context-providers.ts @@ -3,11 +3,11 @@ import {ContextProviderConfig} from "@actions/languageservice"; import {Mode} from "@actions/languageservice/context-providers/default"; import {WorkflowContext} from "@actions/languageservice/context/workflow-context"; import {Octokit} from "@octokit/rest"; -import {getSecrets} from "./context-providers/secrets"; -import {getStepsContext} from "./context-providers/steps"; -import {getVariables} from "./context-providers/variables"; -import {RepositoryContext} from "./initializationOptions"; -import {TTLCache} from "./utils/cache"; +import {getSecrets} from "./context-providers/secrets.js"; +import {getStepsContext} from "./context-providers/steps.js"; +import {getVariables} from "./context-providers/variables.js"; +import {RepositoryContext} from "./initializationOptions.js"; +import {TTLCache} from "./utils/cache.js"; export function contextProviders( client: Octokit | undefined, diff --git a/languageserver/src/context-providers/action-outputs.ts b/languageserver/src/context-providers/action-outputs.ts index ba6e2e0d..80aecb1b 100644 --- a/languageserver/src/context-providers/action-outputs.ts +++ b/languageserver/src/context-providers/action-outputs.ts @@ -1,7 +1,7 @@ import {ActionOutputs, ActionReference} from "@actions/languageservice/action"; import {Octokit} from "@octokit/rest"; -import {fetchActionMetadata} from "../utils/action-metadata"; -import {TTLCache} from "../utils/cache"; +import {fetchActionMetadata} from "../utils/action-metadata.js"; +import {TTLCache} from "../utils/cache.js"; export async function getActionOutputs( octokit: Octokit, diff --git a/languageserver/src/context-providers/secrets.ts b/languageserver/src/context-providers/secrets.ts index 75078355..bba701f8 100644 --- a/languageserver/src/context-providers/secrets.ts +++ b/languageserver/src/context-providers/secrets.ts @@ -6,10 +6,10 @@ import {warn} from "@actions/languageservice/log"; import {isMapping, isString} from "@actions/workflow-parser"; import {Octokit} from "@octokit/rest"; -import {RepositoryContext} from "../initializationOptions"; -import {TTLCache} from "../utils/cache"; -import {errorStatus} from "../utils/error"; -import {getRepoPermission} from "../utils/repo-permission"; +import {RepositoryContext} from "../initializationOptions.js"; +import {TTLCache} from "../utils/cache.js"; +import {errorStatus} from "../utils/error.js"; +import {getRepoPermission} from "../utils/repo-permission.js"; export async function getSecrets( workflowContext: WorkflowContext, diff --git a/languageserver/src/context-providers/steps.test.ts b/languageserver/src/context-providers/steps.test.ts index f5da5cf9..929082d8 100644 --- a/languageserver/src/context-providers/steps.test.ts +++ b/languageserver/src/context-providers/steps.test.ts @@ -3,9 +3,9 @@ import {getStepsContext as getDefaultStepsContext} from "@actions/languageservic import {Octokit} from "@octokit/rest"; import fetchMock from "fetch-mock"; -import {createWorkflowContext} from "../test-utils/workflow-context"; -import {TTLCache} from "../utils/cache"; -import {getStepsContext} from "./steps"; +import {createWorkflowContext} from "../test-utils/workflow-context.js"; +import {TTLCache} from "../utils/cache.js"; +import {getStepsContext} from "./steps.js"; const workflow = ` name: Caching Primes diff --git a/languageserver/src/context-providers/steps.ts b/languageserver/src/context-providers/steps.ts index a78cd9e5..3a335160 100644 --- a/languageserver/src/context-providers/steps.ts +++ b/languageserver/src/context-providers/steps.ts @@ -3,8 +3,8 @@ import {parseActionReference} from "@actions/languageservice/action"; import {WorkflowContext} from "@actions/languageservice/context/workflow-context"; import {isActionStep} from "@actions/workflow-parser/model/type-guards"; import {Octokit} from "@octokit/rest"; -import {TTLCache} from "../utils/cache"; -import {getActionOutputs} from "./action-outputs"; +import {TTLCache} from "../utils/cache.js"; +import {getActionOutputs} from "./action-outputs.js"; export async function getStepsContext( octokit: Octokit, diff --git a/languageserver/src/context-providers/variables.ts b/languageserver/src/context-providers/variables.ts index e26fc81d..073ade1b 100644 --- a/languageserver/src/context-providers/variables.ts +++ b/languageserver/src/context-providers/variables.ts @@ -7,10 +7,10 @@ import {isMapping, isString} from "@actions/workflow-parser"; import {Octokit} from "@octokit/rest"; import {RequestError} from "@octokit/request-error"; -import {RepositoryContext} from "../initializationOptions"; -import {TTLCache} from "../utils/cache"; -import {errorStatus} from "../utils/error"; -import {getRepoPermission} from "../utils/repo-permission"; +import {RepositoryContext} from "../initializationOptions.js"; +import {TTLCache} from "../utils/cache.js"; +import {errorStatus} from "../utils/error.js"; +import {getRepoPermission} from "../utils/repo-permission.js"; export async function getVariables( workflowContext: WorkflowContext, diff --git a/languageserver/src/description-provider.ts b/languageserver/src/description-provider.ts index 9364b5dc..93da8466 100644 --- a/languageserver/src/description-provider.ts +++ b/languageserver/src/description-provider.ts @@ -1,8 +1,8 @@ import {DescriptionProvider} from "@actions/languageservice/hover"; import {Octokit} from "@octokit/rest"; -import {getActionDescription} from "./description-providers/action-description"; -import {getActionInputDescription} from "./description-providers/action-input"; -import {TTLCache} from "./utils/cache"; +import {getActionDescription} from "./description-providers/action-description.js"; +import {getActionInputDescription} from "./description-providers/action-input.js"; +import {TTLCache} from "./utils/cache.js"; export function descriptionProvider(client: Octokit | undefined, cache: TTLCache): DescriptionProvider { const getDescription: DescriptionProvider["getDescription"] = async (context, token, path) => { diff --git a/languageserver/src/description-providers/action-description.test.ts b/languageserver/src/description-providers/action-description.test.ts index 85df7675..b6c5c409 100644 --- a/languageserver/src/description-providers/action-description.test.ts +++ b/languageserver/src/description-providers/action-description.test.ts @@ -1,9 +1,9 @@ import {Octokit} from "@octokit/rest"; import fetchMock from "fetch-mock"; -import {createWorkflowContext} from "../test-utils/workflow-context"; -import {TTLCache} from "../utils/cache"; -import {getActionDescription} from "./action-description"; -import {actionsCheckoutMetadata} from "../test-utils/action-metadata"; +import {createWorkflowContext} from "../test-utils/workflow-context.js"; +import {TTLCache} from "../utils/cache.js"; +import {getActionDescription} from "./action-description.js"; +import {actionsCheckoutMetadata} from "../test-utils/action-metadata.js"; const workflow = ` name: Hello World diff --git a/languageserver/src/description-providers/action-description.ts b/languageserver/src/description-providers/action-description.ts index 97dd17f8..44da29fa 100644 --- a/languageserver/src/description-providers/action-description.ts +++ b/languageserver/src/description-providers/action-description.ts @@ -2,8 +2,8 @@ import {actionUrl, parseActionReference} from "@actions/languageservice/action"; import {isActionStep} from "@actions/workflow-parser/model/type-guards"; import {Step} from "@actions/workflow-parser/model/workflow-template"; import {Octokit} from "@octokit/rest"; -import {fetchActionMetadata} from "../utils/action-metadata"; -import {TTLCache} from "../utils/cache"; +import {fetchActionMetadata} from "../utils/action-metadata.js"; +import {TTLCache} from "../utils/cache.js"; export async function getActionDescription(client: Octokit, cache: TTLCache, step: Step): Promise { if (!isActionStep(step)) { diff --git a/languageserver/src/description-providers/action-input.test.ts b/languageserver/src/description-providers/action-input.test.ts index fb75cc26..a6280358 100644 --- a/languageserver/src/description-providers/action-input.test.ts +++ b/languageserver/src/description-providers/action-input.test.ts @@ -2,10 +2,10 @@ import {StringToken} from "@actions/workflow-parser/templates/tokens/string-toke import {Octokit} from "@octokit/rest"; import fetchMock from "fetch-mock"; -import {actionsCheckoutMetadata} from "../test-utils/action-metadata"; -import {createWorkflowContext} from "../test-utils/workflow-context"; -import {TTLCache} from "../utils/cache"; -import {getActionInputDescription} from "./action-input"; +import {actionsCheckoutMetadata} from "../test-utils/action-metadata.js"; +import {createWorkflowContext} from "../test-utils/workflow-context.js"; +import {TTLCache} from "../utils/cache.js"; +import {getActionInputDescription} from "./action-input.js"; const workflow = ` name: Hello World diff --git a/languageserver/src/description-providers/action-input.ts b/languageserver/src/description-providers/action-input.ts index 3da19247..c6f1deb2 100644 --- a/languageserver/src/description-providers/action-input.ts +++ b/languageserver/src/description-providers/action-input.ts @@ -4,8 +4,8 @@ import {isActionStep} from "@actions/workflow-parser/model/type-guards"; import {Step} from "@actions/workflow-parser/model/workflow-template"; import {TemplateToken} from "@actions/workflow-parser/templates/tokens/template-token"; import {Octokit} from "@octokit/rest"; -import {fetchActionMetadata} from "../utils/action-metadata"; -import {TTLCache} from "../utils/cache"; +import {fetchActionMetadata} from "../utils/action-metadata.js"; +import {TTLCache} from "../utils/cache.js"; export async function getActionInputDescription( client: Octokit, diff --git a/languageserver/src/file-provider.ts b/languageserver/src/file-provider.ts index b0e822da..5db49d07 100644 --- a/languageserver/src/file-provider.ts +++ b/languageserver/src/file-provider.ts @@ -2,7 +2,7 @@ import {File} from "@actions/workflow-parser/workflows/file"; import {FileProvider} from "@actions/workflow-parser/workflows/file-provider"; import {fileIdentifier} from "@actions/workflow-parser/workflows/file-reference"; import {Octokit} from "@octokit/rest"; -import {TTLCache} from "./utils/cache"; +import {TTLCache} from "./utils/cache.js"; import * as vscodeURI from "vscode-uri"; export function getFileProvider( diff --git a/languageserver/src/index.ts b/languageserver/src/index.ts index c823c83f..fcbb1ade 100644 --- a/languageserver/src/index.ts +++ b/languageserver/src/index.ts @@ -6,7 +6,7 @@ import { } from "vscode-languageserver/browser"; import {createConnection as createNodeConnection} from "vscode-languageserver/node"; -import {initConnection} from "./connection"; +import {initConnection} from "./connection.js"; /** Helper function determining whether we are executing with node runtime */ function isNode(): boolean { diff --git a/languageserver/src/on-completion.ts b/languageserver/src/on-completion.ts index 9b1a1f0e..10c1ddf8 100644 --- a/languageserver/src/on-completion.ts +++ b/languageserver/src/on-completion.ts @@ -2,12 +2,12 @@ import {complete} from "@actions/languageservice/complete"; import {Octokit} from "@octokit/rest"; import {CompletionItem, Connection, Position} from "vscode-languageserver"; import {TextDocument} from "vscode-languageserver-textdocument"; -import {contextProviders} from "./context-providers"; -import {getFileProvider} from "./file-provider"; -import {RepositoryContext} from "./initializationOptions"; -import {Requests} from "./request"; -import {TTLCache} from "./utils/cache"; -import {valueProviders} from "./value-providers"; +import {contextProviders} from "./context-providers.js"; +import {getFileProvider} from "./file-provider.js"; +import {RepositoryContext} from "./initializationOptions.js"; +import {Requests} from "./request.js"; +import {TTLCache} from "./utils/cache.js"; +import {valueProviders} from "./value-providers.js"; export async function onCompletion( connection: Connection, diff --git a/languageserver/src/utils/action-metadata.test.ts b/languageserver/src/utils/action-metadata.test.ts index e4761a1c..932cf4a5 100644 --- a/languageserver/src/utils/action-metadata.test.ts +++ b/languageserver/src/utils/action-metadata.test.ts @@ -1,7 +1,7 @@ import {Octokit} from "@octokit/rest"; import fetchMock from "fetch-mock"; -import {fetchActionMetadata} from "./action-metadata"; -import {TTLCache} from "./cache"; +import {fetchActionMetadata} from "./action-metadata.js"; +import {TTLCache} from "./cache.js"; // A simplified version of the action.yml file from actions/checkout const actionMetadataContent = ` diff --git a/languageserver/src/utils/action-metadata.ts b/languageserver/src/utils/action-metadata.ts index dd0b6307..615d9a8b 100644 --- a/languageserver/src/utils/action-metadata.ts +++ b/languageserver/src/utils/action-metadata.ts @@ -3,8 +3,8 @@ import {ActionsMetadataProvider} from "@actions/languageservice"; import {error} from "@actions/languageservice/log"; import {Octokit, RestEndpointMethodTypes} from "@octokit/rest"; import {parse} from "yaml"; -import {TTLCache} from "./cache"; -import {errorMessage, errorStatus} from "./error"; +import {TTLCache} from "./cache.js"; +import {errorMessage, errorStatus} from "./error.js"; export function getActionsMetadataProvider( client: Octokit | undefined, diff --git a/languageserver/src/utils/repo-permission.ts b/languageserver/src/utils/repo-permission.ts index d236b787..d14f86d3 100644 --- a/languageserver/src/utils/repo-permission.ts +++ b/languageserver/src/utils/repo-permission.ts @@ -1,9 +1,9 @@ import {error} from "@actions/languageservice/log"; import {Octokit} from "@octokit/rest"; -import {RepositoryContext} from "../initializationOptions"; -import {TTLCache} from "./cache"; -import {errorStatus} from "./error"; -import {getUsername} from "./username"; +import {RepositoryContext} from "../initializationOptions.js"; +import {TTLCache} from "./cache.js"; +import {errorStatus} from "./error.js"; +import {getUsername} from "./username.js"; export type RepoPermission = "admin" | "write" | "read" | "none"; diff --git a/languageserver/src/utils/username.ts b/languageserver/src/utils/username.ts index e09f79de..b6163aab 100644 --- a/languageserver/src/utils/username.ts +++ b/languageserver/src/utils/username.ts @@ -1,5 +1,5 @@ import {Octokit} from "@octokit/rest"; -import {TTLCache} from "./cache"; +import {TTLCache} from "./cache.js"; export async function getUsername(octokit: Octokit, cache: TTLCache): Promise { return await cache.get(`/username`, undefined, () => fetchUsername(octokit)); diff --git a/languageserver/src/value-providers.ts b/languageserver/src/value-providers.ts index 5b8d9f96..4e442f27 100644 --- a/languageserver/src/value-providers.ts +++ b/languageserver/src/value-providers.ts @@ -2,11 +2,11 @@ import {ValueProviderConfig} from "@actions/languageservice"; import {WorkflowContext} from "@actions/languageservice/context/workflow-context"; import {ValueProviderKind} from "@actions/languageservice/value-providers/config"; import {Octokit} from "@octokit/rest"; -import {RepositoryContext} from "./initializationOptions"; -import {TTLCache} from "./utils/cache"; -import {getActionInputValues} from "./value-providers/action-inputs"; -import {getEnvironments} from "./value-providers/job-environment"; -import {getRunnerLabels} from "./value-providers/runs-on"; +import {RepositoryContext} from "./initializationOptions.js"; +import {TTLCache} from "./utils/cache.js"; +import {getActionInputValues} from "./value-providers/action-inputs.js"; +import {getEnvironments} from "./value-providers/job-environment.js"; +import {getRunnerLabels} from "./value-providers/runs-on.js"; export function valueProviders( client: Octokit | undefined, diff --git a/languageserver/src/value-providers/action-inputs.ts b/languageserver/src/value-providers/action-inputs.ts index 765f41d6..ef8eb3e9 100644 --- a/languageserver/src/value-providers/action-inputs.ts +++ b/languageserver/src/value-providers/action-inputs.ts @@ -3,8 +3,8 @@ import {WorkflowContext} from "@actions/languageservice/context/workflow-context import {Value} from "@actions/languageservice/value-providers/config"; import {isActionStep} from "@actions/workflow-parser/model/type-guards"; import {Octokit} from "@octokit/rest"; -import {fetchActionMetadata} from "../utils/action-metadata"; -import {TTLCache} from "../utils/cache"; +import {fetchActionMetadata} from "../utils/action-metadata.js"; +import {TTLCache} from "../utils/cache.js"; export async function getActionInputs( client: Octokit, diff --git a/languageserver/src/value-providers/job-environment.ts b/languageserver/src/value-providers/job-environment.ts index ac8881b3..2e6ea66c 100644 --- a/languageserver/src/value-providers/job-environment.ts +++ b/languageserver/src/value-providers/job-environment.ts @@ -1,6 +1,6 @@ import {Value} from "@actions/languageservice/value-providers/config"; import {Octokit} from "@octokit/rest"; -import {TTLCache} from "../utils/cache"; +import {TTLCache} from "../utils/cache.js"; export async function getEnvironments(client: Octokit, cache: TTLCache, owner: string, name: string): Promise { const environments = await cache.get(`${owner}/${name}/environments`, undefined, () => diff --git a/languageserver/src/value-providers/runs-on.ts b/languageserver/src/value-providers/runs-on.ts index be5b9010..e3102aa9 100644 --- a/languageserver/src/value-providers/runs-on.ts +++ b/languageserver/src/value-providers/runs-on.ts @@ -2,8 +2,8 @@ import {log} from "@actions/languageservice/log"; import {Value} from "@actions/languageservice/value-providers/config"; import {DEFAULT_RUNNER_LABELS} from "@actions/languageservice/value-providers/default"; import {Octokit} from "@octokit/rest"; -import {TTLCache} from "../utils/cache"; -import {errorMessage} from "../utils/error"; +import {TTLCache} from "../utils/cache.js"; +import {errorMessage} from "../utils/error.js"; // Limitation: getRunnerLabels returns default hosted labels and labels for repository self-hosted runners. // It doesn't return labels for organization runners visible to the repository. diff --git a/languageserver/tsconfig.build.json b/languageserver/tsconfig.build.json index 61c74a35..be3a6be1 100644 --- a/languageserver/tsconfig.build.json +++ b/languageserver/tsconfig.build.json @@ -5,6 +5,7 @@ "declaration": true, "declarationMap": true, "noEmit": false, - "outDir": "./dist" + "outDir": "./dist", + "skipLibCheck": true } }