refactor: remove duplicate server lib files and convert dependency-resolver to ESM

Cleanup Changes:
- Remove 9 duplicate server lib files now available in shared packages:
  - automaker-paths.ts → @automaker/platform
  - conversation-utils.ts → @automaker/utils
  - error-handler.ts → @automaker/utils
  - fs-utils.ts → @automaker/utils
  - image-handler.ts → @automaker/utils
  - logger.ts → @automaker/utils
  - prompt-builder.ts → @automaker/utils
  - security.ts → @automaker/platform
  - subprocess-manager.ts → @automaker/platform

ESM Conversion:
- Convert @automaker/dependency-resolver from CommonJS to ESM
- Fixes UI build compatibility with Vite bundler
- Update package.json: add "type": "module", change "require" to "import"
- Update tsconfig.json: module "ESNext", moduleResolution "bundler"

Import Fixes:
- Update write.ts to import mkdirSafe from @automaker/utils
- Remove broken @automaker/types import from UI (not exported for Vite)

Build Status:
 Server builds successfully
 UI builds successfully
 All migrated package tests pass (dependency-resolver, utils, platform)
 500/554 server tests pass (54 pre-existing subprocess-manager failures)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Kacper
2025-12-20 00:41:35 +01:00
parent 3a69f973d0
commit 4afa73521d
13 changed files with 6 additions and 945 deletions

View File

@@ -1,91 +0,0 @@
/**
* Automaker Paths - Utilities for managing automaker data storage
*
* Stores project data inside the project directory at {projectPath}/.automaker/
*/
import fs from "fs/promises";
import path from "path";
/**
* Get the automaker data directory for a project
* This is stored inside the project at .automaker/
*/
export function getAutomakerDir(projectPath: string): string {
return path.join(projectPath, ".automaker");
}
/**
* Get the features directory for a project
*/
export function getFeaturesDir(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "features");
}
/**
* Get the directory for a specific feature
*/
export function getFeatureDir(projectPath: string, featureId: string): string {
return path.join(getFeaturesDir(projectPath), featureId);
}
/**
* Get the images directory for a feature
*/
export function getFeatureImagesDir(
projectPath: string,
featureId: string
): string {
return path.join(getFeatureDir(projectPath, featureId), "images");
}
/**
* Get the board directory for a project (board backgrounds, etc.)
*/
export function getBoardDir(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "board");
}
/**
* Get the images directory for a project (general images)
*/
export function getImagesDir(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "images");
}
/**
* Get the context files directory for a project (user-added context files)
*/
export function getContextDir(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "context");
}
/**
* Get the worktrees metadata directory for a project
*/
export function getWorktreesDir(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "worktrees");
}
/**
* Get the app spec file path for a project
*/
export function getAppSpecPath(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "app_spec.txt");
}
/**
* Get the branch tracking file path for a project
*/
export function getBranchTrackingPath(projectPath: string): string {
return path.join(getAutomakerDir(projectPath), "active-branches.json");
}
/**
* Ensure the automaker directory structure exists for a project
*/
export async function ensureAutomakerDir(projectPath: string): Promise<string> {
const automakerDir = getAutomakerDir(projectPath);
await fs.mkdir(automakerDir, { recursive: true });
return automakerDir;
}

View File

