Skip to content

Commit 2c5a9f4

Browse files
feat: make packages/ai ESM-only (#265)
## Summary - switch `packages/ai` tsup output to ESM-only - remove CommonJS export conditions and `.cjs`/`.d.cts` references from `packages/ai/package.json` - keep explicit Node-only path/require handling in eval runtime (`run-vitest.ts`, `context/manager.ts`) without global tsup shims ## Why - avoids the Workers startup regression caused by global tsup ESM shims initialization - aligns package distribution with ESM-only direction ## Validation - `pnpm --filter axiom build` - verified `check-vitest-entrypoints` passes - verified no `.cjs` or `.d.cts` artifacts are produced in `packages/ai/dist` ## Breaking change - `packages/ai` no longer supports CommonJS `require(...)` consumers; users must import via ESM. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk because it removes CommonJS support (breaking for `require` consumers) and changes package entry/export behavior; the new Wrangler smoke test should catch runtime regressions in Workers startup. > > **Overview** > Switches `packages/ai` distribution to **ESM-only** by emitting only `esm` from `tsup` and simplifying `package.json` (`main`/`exports`) to remove all CommonJS (`.cjs`/`.d.cts`) conditions. > > Updates Node-only eval runtime code to avoid reliance on tsup global shims by resolving `require`/`__dirname` safely in both CJS and ESM (`context/manager.ts`, `run-vitest.ts`). > > Adds a CI `Wrangler Smoke` GitHub Action plus a Vitest smoke suite and Workers fixture that packs the package, installs it into a minimal Wrangler app, boots `wrangler dev --local`, and verifies a request can call `axiom/ai` APIs; TS config excludes fixture/smoke files from normal typechecking. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e4c1fee. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Christopher Ehrlich <ehrlich.christopher@gmail.com>
1 parent d3b8dd0 commit 2c5a9f4

File tree

12 files changed

+293
-65
lines changed

12 files changed

+293
-65
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Wrangler Smoke
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'packages/ai/**'
9+
- '.github/workflows/wrangler-smoke.yaml'
10+
pull_request:
11+
branches:
12+
- main
13+
paths:
14+
- 'packages/ai/**'
15+
- '.github/workflows/wrangler-smoke.yaml'
16+
17+
env:
18+
PNPM_VERSION: 10.16.1
19+
NODE_VERSION: 22
20+
21+
jobs:
22+
wrangler-smoke:
23+
name: Wrangler smoke test
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v4
27+
- uses: pnpm/action-setup@v4
28+
with:
29+
version: ${{ env.PNPM_VERSION }}
30+
- uses: actions/setup-node@v4
31+
with:
32+
node-version: ${{ env.NODE_VERSION }}
33+
cache: 'pnpm'
34+
- run: pnpm install --frozen-lockfile --ignore-scripts
35+
- run: pnpm -C packages/ai test:wrangler-smoke

packages/ai/package.json

Lines changed: 16 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -18,84 +18,43 @@
1818
"typecheck": "tsc --noEmit",
1919
"test": "vitest run",
2020
"test:watch": "vitest --watch",
21+
"test:wrangler-smoke": "pnpm build && vitest run --config vitest.smoke.config.ts",
2122
"publint": "npx publint"
2223
},
2324
"types": "./dist/index.d.ts",
24-
"main": "./dist/index.cjs",
25+
"main": "./dist/index.js",
2526
"module": "./dist/index.js",
2627
"bin": {
2728
"axiom": "./dist/bin.js"
2829
},
2930
"exports": {
3031
"./ai": {
31-
"import": {
32-
"types": "./dist/index.d.ts",
33-
"default": "./dist/index.js"
34-
},
35-
"require": {
36-
"types": "./dist/index.d.cts",
37-
"default": "./dist/index.cjs"
38-
}
32+
"types": "./dist/index.d.ts",
33+
"default": "./dist/index.js"
3934
},
4035
"./ai/evals": {
41-
"import": {
42-
"types": "./dist/evals.d.ts",
43-
"default": "./dist/evals.js"
44-
},
45-
"require": {
46-
"types": "./dist/evals.d.cts",
47-
"default": "./dist/evals.cjs"
48-
}
36+
"types": "./dist/evals.d.ts",
37+
"default": "./dist/evals.js"
4938
},
5039
"./ai/evals/aggregations": {
51-
"import": {
52-
"types": "./dist/evals/aggregations.d.ts",
53-
"default": "./dist/evals/aggregations.js"
54-
},
55-
"require": {
56-
"types": "./dist/evals/aggregations.d.cts",
57-
"default": "./dist/evals/aggregations.cjs"
58-
}
40+
"types": "./dist/evals/aggregations.d.ts",
41+
"default": "./dist/evals/aggregations.js"
5942
},
6043
"./ai/evals/scorers": {
61-
"import": {
62-
"types": "./dist/evals/scorers.d.ts",
63-
"default": "./dist/evals/scorers.js"
64-
},
65-
"require": {
66-
"types": "./dist/evals/scorers.d.cts",
67-
"default": "./dist/evals/scorers.cjs"
68-
}
44+
"types": "./dist/evals/scorers.d.ts",
45+
"default": "./dist/evals/scorers.js"
6946
},
7047
"./ai/evals/online": {
71-
"import": {
72-
"types": "./dist/evals/online.d.ts",
73-
"default": "./dist/evals/online.js"
74-
},
75-
"require": {
76-
"types": "./dist/evals/online.d.cts",
77-
"default": "./dist/evals/online.cjs"
78-
}
48+
"types": "./dist/evals/online.d.ts",
49+
"default": "./dist/evals/online.js"
7950
},
8051
"./ai/config": {
81-
"import": {
82-
"types": "./dist/config.d.ts",
83-
"default": "./dist/config.js"
84-
},
85-
"require": {
86-
"types": "./dist/config.d.cts",
87-
"default": "./dist/config.cjs"
88-
}
52+
"types": "./dist/config.d.ts",
53+
"default": "./dist/config.js"
8954
},
9055
"./ai/feedback": {
91-
"import": {
92-
"types": "./dist/feedback.d.ts",
93-
"default": "./dist/feedback.js"
94-
},
95-
"require": {
96-
"types": "./dist/feedback.d.cts",
97-
"default": "./dist/feedback.cjs"
98-
}
56+
"types": "./dist/feedback.d.ts",
57+
"default": "./dist/feedback.js"
9958
}
10059
},
10160
"keywords": [

packages/ai/src/evals/context/manager.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createRequire } from 'node:module';
2+
import { join } from 'node:path';
23

