From 9bda7b5b2796c2bef68ab8d08e8bc315e5df4f32 Mon Sep 17 00:00:00 2001 From: ianwremmel-ai-agent Date: Mon, 7 Sep 2026 20:51:51 +0000 Subject: [PATCH 1/3] chore: open PR [skip ci] From 5da3561d776cfd798284a121ab128ce69ed0a1e6 Mon Sep 17 00:00:00 2001 From: ianwremmel-ai-agent Date: Mon, 7 Sep 2026 21:05:45 +0000 Subject: [PATCH 2/3] fix: resolve cli package.json without import.meta.resolve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tool-tool's src/tool.mjs is compiled to CommonJS as well as ESM. esbuild turns `import.meta` into an empty object under cjs, so `import.meta.resolve('@code-like-a-carpenter/cli')` became `import_meta.resolve(...)` in dist/cjs/tool.cjs — a TypeError for anything that reaches addAsCliPlugin through the built CJS executor. executors.json points at the cjs build whenever codegen:tool ran with src/index.mjs absent (create-nodes.mjs sets buildBeforeRun from that check), which is the state tool-foundation/executors.json is committed in. Resolve `@code-like-a-carpenter/cli/package.json` through createRequire(rootPkgPath) instead. That works in both output formats and keeps the property the old comment called out: it does not depend on the CLI entrypoint having been built, because package.json is itself the resolution target and always exists. Resolving the subpath directly also drops the findUp walk back up from the entrypoint. Set esbuild's logOverride so `empty-import-meta` is an error, making a format-incompatible construct fail @clc/nx:esbuild instead of scrolling past as a warning. tool.mjs was the only first-party source that tripped it; @clc/nx itself is exempt because create-nodes gives it no build target. --- .../@clc/nx/executors/esbuild/executor.mjs | 4 ++++ .../tool-tool/src/tool.mjs | 20 +++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/@clc/nx/executors/esbuild/executor.mjs b/packages/@clc/nx/executors/esbuild/executor.mjs index 90da6d5b..9153052f 100644 --- a/packages/@clc/nx/executors/esbuild/executor.mjs +++ b/packages/@clc/nx/executors/esbuild/executor.mjs @@ -27,6 +27,10 @@ const runExecutor = async (options) => { bundle: true, entryPoints: eps, format, + // `import.meta` compiles to an empty object under cjs, so + // `import.meta.resolve(...)` becomes a runtime TypeError. esbuild only + // warns, which is easy to scroll past; promote it to an error. + logOverride: {'empty-import-meta': 'error'}, outExtension: {'.js': format === 'cjs' ? '.cjs' : '.mjs'}, outdir: outDir, packages: 'external', diff --git a/packages/@code-like-a-carpenter/tool-tool/src/tool.mjs b/packages/@code-like-a-carpenter/tool-tool/src/tool.mjs index cfa73cb0..316a230c 100644 --- a/packages/@code-like-a-carpenter/tool-tool/src/tool.mjs +++ b/packages/@code-like-a-carpenter/tool-tool/src/tool.mjs @@ -1,7 +1,7 @@ import {existsSync} from 'node:fs'; import {mkdir, readFile, writeFile} from 'node:fs/promises'; +import {createRequire} from 'node:module'; import path from 'node:path'; -import {fileURLToPath} from 'node:url'; import findUp from 'find-up'; import kebabCase from 'lodash.kebabcase'; @@ -64,17 +64,15 @@ async function addAsCliPlugin(metadata) { // Need to put this in a variable so deps doesn't add it to package.json, // which would lead to a circular dependency. const cliPackageName = '@code-like-a-carpenter/cli'; - // Reminder: import.meta.resolve works because it doesn't check for - // existence. createRequire().resolve() fails if the file does not exist. - // Since we're trying to fins package.json, we don't actually care if the - // entrypoint has been built yet. - const cliPkgPathResolvePath = fileURLToPath( - import.meta.resolve(cliPackageName) + // Resolve the package.json subpath rather than the package entrypoint: + // resolution has to succeed before the CLI has been built, and + // package.json is the one file guaranteed to be on disk. This relies on + // the CLI exporting "./package.json", which the package-json executor + // emits for every package; without that entry Node raises + // ERR_PACKAGE_PATH_NOT_EXPORTED no matter what is on disk. + const cliPkgPath = createRequire(rootPkgPath).resolve( + `${cliPackageName}/package.json` ); - const cliPkgPath = await findUp('package.json', { - cwd: path.dirname(cliPkgPathResolvePath), - }); - assert(cliPkgPath, 'Could not locate directory containing package.json'); const cliPkg = await readPackageJson(cliPkgPath); await addToPackageJson(cliPkg, cliPkgPath, pkg.name); return; From 3862cd80b612522a49c90d66f6c8768fa97015e5 Mon Sep 17 00:00:00 2001 From: ianwremmel-ai-agent Date: Mon, 7 Sep 2026 22:24:57 +0000 Subject: [PATCH 3/3] docs: drop overbroad claim about package.json export generation The comment said the package-json executor emits "./package.json" for every package. Examples take configExample, which does not build an exports map, and @clc/nx never registers the target at all. The precondition that matters is that the CLI itself exports it, so state only that. --- packages/@code-like-a-carpenter/tool-tool/src/tool.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/@code-like-a-carpenter/tool-tool/src/tool.mjs b/packages/@code-like-a-carpenter/tool-tool/src/tool.mjs index 316a230c..1a1b7926 100644 --- a/packages/@code-like-a-carpenter/tool-tool/src/tool.mjs +++ b/packages/@code-like-a-carpenter/tool-tool/src/tool.mjs @@ -67,8 +67,7 @@ async function addAsCliPlugin(metadata) { // Resolve the package.json subpath rather than the package entrypoint: // resolution has to succeed before the CLI has been built, and // package.json is the one file guaranteed to be on disk. This relies on - // the CLI exporting "./package.json", which the package-json executor - // emits for every package; without that entry Node raises + // the CLI exporting "./package.json"; without that entry Node raises // ERR_PACKAGE_PATH_NOT_EXPORTED no matter what is on disk. const cliPkgPath = createRequire(rootPkgPath).resolve( `${cliPackageName}/package.json`