@@ -1,97 +0,0 @@
/**
* Conversation history utilities for processing message history
*
* Provides standardized conversation history handling:
* - Extract text from content (string or array format)
* - Normalize content blocks to array format
* - Format history as plain text for CLI-based providers
* - Convert history to Claude SDK message format
*/
import type { ConversationMessage } from "@automaker/types";
/**
* Extract plain text from message content (handles both string and array formats)
*
* @param content - Message content (string or array of content blocks)
* @returns Extracted text content
*/
export function extractTextFromContent(
content: string | Array<{ type: string; text?: string; source?: object }>
): string {
if (typeof content === "string") {
return content;
}
// Extract text blocks only
return content
.filter((block) => block.type === "text")
.map((block) => block.text || "")
.join("\n");
}
/**
* Normalize message content to array format
*
* @param content - Message content (string or array)
* @returns Content as array of blocks
*/
export function normalizeContentBlocks(
content: string | Array<{ type: string; text?: string; source?: object }>
): Array<{ type: string; text?: string; source?: object }> {
if (Array.isArray(content)) {
return content;
}
return [{ type: "text", text: content }];
}
/**
* Format conversation history as plain text for CLI-based providers
*
* @param history - Array of conversation messages
* @returns Formatted text with role labels
*/
export function formatHistoryAsText(history: ConversationMessage[]): string {
if (history.length === 0) {
return "";
}
let historyText = "Previous conversation:\n\n";
for (const msg of history) {
const contentText = extractTextFromContent(msg.content);
const role = msg.role === "user" ? "User" : "Assistant";
historyText += `${role}: ${contentText}\n\n`;
}
historyText += "---\n\n";
return historyText;
}
/**
* Convert conversation history to Claude SDK message format
*
* @param history - Array of conversation messages
* @returns Array of Claude SDK formatted messages
*/
export function convertHistoryToMessages(
history: ConversationMessage[]
): Array<{
type: "user" | "assistant";
session_id: string;
message: {
role: "user" | "assistant";
content: Array<{ type: string; text?: string; source?: object }>;
};
parent_tool_use_id: null;
}> {
return history.map((historyMsg) => ({
type: historyMsg.role,
session_id: "",
message: {
role: historyMsg.role,
content: normalizeContentBlocks(historyMsg.content),
},
parent_tool_use_id: null,
}));
}

View File

@@ -1,125 +0,0 @@
/**
* Error handling utilities for standardized error classification
*
* Provides utilities for:
* - Detecting abort/cancellation errors
* - Detecting authentication errors
* - Classifying errors by type
* - Generating user-friendly error messages
*/
/**
* Check if an error is an abort/cancellation error
*
* @param error - The error to check
* @returns True if the error is an abort error
*/
export function isAbortError(error: unknown): boolean {
return (
error instanceof Error &&
(error.name === "AbortError" || error.message.includes("abort"))
);
}
/**
* Check if an error is a user-initiated cancellation
*
* @param errorMessage - The error message to check
* @returns True if the error is a user-initiated cancellation
*/
export function isCancellationError(errorMessage: string): boolean {
const lowerMessage = errorMessage.toLowerCase();
return (
lowerMessage.includes("cancelled") ||
lowerMessage.includes("canceled") ||
lowerMessage.includes("stopped") ||
lowerMessage.includes("aborted")
);
}
/**
* Check if an error is an authentication/API key error
*
* @param errorMessage - The error message to check
* @returns True if the error is authentication-related
*/
export function isAuthenticationError(errorMessage: string): boolean {
return (
errorMessage.includes("Authentication failed") ||
errorMessage.includes("Invalid API key") ||
errorMessage.includes("authentication_failed") ||
errorMessage.includes("Fix external API key")
);
}
/**
* Error type classification
*/
export type ErrorType = "authentication" | "cancellation" | "abort" | "execution" | "unknown";
/**
* Classified error information
*/
export interface ErrorInfo {
type: ErrorType;
message: string;
isAbort: boolean;
isAuth: boolean;
isCancellation: boolean;
originalError: unknown;
}
/**
* Classify an error into a specific type
*
* @param error - The error to classify
* @returns Classified error information
*/
export function classifyError(error: unknown): ErrorInfo {
const message = error instanceof Error ? error.message : String(error || "Unknown error");
const isAbort = isAbortError(error);
const isAuth = isAuthenticationError(message);
const isCancellation = isCancellationError(message);
let type: ErrorType;
if (isAuth) {
type = "authentication";
} else if (isAbort) {
type = "abort";
} else if (isCancellation) {
type = "cancellation";
} else if (error instanceof Error) {
type = "execution";
} else {
type = "unknown";
}
return {
type,
message,
isAbort,
isAuth,
isCancellation,
originalError: error,
};
}
/**
* Get a user-friendly error message
*
* @param error - The error to convert
* @returns User-friendly error message
*/
export function getUserFriendlyErrorMessage(error: unknown): string {
const info = classifyError(error);
if (info.isAbort) {
return "Operation was cancelled";
}
if (info.isAuth) {
return "Authentication failed. Please check your API key.";
}
return info.message;
}

