@@ -7,6 +7,7 @@ import fs from "node:fs";
77import os from "node:os" ;
88import path from "node:path" ;
99import { performance } from "node:perf_hooks" ;
10+ import { pathToFileURL } from "node:url" ;
1011import { LOCAL_BUILD_METADATA_DIST_PATHS } from "./lib/local-build-metadata-paths.mjs" ;
1112import {
1213 collectPackageDistImports ,
@@ -73,6 +74,22 @@ const PACKAGE_DEPENDENCY_SECTIONS = [
7374 "devDependencies" ,
7475] ;
7576const REQUIRED_BUNDLED_WORKSPACE_DEPENDENCIES = [ "@openclaw/ai" ] ;
77+ // Strict Docker artifacts bundle this private runtime rather than resolving it
78+ // from npm. Keep the concrete load-bearing entries explicit instead of
79+ // reimplementing Node's conditional package-exports resolver here.
80+ const REQUIRED_BUNDLED_WORKSPACE_RUNTIME_ENTRIES = new Map ( [
81+ [
82+ "@openclaw/ai" ,
83+ [
84+ { specifier : "@openclaw/ai" , entry : "dist/index.mjs" } ,
85+ { specifier : "@openclaw/ai/providers" , entry : "dist/providers.mjs" } ,
86+ {
87+ specifier : "@openclaw/ai/internal/runtime" ,
88+ entry : "dist/internal/runtime.mjs" ,
89+ } ,
90+ ] ,
91+ ] ,
92+ ] ) ;
7693
7794function collectWorkspaceProtocolDependencyErrors ( packageJson , label ) {
7895 const errors = [ ] ;
@@ -111,7 +128,96 @@ function listBundleDependencies(packageJson) {
111128 : [ ] ;
112129}
113130
114- function collectRequiredBundledWorkspaceDependencyErrors ( packageJson , entrySet ) {
131+ function resolveBundledPackageSpecifiers ( packageRoot , specifiers ) {
132+ const result = spawnSync (
133+ process . execPath ,
134+ [
135+ "--input-type=module" ,
136+ "--eval" ,
137+ `const resolutions = {};
138+ for (const specifier of JSON.parse(process.argv[1])) {
139+ try {
140+ resolutions[specifier] = import.meta.resolve(specifier);
141+ } catch {
142+ resolutions[specifier] = "";
143+ }
144+ }
145+ process.stdout.write(JSON.stringify(resolutions));` ,
146+ JSON . stringify ( specifiers ) ,
147+ ] ,
148+ { cwd : packageRoot , encoding : "utf8" , stdio : [ "ignore" , "pipe" , "pipe" ] } ,
149+ ) ;
150+ if ( result . status !== 0 ) {
151+ return null ;
152+ }
153+ try {
154+ return JSON . parse ( result . stdout ) ;
155+ } catch {
156+ return null ;
157+ }
158+ }
159+
160+ function collectBundledPackageRuntimeErrors ( { name, entries, files, packageRoot, readText } ) {
161+ const errors = [ ] ;
162+ const packagePrefix = `node_modules/${ name } /` ;
163+ const manifestPath = `${ packagePrefix } package.json` ;
164+ let bundledPackageJson ;
165+ try {
166+ bundledPackageJson = JSON . parse ( readText ( manifestPath ) ) ;
167+ } catch ( error ) {
168+ errors . push (
169+ `unreadable bundled ${ name } package.json: ${
170+ error instanceof Error ? error . message : String ( error )
171+ } `,
172+ ) ;
173+ return errors ;
174+ }
175+ if ( bundledPackageJson . name !== name ) {
176+ errors . push ( `bundled ${ name } package.json must name ${ name } ` ) ;
177+ }
178+ const runtimeEntries = REQUIRED_BUNDLED_WORKSPACE_RUNTIME_ENTRIES . get ( name ) ?? [ ] ;
179+ const resolutions = resolveBundledPackageSpecifiers (
180+ packageRoot ,
181+ runtimeEntries . map ( ( { specifier } ) => specifier ) ,
182+ ) ;
183+ if ( ! resolutions ) {
184+ errors . push ( `bundled ${ name } runtime specifier resolution failed` ) ;
185+ }
186+ for ( const { entry, specifier } of runtimeEntries ) {
187+ if ( ! entries . has ( `${ packagePrefix } ${ entry } ` ) ) {
188+ errors . push ( `bundled ${ name } is missing required runtime entry ${ entry } ` ) ;
189+ }
190+ const resolvedUrl = resolutions ?. [ specifier ] ?? "" ;
191+ if ( ! resolvedUrl ) {
192+ errors . push ( `bundled ${ name } runtime specifier ${ specifier } is not resolvable` ) ;
193+ continue ;
194+ }
195+ const expectedUrl = pathToFileURL ( path . join ( packageRoot , packagePrefix , entry ) ) . href ;
196+ if ( resolvedUrl !== expectedUrl ) {
197+ errors . push (
198+ `bundled ${ name } runtime specifier ${ specifier } resolves to ${ resolvedUrl } instead of ${ expectedUrl } ` ,
199+ ) ;
200+ }
201+ }
202+ const bundledFiles = files
203+ . filter ( ( file ) => file . startsWith ( packagePrefix ) )
204+ . map ( ( file ) => file . slice ( packagePrefix . length ) ) ;
205+ errors . push (
206+ ...collectPackageDistImportErrors ( {
207+ files : bundledFiles ,
208+ readText : ( file ) => readText ( `${ packagePrefix } ${ file } ` ) ,
209+ } ) . map ( ( error ) => `bundled ${ name } ${ error } ` ) ,
210+ ) ;
211+ return errors ;
212+ }
213+
214+ function collectRequiredBundledWorkspaceDependencyErrors (
215+ packageJson ,
216+ entrySet ,
217+ files ,
218+ packageRoot ,
219+ readText ,
220+ ) {
115221 const errors = [ ] ;
116222 if ( ! packageJson || typeof packageJson !== "object" ) {
117223 return errors ;
@@ -134,7 +240,17 @@ function collectRequiredBundledWorkspaceDependencyErrors(packageJson, entrySet)
134240 }
135241 if ( ! entrySet . has ( `node_modules/${ name } /package.json` ) ) {
136242 errors . push ( `package.json dependencies.${ name } must be bundled in node_modules/${ name } ` ) ;
243+ continue ;
137244 }
245+ errors . push (
246+ ...collectBundledPackageRuntimeErrors ( {
247+ name,
248+ entries : entrySet ,
249+ files,
250+ packageRoot,
251+ readText,
252+ } ) ,
253+ ) ;
138254 }
139255
140256 return errors ;
@@ -273,6 +389,12 @@ function readTarEntry(entryPath) {
273389 return "" ;
274390}
275391
392+ const extractedPackageRoot = fs . realpathSync (
393+ fs . existsSync ( path . join ( extractDir , "package" , "package.json" ) )
394+ ? path . join ( extractDir , "package" )
395+ : extractDir ,
396+ ) ;
397+
276398for ( const entry of normalized ) {
277399 if ( entry . startsWith ( "/" ) || entry . split ( "/" ) . includes ( ".." ) ) {
278400 errors . push ( `unsafe tar entry: ${ entry } ` ) ;
@@ -302,7 +424,15 @@ if (entrySet.has("package.json")) {
302424 packageVersion = typeof packageJson . version === "string" ? packageJson . version : "" ;
303425 errors . push ( ...collectWorkspaceProtocolDependencyErrors ( packageJson , "package.json" ) ) ;
304426 if ( cliArgs . requireBundledWorkspaceDeps ) {
305- errors . push ( ...collectRequiredBundledWorkspaceDependencyErrors ( packageJson , entrySet ) ) ;
427+ errors . push (
428+ ...collectRequiredBundledWorkspaceDependencyErrors (
429+ packageJson ,
430+ entrySet ,
431+ normalized ,
432+ extractedPackageRoot ,
433+ readTarEntry ,
434+ ) ,
435+ ) ;
306436 }
307437 } catch {
308438 packageVersion = "" ;
0 commit comments