multi-lang-build 0.2.7__py3-none-any.whl → 0.2.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. multi_lang_build/__init__.py +4 -4
  2. multi_lang_build/cli.py +3 -2
  3. multi_lang_build/compiler/__init__.py +1 -1
  4. multi_lang_build/compiler/go_compiler.py +388 -0
  5. multi_lang_build/compiler/go_support/__init__.py +19 -0
  6. multi_lang_build/compiler/go_support/binary.py +117 -0
  7. multi_lang_build/compiler/go_support/builder.py +228 -0
  8. multi_lang_build/compiler/go_support/cleaner.py +40 -0
  9. multi_lang_build/compiler/go_support/env.py +41 -0
  10. multi_lang_build/compiler/go_support/mirror.py +111 -0
  11. multi_lang_build/compiler/go_support/module.py +77 -0
  12. multi_lang_build/compiler/go_support/tester.py +199 -0
  13. multi_lang_build/compiler/pnpm.py +61 -209
  14. multi_lang_build/compiler/pnpm_support/__init__.py +6 -0
  15. multi_lang_build/compiler/pnpm_support/executor.py +148 -0
  16. multi_lang_build/compiler/pnpm_support/project.py +53 -0
  17. multi_lang_build/compiler/python.py +65 -222
  18. multi_lang_build/compiler/python_support/__init__.py +13 -0
  19. multi_lang_build/compiler/python_support/cleaner.py +56 -0
  20. multi_lang_build/compiler/python_support/cli.py +46 -0
  21. multi_lang_build/compiler/python_support/detector.py +64 -0
  22. multi_lang_build/compiler/python_support/installer.py +162 -0
  23. multi_lang_build/compiler/python_support/venv.py +63 -0
  24. multi_lang_build/ide_register.py +97 -0
  25. multi_lang_build/mirror/config.py +19 -0
  26. multi_lang_build/register/support/__init__.py +13 -0
  27. multi_lang_build/register/support/claude.py +94 -0
  28. multi_lang_build/register/support/codebuddy.py +100 -0
  29. multi_lang_build/register/support/opencode.py +62 -0
  30. multi_lang_build/register/support/trae.py +72 -0
  31. {multi_lang_build-0.2.7.dist-info → multi_lang_build-0.2.10.dist-info}/METADATA +1 -1
  32. multi_lang_build-0.2.10.dist-info/RECORD +40 -0
  33. multi_lang_build/compiler/go.py +0 -532
  34. multi_lang_build/register.py +0 -412
  35. multi_lang_build-0.2.7.dist-info/RECORD +0 -18
  36. {multi_lang_build-0.2.7.dist-info → multi_lang_build-0.2.10.dist-info}/WHEEL +0 -0
  37. {multi_lang_build-0.2.7.dist-info → multi_lang_build-0.2.10.dist-info}/entry_points.txt +0 -0
  38. {multi_lang_build-0.2.7.dist-info → multi_lang_build-0.2.10.dist-info}/licenses/LICENSE +0 -0
@@ -3,43 +3,42 @@
3
3
  from pathlib import Path
4
4
  from typing import Final
5
5
  import shutil
6
- import json
7
6
  import os
8
- import time
9
7
 
10
8
  from multi_lang_build.compiler.base import CompilerBase, BuildResult, CompilerInfo
11
- from multi_lang_build.mirror.config import get_mirror_config, apply_mirror_environment
9
+ from multi_lang_build.mirror.config import apply_mirror_environment
10
+ from multi_lang_build.compiler.pnpm_support import PnpmProject, PnpmExecutor
12
11
 
13
12
 
14
13
  class PnpmCompiler(CompilerBase):
15
14
  """Compiler for pnpm-based frontend projects."""
16
-
15
+
17
16
  NAME: Final[str] = "pnpm"
18
17
  DEFAULT_MIRROR: Final[str] = "https://registry.npmmirror.com"
19
-
18
+
20
19
  def __init__(self, pnpm_path: str | None = None) -> None:
21
20
  """Initialize the PNPM compiler.
22
-
21
+
23
22
  Args:
24
23
  pnpm_path: Optional path to pnpm executable. If None, uses system PATH.
25
24
  """
26
25
  self._pnpm_path = pnpm_path
