Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/fresh-buckets-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'curl.md': patch
---

Fixed published TypeScript SDK declarations so `createClient` retained its route-aware types for package consumers.
30 changes: 26 additions & 4 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,46 @@
},
"files": [
"CHANGELOG.md",
"dist"
"dist",
"src/cf-env.d.ts",
"src/shims.d.ts"
],
"sideEffects": false,
"type": "module",
"types": "./dist/exports/index.d.ts",
"imports": {
"#api.ts": {
"types": "./dist/src/api.ts",
"default": "./dist/src/api.ts"
},
"#db/client.ts": {
"types": "./dist/db/client.ts",
"default": "./dist/db/client.ts"
},
"#db/*.ts": {
"types": "./dist/db/*.ts",
"default": "./dist/db/*.ts"
},
"#db/*": {
"types": "./dist/db/*",
"default": "../db/*"
"default": "./dist/db/*"
},
"#*.ts": {
"types": "./dist/src/*.ts",
"default": "./dist/src/*.ts"
},
"#*.tsx": {
"types": "./dist/src/*.tsx",
"default": "./dist/src/*.tsx"
},
"#*": {
"types": "./dist/src/*",
"default": "../src/*"
"default": "./dist/src/*"
},
"#test/*": "../test/*"
},
"scripts": {
"build": "rm -rf dist && tsgo --noCheck -p tsconfig.build.json && tsgo --noCheck -p tsconfig.pkg.json && mkdir -p dist/exports dist/cli/src && cp dist/cli/src/exports/index.d.ts dist/exports/index.d.ts && cp dist/cli/src/exports/internal.d.ts dist/exports/internal.d.ts && cp src/cf-env.d.ts dist/cli/src/cf-env.d.ts && find ../src ../db -name '*.js' -delete",
"build": "rm -rf dist && tsgo --noCheck -p tsconfig.build.json && tsgo --noCheck -p tsconfig.pkg.json && tsgo --noCheck -p tsconfig.pkg.api.json && find ../src ../db -name '*.js' -delete",
"check:types": "tsgo --noEmit",
"gen:types": "incur gen"
},
Expand Down
6 changes: 3 additions & 3 deletions cli/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// oxlint-disable-next-line typescript-eslint/triple-slash-reference -- ambient worker shims for published type output
/// <reference path="./cf-env.d.ts" />
/// <reference path="./cf-env.d.ts" preserve="true" />

import { hc, type ClientRequestOptions } from 'hono/client'
import type { api } from '../../src/api.ts'
import type { api } from '#api.ts'

export const defaultBaseUrl = 'https://curl.md'