View File

@@ -1,67 +0,0 @@
/**
* File system utilities that handle symlinks safely
*/
import fs from "fs/promises";
import path from "path";
/**
* Create a directory, handling symlinks safely to avoid ELOOP errors.
* If the path already exists as a directory or symlink, returns success.
*/
export async function mkdirSafe(dirPath: string): Promise<void> {
const resolvedPath = path.resolve(dirPath);
// Check if path already exists using lstat (doesn't follow symlinks)
try {
const stats = await fs.lstat(resolvedPath);
// Path exists - if it's a directory or symlink, consider it success
if (stats.isDirectory() || stats.isSymbolicLink()) {
return;
}
// It's a file - can't create directory
throw new Error(`Path exists and is not a directory: ${resolvedPath}`);
} catch (error: any) {
// ENOENT means path doesn't exist - we should create it
if (error.code !== "ENOENT") {
// Some other error (could be ELOOP in parent path)
// If it's ELOOP, the path involves symlinks - don't try to create
if (error.code === "ELOOP") {
console.warn(`[fs-utils] Symlink loop detected at ${resolvedPath}, skipping mkdir`);
return;
}
throw error;
}
}
// Path doesn't exist, create it
try {
await fs.mkdir(resolvedPath, { recursive: true });
} catch (error: any) {
// Handle race conditions and symlink issues
if (error.code === "EEXIST" || error.code === "ELOOP") {
return;
}
throw error;
}
}
/**
* Check if a path exists, handling symlinks safely.
* Returns true if the path exists as a file, directory, or symlink.
*/
export async function existsSafe(filePath: string): Promise<boolean> {
try {
await fs.lstat(filePath);
return true;
} catch (error: any) {
if (error.code === "ENOENT") {
return false;
}
// ELOOP or other errors - path exists but is problematic
if (error.code === "ELOOP") {
return true; // Symlink exists, even if looping
}
throw error;
}
}

View File

@@ -1,135 +0,0 @@
/**
* Image handling utilities for processing image files
*
* Provides utilities for:
* - MIME type detection based on file extensions
* - Base64 encoding of image files
* - Content block generation for Claude SDK format
* - Path resolution (relative/absolute)
*/
import fs from "fs/promises";
import path from "path";
/**
* MIME type mapping for image file extensions
*/
const IMAGE_MIME_TYPES: Record<string, string> = {
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".webp": "image/webp",
} as const;
/**
* Image data with base64 encoding and metadata
*/
export interface ImageData {
base64: string;
mimeType: string;
filename: string;
originalPath: string;
}
/**
* Content block for image (Claude SDK format)
*/
export interface ImageContentBlock {
type: "image";
source: {
type: "base64";
media_type: string;
data: string;
};
}
/**
* Get MIME type for an image file based on extension
*
* @param imagePath - Path to the image file
* @returns MIME type string (defaults to "image/png" for unknown extensions)
*/
export function getMimeTypeForImage(imagePath: string): string {
const ext = path.extname(imagePath).toLowerCase();
return IMAGE_MIME_TYPES[ext] || "image/png";
}
/**
* Read an image file and convert to base64 with metadata
*
* @param imagePath - Path to the image file
* @returns Promise resolving to image data with base64 encoding
* @throws Error if file cannot be read
*/
export async function readImageAsBase64(imagePath: string): Promise<ImageData> {
const imageBuffer = await fs.readFile(imagePath);
const base64Data = imageBuffer.toString("base64");
const mimeType = getMimeTypeForImage(imagePath);
return {
base64: base64Data,
mimeType,
filename: path.basename(imagePath),
originalPath: imagePath,
};
}
/**
* Convert image paths to content blocks (Claude SDK format)
* Handles both relative and absolute paths
*
* @param imagePaths - Array of image file paths
* @param workDir - Optional working directory for resolving relative paths
* @returns Promise resolving to array of image content blocks
*/
export async function convertImagesToContentBlocks(
imagePaths: string[],
workDir?: string
): Promise<ImageContentBlock[]> {
const blocks: ImageContentBlock[] = [];
for (const imagePath of imagePaths) {
try {
// Resolve to absolute path if needed
const absolutePath = workDir && !path.isAbsolute(imagePath)
? path.join(workDir, imagePath)
: imagePath;
const imageData = await readImageAsBase64(absolutePath);
blocks.push({
type: "image",
source: {
type: "base64",
media_type: imageData.mimeType,
data: imageData.base64,
},
});
} catch (error) {
console.error(`[ImageHandler] Failed to load image ${imagePath}:`, error);
// Continue processing other images
}
}
return blocks;
}
/**
* Build a list of image paths for text prompts
* Formats image paths as a bulleted list for inclusion in text prompts
*
* @param imagePaths - Array of image file paths
* @returns Formatted string with image paths, or empty string if no images
*/
export function formatImagePathsForPrompt(imagePaths: string[]): string {
if (imagePaths.length === 0) {
return "";
}
let text = "\n\nAttached images:\n";
for (const imagePath of imagePaths) {
text += `- ${imagePath}\n`;
}
return text;
}

