ESM migration: Add .js extensions for node16 moduleResolution (#257)

Migrate expressions, workflow-parser, and languageservice packages to use
proper ESM imports with .js extensions that work with node16 moduleResolution.

Changes:
- Update tsconfig.build.json in each package to use module: node16 and
  moduleResolution: node16
- Add .js extensions to all relative import paths (Option B approach)
- Fix yaml internal type imports in workflow-parser by defining local types
- Add skipLibCheck to handle @types/node compatibility issues
- Add TypeScript 5.8.3 override in root package.json
- Add ESM migration plan documentation

The languageserver package is deferred due to test hang issues that need
further investigation.

Related #154 - Upgrade moduleResolution from node to node16 or nodenext
Related #110 - Published ESM code has imports without file extensions
Related #64 - expressions: ERR_MODULE_NOT_FOUND attempting to run example
Related #146 - Can not import @actions/workflow-parser

Test results:
- expressions: 1068 tests passed
- workflow-parser: 292 tests passed
- languageservice: 452 tests passed

* docs: update ESM migration plan with findings

- Update languageserver blocker: vscode-languageserver v8.0.2 lacks ESM
  exports (not a test hang issue)
- Document that Option B (manual .js extensions) was chosen over Option A
  due to ts-jest compatibility issues
- Add workaround for yaml package internal types (LinePos, NodeBase)
- Update migration status table with accurate reason for deferral
- Add skipLibCheck note for @types/node compatibility
This commit is contained in:
eric sciple
2025-12-18 13:35:48 -06:00
committed by GitHub
parent fbdc2a5749
commit 656a821a94
186 changed files with 1136 additions and 763 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionData, ExpressionDataInterface, Kind, kindStr} from "./expressiondata";
import {ExpressionData, ExpressionDataInterface, Kind, kindStr} from "./expressiondata.js";
export class Array implements ExpressionDataInterface {
private v: ExpressionData[] = [];
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionDataInterface, Kind} from "./expressiondata";
import {ExpressionDataInterface, Kind} from "./expressiondata.js";
export class BooleanData implements ExpressionDataInterface {
constructor(public readonly value: boolean) {}
+2 -2
View File
@@ -1,5 +1,5 @@
import {Dictionary} from "./dictionary";
import {StringData} from "./string";
import {Dictionary} from "./dictionary.js";
import {StringData} from "./string.js";
describe("dictionary", () => {
it("pairs contains all values", () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionData, ExpressionDataInterface, Kind, kindStr, Pair} from "./expressiondata";
import {ExpressionData, ExpressionDataInterface, Kind, kindStr, Pair} from "./expressiondata.js";
export class Dictionary implements ExpressionDataInterface {
private keys: string[] = [];
+6 -6
View File
@@ -1,9 +1,9 @@
import {Dictionary} from "./dictionary";
import {Null} from "./null";
import {Array} from "./array";
import {StringData} from "./string";
import {NumberData} from "./number";
import {BooleanData} from "./boolean";
import {Dictionary} from "./dictionary.js";
import {Null} from "./null.js";
import {Array} from "./array.js";
import {StringData} from "./string.js";
import {NumberData} from "./number.js";
import {BooleanData} from "./boolean.js";
export enum Kind {
String = 0,
+9 -9
View File
@@ -1,9 +1,9 @@
export {Array} from "./array";
export {BooleanData} from "./boolean";
export {Dictionary} from "./dictionary";
export {ExpressionData, Kind} from "./expressiondata";
export {Null} from "./null";
export {NumberData} from "./number";
export {replacer} from "./replacer";
export {reviver} from "./reviver";
export {StringData} from "./string";
export {Array} from "./array.js";
export {BooleanData} from "./boolean.js";
export {Dictionary} from "./dictionary.js";
export {ExpressionData, Kind} from "./expressiondata.js";
export {Null} from "./null.js";
export {NumberData} from "./number.js";
export {replacer} from "./replacer.js";
export {reviver} from "./reviver.js";
export {StringData} from "./string.js";
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionDataInterface, Kind} from "./expressiondata";
import {ExpressionDataInterface, Kind} from "./expressiondata.js";
export class Null implements ExpressionDataInterface {
public readonly kind = Kind.Null;
+1 -1
View File
@@ -1,4 +1,4 @@
import {NumberData} from "./number";
import {NumberData} from "./number.js";
describe("number", () => {
it("coerces to string", () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionDataInterface, Kind} from "./expressiondata";
import {ExpressionDataInterface, Kind} from "./expressiondata.js";
export class NumberData implements ExpressionDataInterface {
constructor(public readonly value: number) {}
+6 -6
View File
@@ -1,9 +1,9 @@
import {Array} from "./array";
import {Dictionary} from "./dictionary";
import {Null} from "./null";
import {NumberData} from "./number";
import {replacer} from "./replacer";
import {StringData} from "./string";
import {Array} from "./array.js";
import {Dictionary} from "./dictionary.js";
import {Null} from "./null.js";
import {NumberData} from "./number.js";
import {replacer} from "./replacer.js";
import {StringData} from "./string.js";
describe("replacer", () => {
it("null", () => {
+6 -6
View File
@@ -1,9 +1,9 @@
import {Array} from "./array";
import {BooleanData} from "./boolean";
import {Dictionary} from "./dictionary";
import {Null} from "./null";
import {NumberData} from "./number";
import {StringData} from "./string";
import {Array} from "./array.js";
import {BooleanData} from "./boolean.js";
import {Dictionary} from "./dictionary.js";
import {Null} from "./null.js";
import {NumberData} from "./number.js";
import {StringData} from "./string.js";
/**
* Replacer can be passed to JSON.stringify to convert an ExpressionData object into plain JSON
+8 -8
View File
@@ -1,11 +1,11 @@
import {Array} from "./array";
import {BooleanData} from "./boolean";
import {Dictionary} from "./dictionary";
import {ExpressionData} from "./expressiondata";
import {Null} from "./null";
import {NumberData} from "./number";
import {reviver} from "./reviver";
import {StringData} from "./string";
import {Array} from "./array.js";
import {BooleanData} from "./boolean.js";
import {Dictionary} from "./dictionary.js";
import {ExpressionData} from "./expressiondata.js";
import {Null} from "./null.js";
import {NumberData} from "./number.js";
import {reviver} from "./reviver.js";
import {StringData} from "./string.js";
describe("reviver", () => {
const tests: {
+7 -7
View File
@@ -1,10 +1,10 @@
import {Array as dArray} from "./array";
import {BooleanData} from "./boolean";
import {Dictionary} from "./dictionary";
import {ExpressionData} from "./expressiondata";
import {Null} from "./null";
import {NumberData} from "./number";
import {StringData} from "./string";
import {Array as dArray} from "./array.js";
import {BooleanData} from "./boolean.js";
import {Dictionary} from "./dictionary.js";
import {ExpressionData} from "./expressiondata.js";
import {Null} from "./null.js";
import {NumberData} from "./number.js";
import {StringData} from "./string.js";
/**
* Reviver can be passed to `JSON.parse` to convert plain JSON into an `ExpressionData` object.
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionDataInterface, Kind} from "./expressiondata";
import {ExpressionDataInterface, Kind} from "./expressiondata.js";
export class StringData implements ExpressionDataInterface {
constructor(public readonly value: string) {}