Expand Down Expand Up @@ -78,7 +78,7 @@ export type Client = Omit<RpcClient, 'api'> & {

type RpcClient = ReturnType<typeof hc<typeof api>>
type Api = RpcClient['api']
type PublicApi = Omit<Api, 'og.png' | 'sentry' | 'stats' | 'stripe'>
type PublicApi = Omit<Api, 'og.png' | 'sentry' | 'stats' | 'stripe' | 'tunnel'>
type Fetch = RpcClient['api'][':url{.+}']['$get']
type FetchQuery = Pick<
NonNullable<NonNullable<Parameters<Fetch>[0]>['query']>,
Expand Down
5 changes: 5 additions & 0 deletions cli/src/shims.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Minimal internal type shims used by published SDK declarations so consumers do not need app/database types.

export declare const api: unknown

export type Database = unknown
93 changes: 93 additions & 0 deletions cli/test/package-types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { execFile } from 'node:child_process'
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { expect, test } from 'vitest'

const root = resolve(import.meta.dirname, '../..')

test('published package exposes createClient types', async () => {
const dir = await mkdtemp(join(tmpdir(), 'curl-md-package-types-'))
let formatted = false

try {
await exec('pnpm', ['--filter', 'curl.md', 'build'], { cwd: root, timeout: 120_000 })
await exec('node', ['--experimental-strip-types', 'scripts/formatPackage.ts'], { cwd: root })
formatted = true

const packed = await exec('pnpm', ['--filter', 'curl.md', 'pack', '--pack-destination', dir], {
cwd: root,
})
const tarball = packed.stdout.trim().split('\n').at(-1)
expect(tarball).toBeTruthy()

const consumer = join(dir, 'consumer')
await mkdir(consumer)
await writeFile(join(consumer, 'package.json'), '{"type":"module"}\n')
await writeFile(
join(consumer, 'tsconfig.json'),
`${JSON.stringify(
{
compilerOptions: {
module: 'nodenext',
moduleResolution: 'nodenext',
noEmit: true,
strict: true,
target: 'es2022',
},
},
null,
2,
)}\n`,
)
await writeFile(
join(consumer, 'index.ts'),
`import { createClient, type Client } from 'curl.md'
import { Auth } from 'curl.md/internal'

const client = createClient()
const res = await client.fetch('example.com', {
fresh: true,
keywords: ['example'],
mode: 'rush',
objective: 'example domain',
})

const typed: Client = client
// @ts-expect-error internal Sentry tunnel is not SDK surface
client.api.tunnel
void Auth
void res
void typed
`,
)

await exec('pnpm', ['--dir', consumer, 'add', tarball!], { cwd: root, timeout: 120_000 })
await exec('pnpm', ['exec', 'tsgo', '-p', join(consumer, 'tsconfig.json')], {
cwd: root,
timeout: 120_000,
})
} finally {
if (formatted)
await exec('node', ['--experimental-strip-types', 'scripts/restorePackage.ts'], { cwd: root })
await rm(dir, { force: true, recursive: true })
}
}, 180_000)

function exec(
cmd: string,
args: string[],
options: { cwd: string; timeout?: number },
): Promise<{ stderr: string; stdout: string }> {
return new Promise((resolve, reject) => {
execFile(
cmd,
args,
{ cwd: options.cwd, timeout: options.timeout ?? 30_000 },
(error, stdout, stderr) => {
if (error) reject(new Error(stderr.trim() || stdout.trim() || error.message))
else resolve({ stderr, stdout })
},
)
})
}
13 changes: 13 additions & 0 deletions cli/tsconfig.pkg.api.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"noEmit": false,
"outDir": "dist",
"rootDir": "..",
"rewriteRelativeImportExtensions": true
},
"include": ["../src/api.ts"],
"exclude": ["src/**/*.test.*", "test"]
}
5 changes: 4 additions & 1 deletion cli/tsconfig.pkg.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
"emitDeclarationOnly": true,
"noEmit": false,
"outDir": "dist",
"rootDir": "..",
"paths": {
"#api.ts": ["./src/shims.d.ts"]
},
"rootDir": "src",
"rewriteRelativeImportExtensions": true
},
"include": ["src/exports/index.ts", "src/exports/internal.ts", "src/cf-env.d.ts"],
Expand Down
20 changes: 3 additions & 17 deletions plugins/amp/src/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ test('preserves URL fragments', async () => {
} as any)

expect(requests[0]?.url).toContain('anchor=section')
expect((result as any).url).toBe('https://example.com/docs?q=1#section')
expect(result).toBe('# Fragment')
})

// --- Anonymous fetch ---
Expand Down Expand Up @@ -244,20 +244,7 @@ test('fetches anonymously and returns expected shape', async () => {

expect(requests[0]?.url).toContain(`${defaultBaseUrl}/api/https://example.com/docs`)
expect(requests[0]?.url).toContain('anchor=intro')
expect(result).toEqual({
auth: 'anon',
cache: 'HIT',
credits_remaining: 42,
fresh: true,
keywords: ['a'],
markdown: '# Example\n\n---\n\nPowered by [curl.md](https://curl.md)',
mode: 'rush',
objective: 'test',
request_id: 'req_abc',
tokens_count: 100,
tokens_saved: 50,
url: 'https://example.com/docs#intro',
})
expect(result).toBe('# Example\n\n---\n\nPowered by [curl.md](https://curl.md)')
})

// --- API key auth ---
Expand Down Expand Up @@ -411,8 +398,7 @@ test('retries once on session 401 with forced auth refresh', async () => {
'Bearer access-token-stale',
'Bearer access-token-fresh',
])
expect((result as any).auth).toBe('session')
expect((result as any).markdown).toBe('# Retried')
expect(result).toBe('# Retried')

Session.delete()
})
Expand Down
3 changes: 2 additions & 1 deletion plugins/amp/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,14 @@ export default function (amp: PluginAPI) {
required: ['url'],
},
async execute(input) {
return fetchPage({
const result = await fetchPage({
fresh: input.fresh as boolean | undefined,
keywords: input.keywords as string[] | undefined,
mode: input.mode as 'rush' | 'smart' | undefined,
objective: input.objective as string | undefined,
url: input.url as string,
})
return result.markdown
},
})

Expand Down
30 changes: 15 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ auditConfig:
- GHSA-6cpc-mj5c-m9rq
blockExoticSubdeps: true
catalog:
hono: ^4.12.16
hono: ^4.12.21
# Vite+ dependency aliases.
vite: npm:@voidzero-dev/vite-plus-core@0.1.20
vite-plus: 0.1.20
Expand Down
Loading
Loading