View File

@@ -1,75 +0,0 @@
/**
* Simple logger utility with log levels
* Configure via LOG_LEVEL environment variable: error, warn, info, debug
* Defaults to 'info' if not set
*/
export enum LogLevel {
ERROR = 0,
WARN = 1,
INFO = 2,
DEBUG = 3,
}
const LOG_LEVEL_NAMES: Record<string, LogLevel> = {
error: LogLevel.ERROR,
warn: LogLevel.WARN,
info: LogLevel.INFO,
debug: LogLevel.DEBUG,
};
let currentLogLevel: LogLevel = LogLevel.INFO;
// Initialize log level from environment variable
const envLogLevel = process.env.LOG_LEVEL?.toLowerCase();
if (envLogLevel && LOG_LEVEL_NAMES[envLogLevel] !== undefined) {
currentLogLevel = LOG_LEVEL_NAMES[envLogLevel];
}
/**
* Create a logger instance with a context prefix
*/
export function createLogger(context: string) {
const prefix = `[${context}]`;
return {
error: (...args: unknown[]): void => {
if (currentLogLevel >= LogLevel.ERROR) {
console.error(prefix, ...args);
}
},
warn: (...args: unknown[]): void => {
if (currentLogLevel >= LogLevel.WARN) {
console.warn(prefix, ...args);
}
},
info: (...args: unknown[]): void => {
if (currentLogLevel >= LogLevel.INFO) {
console.log(prefix, ...args);
}
},
debug: (...args: unknown[]): void => {
if (currentLogLevel >= LogLevel.DEBUG) {
console.log(prefix, "[DEBUG]", ...args);
}
},
};
}
/**
* Get the current log level
*/
export function getLogLevel(): LogLevel {
return currentLogLevel;
}
/**
* Set the log level programmatically (useful for testing)
*/
export function setLogLevel(level: LogLevel): void {
currentLogLevel = level;
}

View File

@@ -1,79 +0,0 @@
/**
* Prompt building utilities for constructing prompts with images
*
* Provides standardized prompt building that:
* - Combines text prompts with image attachments
* - Handles content block array generation
* - Optionally includes image paths in text
* - Supports both vision and non-vision models
*/
import { convertImagesToContentBlocks, formatImagePathsForPrompt } from "./image-handler.js";
/**
* Content that can be either simple text or structured blocks
*/
export type PromptContent = string | Array<{
type: string;
text?: string;
source?: object;
}>;
/**
* Result of building a prompt with optional images
*/
export interface PromptWithImages {
content: PromptContent;
hasImages: boolean;
}
/**
* Build a prompt with optional image attachments
*
* @param basePrompt - The text prompt
* @param imagePaths - Optional array of image file paths
* @param workDir - Optional working directory for resolving relative paths
* @param includeImagePaths - Whether to append image paths to the text (default: false)
* @returns Promise resolving to prompt content and metadata
*/
export async function buildPromptWithImages(
basePrompt: string,
imagePaths?: string[],
workDir?: string,
includeImagePaths: boolean = false
): Promise<PromptWithImages> {
// No images - return plain text
if (!imagePaths || imagePaths.length === 0) {
return { content: basePrompt, hasImages: false };
}
// Build text content with optional image path listing
let textContent = basePrompt;
if (includeImagePaths) {
textContent += formatImagePathsForPrompt(imagePaths);
}
// Build content blocks array
const contentBlocks: Array<{
type: string;
text?: string;
source?: object;
}> = [];
// Add text block if we have text
if (textContent.trim()) {
contentBlocks.push({ type: "text", text: textContent });
}
// Add image blocks
const imageBlocks = await convertImagesToContentBlocks(imagePaths, workDir);
contentBlocks.push(...imageBlocks);
// Return appropriate format
const content: PromptContent =
contentBlocks.length > 1 || contentBlocks[0]?.type === "image"
? contentBlocks
: textContent;
return { content, hasImages: true };
}

