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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Applied directory-wide by `scripts/esm-chunk-patch.mjs`:
|---------|-------------|
| E1 | Virtual-filesystem paths → specifiers relative to the referencing file (~100k–124k per platform). Both sides may be nested; the prefix is computed per file |
| E2 | Runtime paths — native modules and `fs.readFile` assets — get the same treatment. They stay where upstream puts them, beside the modules, so nothing has to move |
| E3 | `import.meta.require` (Bun-only) → a `createRequire` wrapper. Returns file *content* for `.md`/`.txt`, matching Bun's text loader — since 2.1.246 the bundled skills are pulled in that way, and Node would otherwise compile markdown as JS |
| E3 | `import.meta.require` (Bun-only) → a `createRequire` wrapper. Returns file *content* for `.md`/`.txt` and embedded `.mjs` source assets, matching Bun's text loader — since 2.1.246 bundled skills are pulled in that way, and Node would otherwise compile or execute them as JS |
| E4 | The Bun polyfill ships as `bun-polyfill.mjs`, imported first by `cli.js` and by the hooks worker, so `globalThis.Bun` exists before any module body runs |
| E5 | Since 2.1.250 modules `require()` each other at top level, and some of those close a cycle the import graph cannot break. Two layers: hoist the require to a bare `import` so the target evaluates first (clears ~90% of sites), and hand the rest a lazy stand-in that resolves on first touch. Returning `undefined` there is what broke startup in 2.1.259 |

Expand Down Expand Up @@ -91,7 +91,7 @@ cli.js ESM entry point
chunk-*.js Code-split modules (~1400–1800)
bun-polyfill.mjs Bun globals, imported first
*.node Native modules — see below
*.md, *.txt Embedded skill prompts and templates (2.1.246+)
*.md, *.txt, *.mjs Embedded skill prompts, templates, and source assets (2.1.246+)
*.min.js, *.asset Artifact runtimes and the design-canvas template
sdk-tools.d.ts SDK type definitions
vendor/
Expand Down
6 changes: 4 additions & 2 deletions scripts/esm-chunk-patch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ export function rewriteBunfsPaths(code, prefix) {
//
// Bun's require also honours its text loader: requiring a .md/.txt returns
// the file's CONTENT, not a module. v2.1.246 leans on that for the bundled
// skills — 164 prompt and template files pulled in at chunk top level:
// skills — 164 prompt and template files pulled in at chunk top level. As of
// v2.1.302, skills can also embed .mjs helpers as source text; executing one
// here can run its CLI entry point and terminate Claude Code.
//
// var a = e("/$bunfs/root/anti-patterns-c1rmzbdk.md");
//
Expand Down Expand Up @@ -132,7 +134,7 @@ export function rewriteBunfsPaths(code, prefix) {
// Object.assign keeps require.resolve/cache on the wrapper.
// ──────────────────────────────────────────────

const TEXT_LOADER_EXT = /\.(?:md|txt)$/;
const TEXT_LOADER_EXT = /\.(?:md|txt|mjs)$/;

const REQUIRE_SHIM =
'import{createRequire as __ccMakeRequire}from"module";' +
Expand Down
5 changes: 4 additions & 1 deletion test/esm-text-assets.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ for (const bunfsRoot of BUNFS_ROOTS) {

const markdown = '# Packaged prompt\n你好\n';
const template = 'Template: {{value}}\n';
const script = 'throw new Error("embedded source asset was executed");\n';
const data = { source: 'package' };
await Promise.all([
writeFile(join(assetDir, 'prompt.md'), markdown),
writeFile(join(assetDir, 'template.txt'), template),
writeFile(join(assetDir, 'helper.mjs'), script),
writeFile(join(assetDir, 'data.json'), JSON.stringify(data)),
writeFile(join(moduleDir, 'runtime.mjs'), patchImportMetaRequire(
'export const load = import.meta.require;',
Expand All @@ -43,14 +45,15 @@ for (const bunfsRoot of BUNFS_ROOTS) {
'console.log(JSON.stringify([',
`load("${bunfsRoot}assets/prompt.md"),`,
`load("${bunfsRoot}assets/template.txt"),`,
`load("${bunfsRoot}assets/helper.mjs"),`,
`load("${bunfsRoot}assets/data.json"),`,
`load(${JSON.stringify(join(assetDir, 'prompt.md'))}),`,
`load.resolve("${bunfsRoot}assets/prompt.md"),`,
']));',
].join('\n');
const entry = join(moduleDir, 'entry.mjs');
await writeFile(entry, rewriteBunfsPaths(source, nested ? '../' : './').code);
const expected = [markdown, template, data, markdown, join(assetDir, 'prompt.md')];
const expected = [markdown, template, script, data, markdown, join(assetDir, 'prompt.md')];
async function check(cwd) {
const { stdout } = await execFileAsync(process.execPath, [entry], { cwd, timeout: 15000 });
assert.deepEqual(JSON.parse(stdout), expected, `assets read from ${cwd}`);
Expand Down