From 3bb15b113887734b35057f1edf55647efc2ce7e1 Mon Sep 17 00:00:00 2001 From: thinkiny Date: Fri, 23 Oct 2026 13:58:40 +0800 Subject: [PATCH] fix(esm): load embedded .mjs skill assets as text --- README.md | 4 ++-- scripts/esm-chunk-patch.mjs | 6 ++++-- test/esm-text-assets.test.mjs | 5 ++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9b2555f..78de2ef 100644 --- a/README.md +++ b/README.md @@ -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 | @@ -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/ diff --git a/scripts/esm-chunk-patch.mjs b/scripts/esm-chunk-patch.mjs index ef52d9e..1ee0370 100644 --- a/scripts/esm-chunk-patch.mjs +++ b/scripts/esm-chunk-patch.mjs @@ -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"); // @@ -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";' + diff --git a/test/esm-text-assets.test.mjs b/test/esm-text-assets.test.mjs index 739b687..8fc33d9 100644 --- a/test/esm-text-assets.test.mjs +++ b/test/esm-text-assets.test.mjs @@ -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;', @@ -43,6 +45,7 @@ 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"),`, @@ -50,7 +53,7 @@ for (const bunfsRoot of BUNFS_ROOTS) { ].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}`);