View File

@@ -1,63 +0,0 @@
/**
* Security utilities for path validation
* Note: All permission checks have been disabled to allow unrestricted access
*/
import path from "path";
// Allowed project directories - kept for API compatibility
const allowedPaths = new Set<string>();
/**
* Initialize allowed paths from environment variable
* Note: All paths are now allowed regardless of this setting
*/
export function initAllowedPaths(): void {
const dirs = process.env.ALLOWED_PROJECT_DIRS;
if (dirs) {
for (const dir of dirs.split(",")) {
const trimmed = dir.trim();
if (trimmed) {
allowedPaths.add(path.resolve(trimmed));
}
}
}
const dataDir = process.env.DATA_DIR;
if (dataDir) {
allowedPaths.add(path.resolve(dataDir));
}
const workspaceDir = process.env.WORKSPACE_DIR;
if (workspaceDir) {
allowedPaths.add(path.resolve(workspaceDir));
}
}
/**
* Add a path to the allowed list (no-op, all paths allowed)
*/
export function addAllowedPath(filePath: string): void {
allowedPaths.add(path.resolve(filePath));
}
/**
* Check if a path is allowed - always returns true
*/
export function isPathAllowed(_filePath: string): boolean {
return true;
}
/**
* Validate a path - just resolves the path without checking permissions
*/
export function validatePath(filePath: string): string {
return path.resolve(filePath);
}
/**
* Get list of allowed paths (for debugging)
*/
export function getAllowedPaths(): string[] {
return Array.from(allowedPaths);
}

View File

