Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 689dca8802 | |||
| 10171d84a7 | |||
| 742b36d6b7 | |||
| 8507419ebf |
@@ -0,0 +1,388 @@
|
||||
# ESM Migration Plan: Add File Extensions to Imports
|
||||
|
||||
## Overview
|
||||
|
||||
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.
|
||||
|
||||
## Issues Fixed
|
||||
|
||||
This migration will resolve the following issues:
|
||||
|
||||
- **#154** - Upgrade `moduleResolution` from `node` to `node16` or `nodenext` in tsconfig
|
||||
- **#110** - Published ESM code has imports without file extensions
|
||||
- **#64** - expressions: ERR_MODULE_NOT_FOUND attempting to run example demo script
|
||||
- **#146** - Can not import `@actions/workflow-parser`
|
||||
|
||||
## Problem Statement
|
||||
|
||||
### Current State
|
||||
|
||||
All packages use `"moduleResolution": "node"`:
|
||||
|
||||
| Package | moduleResolution | TypeScript |
|
||||
|---------|------------------|------------|
|
||||
| expressions | `"node"` | ^4.7.4 |
|
||||
| workflow-parser | `"node"` | ^4.8.4 |
|
||||
| languageservice | `"node"` | ^4.8.4 |
|
||||
| languageserver | `"node"` | ^4.8.4 |
|
||||
| browser-playground | `"Node16"` ✅ | ^4.9.4 |
|
||||
|
||||
This causes TypeScript to emit code like:
|
||||
```javascript
|
||||
// Published to npm - INVALID ESM
|
||||
export { Expr } from "./ast"; // Missing .js extension!
|
||||
```
|
||||
|
||||
### Why This Fails
|
||||
|
||||
ESM in Node.js 12+ **requires** explicit file extensions. When users try to import these packages:
|
||||
|
||||
```javascript
|
||||
// User's code
|
||||
import { Expr } from "@actions/expressions";
|
||||
```
|
||||
|
||||
Node.js fails with:
|
||||
```
|
||||
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../node_modules/@actions/expressions/dist/ast'
|
||||
```
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Option A: TypeScript 5.7+ with `rewriteRelativeImportExtensions` (Recommended)
|
||||
|
||||
TypeScript 5.7 introduced a new compiler option that automatically rewrites `.ts` extensions to `.js` in output:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node16", // or "nodenext"
|
||||
"rewriteRelativeImportExtensions": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Source code:**
|
||||
```typescript
|
||||
import { Expr } from "./ast.ts";
|
||||
```
|
||||
|
||||
**Compiled output:**
|
||||
```javascript
|
||||
export { Expr } from "./ast.js";
|
||||
```
|
||||
|
||||
**Pros:**
|
||||
- Source uses `.ts` extensions (matches actual files)
|
||||
- Works with Deno (which requires `.ts` extensions)
|
||||
- TypeScript automatically transforms to `.js`
|
||||
- Modern, forward-looking approach
|
||||
|
||||
**Cons:**
|
||||
- Requires TypeScript 5.7+
|
||||
- Relatively new feature
|
||||
- **BUG:** See "Known Issues" section below
|
||||
|
||||
### Option B: Manual `.js` Extensions
|
||||
|
||||
Use `.js` extensions in source TypeScript files:
|
||||
|
||||
```typescript
|
||||
import { Expr } from "./ast.js"; // Points to .ts file, but use .js extension
|
||||
```
|
||||
|
||||
**Pros:**
|
||||
- Works with TypeScript 4.7+ (with node16 moduleResolution)
|
||||
- Well-established pattern
|
||||
- No post-processing needed
|
||||
|
||||
**Cons:**
|
||||
- Confusing - `.js` files don't exist at write time
|
||||
- Doesn't work with Deno out of the box
|
||||
|
||||
### Recommendation
|
||||
|
||||
**Use Option A** with workarounds for known issues (see below).
|
||||
|
||||
---
|
||||
|
||||
## Known Issues and Workarounds (December 2025)
|
||||
|
||||
### 1. TypeScript Version Conflicts in Monorepo
|
||||
|
||||
**Problem:** The root `node_modules/typescript` was version 4.9.5 (pulled in by `ts-node` and `tsutils` dependencies), while workspace packages specified `^5.8.3`.
|
||||
|
||||
**Symptoms:**
|
||||
- `npx tsc --version` showed 4.9.5
|
||||
- `require('typescript').version` in ts-jest showed 5.8.3
|
||||
- Confusing build failures
|
||||
|
||||
**Solution:** Add npm overrides in root `package.json`:
|
||||
```json
|
||||
{
|
||||
"overrides": {
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. ts-jest Compatibility with TypeScript 5.9+
|
||||
|
||||
**Problem:** ts-jest 29.4.6 uses `typescript.JSDocParsingMode.ParseAll` which doesn't exist in TypeScript's ES module exports.
|
||||
|
||||
**Error:**
|
||||
```
|
||||
TypeError: Cannot read properties of undefined (reading 'ParseAll')
|
||||
at Object.<anonymous> (node_modules/ts-jest/dist/compiler/ts-compiler.js:43:123)
|
||||
```
|
||||
|
||||
**Root Cause:** ts-jest accesses `typescript_1.default.JSDocParsingMode.ParseAll` but TypeScript has no default export in ESM.
|
||||
|
||||
**Solution:**
|
||||
- Use ts-jest 29.0.3 (older version that doesn't use this API)
|
||||
- OR wait for ts-jest fix
|
||||
- **Stay on TypeScript 5.8.3, not 5.9+**
|
||||
|
||||
### 3. TypeScript `rewriteRelativeImportExtensions` Bug with .d.ts Files
|
||||
|
||||
**Problem:** TypeScript's `rewriteRelativeImportExtensions: true` correctly rewrites `.ts` → `.js` in `.js` output files, but **incorrectly keeps `.ts` extensions in `.d.ts` declaration files**.
|
||||
|
||||
**Example:**
|
||||
- Source: `export { Expr } from "./ast.ts";`
|
||||
- Output `index.js`: `export { Expr } from "./ast.js";` ✅ Correct
|
||||
- Output `index.d.ts`: `export { Expr } from "./ast.ts";` ❌ Wrong (should be `.js`)
|
||||
|
||||
**Upstream Issue:** https://github.com/microsoft/TypeScript/issues/61037 (marked "Help Wanted", in Backlog, NOT FIXED as of Dec 2025)
|
||||
|
||||
**Workaround:** Post-process `.d.ts` files with a script. Create `script/fix-dts-extensions.cjs`:
|
||||
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Post-build script to fix TypeScript's rewriteRelativeImportExtensions bug
|
||||
* where .d.ts files get .ts extensions instead of .js extensions.
|
||||
* See: https://github.com/microsoft/TypeScript/issues/61037
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function fixDtsFile(filePath) {
|
||||
let content = fs.readFileSync(filePath, 'utf8');
|
||||
const original = content;
|
||||
|
||||
// Replace .ts extensions in import/export statements with .js
|
||||
content = content.replace(/(from\s+["'])([^"']+)\.ts(["'])/g, '$1$2.js$3');
|
||||
content = content.replace(/(import\s*\(\s*["'])([^"']+)\.ts(["']\s*\))/g, '$1$2.js$3');
|
||||
content = content.replace(/(export\s+\*\s+from\s+["'])([^"']+)\.ts(["'])/g, '$1$2.js$3');
|
||||
|
||||
if (content !== original) {
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
||||
console.log(`Fixed: ${filePath}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function walkDir(dir, callback) {
|
||||
const files = fs.readdirSync(dir);
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
if (stat.isDirectory()) {
|
||||
walkDir(filePath, callback);
|
||||
} else if (file.endsWith('.d.ts')) {
|
||||
callback(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length === 0) {
|
||||
console.error('Usage: fix-dts-extensions.cjs <dist-dir> [<dist-dir2> ...]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let fixedCount = 0;
|
||||
for (const dir of args) {
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.warn(`Directory not found: ${dir}`);
|
||||
continue;
|
||||
}
|
||||
walkDir(dir, (filePath) => {
|
||||
if (fixDtsFile(filePath)) {
|
||||
fixedCount++;
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log(`Fixed ${fixedCount} .d.ts file(s)`);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
**Note:** Use `.cjs` extension since root `package.json` has `"type": "module"`.
|
||||
|
||||
**Usage in package.json build scripts:**
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build": "tsc --build tsconfig.build.json && node ../script/fix-dts-extensions.cjs dist"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. languageserver Tests Hang
|
||||
|
||||
**Problem:** The languageserver tests hang indefinitely when running with the ESM configuration.
|
||||
|
||||
**Status:** Not fully diagnosed. Tests pass on main branch but hang on ESM branch.
|
||||
|
||||
**Possible causes:**
|
||||
- Jest ESM module resolution issues
|
||||
- Cross-package source mappings in jest.config.js
|
||||
- vscode-languageserver ESM compatibility issues
|
||||
- Specific test file causing hang (needs isolation testing)
|
||||
|
||||
**Investigation needed:**
|
||||
- Run individual test files to isolate the hanging test
|
||||
- Check if vscode-languageserver has ESM compatibility issues
|
||||
- Review jest configuration for problematic mappings
|
||||
- Try running with `--detectOpenHandles` flag
|
||||
|
||||
---
|
||||
|
||||
## Required Configuration Changes
|
||||
|
||||
### tsconfig.json (each package)
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "node16",
|
||||
"moduleResolution": "node16",
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"lib": ["ES2022"],
|
||||
"target": "ES2022"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### jest.config.js (each package)
|
||||
|
||||
```javascript
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
export default {
|
||||
preset: "ts-jest/presets/default-esm",
|
||||
moduleNameMapper: {
|
||||
"^(\\.{1,2}/.*)\\.js$": "$1",
|
||||
"^(\\.{1,2}/.*)\\.ts$": "$1",
|
||||
},
|
||||
transform: {
|
||||
"^.+\\.tsx?$": [
|
||||
"ts-jest",
|
||||
{
|
||||
useESM: true,
|
||||
isolatedModules: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
moduleFileExtensions: ["ts", "js"],
|
||||
};
|
||||
```
|
||||
|
||||
### Root package.json
|
||||
|
||||
```json
|
||||
{
|
||||
"overrides": {
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Each workspace package.json
|
||||
|
||||
```json
|
||||
{
|
||||
"devDependencies": {
|
||||
"typescript": "^5.8.3",
|
||||
"ts-jest": "^29.0.3"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test Results (December 2025 Attempt)
|
||||
|
||||
| Package | Tests | Status |
|
||||
|---------|-------|--------|
|
||||
| expressions | 1068 | ✅ Pass |
|
||||
| workflow-parser | 292 | ✅ Pass |
|
||||
| languageservice | 452 | ✅ Pass |
|
||||
| languageserver | 6 files | ❌ Hangs (needs investigation) |
|
||||
|
||||
---
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Prerequisites
|
||||
- [ ] Merge all pending PRs to avoid conflicts
|
||||
- [ ] Ensure clean main branch state
|
||||
|
||||
### Phase 2: TypeScript & Dependencies
|
||||
- [ ] Add `"overrides": { "typescript": "5.8.3" }` to root package.json
|
||||
- [ ] Update all workspace packages to TypeScript ^5.8.3
|
||||
- [ ] Downgrade ts-jest to ^29.0.3 in all packages
|
||||
- [ ] Run `npm install` to apply changes
|
||||
|
||||
### Phase 3: tsconfig Updates
|
||||
- [ ] Update tsconfig.json in each package with node16 settings
|
||||
- [ ] Add `rewriteRelativeImportExtensions: true`
|
||||
|
||||
### Phase 4: Add .ts Extensions to Imports
|
||||
- [ ] Update all relative imports in source files to use `.ts` extensions
|
||||
- [ ] This was already done in PR #243
|
||||
|
||||
### Phase 5: Create Fix Script
|
||||
- [ ] Add `script/fix-dts-extensions.cjs` to repository
|
||||
- [ ] Update build scripts to run fix after tsc
|
||||
|
||||
### Phase 6: Fix languageserver Tests
|
||||
- [ ] Investigate why tests hang
|
||||
- [ ] Isolate problematic test file
|
||||
- [ ] Apply fix or workaround
|
||||
|
||||
### Phase 7: Verification
|
||||
- [ ] Run `npm run build` in all packages
|
||||
- [ ] Run `npm test` in all packages
|
||||
- [ ] Test importing published packages in Node.js ESM mode
|
||||
- [ ] Verify browser-playground still works
|
||||
|
||||
### Phase 8: CI Updates
|
||||
- [ ] Update GitHub Actions workflows if needed
|
||||
- [ ] Ensure fix-dts-extensions.cjs runs in CI
|
||||
|
||||
---
|
||||
|
||||
## Summary of Required Changes
|
||||
|
||||
1. **Root package.json**: Add TypeScript override
|
||||
2. **All packages**: TypeScript 5.8.3, ts-jest 29.0.3
|
||||
3. **All tsconfigs**: node16 moduleResolution, rewriteRelativeImportExtensions
|
||||
4. **All imports**: Add .ts extensions (already done)
|
||||
5. **Build scripts**: Add post-build .d.ts fix
|
||||
6. **languageserver**: Debug test hang issue
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [TypeScript moduleResolution reference](https://www.typescriptlang.org/docs/handbook/modules/reference.html)
|
||||
- [TypeScript 5.7 rewriteRelativeImportExtensions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#path-rewriting-for-relative-paths)
|
||||
- [TypeScript .d.ts extension bug #61037](https://github.com/microsoft/TypeScript/issues/61037)
|
||||
- [Node.js ESM mandatory extensions](https://nodejs.org/api/esm.html#mandatory-file-extensions)
|
||||
- [ts-jest ESM support](https://kulshekhar.github.io/ts-jest/docs/guides/esm-support)
|
||||
- [Community fork that works](https://github.com/boxbuild-io/actions-languageservices/commit/077fb2b58dfd2cca3d6e3df1fdf9e26e75db24ae)
|
||||
@@ -148,3 +148,50 @@ Only `name`, `description`, and `childParamsGroups` are kept — these are used
|
||||
To compare all fields vs stripped, run `npm run update-webhooks -- --all` and diff the `.all.json` files against the regular ones.
|
||||
|
||||
See `EVENT_ACTION_FIELDS` and `BODY_PARAM_FIELDS` in `script/webhooks/index.ts` to modify what gets stripped.
|
||||
|
||||
## Schema Synchronization
|
||||
|
||||
The `workflow-v1.0.json` schema defines which activity types are valid for each workflow trigger event. A test in `workflow-parser/src/schema-sync.test.ts` verifies these stay in sync with `webhooks.json`.
|
||||
|
||||
### When the Test Fails
|
||||
|
||||
If the schema-sync test fails, you'll see an error like:
|
||||
|
||||
```
|
||||
Event "pull_request" is missing activity type "new_activity" in workflow-v1.0.json
|
||||
```
|
||||
|
||||
**To resolve:**
|
||||
|
||||
1. Check [Events that trigger workflows](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows) to verify the activity type is a valid workflow trigger:
|
||||
- Find the event section (e.g., "pull_request")
|
||||
- Look at the "Activity types" table — it lists which types can be used in `on.<event>.types`
|
||||
- If the type is listed there, it's a valid workflow trigger
|
||||
- If the type only appears in webhook docs but NOT in the workflow trigger docs, it's webhook-only
|
||||
|
||||
2. If it IS a valid workflow trigger:
|
||||
- Edit `workflow-parser/src/workflow-v1.0.json`
|
||||
- Find the `<event>-activity-type` definition (e.g., `pull-request-activity-type`)
|
||||
- Add the new activity type to `allowed-values`
|
||||
- Update the `description` in `<event>-activity` to list all types
|
||||
- Run `npm test` to regenerate the minified JSON
|
||||
|
||||
3. If it is NOT a valid workflow trigger (webhook-only):
|
||||
- Edit `workflow-parser/src/schema-sync.test.ts`
|
||||
- Add the type to `WEBHOOK_ONLY` for that event
|
||||
|
||||
### Known Discrepancies
|
||||
|
||||
The test tracks several types of known discrepancies:
|
||||
|
||||
| Category | Purpose | Example |
|
||||
|----------|---------|---------|
|
||||
| `WEBHOOK_ONLY` | Types in webhooks that aren't valid workflow triggers | `check_suite.requested` |
|
||||
| `SCHEMA_ONLY` | Types valid for workflows but missing from webhooks | `registry_package.updated` |
|
||||
| `NAME_MAPPINGS` | Different names for the same concept | `project_column`: webhook uses `edited`, schema uses `updated` |
|
||||
|
||||
### Bidirectional Checking
|
||||
|
||||
The test checks both directions:
|
||||
- **webhooks → schema**: Ensures all webhook activity types are in the schema (or listed in `WEBHOOK_ONLY`)
|
||||
- **schema → webhooks**: Ensures the schema doesn't have types that don't exist in webhooks (or listed in `SCHEMA_ONLY` or `NAME_MAPPINGS`)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actions/expressions",
|
||||
"version": "0.3.24",
|
||||
"version": "0.3.25",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actions/languageserver",
|
||||
"version": "0.3.24",
|
||||
"version": "0.3.25",
|
||||
"description": "Language server for GitHub Actions",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
@@ -43,8 +43,8 @@
|
||||
"watch": "tsc --build tsconfig.build.json --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/languageservice": "^0.3.24",
|
||||
"@actions/workflow-parser": "^0.3.24",
|
||||
"@actions/languageservice": "^0.3.25",
|
||||
"@actions/workflow-parser": "^0.3.25",
|
||||
"@octokit/rest": "^21.1.1",
|
||||
"@octokit/types": "^9.0.0",
|
||||
"vscode-languageserver": "^8.0.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actions/languageservice",
|
||||
"version": "0.3.24",
|
||||
"version": "0.3.25",
|
||||
"description": "Language service for GitHub Actions",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
@@ -47,8 +47,8 @@
|
||||
"watch": "tsc --build tsconfig.build.json --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/expressions": "^0.3.24",
|
||||
"@actions/workflow-parser": "^0.3.24",
|
||||
"@actions/expressions": "^0.3.25",
|
||||
"@actions/workflow-parser": "^0.3.25",
|
||||
"vscode-languageserver-textdocument": "^1.0.7",
|
||||
"vscode-languageserver-types": "^3.17.2",
|
||||
"vscode-uri": "^3.0.8",
|
||||
|
||||
+1
-1
@@ -6,5 +6,5 @@
|
||||
"languageservice",
|
||||
"languageserver"
|
||||
],
|
||||
"version": "0.3.24"
|
||||
"version": "0.3.25"
|
||||
}
|
||||
Generated
+9
-9
@@ -135,7 +135,7 @@
|
||||
},
|
||||
"expressions": {
|
||||
"name": "@actions/expressions",
|
||||
"version": "0.3.24",
|
||||
"version": "0.3.25",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.0.3",
|
||||
@@ -395,11 +395,11 @@
|
||||
},
|
||||
"languageserver": {
|
||||
"name": "@actions/languageserver",
|
||||
"version": "0.3.24",
|
||||
"version": "0.3.25",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/languageservice": "^0.3.24",
|
||||
"@actions/workflow-parser": "^0.3.24",
|
||||
"@actions/languageservice": "^0.3.25",
|
||||
"@actions/workflow-parser": "^0.3.25",
|
||||
"@octokit/rest": "^21.1.1",
|
||||
"@octokit/types": "^9.0.0",
|
||||
"vscode-languageserver": "^8.0.2",
|
||||
@@ -921,11 +921,11 @@
|
||||
},
|
||||
"languageservice": {
|
||||
"name": "@actions/languageservice",
|
||||
"version": "0.3.24",
|
||||
"version": "0.3.25",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/expressions": "^0.3.24",
|
||||
"@actions/workflow-parser": "^0.3.24",
|
||||
"@actions/expressions": "^0.3.25",
|
||||
"@actions/workflow-parser": "^0.3.25",
|
||||
"vscode-languageserver-textdocument": "^1.0.7",
|
||||
"vscode-languageserver-types": "^3.17.2",
|
||||
"vscode-uri": "^3.0.8",
|
||||
@@ -12834,10 +12834,10 @@
|
||||
},
|
||||
"workflow-parser": {
|
||||
"name": "@actions/workflow-parser",
|
||||
"version": "0.3.24",
|
||||
"version": "0.3.25",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/expressions": "^0.3.24",
|
||||
"@actions/expressions": "^0.3.25",
|
||||
"cronstrue": "^2.21.0",
|
||||
"yaml": "^2.0.0-8"
|
||||
},
|
||||
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Post-build script to fix TypeScript's rewriteRelativeImportExtensions bug
|
||||
* where .d.ts files get .ts extensions instead of .js extensions.
|
||||
* See: https://github.com/microsoft/TypeScript/issues/61037
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function fixDtsFile(filePath) {
|
||||
let content = fs.readFileSync(filePath, 'utf8');
|
||||
const original = content;
|
||||
|
||||
// Replace .ts extensions in import/export statements with .js
|
||||
// Matches: from "./foo.ts" or from './foo.ts'
|
||||
content = content.replace(/(from\s+["'])([^"']+)\.ts(["'])/g, '$1$2.js$3');
|
||||
|
||||
// Matches: import("./foo.ts") or import('./foo.ts')
|
||||
content = content.replace(/(import\s*\(\s*["'])([^"']+)\.ts(["']\s*\))/g, '$1$2.js$3');
|
||||
|
||||
// Matches: export * from "./foo.ts"
|
||||
content = content.replace(/(export\s+\*\s+from\s+["'])([^"']+)\.ts(["'])/g, '$1$2.js$3');
|
||||
|
||||
if (content !== original) {
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
||||
console.log(`Fixed: ${filePath}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function walkDir(dir, callback) {
|
||||
const files = fs.readdirSync(dir);
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
if (stat.isDirectory()) {
|
||||
walkDir(filePath, callback);
|
||||
} else if (file.endsWith('.d.ts')) {
|
||||
callback(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length === 0) {
|
||||
console.error('Usage: fix-dts-extensions.js <dist-dir> [<dist-dir2> ...]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let fixedCount = 0;
|
||||
for (const dir of args) {
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.warn(`Directory not found: ${dir}`);
|
||||
continue;
|
||||
}
|
||||
walkDir(dir, (filePath) => {
|
||||
if (fixDtsFile(filePath)) {
|
||||
fixedCount++;
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log(`Fixed ${fixedCount} .d.ts file(s)`);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actions/workflow-parser",
|
||||
"version": "0.3.24",
|
||||
"version": "0.3.25",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
@@ -48,7 +48,7 @@
|
||||
"watch": "tsc --build tsconfig.build.json --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/expressions": "^0.3.24",
|
||||
"@actions/expressions": "^0.3.25",
|
||||
"cronstrue": "^2.21.0",
|
||||
"yaml": "^2.0.0-8"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import * as fs from "fs";
|
||||
|
||||
/**
|
||||
* This test ensures that activity types in workflow-v1.0.json stay in sync with
|
||||
* the webhooks.json file from the languageservice package.
|
||||
*
|
||||
* When this test fails, it means new activity types were added to webhooks.json
|
||||
* that need to be handled. See docs/json-data-files.md for detailed instructions.
|
||||
*
|
||||
* Quick reference for fixing failures:
|
||||
* 1. Check https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows
|
||||
* Find the event and look at its "Activity types" table to see if the type is a valid workflow trigger.
|
||||
* 2. If the activity type IS a valid workflow trigger:
|
||||
* → Add it to the corresponding *-activity-type definition in workflow-v1.0.json
|
||||
* 3. If the activity type is webhook-only (not in workflow docs):
|
||||
* → Add it to the WEBHOOK_ONLY list below
|
||||
* 4. If there's a naming difference between webhook and schema:
|
||||
* → Add it to the NAME_MAPPINGS list below
|
||||
* 5. If the schema has a type not in webhooks.json:
|
||||
* → Add it to the SCHEMA_ONLY list below
|
||||
*/
|
||||
|
||||
describe("schema-sync", () => {
|
||||
// Activity types that exist in webhooks.json but are intentionally NOT
|
||||
// supported as workflow triggers. These will be ignored when checking
|
||||
// webhooks → schema direction.
|
||||
const WEBHOOK_ONLY: Record<string, string[]> = {
|
||||
// check_suite: requested and rerequested are webhook-only, not valid workflow triggers
|
||||
// See: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#check_suite
|
||||
check_suite: ["requested", "rerequested"],
|
||||
|
||||
// registry_package: "default" is a webhook concept, not a workflow trigger type
|
||||
registry_package: ["default"]
|
||||
};
|
||||
|
||||
// Activity types that exist in workflow schema but are intentionally NOT
|
||||
// in webhooks.json (schema-only types). These will be ignored when checking
|
||||
// schema → webhooks direction.
|
||||
const SCHEMA_ONLY: Record<string, string[]> = {
|
||||
// registry_package: "updated" is a valid workflow trigger per GitHub docs
|
||||
// but doesn't exist in webhooks.json (webhooks only has "published" and "default")
|
||||
// See: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#registry_package
|
||||
registry_package: ["updated"]
|
||||
};
|
||||
|
||||
// Known naming differences between webhooks.json and workflow-v1.0.json.
|
||||
// Key: event name, Value: { webhook: "webhookName", schema: "schemaName" }
|
||||
// These are treated as equivalent when comparing in both directions.
|
||||
const NAME_MAPPINGS: Record<string, Array<{webhook: string; schema: string}>> = {
|
||||
// project_column: webhooks.json uses "edited" but workflow triggers use "updated"
|
||||
// This is a known naming difference - they represent the same action
|
||||
project_column: [{webhook: "edited", schema: "updated"}]
|
||||
};
|
||||
|
||||
it("activity types in workflow-v1.0.json match webhooks.json", () => {
|
||||
// Load webhooks.json (relative path from the test runner CWD which is the package root)
|
||||
const webhooksPath = "../languageservice/src/context-providers/events/webhooks.json";
|
||||
const webhooks = JSON.parse(fs.readFileSync(webhooksPath, "utf-8")) as Record<string, Record<string, unknown>>;
|
||||
|
||||
// Load workflow-v1.0.json
|
||||
const schemaPath = "./src/workflow-v1.0.json";
|
||||
const schema = JSON.parse(fs.readFileSync(schemaPath, "utf-8")) as {
|
||||
definitions: Record<string, {"allowed-values"?: string[]; description?: string}>;
|
||||
};
|
||||
|
||||
const mismatches: string[] = [];
|
||||
|
||||
// Build mapping helpers for each event
|
||||
const getWebhookToSchemaMapping = (eventName: string): Map<string, string> => {
|
||||
const map = new Map<string, string>();
|
||||
for (const mapping of NAME_MAPPINGS[eventName] || []) {
|
||||
map.set(mapping.webhook, mapping.schema);
|
||||
}
|
||||
return map;
|
||||
};
|
||||
|
||||
const getSchemaToWebhookMapping = (eventName: string): Map<string, string> => {
|
||||
const map = new Map<string, string>();
|
||||
for (const mapping of NAME_MAPPINGS[eventName] || []) {
|
||||
map.set(mapping.schema, mapping.webhook);
|
||||
}
|
||||
return map;
|
||||
};
|
||||
|
||||
// Check both directions for each event
|
||||
for (const [eventName, eventData] of Object.entries(webhooks)) {
|
||||
const webhookTypes = Object.keys(eventData);
|
||||
if (webhookTypes.length === 0) continue;
|
||||
|
||||
const schemaTypeName = `${eventName.replace(/_/g, "-")}-activity-type`;
|
||||
const schemaDef = schema.definitions[schemaTypeName];
|
||||
|
||||
// If there's no activity type definition in the schema, this event
|
||||
// doesn't support activity types in workflows (e.g., push, pull)
|
||||
if (!schemaDef || !schemaDef["allowed-values"]) continue;
|
||||
|
||||
const schemaTypes = new Set(schemaDef["allowed-values"]);
|
||||
const webhookOnly = new Set(WEBHOOK_ONLY[eventName] || []);
|
||||
const schemaOnly = new Set(SCHEMA_ONLY[eventName] || []);
|
||||
const webhookToSchema = getWebhookToSchemaMapping(eventName);
|
||||
const schemaToWebhook = getSchemaToWebhookMapping(eventName);
|
||||
|
||||
// Direction 1: webhooks → schema
|
||||
// Check that each webhook type exists in schema (or has a mapping, or is webhook-only)
|
||||
for (const webhookType of webhookTypes) {
|
||||
if (webhookOnly.has(webhookType)) continue;
|
||||
|
||||
const mappedSchemaType = webhookToSchema.get(webhookType);
|
||||
if (mappedSchemaType) {
|
||||
// Has a mapping - check the mapped name exists in schema
|
||||
if (!schemaTypes.has(mappedSchemaType)) {
|
||||
mismatches.push(
|
||||
`Event "${eventName}": webhook type "${webhookType}" maps to "${mappedSchemaType}" but "${mappedSchemaType}" not found in schema`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// No mapping - check the type exists directly
|
||||
if (!schemaTypes.has(webhookType)) {
|
||||
mismatches.push(
|
||||
`Event "${eventName}": missing activity type "${webhookType}" in workflow-v1.0.json (exists in webhooks.json)`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Direction 2: schema → webhooks
|
||||
// Check that each schema type exists in webhooks (or has a mapping, or is schema-only)
|
||||
const webhookTypesSet = new Set(webhookTypes);
|
||||
for (const schemaType of schemaTypes) {
|
||||
if (schemaOnly.has(schemaType)) continue;
|
||||
|
||||
const mappedWebhookType = schemaToWebhook.get(schemaType);
|
||||
if (mappedWebhookType) {
|
||||
// Has a mapping - check the mapped name exists in webhooks
|
||||
if (!webhookTypesSet.has(mappedWebhookType)) {
|
||||
mismatches.push(
|
||||
`Event "${eventName}": schema type "${schemaType}" maps to "${mappedWebhookType}" but "${mappedWebhookType}" not found in webhooks.json`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// No mapping - check the type exists directly
|
||||
if (!webhookTypesSet.has(schemaType)) {
|
||||
mismatches.push(
|
||||
`Event "${eventName}": extra activity type "${schemaType}" in workflow-v1.0.json (not in webhooks.json)`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the description mentions all allowed values
|
||||
const activityDefName = `${eventName.replace(/_/g, "-")}-activity`;
|
||||
const activityDef = schema.definitions[activityDefName];
|
||||
if (activityDef?.description) {
|
||||
for (const schemaType of schemaTypes) {
|
||||
if (!activityDef.description.includes(`\`${schemaType}\``)) {
|
||||
mismatches.push(
|
||||
`Event "${eventName}": description in "${activityDefName}" is missing activity type \`${schemaType}\``
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mismatches.length > 0) {
|
||||
const errorMessage = [
|
||||
"Activity type mismatches found between webhooks.json and workflow-v1.0.json:",
|
||||
"",
|
||||
...mismatches,
|
||||
"",
|
||||
"To fix these mismatches:",
|
||||
"1. Check GitHub docs: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows",
|
||||
"2. Verify the activity type is valid for workflow triggers",
|
||||
"3. Update the *-activity-type definition in workflow-parser/src/workflow-v1.0.json",
|
||||
"4. Update the description to list all supported activity types",
|
||||
"5. If there's a naming difference, add it to NAME_MAPPINGS in schema-sync.test.ts",
|
||||
"6. If the type is webhook-only, add it to WEBHOOK_ONLY",
|
||||
"7. If the type is schema-only, add it to SCHEMA_ONLY"
|
||||
].join("\n");
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -856,7 +856,7 @@
|
||||
}
|
||||
},
|
||||
"pull-request-activity": {
|
||||
"description": "The types of pull request activity that trigger the workflow. Supported activity types: `assigned`, `unassigned`, `labeled`, `unlabeled`, `opened`, `edited`, `closed`, `reopened`, `synchronize`, `converted_to_draft`, `ready_for_review`, `locked`, `unlocked`, `review_requested`, `review_request_removed`, `auto_merge_enabled`, `auto_merge_disabled`.",
|
||||
"description": "The types of pull request activity that trigger the workflow. Supported activity types: `assigned`, `unassigned`, `labeled`, `unlabeled`, `opened`, `edited`, `closed`, `reopened`, `synchronize`, `converted_to_draft`, `locked`, `unlocked`, `enqueued`, `dequeued`, `milestoned`, `demilestoned`, `ready_for_review`, `review_requested`, `review_request_removed`, `auto_merge_enabled`, `auto_merge_disabled`.",
|
||||
"one-of": [
|
||||
"pull-request-activity-type",
|
||||
"pull-request-activity-types"
|
||||
@@ -879,9 +879,13 @@
|
||||
"reopened",
|
||||
"synchronize",
|
||||
"converted_to_draft",
|
||||
"ready_for_review",
|
||||
"locked",
|
||||
"unlocked",
|
||||
"enqueued",
|
||||
"dequeued",
|
||||
"milestoned",
|
||||
"demilestoned",
|
||||
"ready_for_review",
|
||||
"review_requested",
|
||||
"review_request_removed",
|
||||
"auto_merge_enabled",
|
||||
@@ -1004,7 +1008,7 @@
|
||||
}
|
||||
},
|
||||
"pull-request-target-activity": {
|
||||
"description": "The types of pull request activity that trigger the workflow. Supported activity types: `assigned`, `unassigned`, `labeled`, `unlabeled`, `opened`, `edited`, `closed`, `reopened`, `synchronize`, `converted_to_draft`, `ready_for_review`, `locked`, `unlocked`, `review_requested`, `review_request_removed`, `auto_merge_enabled`, `auto_merge_disabled`.",
|
||||
"description": "The types of pull request activity that trigger the workflow. Supported activity types: `assigned`, `unassigned`, `labeled`, `unlabeled`, `opened`, `edited`, `closed`, `reopened`, `synchronize`, `converted_to_draft`, `locked`, `unlocked`, `enqueued`, `dequeued`, `milestoned`, `demilestoned`, `ready_for_review`, `review_requested`, `review_request_removed`, `auto_merge_enabled`, `auto_merge_disabled`.",
|
||||
"one-of": [
|
||||
"pull-request-target-activity-type",
|
||||
"pull-request-target-activity-types"
|
||||
@@ -1027,9 +1031,13 @@
|
||||
"reopened",
|
||||
"synchronize",
|
||||
"converted_to_draft",
|
||||
"ready_for_review",
|
||||
"locked",
|
||||
"unlocked",
|
||||
"enqueued",
|
||||
"dequeued",
|
||||
"milestoned",
|
||||
"demilestoned",
|
||||
"ready_for_review",
|
||||
"review_requested",
|
||||
"review_request_removed",
|
||||
"auto_merge_enabled",
|
||||
|
||||
@@ -120,6 +120,8 @@ on:
|
||||
- unassigned
|
||||
- labeled
|
||||
- unlabeled
|
||||
- milestoned
|
||||
- demilestoned
|
||||
- opened
|
||||
- edited
|
||||
- closed
|
||||
@@ -129,6 +131,8 @@ on:
|
||||
- ready_for_review
|
||||
- locked
|
||||
- unlocked
|
||||
- enqueued
|
||||
- dequeued
|
||||
- review_requested
|
||||
- review_request_removed
|
||||
- auto_merge_enabled
|
||||
@@ -160,6 +164,8 @@ on:
|
||||
- unassigned
|
||||
- labeled
|
||||
- unlabeled
|
||||
- milestoned
|
||||
- demilestoned
|
||||
- opened
|
||||
- edited
|
||||
- closed
|
||||
@@ -169,6 +175,8 @@ on:
|
||||
- ready_for_review
|
||||
- locked
|
||||
- unlocked
|
||||
- enqueued
|
||||
- dequeued
|
||||
- review_requested
|
||||
- review_request_removed
|
||||
- auto_merge_enabled
|
||||
@@ -386,6 +394,8 @@ jobs:
|
||||
"unassigned",
|
||||
"labeled",
|
||||
"unlabeled",
|
||||
"milestoned",
|
||||
"demilestoned",
|
||||
"opened",
|
||||
"edited",
|
||||
"closed",
|
||||
@@ -395,6 +405,8 @@ jobs:
|
||||
"ready_for_review",
|
||||
"locked",
|
||||
"unlocked",
|
||||
"enqueued",
|
||||
"dequeued",
|
||||
"review_requested",
|
||||
"review_request_removed",
|
||||
"auto_merge_enabled",
|
||||
@@ -441,6 +453,8 @@ jobs:
|
||||
"unassigned",
|
||||
"labeled",
|
||||
"unlabeled",
|
||||
"milestoned",
|
||||
"demilestoned",
|
||||
"opened",
|
||||
"edited",
|
||||
"closed",
|
||||
@@ -450,6 +464,8 @@ jobs:
|
||||
"ready_for_review",
|
||||
"locked",
|
||||
"unlocked",
|
||||
"enqueued",
|
||||
"dequeued",
|
||||
"review_requested",
|
||||
"review_request_removed",
|
||||
"auto_merge_enabled",
|
||||
|
||||
Reference in New Issue
Block a user