34
interface ContextManager<T = any> {
45
getStore(): T | undefined;
@@ -17,6 +18,15 @@ function setGlobalContextManager(manager: ContextManager): void {
1718

1819
const isNodeJS = typeof process !== 'undefined' && !!process.versions?.node;
1920

21+
function getNodeRequire(): NodeJS.Require {
22+
if (typeof require === 'function') {
23+
return require;
24+
}
25+
26+
// We only require Node builtins, so any absolute path is valid as a createRequire base.
27+
return createRequire(join(process.cwd(), '__axiom_require__.js'));
28+
}
29+
2030
function getContextManager(): ContextManager {
2131
// Check global Symbol registry cache first (shared across VM contexts)
2232
const existing = getGlobalContextManager();
@@ -29,8 +39,8 @@ function getContextManager(): ContextManager {
2939
// Resolve AsyncLocalStorage in both ESM and CJS Node contexts without bundler interference
3040
let AsyncLocalStorage: any;
3141

32-
// Use createRequire to obtain a require in ESM
33-
const req = createRequire(import.meta.url);
42+
// Obtain require in both CJS and ESM Node runtimes without relying on tsup shims.
43+
const req = getNodeRequire();
3444
try {
3545
AsyncLocalStorage = req('node:async_hooks').AsyncLocalStorage;
3646
} catch {

packages/ai/src/evals/run-vitest.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import c from 'tinyrainbow';
22
import { resolve, join } from 'node:path';
33
import { mkdirSync, writeFileSync, unlinkSync, existsSync, readFileSync } from 'node:fs';
44
import { tmpdir } from 'node:os';
5+
import { fileURLToPath } from 'node:url';
56
import path from 'node:path';
67
import tsconfigPaths from 'vite-tsconfig-paths';
78

@@ -12,6 +13,16 @@ import { flush, initInstrumentation } from './instrument';
1213
import { setAxiomConfig } from './context/storage';
1314
import type { ResolvedAxiomConfig } from '../config/index';
1415

16+
const getCurrentDir = (): string => {
17+
if (typeof __dirname !== 'undefined') {
18+
return __dirname;
19+
}
20+
21+
return path.dirname(fileURLToPath(import.meta.url));
22+
};
23+
24+
const evalsRunnerPath = resolve(getCurrentDir(), 'evals', 'custom-runner.js');
25+
1526
const printCollectedEvals = (result: TestRunResult, rootDir: string) => {
1627
if (!result.testModules || result.testModules.length === 0) {
1728
console.log(c.yellow('\nNo evaluations found\n'));
@@ -117,7 +128,7 @@ export const runVitest = async (
117128
disableConsoleIntercept: true,
118129
testTimeout: opts.config?.eval?.timeoutMs || 60_000,
119130
globals: true,
120-
runner: resolve(__dirname, 'evals', 'custom-runner.js'),
131+
runner: evalsRunnerPath,
121132
provide: {
122133
baseline: opts.baseline,
123134
debug: opts.debug,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "axiom-wrangler-smoke",
3+
"private": true,
4+
"type": "module"
5+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { withSpan, axiomAIMiddleware } from 'axiom/ai';
2+
3+
export default {
4+
async fetch() {
5+
const result = await withSpan({ capability: 'chat', step: 'generate' }, async () => 'ok');
6+
return new Response(
7+
JSON.stringify({
8+
withSpanType: typeof withSpan,
9+
middlewareType: typeof axiomAIMiddleware,
10+
result,
11+
}),
12+
{ headers: { 'content-type': 'application/json' } },
13+
);
14+
},
15+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name = "axiom-wrangler-smoke"
2+
main = "src/index.ts"
3+
compatibility_date = "2025-01-01"
4+
compatibility_flags = ["nodejs_compat"]

0 commit comments

Comments
 (0)