@@ -1,206 +0,0 @@
/**
* Subprocess management utilities for CLI providers
*/
import { spawn, type ChildProcess } from "child_process";
import readline from "readline";
export interface SubprocessOptions {
command: string;
args: string[];
cwd: string;
env?: Record<string, string>;
abortController?: AbortController;
timeout?: number; // Milliseconds of no output before timeout
}
export interface SubprocessResult {
stdout: string;
stderr: string;
exitCode: number | null;
}
/**
* Spawns a subprocess and streams JSONL output line-by-line
*/
export async function* spawnJSONLProcess(
options: SubprocessOptions
): AsyncGenerator<unknown> {
const { command, args, cwd, env, abortController, timeout = 30000 } = options;
const processEnv = {
...process.env,
...env,
};
console.log(`[SubprocessManager] Spawning: ${command} ${args.slice(0, -1).join(" ")}`);
console.log(`[SubprocessManager] Working directory: ${cwd}`);
const childProcess: ChildProcess = spawn(command, args, {
cwd,
env: processEnv,
stdio: ["ignore", "pipe", "pipe"],
});
let stderrOutput = "";
let lastOutputTime = Date.now();
let timeoutHandle: NodeJS.Timeout | null = null;
// Collect stderr for error reporting
if (childProcess.stderr) {
childProcess.stderr.on("data", (data: Buffer) => {
const text = data.toString();
stderrOutput += text;
console.error(`[SubprocessManager] stderr: ${text}`);
});
}
// Setup timeout detection
const resetTimeout = () => {
lastOutputTime = Date.now();
if (timeoutHandle) {
clearTimeout(timeoutHandle);
}
timeoutHandle = setTimeout(() => {
const elapsed = Date.now() - lastOutputTime;
if (elapsed >= timeout) {
console.error(
`[SubprocessManager] Process timeout: no output for ${timeout}ms`
);
childProcess.kill("SIGTERM");
}
}, timeout);
};
resetTimeout();
// Setup abort handling
if (abortController) {
abortController.signal.addEventListener("abort", () => {
console.log("[SubprocessManager] Abort signal received, killing process");
if (timeoutHandle) {
clearTimeout(timeoutHandle);
}
childProcess.kill("SIGTERM");
});
}
// Parse stdout as JSONL (one JSON object per line)
if (childProcess.stdout) {
const rl = readline.createInterface({
input: childProcess.stdout,
crlfDelay: Infinity,
});
try {
for await (const line of rl) {
resetTimeout();
if (!line.trim()) continue;
try {
const parsed = JSON.parse(line);
yield parsed;
} catch (parseError) {
console.error(
`[SubprocessManager] Failed to parse JSONL line: ${line}`,
parseError
);
// Yield error but continue processing
yield {
type: "error",
error: `Failed to parse output: ${line}`,
};
}
}
} catch (error) {
console.error("[SubprocessManager] Error reading stdout:", error);
throw error;
} finally {
if (timeoutHandle) {
clearTimeout(timeoutHandle);
}
}
}
// Wait for process to exit
const exitCode = await new Promise<number | null>((resolve) => {
childProcess.on("exit", (code) => {
console.log(`[SubprocessManager] Process exited with code: ${code}`);
resolve(code);
});
childProcess.on("error", (error) => {
console.error("[SubprocessManager] Process error:", error);
resolve(null);
});
});
// Handle non-zero exit codes
if (exitCode !== 0 && exitCode !== null) {
const errorMessage = stderrOutput || `Process exited with code ${exitCode}`;
console.error(`[SubprocessManager] Process failed: ${errorMessage}`);
yield {
type: "error",
error: errorMessage,
};
}
// Process completed successfully
if (exitCode === 0 && !stderrOutput) {
console.log("[SubprocessManager] Process completed successfully");
}
}
/**
* Spawns a subprocess and collects all output
*/
export async function spawnProcess(
options: SubprocessOptions
): Promise<SubprocessResult> {
const { command, args, cwd, env, abortController } = options;
const processEnv = {
...process.env,
...env,
};
return new Promise((resolve, reject) => {
const childProcess = spawn(command, args, {
cwd,
env: processEnv,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
if (childProcess.stdout) {
childProcess.stdout.on("data", (data: Buffer) => {
stdout += data.toString();
});
}
if (childProcess.stderr) {
childProcess.stderr.on("data", (data: Buffer) => {
stderr += data.toString();
});
}
// Setup abort handling
if (abortController) {
abortController.signal.addEventListener("abort", () => {
childProcess.kill("SIGTERM");
reject(new Error("Process aborted"));
});
}
childProcess.on("exit", (code) => {
resolve({ stdout, stderr, exitCode: code });
});
childProcess.on("error", (error) => {
reject(error);
});
});
}

View File

@@ -6,8 +6,8 @@ import type { Request, Response } from "express";
import fs from "fs/promises";
import path from "path";
import { validatePath } from "@automaker/platform";
import { mkdirSafe } from "@automaker/utils";
import { getErrorMessage, logError } from "../common.js";
import { mkdirSafe } from "../../../lib/fs-utils.js";
export function createWriteHandler() {
return async (req: Request, res: Response): Promise<void> => {

View File

@@ -3,8 +3,6 @@
* Extracts useful information from agent context files for display in kanban cards
*/
import { DEFAULT_MODELS } from "@automaker/types";
export interface AgentTaskInfo {
// Task list extracted from TodoWrite tool calls
todos: {
@@ -29,7 +27,7 @@ export interface AgentTaskInfo {
/**
* Default model used by the feature executor
*/
export const DEFAULT_MODEL = DEFAULT_MODELS.claude;
export const DEFAULT_MODEL = "claude-opus-4-5-20251101";
/**
* Formats a model name for display

View File

@@ -2,12 +2,13 @@
"name": "@automaker/dependency-resolver",
"version": "1.0.0",
"description": "Feature dependency resolution for AutoMaker",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},

View File

@@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"module": "ESNext",
"lib": ["ES2020"],
"types": ["node"],
"declaration": true,
@@ -13,7 +13,7 @@
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node"
"moduleResolution": "bundler"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]