1010 * node scripts/copy-rust-binaries.mjs --all # Copy all platforms (requires cross-compilation)
1111 */
1212import { promises as fs } from 'node:fs' ;
13- import os from 'node:os' ;
1413import path from 'node:path' ;
1514import { fileURLToPath } from 'node:url' ;
1615
1716const __filename = fileURLToPath ( import . meta. url ) ;
1817const __dirname = path . dirname ( __filename ) ;
1918const ROOT = path . resolve ( __dirname , '..' ) ;
19+ const REPOSITORY_URL = 'https://github.com/codervisor/lean-spec.git' ;
2020
2121// Platform mapping
2222const PLATFORM_MAP = {
@@ -25,14 +25,53 @@ const PLATFORM_MAP = {
2525 win32 : { x64 : 'windows-x64' , arm64 : 'windows-arm64' }
2626} ;
2727
28+ const PLATFORM_INFO = {
29+ 'darwin-x64' : { os : 'darwin' , cpu : 'x64' , label : 'macOS x64' } ,
30+ 'darwin-arm64' : { os : 'darwin' , cpu : 'arm64' , label : 'macOS ARM64' } ,
31+ 'linux-x64' : { os : 'linux' , cpu : 'x64' , label : 'Linux x64' } ,
32+ 'linux-arm64' : { os : 'linux' , cpu : 'arm64' , label : 'Linux ARM64' } ,
33+ 'windows-x64' : { os : 'win32' , cpu : 'x64' , label : 'Windows x64' }
34+ } ;
35+
2836// All platforms for --all flag
29- const ALL_PLATFORMS = [
30- 'darwin-x64' ,
31- 'darwin-arm64' ,
32- 'linux-x64' ,
33- 'linux-arm64' ,
34- 'windows-x64' ,
35- ] ;
37+ const ALL_PLATFORMS = Object . keys ( PLATFORM_INFO ) ;
38+
39+ const BINARY_CONFIG = {
40+ 'lean-spec' : {
41+ packagePath : 'cli' ,
42+ packagePrefix : '@leanspec/cli' ,
43+ description : 'LeanSpec CLI binary'
44+ } ,
45+ 'leanspec-mcp' : {
46+ packagePath : 'mcp' ,
47+ packagePrefix : '@leanspec/mcp' ,
48+ description : 'LeanSpec MCP server binary'
49+ } ,
50+ 'leanspec-http' : {
51+ packagePath : 'http-server' ,
52+ packagePrefix : '@leanspec/http' ,
53+ description : 'LeanSpec HTTP server binary'
54+ }
55+ } ;
56+
57+ async function resolveTargetVersion ( ) {
58+ const rootPackagePath = path . join ( ROOT , 'package.json' ) ;
59+ const rootPackage = JSON . parse ( await fs . readFile ( rootPackagePath , 'utf-8' ) ) ;
60+
61+ if ( rootPackage . version ) {
62+ return rootPackage . version ;
63+ }
64+
65+ const cliPackagePath = path . join ( ROOT , 'packages' , 'cli' , 'package.json' ) ;
66+ const cliPackage = JSON . parse ( await fs . readFile ( cliPackagePath , 'utf-8' ) ) ;
67+
68+ if ( ! cliPackage . version ) {
69+ throw new Error ( 'Unable to resolve version from root or packages/cli/package.json' ) ;
70+ }
71+
72+ console . warn ( '⚠️ Root package.json missing version; using packages/cli/package.json version.' ) ;
73+ return cliPackage . version ;
74+ }
3675
3776function getCurrentPlatform ( ) {
3877 const platform = process . platform ;
@@ -46,6 +85,60 @@ function getCurrentPlatform() {
4685 return platformKey ;
4786}
4887
88+ function getPlatformInfo ( platformKey ) {
89+ const info = PLATFORM_INFO [ platformKey ] ;
90+
91+ if ( ! info ) {
92+ throw new Error ( `Unknown platform key: ${ platformKey } ` ) ;
93+ }
94+
95+ return info ;
96+ }
97+
98+ function getBinaryFileName ( binaryName , platformKey ) {
99+ return platformKey . startsWith ( 'windows-' ) ? `${ binaryName } .exe` : binaryName ;
100+ }
101+
102+ async function ensurePackageJson ( {
103+ destDir,
104+ platformKey,
105+ binaryName,
106+ packagePrefix,
107+ description,
108+ version,
109+ } ) {
110+ const packageJsonPath = path . join ( destDir , 'package.json' ) ;
111+ const platformInfo = getPlatformInfo ( platformKey ) ;
112+
113+ try {
114+ await fs . access ( packageJsonPath ) ;
115+ return ;
116+ } catch ( e ) {
117+ // Missing manifest; create below
118+ }
119+
120+ const binaryFileName = getBinaryFileName ( binaryName , platformKey ) ;
121+ const packageName = `${ packagePrefix } -${ platformKey } ` ;
122+
123+ const packageJson = {
124+ name : packageName ,
125+ version,
126+ description : `${ description } for ${ platformInfo . label } ` ,
127+ os : [ platformInfo . os ] ,
128+ cpu : [ platformInfo . cpu ] ,
129+ main : binaryFileName ,
130+ files : [ binaryFileName ] ,
131+ repository : {
132+ type : 'git' ,
133+ url : REPOSITORY_URL
134+ } ,
135+ license : 'MIT'
136+ } ;
137+
138+ await fs . writeFile ( packageJsonPath , JSON . stringify ( packageJson , null , 2 ) + '\n' ) ;
139+ console . log ( `🆕 Created ${ packageName } package.json` ) ;
140+ }
141+
49142async function killProcessesUsingBinary ( binaryPath ) {
50143 if ( process . platform === 'win32' ) {
51144 // Windows: use handle.exe or just try to copy
@@ -82,24 +175,18 @@ async function killProcessesUsingBinary(binaryPath) {
82175 }
83176}
84177
85- async function copyBinary ( binaryName , platformKey ) {
86- const isWindows = platformKey . startsWith ( 'windows-' ) ;
87- const sourceExt = isWindows ? '.exe' : '' ;
88- const sourcePath = path . join ( ROOT , 'rust' , 'target' , 'release' , `${ binaryName } ${ sourceExt } ` ) ;
89-
90- // Determine destination based on binary name
91- let packagePath ;
92- if ( binaryName === 'lean-spec' ) {
93- packagePath = 'cli' ;
94- } else if ( binaryName === 'leanspec-mcp' ) {
95- packagePath = 'mcp' ;
96- } else if ( binaryName === 'leanspec-http' ) {
97- packagePath = 'http-server' ;
98- } else {
178+ async function copyBinary ( binaryName , platformKey , version ) {
179+ const config = BINARY_CONFIG [ binaryName ] ;
180+
181+ if ( ! config ) {
99182 throw new Error ( `Unknown binary: ${ binaryName } ` ) ;
100183 }
101- const destDir = path . join ( ROOT , 'packages' , packagePath , 'binaries' , platformKey ) ;
102- const destPath = path . join ( destDir , binaryName + sourceExt ) ;
184+
185+ const isWindows = platformKey . startsWith ( 'windows-' ) ;
186+ const binaryFileName = getBinaryFileName ( binaryName , platformKey ) ;
187+ const sourcePath = path . join ( ROOT , 'rust' , 'target' , 'release' , binaryFileName ) ;
188+ const destDir = path . join ( ROOT , 'packages' , config . packagePath , 'binaries' , platformKey ) ;
189+ const destPath = path . join ( destDir , binaryFileName ) ;
103190
104191 // Check if source exists
105192 try {
@@ -112,6 +199,16 @@ async function copyBinary(binaryName, platformKey) {
112199 // Ensure destination directory exists
113200 await fs . mkdir ( destDir , { recursive : true } ) ;
114201
202+ // Ensure platform package manifest exists
203+ await ensurePackageJson ( {
204+ destDir,
205+ platformKey,
206+ binaryName,
207+ packagePrefix : config . packagePrefix ,
208+ description : config . description ,
209+ version,
210+ } ) ;
211+
115212 // Kill any processes using the destination binary
116213 try {
117214 await fs . access ( destPath ) ;
@@ -128,14 +225,16 @@ async function copyBinary(binaryName, platformKey) {
128225 await fs . chmod ( destPath , 0o755 ) ;
129226 }
130227
131- console . log ( `✅ Copied ${ binaryName } to ${ packagePath } /binaries/${ platformKey } /` ) ;
228+ console . log ( `✅ Copied ${ binaryName } to ${ config . packagePath } /binaries/${ platformKey } /` ) ;
132229 return true ;
133230}
134231
135232async function main ( ) {
136233 const args = process . argv . slice ( 2 ) ;
137234 const copyAll = args . includes ( '--all' ) ;
138235
236+ const rootVersion = await resolveTargetVersion ( ) ;
237+
139238 console . log ( '🔧 Copying Rust binaries...\n' ) ;
140239
141240 const binaries = [ 'lean-spec' , 'leanspec-mcp' , 'leanspec-http' ] ;
@@ -146,15 +245,15 @@ async function main() {
146245 for ( const platformKey of ALL_PLATFORMS ) {
147246 console . log ( `\nPlatform: ${ platformKey } ` ) ;
148247 for ( const binary of binaries ) {
149- await copyBinary ( binary , platformKey ) ;
248+ await copyBinary ( binary , platformKey , rootVersion ) ;
150249 }
151250 }
152251 } else {
153252 const currentPlatform = getCurrentPlatform ( ) ;
154253 console . log ( `📦 Copying for current platform: ${ currentPlatform } \n` ) ;
155254
156255 for ( const binary of binaries ) {
157- await copyBinary ( binary , currentPlatform ) ;
256+ await copyBinary ( binary , currentPlatform , rootVersion ) ;
158257 }
159258 }
160259
0 commit comments