diff --git a/jest.config.js b/jest.config.js index 46d5faef..885aaee0 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,15 +1,12 @@ -module.exports = { - preset: 'ts-jest', - transform: { '^.+\\.ts': 'ts-jest' }, +export default { + preset: 'ts-jest/presets/default-esm', + extensionsToTreatAsEsm: ['.ts'], + transform: { + '^.+\\.ts$': ['ts-jest', { useESM: true }], + }, testMatch: ['**/*.test.ts'], clearMocks: true, testEnvironment: 'node', - extensionsToTreatAsEsm: ['.ts'], - globals: { - 'ts-jest': { - useESM: true, - }, - }, transformIgnorePatterns: [ 'node_modules/(?!(msw|@mswjs|@bundled-es-modules|until-async|chalk|@open-draft|@inquirer|strict-event-emitter)/)', ], diff --git a/package.json b/package.json index 6a4bcef6..2222771a 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,17 @@ "repository": "git@github.com:doist/todoist-api-typescript.git", "homepage": "https://doist.github.io/todoist-api-typescript/", "license": "MIT", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "type": "module", + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + "types": "dist/types/index.d.ts", + "exports": { + ".": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts" + } + }, "sideEffects": false, "engines": { "node": ">=20.0.0" @@ -21,7 +30,11 @@ "ts-compile-check": "npx tsc -p tsconfig.typecheck.json", "audit": "npm audit --audit-level=moderate", "test": "jest", - "build": "npx tsc -p tsconfig.json", + "build:cjs": "npx tsc -p tsconfig.cjs.json", + "build:esm": "npx tsc -p tsconfig.esm.json", + "build:fix-esm": "node scripts/fix-esm-imports.cjs", + "build:post": "echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json", + "build": "npm-run-all clean build:cjs build:esm build:fix-esm build:post", "integrity-checks": "npm-run-all clean format-check lint-check test build", "prepublishOnly": "npm run integrity-checks", "prepare": "npm run build" @@ -71,7 +84,9 @@ "*.{ts,tsx,json,html,yml,yaml,md}": "prettier --check" }, "files": [ - "dist/**/*", + "dist/cjs/**/*", + "dist/esm/**/*", + "dist/types/**/*", "!dist/**/*.test.js", "!dist/**/*.test.d.ts" ] diff --git a/scripts/fix-esm-imports.cjs b/scripts/fix-esm-imports.cjs new file mode 100644 index 00000000..90bc10a6 --- /dev/null +++ b/scripts/fix-esm-imports.cjs @@ -0,0 +1,99 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +/** + * Post-build script to fix ESM imports by adding .js extensions. + * This is required because ESM requires explicit file extensions for relative imports. + */ + +const ESM_DIR = path.join(__dirname, '../dist/esm'); + +function fixImportsInFile(filePath) { + let content = fs.readFileSync(filePath, 'utf8'); + let modified = false; + + // Fix relative imports - add .js extension if missing + content = content.replace( + /from\s+['"](\.[^'"]*?)['"];?/g, + (match, importPath) => { + // Skip if already has extension or is directory import + if (path.extname(importPath) || importPath.endsWith('/')) { + return match; + } + + // Check if this is a directory import (look for index file) + const fullPath = path.resolve(path.dirname(filePath), importPath); + const indexPath = path.join(fullPath, 'index.js'); + + if (fs.existsSync(indexPath)) { + // Directory import - add /index.js + modified = true; + return match.replace(importPath, importPath + '/index.js'); + } else { + // File import - add .js + modified = true; + return match.replace(importPath, importPath + '.js'); + } + } + ); + + // Fix export statements as well + content = content.replace( + /export\s+(?:\*|\{[^}]*\})\s+from\s+['"](\.[^'"]*?)['"];?/g, + (match, importPath) => { + // Skip if already has extension or is directory import + if (path.extname(importPath) || importPath.endsWith('/')) { + return match; + } + + // Check if this is a directory import (look for index file) + const fullPath = path.resolve(path.dirname(filePath), importPath); + const indexPath = path.join(fullPath, 'index.js'); + + if (fs.existsSync(indexPath)) { + // Directory import - add /index.js + modified = true; + return match.replace(importPath, importPath + '/index.js'); + } else { + // File import - add .js + modified = true; + return match.replace(importPath, importPath + '.js'); + } + } + ); + + if (modified) { + fs.writeFileSync(filePath, content, 'utf8'); + console.log(`Fixed imports in: ${path.relative(process.cwd(), filePath)}`); + } +} + +function walkDirectory(dir) { + const files = fs.readdirSync(dir); + + for (const file of files) { + const filePath = path.join(dir, file); + const stat = fs.statSync(filePath); + + if (stat.isDirectory()) { + walkDirectory(filePath); + } else if (file.endsWith('.js')) { + fixImportsInFile(filePath); + } + } +} + +function main() { + if (!fs.existsSync(ESM_DIR)) { + console.log('ESM directory does not exist, skipping import fixing'); + return; + } + + console.log('Fixing ESM imports by adding .js extensions...'); + walkDirectory(ESM_DIR); + console.log('ESM import fixing completed'); +} + +main(); \ No newline at end of file diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json new file mode 100644 index 00000000..a2504144 --- /dev/null +++ b/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "dist/cjs", + "declaration": true, + "declarationDir": "dist/types" + } +} diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 00000000..8d7962f9 --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "dist/esm", + "declaration": false + } +}