File size: 5,954 Bytes
a31c0fe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/usr/bin/env node

/**
 * This script prepares the server for bundling with Electron.
 * It copies the server dist and installs production dependencies
 * in a way that works with npm workspaces.
 */

import { execFileSync } from 'child_process';
import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, lstatSync } from 'fs';
import { join, dirname, resolve } from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const APP_DIR = join(__dirname, '..');
const SERVER_DIR = join(APP_DIR, '..', 'server');
const LIBS_DIR = join(APP_DIR, '..', '..', 'libs');
const BUNDLE_DIR = join(APP_DIR, 'server-bundle');
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';

// Local workspace packages that need to be bundled
const LOCAL_PACKAGES = [
  '@automaker/types',
  '@automaker/utils',
  '@automaker/prompts',
  '@automaker/platform',
  '@automaker/model-resolver',
  '@automaker/dependency-resolver',
  '@automaker/git-utils',
];

console.log('πŸ”§ Preparing server for Electron bundling...\n');

// Step 1: Clean up previous bundle
if (existsSync(BUNDLE_DIR)) {
  console.log('πŸ—‘οΈ  Cleaning previous server-bundle...');
  rmSync(BUNDLE_DIR, { recursive: true });
}
mkdirSync(BUNDLE_DIR, { recursive: true });

// Step 2: Build the server TypeScript
console.log('πŸ“¦ Building server TypeScript...');
execFileSync(npmCommand, ['run', 'build'], {
  cwd: SERVER_DIR,
  stdio: 'inherit',
  shell: process.platform === 'win32',
});

// Step 3: Copy server dist
console.log('πŸ“‹ Copying server dist...');
cpSync(join(SERVER_DIR, 'dist'), join(BUNDLE_DIR, 'dist'), { recursive: true });

// Step 4: Copy local workspace packages
console.log('πŸ“¦ Copying local workspace packages...');
const bundleLibsDir = join(BUNDLE_DIR, 'libs');
mkdirSync(bundleLibsDir, { recursive: true });

for (const pkgName of LOCAL_PACKAGES) {
  const pkgDir = pkgName.replace('@automaker/', '');
  const srcDir = join(LIBS_DIR, pkgDir);
  const destDir = join(bundleLibsDir, pkgDir);

  if (!existsSync(srcDir)) {
    throw new Error(`Required package ${pkgName} not found at ${srcDir}`);
  }

  mkdirSync(destDir, { recursive: true });

  // Copy dist folder
  if (existsSync(join(srcDir, 'dist'))) {
    cpSync(join(srcDir, 'dist'), join(destDir, 'dist'), { recursive: true });
  }

  // The copied packages are production artifacts, not development workspaces.
  // npm still resolves omitted dev peers for file: links; retaining Vitest here
  // can pull an unrelated test toolchain into standalone dependency resolution.
  const pkg = JSON.parse(readFileSync(join(srcDir, 'package.json'), 'utf-8'));
  delete pkg.devDependencies;
  delete pkg.scripts;
  for (const section of ['dependencies', 'optionalDependencies']) {
    for (const name of Object.keys(pkg[section] || {})) {
      if (!name.startsWith('@automaker/')) continue;
      if (!LOCAL_PACKAGES.includes(name)) {
        throw new Error(`Required local dependency ${name} is missing from LOCAL_PACKAGES`);
      }
      pkg[section][name] = `file:../${name.replace('@automaker/', '')}`;
    }
  }
  writeFileSync(join(destDir, 'package.json'), JSON.stringify(pkg, null, 2) + '\n');

  console.log(`   βœ“ ${pkgName}`);
}

// Step 5: Create a minimal package.json for the server
console.log('πŸ“ Creating server package.json...');
const serverPkg = JSON.parse(readFileSync(join(SERVER_DIR, 'package.json'), 'utf-8'));

// Replace local package versions with file: references
const dependencies = { ...serverPkg.dependencies };
for (const pkgName of LOCAL_PACKAGES) {
  if (dependencies[pkgName]) {
    const pkgDir = pkgName.replace('@automaker/', '');
    dependencies[pkgName] = `file:libs/${pkgDir}`;
  }
}

const bundlePkg = {
  name: '@automaker/server-bundle',
  version: serverPkg.version,
  type: 'module',
  main: 'dist/index.js',
  dependencies,
};

writeFileSync(join(BUNDLE_DIR, 'package.json'), JSON.stringify(bundlePkg, null, 2));

// Step 6: Install production dependencies
console.log('πŸ“₯ Installing server production dependencies...');
execFileSync(npmCommand, ['install', '--omit=dev', '--ignore-scripts', '--workspaces=false'], {
  cwd: BUNDLE_DIR,
  stdio: 'inherit',
  shell: process.platform === 'win32',
  env: {
    ...process.env,
    // Prevent npm from using workspace resolution
    npm_config_workspaces: 'false',
  },
});

// Step 6b: Replace symlinks for local packages with real copies
// npm install creates symlinks for file: references, but these break when packaged by electron-builder
console.log('πŸ”— Replacing symlinks with real directory copies...');
const nodeModulesAutomaker = join(BUNDLE_DIR, 'node_modules', '@automaker');
for (const pkgName of LOCAL_PACKAGES) {
  const pkgDir = pkgName.replace('@automaker/', '');
  const nmPkgPath = join(nodeModulesAutomaker, pkgDir);
  try {
    // lstatSync does not follow symlinks, allowing us to check for broken ones
    if (lstatSync(nmPkgPath).isSymbolicLink()) {
      const realPath = resolve(BUNDLE_DIR, 'libs', pkgDir);
      rmSync(nmPkgPath);
      cpSync(realPath, nmPkgPath, { recursive: true });
      console.log(`   βœ“ Replaced symlink: ${pkgName}`);
    }
  } catch (error) {
    // If the path doesn't exist, lstatSync throws ENOENT. We can safely ignore this.
    if (error.code !== 'ENOENT') {
      throw error;
    }
  }
}

// Step 7: Rebuild native modules for current architecture
// This is critical for modules like node-pty that have native bindings
console.log('πŸ”¨ Rebuilding native modules for current architecture...');
execFileSync(npmCommand, ['rebuild', 'node-pty', '--workspaces=false'], {
  cwd: BUNDLE_DIR,
  stdio: 'inherit',
  shell: process.platform === 'win32',
  env: { ...process.env, npm_config_build_from_source: 'true' },
});
console.log('βœ… Native modules rebuilt successfully');

console.log('\nβœ… Server prepared for bundling at:', BUNDLE_DIR);