Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## [Unreleased] - 2026-07-08
### Fixed
- Explain how to load TypeScript prompt files when Node reports an unknown file extension.
### Maintenance
- Routine maintenance sync (14:47) — dependencies & docs review.
4 changes: 4 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export default definePrompt<{ name: string; language?: string }>({
})
```

TypeScript prompt files require a runtime that supports them, such as `tsx`,
`bun`, or `ts-node`. Plain Node cannot import `.ts` files directly, so use one
of those runtimes or pre-compile the files to `.js`.

Render it without a model:

```ts
Expand Down
13 changes: 12 additions & 1 deletion src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { LoaderError } from './errors.js'
import type { PromptDefinition, PromptInput } from './types.js'

const SUPPORTED_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'])
const TYPESCRIPT_EXTENSIONS = new Set(['.ts', '.mts', '.cts'])
const TYPESCRIPT_LOADER_HINT =
"Node can't import `.ts` files directly. Run with `tsx`, `bun`, or `ts-node`, or pre-compile to `.js`."

/** Options for {@link loadPromptsFromDir}. */
export interface LoadOptions {
Expand Down Expand Up @@ -92,8 +95,16 @@ export async function loadPromptFile(
const url = pathToFileURL(filePath).href
mod = (await import(url)) as Record<string, unknown>
} catch (e) {
const errorMessage = e instanceof Error ? e.message : String(e)
const needsTypeScriptLoader =
TYPESCRIPT_EXTENSIONS.has(extname(filePath)) &&
typeof e === 'object' &&
e !== null &&
'code' in e &&
e.code === 'ERR_UNKNOWN_FILE_EXTENSION'

throw new LoaderError(
`Failed to import "${filePath}": ${e instanceof Error ? e.message : String(e)}`,
`Failed to import "${filePath}": ${errorMessage}${needsTypeScriptLoader ? ` ${TYPESCRIPT_LOADER_HINT}` : ''}`,
filePath,
)
}
Expand Down
25 changes: 25 additions & 0 deletions tests/loader.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, it, expect, vi } from 'vitest'
import { loadPromptFile } from '../src/loader.js'

vi.mock('node:url', () => ({
pathToFileURL(path: string) {
if (path === 'unsupported-prompt.ts') {
const source = `
const error = new Error('Unknown file extension ".ts"')
error.code = 'ERR_UNKNOWN_FILE_EXTENSION'
throw error
`
return new URL(`data:text/javascript,${encodeURIComponent(source)}`)
}

throw new Error(`Unexpected test path: ${path}`)
},
}))

describe('loadPromptFile', () => {
it('explains how to load TypeScript files without a loader', async () => {
await expect(loadPromptFile('unsupported-prompt.ts')).rejects.toThrow(
"Node can't import `.ts` files directly. Run with `tsx`, `bun`, or `ts-node`, or pre-compile to `.js`.",
)
})
})