27
26
  self._version_cache: str | None = None
28
-
27
+
29
28
  @property
30
29
  def name(self) -> str:
31
30
  """Get the compiler name."""
32
31
  return self.NAME
33
-
32
+
34
33
  @property
35
34
  def version(self) -> str:
36
35
  """Get the pnpm version."""
37
36
  if self._version_cache:
38
37
  return self._version_cache
39
-
38
+
40
39
  pnpm_executable = self._get_executable_path()
41
40
  import subprocess
42
-
41
+
43
42
  try:
44
43
  result = subprocess.run(
45
44
  [pnpm_executable, "--version"],
@@ -50,14 +49,14 @@ class PnpmCompiler(CompilerBase):
50
49
  self._version_cache = result.stdout.strip()
51
50
  except Exception:
52
51
  self._version_cache = "unknown"
53
-
52
+
54
53
  return self._version_cache
55
-
54
+
56
55
  @property
57
56
  def supported_mirrors(self) -> list[str]:
58
57
  """Get list of supported mirror configurations."""
59
58
  return ["npm", "pnpm", "yarn"]
60
-
59
+
61
60
  def get_info(self) -> CompilerInfo:
62
61
  """Get compiler information."""
63
62
  return {
@@ -67,158 +66,11 @@ class PnpmCompiler(CompilerBase):
67
66
  "default_mirror": self.DEFAULT_MIRROR,
68
67
  "executable": self._get_executable_path(),
69
68
  }
70
-
71
- def find_project_root(self, start_path: Path) -> Path | None:
72
- """Find the frontend project root directory containing package.json.
73
-
74
- Searches from start_path up to its ancestors and also checks subdirectories.
75
- Returns the first directory containing package.json, or None if not found.
76
-
77
- Args:
78
- start_path: Starting path to search from
79
-
80
- Returns:
81
- Path to project root or None if not found
82
- """
83
- start_path = start_path.resolve()
84
-
85
- # Check if start_path itself is the root
86
- if (start_path / "package.json").exists():
87
- return start_path
88
-
89
- # Search upwards in the directory tree
90
- current = start_path.parent
91
- original_cwd = start_path
92
- while current != current.parent:
93
- if (current / "package.json").exists():
94
- return current
95
- current = current.parent
96
-
97
- # Search subdirectories
98
- for subdir in start_path.iterdir():
99
- if subdir.is_dir() and not subdir.name.startswith("."):
100
- subdir_root = self.find_project_root(subdir)
101
- if subdir_root:
102
- return subdir_root
103
-
104
- return None
105
-
106
- def _execute_in_directory(
107
- self,
108
- command: list[str],
109
- working_dir: Path,
110
- environment: dict[str, str],
111
- stream_output: bool = True,
112
- ) -> BuildResult:
113
- """Execute a command in the specified directory, returning to original cwd afterwards.
114
-
115
- Args:
116
- command: Command to execute
117
- working_dir: Working directory for the command
118
- environment: Environment variables
119
- stream_output: Whether to stream output in real-time (default: True)
120
69
 
121
- Returns:
122
- BuildResult containing success status and output information
123
- """
124
- import subprocess
125
- import sys
126
-
127
- original_cwd = Path.cwd()
128
- full_command = command.copy()
129
-
130
- start_time: float = 0.0
70
+ def find_project_root(self, start_path: Path) -> Path | None:
71
+ """Find the frontend project root directory containing package.json."""
72
+ return PnpmProject.find_root(start_path)
131
73
 
132
- try:
133
- os.chdir(working_dir)
134
-
135
- start_time = time.perf_counter()
136
-
137
- if stream_output:
138
- stdout_buffer = []
139
- stderr_buffer = []
140
-
141
- process = subprocess.Popen(
142
- full_command,
143
- stdout=subprocess.PIPE,
144
- stderr=subprocess.PIPE,
145
- text=True,
146
- env={**os.environ, **environment},
147
- )
148
-
149
- stdout_buffer = []
150
- stderr_buffer = []
151
-
152
- # Read stdout in real-time
153
- if process.stdout:
154
- for line in process.stdout:
155
- line = line.rstrip('\n\r')
156
- stdout_buffer.append(line)
157
- print(line)
158
- sys.stdout.flush()
159
-
160
- # Read stderr in real-time
161
- if process.stderr:
162
- for line in process.stderr:
163
- line = line.rstrip('\n\r')
164
- stderr_buffer.append(line)
165
- print(line, file=sys.stderr)
166
- sys.stderr.flush()
167
-
168
- return_code = process.wait()
169
- duration = time.perf_counter() - start_time
170
-
171
- return BuildResult(
172
- success=return_code == 0,
173
- return_code=return_code,
174
- stdout='\n'.join(stdout_buffer),
175
- stderr='\n'.join(stderr_buffer),
176
- output_path=working_dir,
177
- duration_seconds=duration,
178
- )
179
- else:
180
- result = subprocess.run(
181
- full_command,
182
- capture_output=True,
183
- text=True,
184
- timeout=3600,
185
- env={**os.environ, **environment},
186
- )
187
-
188
- duration = time.perf_counter() - start_time
189
-
190
- return BuildResult(
191
- success=result.returncode == 0,
192
- return_code=result.returncode,
193
- stdout=result.stdout,
194
- stderr=result.stderr,
195
- output_path=working_dir,
196
- duration_seconds=duration,
197
- )
198
-
199
- except subprocess.TimeoutExpired:
200
- duration = time.perf_counter() - start_time
201
- return BuildResult(
202
- success=False,
203
- return_code=-1,
204
- stdout="",
205
- stderr="Build timed out after 1 hour",
206
- output_path=None,
207
- duration_seconds=duration,
208
- )
209
- except Exception as e:
210
- duration = time.perf_counter() - start_time
211
- return BuildResult(
212
- success=False,
213
- return_code=-2,
214
- stdout="",
215
- stderr=f"Build error: {str(e)}",
216
- output_path=None,
217
- duration_seconds=duration,
218
- )
219
- finally:
220
- os.chdir(original_cwd)
221
-
222
74
  def build(
223
75
  self,
224
76
  source_dir: Path,
@@ -243,10 +95,10 @@ class PnpmCompiler(CompilerBase):
243
95
  BuildResult containing success status and output information.
244
96
  """
245
97
  pnpm_executable = self._get_executable_path()
246
-
98
+
247
99
  # Auto-detect project root
248
100
  project_root = self.find_project_root(source_dir)
249
-
101
+
250
102
  if project_root is None:
251
103
  return BuildResult(
252
104
  success=False,
@@ -256,19 +108,19 @@ class PnpmCompiler(CompilerBase):
256
108
  output_path=None,
257
109
  duration_seconds=0.0,
258
110
  )
259
-
111
+
260
112
  # Validate directories
261
113
  output_dir = self._validate_directory(output_dir, create_if_not_exists=True)
262
-
114
+
263
115
  # Prepare environment
264
116
  env = environment.copy() if environment else {}
265
-
117
+
266
118
  if mirror_enabled:
267
119
  env = apply_mirror_environment("pnpm", env)
268
120
  env = apply_mirror_environment("npm", env)
269
-
121
+
270
122
  # Install dependencies in project root
271
- install_result = self._execute_in_directory(
123
+ install_result = PnpmExecutor.execute(
272
124
  [pnpm_executable, "install"],
273
125
  project_root,
274
126
  env,
@@ -283,13 +135,13 @@ class PnpmCompiler(CompilerBase):
283
135
  if extra_args:
284
136
  build_args.extend(extra_args)
285
137
 
286
- return self._execute_in_directory(
138
+ return PnpmExecutor.execute(
287
139
  build_args,
288
140
  project_root,
289
141
  env,
290
142
  stream_output=stream_output,
291
143
  )
292
-
144
+
293
145
  def install_dependencies(
294
146
  self,
295
147
  source_dir: Path,
@@ -299,20 +151,20 @@ class PnpmCompiler(CompilerBase):
299
151
  production: bool = False,
300
152
  ) -> BuildResult:
301
153
  """Install dependencies using pnpm with auto-detection of project root.
302
-
154
+
303
155
  Args:
304
156
  source_dir: Source code directory or subdirectory
305
157
  environment: Additional environment variables
306
158
  mirror_enabled: Whether to use mirror acceleration
307
159
  production: Install only production dependencies
308
-
160
+
309
161
  Returns:
310
162
  BuildResult containing success status and output information.
311
163
  """
312
164
  pnpm_executable = self._get_executable_path()
313
-
165
+
314
166
  project_root = self.find_project_root(source_dir)
315
-
167
+
316
168
  if project_root is None:
317
169
  return BuildResult(
318
170
  success=False,
@@ -322,23 +174,23 @@ class PnpmCompiler(CompilerBase):
322
174
  output_path=None,
323
175
  duration_seconds=0.0,
324
176
  )
325
-
177
+
326
178
  env = environment.copy() if environment else {}
327
-
179
+
328
180
  if mirror_enabled:
329
181
  env = apply_mirror_environment("pnpm", env)
330
-
182
+
331
183
  command = [pnpm_executable, "install"]
332
184
  if production:
333
185
  command.append("--prod")
334
186
 
335
- return self._execute_in_directory(
187
+ return PnpmExecutor.execute(
336
188
  command,
337
189
  project_root,
338
190
  env,
339
191
  stream_output=True,
340
192
  )
341
-
193
+
342
194
  def run_script(
343
195
  self,
344
196
  source_dir: Path,
@@ -348,20 +200,20 @@ class PnpmCompiler(CompilerBase):
348
200
  mirror_enabled: bool = True,
349
201
  ) -> BuildResult:
350
202
  """Run a specific npm script with auto-detection of project root.
351
-
203
+
352
204
  Args:
353
205
  source_dir: Source code directory or subdirectory
354
206
  script_name: Name of the script to run
355
207
  environment: Additional environment variables
356
208
  mirror_enabled: Whether to use mirror acceleration
357
-
209
+
358
210
  Returns:
359
211
  BuildResult containing success status and output information.
360
212
  """
361
213
  pnpm_executable = self._get_executable_path()
362
-
214
+
363
215
  project_root = self.find_project_root(source_dir)
364
-
216
+
365
217
  if project_root is None:
366
218
  return BuildResult(
367
219
  success=False,
@@ -371,71 +223,71 @@ class PnpmCompiler(CompilerBase):
371
223
  output_path=None,
372
224
  duration_seconds=0.0,
373
225
  )
374
-
226
+
375
227
  env = environment.copy() if environment else {}
376
-
228
+
377
229
  if mirror_enabled:
378
230
  env = apply_mirror_environment("pnpm", env)
379
-
380
- return self._execute_in_directory(
231
+
232
+ return PnpmExecutor.execute(
381
233
  [pnpm_executable, "run", script_name],
382
234
  project_root,
383
235
  env,
384
236
  )
385
-
237
+
386
238
  def clean(self, directory: Path) -> bool:
387
239
  """Clean pnpm artifacts in the specified directory.
388
-
240
+
389
241
  Args:
390
242
  directory: Directory to clean
391
-
243
+
392
244
  Returns:
393
245
  True if successful, False otherwise.
394
246
  """
395
247
  import shutil
396
-
248
+
397
249
  try:
398
250
  directory = self._validate_directory(directory, create_if_not_exists=False)
399
-
251
+
400
252
  # Remove node_modules
401
253
  node_modules = directory / "node_modules"
402
254
  if node_modules.exists():
403
255
  shutil.rmtree(node_modules)
404
-
256
+
405
257
  # Remove pnpm-lock.yaml
406
258
  lock_file = directory / "pnpm-lock.yaml"
407
259
  if lock_file.exists():
408
260
  lock_file.unlink()
409
-
261
+
410
262
  # Remove .pnpm-store if exists
411
263
  pnpm_store = directory / ".pnpm-store"
412
264
  if pnpm_store.exists():
413
265
  shutil.rmtree(pnpm_store)
414
-
266
+
415
267
  return True
416
-
268
+
417
269
  except Exception:
418
270
  return False
419
-
271
+
420
272
  def _get_executable_path(self) -> str:
421
273
  """Get the pnpm executable path."""
422
274
  if self._pnpm_path:
423
275
  return self._pnpm_path
424
-
276
+
425
277
  pnpm_path = shutil.which("pnpm")
426
278
  if pnpm_path is None:
427
279
  raise RuntimeError("pnpm not found in PATH. Please install pnpm or provide pnpm_path.")
428
-
280
+
429
281
  return pnpm_path
430
-
282
+
431
283
  @staticmethod
432
284
  def create(source_dir: Path, *, mirror_enabled: bool = True) -> "PnpmCompiler":
433
285
  """Factory method to create a PnpmCompiler instance.
434
-
286
+
435
287
  Args:
436
288
  source_dir: Source directory for the project
437
289
  mirror_enabled: Whether to enable mirror acceleration by default
438
-
290
+
439
291
  Returns:
440
292
  Configured PnpmCompiler instance
441
293
  """
@@ -447,7 +299,7 @@ def main() -> None:
447
299
  """PNPM compiler CLI entry point."""
448
300
  import argparse
449
301
  import sys
450
-
302
+
451
303
  parser = argparse.ArgumentParser(description="PNPM Build Compiler")
452
304
  parser.add_argument("source_dir", type=Path, help="Source directory")
453
305
  parser.add_argument("-o", "--output", type=Path, required=True, help="Output directory")
@@ -455,11 +307,11 @@ def main() -> None:
455
307
  parser.add_argument("--no-mirror", dest="mirror", action="store_false", help="Disable mirror acceleration")
456
308
  parser.add_argument("--script", type=str, help="Run specific npm script instead of build")
457
309
  parser.add_argument("--install", action="store_true", help="Install dependencies only")
458
-
310
+
459
311
  args = parser.parse_args()
460
-
312
+
461
313
  compiler = PnpmCompiler()
462
-
314
+
463
315
  if args.script:
464
316
  result = compiler.run_script(
465
317
  args.source_dir,
@@ -477,5 +329,5 @@ def main() -> None:
477
329
  args.output,
478
330
  mirror_enabled=args.mirror,
479
331
  )
480
-
332
+
481
333
  sys.exit(0 if result["success"] else 1)
@@ -0,0 +1,6 @@
1
+ """PNPM compiler support modules."""
2
+
3
+ from multi_lang_build.compiler.pnpm_support.project import PnpmProject
4
+ from multi_lang_build.compiler.pnpm_support.executor import PnpmExecutor
5
+
6
+ __all__ = ["PnpmProject", "PnpmExecutor"]
@@ -0,0 +1,148 @@
1
+ """PNPM command execution."""
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ import time
7
+ from pathlib import Path
8
+ from typing import Final
9
+
10
+ from multi_lang_build.compiler.base import BuildResult
11
+
12
+
13
+ class PnpmExecutor:
14
+ """Execute pnpm commands with streaming support."""
15
+
16
+ DEFAULT_TIMEOUT: Final[int] = 3600 # 1 hour
17
+
18
+ @staticmethod
19
+ def execute(
20
+ command: list[str],
21
+ working_dir: Path,
22
+ environment: dict[str, str],
23
+ stream_output: bool = True,
24
+ ) -> BuildResult:
25
+ """Execute a command in the specified directory.
26
+
27
+ Args:
28
+ command: Command to execute
29
+ working_dir: Working directory for the command
30
+ environment: Environment variables
31
+ stream_output: Whether to stream output in real-time
32
+
33
+ Returns:
34
+ BuildResult containing success status and output information
35
+ """
36
+ original_cwd = Path.cwd()
37
+ full_command = command.copy()
38
+ start_time: float = 0.0
39
+
40
+ try:
41
+ os.chdir(working_dir)
42
+ start_time = time.perf_counter()
43
+
44
+ if stream_output:
45
+ return PnpmExecutor._execute_stream(
46
+ full_command, working_dir, environment, start_time
47
+ )
48
+ else:
49
+ return PnpmExecutor._execute_capture(
50
+ full_command, working_dir, environment, start_time
51
+ )
52
+
53
+ except subprocess.TimeoutExpired:
54
+ duration = time.perf_counter() - start_time
55
+ return BuildResult(
56
+ success=False,
57
+ return_code=-1,
58
+ stdout="",
59
+ stderr="Build timed out after 1 hour",
60
+ output_path=None,
61
+ duration_seconds=duration,
62
+ )
63
+ except Exception as e:
64
+ duration = time.perf_counter() - start_time
65
+ return BuildResult(
66
+ success=False,
67
+ return_code=-2,
68
+ stdout="",
69
+ stderr=f"Build error: {str(e)}",
70
+ output_path=None,
71
+ duration_seconds=duration,
72
+ )
73
+ finally:
74
+ os.chdir(original_cwd)
75
+
76
+ @staticmethod
77
+ def _execute_stream(
78
+ command: list[str],
79
+ working_dir: Path,
80
+ environment: dict[str, str],
81
+ start_time: float,
82
+ ) -> BuildResult:
83
+ """Execute with real-time output streaming."""
84
+ stdout_buffer = []
85
+ stderr_buffer = []
86
+
87
+ process = subprocess.Popen(
88
+ command,
89
+ stdout=subprocess.PIPE,
90
+ stderr=subprocess.PIPE,
91
+ text=True,
92
+ env={**os.environ, **environment},
93
+ )
94
+
95
+ # Read stdout in real-time
96
+ if process.stdout:
97
+ for line in process.stdout:
98
+ line = line.rstrip('\n\r')
99
+ stdout_buffer.append(line)
100
+ print(line)
101
+ sys.stdout.flush()
102
+
103
+ # Read stderr in real-time
104
+ if process.stderr:
105
+ for line in process.stderr:
106
+ line = line.rstrip('\n\r')
107
+ stderr_buffer.append(line)
108
+ print(line, file=sys.stderr)
109
+ sys.stderr.flush()
110
+
111
+ return_code = process.wait()
112
+ duration = time.perf_counter() - start_time
113
+
114
+ return BuildResult(
115
+ success=return_code == 0,
116
+ return_code=return_code,
117
+ stdout='\n'.join(stdout_buffer),
118
+ stderr='\n'.join(stderr_buffer),
119
+ output_path=working_dir,
120
+ duration_seconds=duration,
121
+ )
122
+
123
+ @staticmethod
124
+ def _execute_capture(
125
+ command: list[str],
126
+ working_dir: Path,
127
+ environment: dict[str, str],
128
+ start_time: float,
129
+ ) -> BuildResult:
130
+ """Execute with captured output."""
131
+ result = subprocess.run(
132
+ command,
133
+ capture_output=True,
134
+ text=True,
135
+ timeout=PnpmExecutor.DEFAULT_TIMEOUT,
136
+ env={**os.environ, **environment},
137
+ )
138
+
139
+ duration = time.perf_counter() - start_time
140
+
141
+ return BuildResult(
142
+ success=result.returncode == 0,
143
+ return_code=result.returncode,
144
+ stdout=result.stdout,
145
+ stderr=result.stderr,
146
+ output_path=working_dir,
147
+ duration_seconds=duration,
148
+ )
@@ -0,0 +1,53 @@
1
+ """PNPM project detection and utilities."""
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ class PnpmProject:
7
+ """PNPM project detection and root finding."""
8
+
9
+ @staticmethod
10
+ def find_root(start_path: Path) -> Path | None:
11
+ """Find the frontend project root directory containing package.json.
12
+
13
+ Searches from start_path up to its ancestors and also checks subdirectories.
14
+
15
+ Args:
16
+ start_path: Starting path to search from
17
+
18
+ Returns:
19
+ Path to project root or None if not found
20
+ """
21
+ start_path = start_path.resolve()
22
+
23
+ # Check if start_path itself is the root
24
+ if (start_path / "package.json").exists():
25
+ return start_path
26
+
27
+ # Search upwards in the directory tree
28
+ current = start_path.parent
29
+ while current != current.parent:
30
+ if (current / "package.json").exists():
31
+ return current
32
+ current = current.parent
33
+
34
+ # Search subdirectories
35
+ for subdir in start_path.iterdir():
36
+ if subdir.is_dir() and not subdir.name.startswith("."):
37
+ subdir_root = PnpmProject.find_root(subdir)
38
+ if subdir_root:
39
+ return subdir_root
40
+
41
+ return None
42
+
43
+ @staticmethod
44
+ def validate(root: Path) -> bool:
45
+ """Validate that the path is a valid PNPM project.
46
+
47
+ Args:
48
+ root: Project root path
49
+
50
+ Returns:
51
+ True if valid, False otherwise
52
+ """
53
+ return (root / "package.json").exists()