lwazi 1.16.2 → 1.16.4

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.
package/bin/crawl.php CHANGED
@@ -96,8 +96,7 @@ if ($options['url'] === 'http://localhost' && $argc === 1) {
96
96
  $input = readline("Login email/username: ");
97
97
  $options['login_user'] = trim($input);
98
98
 
99
- $input = readline("Login password: ");
100
- $options['login_pass'] = trim($input);
99
+ $options['login_pass'] = readPasswordMasked("Login password: ");
101
100
  }
102
101
  }
103
102
 
@@ -197,6 +196,28 @@ function extractMeta(string $html, string $name): string {
197
196
  return '';
198
197
  }
199
198
 
199
+ function extractText(string $html): string {
200
+ $dom = new DOMDocument();
201
+ libxml_use_internal_errors(true);
202
+ $dom->loadHTML($html);
203
+ libxml_clear_errors();
204
+
205
+ $remove = [];
206
+ foreach (['script', 'style', 'noscript'] as $tag) {
207
+ $nodes = $dom->getElementsByTagName($tag);
208
+ foreach ($nodes as $node) {
209
+ $remove[] = $node;
210
+ }
211
+ }
212
+ foreach ($remove as $node) {
213
+ $node->parentNode->removeChild($node);
214
+ }
215
+
216
+ $text = $dom->textContent ?? '';
217
+ $text = preg_replace('/\s+/', ' ', $text);
218
+ return trim($text);
219
+ }
220
+
200
221
  function extractCsrfToken(string $html): ?string {
201
222
  if (preg_match('/name="_token"\s+value="([^"]+)"/', $html, $m)) return $m[1];
202
223
  if (preg_match('/name="_token"\s*value="([^"]+)"/', $html, $m)) return $m[1];
@@ -204,6 +225,163 @@ function extractCsrfToken(string $html): ?string {
204
225
  return null;
205
226
  }
206
227
 
228
+ function isLoginPage(string $html): bool {
229
+ $dom = new DOMDocument();
230
+ libxml_use_internal_errors(true);
231
+ $dom->loadHTML($html);
232
+ libxml_clear_errors();
233
+
234
+ $inputs = $dom->getElementsByTagName('input');
235
+ foreach ($inputs as $input) {
236
+ $type = strtolower($input->getAttribute('type'));
237
+ if ($type === 'password') {
238
+ return true;
239
+ }
240
+ }
241
+ return false;
242
+ }
243
+
244
+ function fetchPage(string $url, string $cookies): array {
245
+ $headers = [];
246
+ $ch = curl_init();
247
+ curl_setopt($ch, CURLOPT_URL, $url);
248
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
249
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
250
+ curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
251
+ curl_setopt($ch, CURLOPT_TIMEOUT, 30);
252
+ curl_setopt($ch, CURLOPT_USERAGENT, 'Lwazi/1.0');
253
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
254
+ curl_setopt($ch, CURLOPT_COOKIEFILE, $cookies);
255
+ curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $headerLine) use (&$headers) {
256
+ $line = trim($headerLine);
257
+ if ($line !== '') {
258
+ $headers[] = $line;
259
+ }
260
+ return strlen($headerLine);
261
+ });
262
+
263
+ $html = curl_exec($ch);
264
+ $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
265
+ $finalUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL) ?: $url;
266
+ $error = curl_error($ch);
267
+ curl_close($ch);
268
+
269
+ return [
270
+ 'html' => $html ?: '',
271
+ 'http_code' => $httpCode,
272
+ 'final_url' => $finalUrl,
273
+ 'headers' => $headers,
274
+ 'error' => $error,
275
+ ];
276
+ }
277
+
278
+ function parseLoginForm(string $html, string $loginUrl): array {
279
+ $dom = new DOMDocument();
280
+ libxml_use_internal_errors(true);
281
+ $dom->loadHTML($html);
282
+ libxml_clear_errors();
283
+
284
+ $forms = $dom->getElementsByTagName('form');
285
+ $targetForm = null;
286
+ foreach ($forms as $form) {
287
+ $inputs = $form->getElementsByTagName('input');
288
+ foreach ($inputs as $input) {
289
+ if (strtolower($input->getAttribute('type')) === 'password') {
290
+ $targetForm = $form;
291
+ break 2;
292
+ }
293
+ }
294
+ }
295
+
296
+ $action = $loginUrl;
297
+ $method = 'post';
298
+ $fields = [];
299
+ $userField = null;
300
+ $passField = null;
301
+
302
+ if ($targetForm) {
303
+ $actionAttr = trim($targetForm->getAttribute('action'));
304
+ if ($actionAttr !== '') {
305
+ $action = normalizeUrl($actionAttr, $loginUrl);
306
+ }
307
+ $methodAttr = strtolower(trim($targetForm->getAttribute('method')));
308
+ if ($methodAttr !== '') {
309
+ $method = $methodAttr;
310
+ }
311
+
312
+ $inputs = $targetForm->getElementsByTagName('input');
313
+ foreach ($inputs as $input) {
314
+ $name = $input->getAttribute('name');
315
+ if ($name === '') {
316
+ continue;
317
+ }
318
+ $type = strtolower($input->getAttribute('type'));
319
+ $value = $input->getAttribute('value');
320
+ if ($type === 'hidden') {
321
+ $fields[$name] = $value;
322
+ }
323
+ if ($userField === null && in_array($type, ['text', 'email'], true)) {
324
+ if (preg_match('/email|user|username|login/i', $name)) {
325
+ $userField = $name;
326
+ }
327
+ }
328
+ if ($type === 'password' && $passField === null) {
329
+ $passField = $name;
330
+ }
331
+ }
332
+ }
333
+
334
+ return [
335
+ 'action' => $action,
336
+ 'method' => $method,
337
+ 'fields' => $fields,
338
+ 'user_field' => $userField,
339
+ 'pass_field' => $passField,
340
+ ];
341
+ }
342
+
343
+ function readPasswordMasked(string $prompt): string {
344
+ // Masked password input for POSIX terminals, with a safe fallback.
345
+ $sttyMode = null;
346
+ if (function_exists('shell_exec')) {
347
+ $sttyMode = shell_exec('stty -g 2>/dev/null');
348
+ if ($sttyMode) {
349
+ shell_exec('stty -echo -icanon min 1 time 0 2>/dev/null');
350
+ }
351
+ }
352
+
353
+ echo $prompt;
354
+ $password = '';
355
+
356
+ if ($sttyMode) {
357
+ while (true) {
358
+ $char = fread(STDIN, 1);
359
+ if ($char === false || $char === '') {
360
+ break;
361
+ }
362
+ if ($char === "\n" || $char === "\r") {
363
+ echo "\n";
364
+ break;
365
+ }
366
+ if ($char === "\x7f" || $char === "\x08") { // backspace/delete
367
+ if (strlen($password) > 0) {
368
+ $password = substr($password, 0, -1);
369
+ echo "\x08 \x08";
370
+ }
371
+ continue;
372
+ }
373
+ $password .= $char;
374
+ echo "*";
375
+ }
376
+ shell_exec('stty ' . $sttyMode . ' 2>/dev/null');
377
+ return $password;
378
+ }
379
+
380
+ // Fallback (no masking) if stty isn't available.
381
+ $line = fgets(STDIN);
382
+ return $line === false ? '' : rtrim($line, "\r\n");
383
+ }
384
+
207
385
  function loginAndGetCookies(string $loginUrl, string $username, string $password, string $cookies): bool {
208
386
  echo "Attempting to login to $loginUrl...\n";
209
387
 
@@ -224,32 +402,28 @@ function loginAndGetCookies(string $loginUrl, string $username, string $password
224
402
  echo "Failed to fetch login page\n";
225
403
  return false;
226
404
  }
227
-
405
+
406
+ $form = parseLoginForm($loginPage, $loginUrl);
228
407
  $csrfToken = extractCsrfToken($loginPage);
229
-
230
- $emailField = 'email';
231
- $passwordField = 'password';
232
-
233
- if (preg_match('/name="(email|user|username|login)"[^>]*>/i', $loginPage, $m)) {
234
- $emailField = $m[1];
235
- }
236
- if (preg_match('/name="(pass|pwd|password)"[^>]*>/i', $loginPage, $m)) {
237
- $passwordField = $m[1];
238
- }
239
-
240
- $loginData = [
241
- $emailField => $username,
242
- $passwordField => $password,
243
- ];
244
-
245
- if ($csrfToken) {
408
+ $loginData = $form['fields'];
409
+
410
+ $userField = $form['user_field'] ?: 'email';
411
+ $passField = $form['pass_field'] ?: 'password';
412
+ $loginData[$userField] = $username;
413
+ $loginData[$passField] = $password;
414
+
415
+ if ($csrfToken && !isset($loginData['_token'])) {
246
416
  $loginData['_token'] = $csrfToken;
247
417
  }
248
418
 
249
419
  $ch = curl_init();
250
- curl_setopt($ch, CURLOPT_URL, $loginUrl);
251
- curl_setopt($ch, CURLOPT_POST, true);
252
- curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($loginData));
420
+ curl_setopt($ch, CURLOPT_URL, $form['action']);
421
+ curl_setopt($ch, CURLOPT_POST, ($form['method'] !== 'get'));
422
+ if ($form['method'] === 'get') {
423
+ curl_setopt($ch, CURLOPT_URL, $form['action'] . '?' . http_build_query($loginData));
424
+ } else {
425
+ curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($loginData));
426
+ }
253
427
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
254
428
  curl_setopt($ch, CURLOPT_COOKIEJAR, $cookies);
255
429
  curl_setopt($ch, CURLOPT_COOKIEFILE, $cookies);
@@ -262,15 +436,28 @@ function loginAndGetCookies(string $loginUrl, string $username, string $password
262
436
  $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
263
437
  curl_close($ch);
264
438
 
265
- if ($httpCode >= 200 && $httpCode < 400) {
439
+ if ($httpCode >= 200 && $httpCode < 400 && $result && !isLoginPage($result)) {
266
440
  echo "Login successful!\n";
267
441
  return true;
268
442
  }
269
-
443
+
270
444
  echo "Login may have failed (HTTP $httpCode)\n";
271
445
  return false;
272
446
  }
273
447
 
448
+ function ensureSessionAlive(string $checkUrl, string $loginUrl, string $loginUser, string $loginPass, string $cookies): bool {
449
+ if (!$loginUrl || !$loginUser || !$loginPass) {
450
+ return false;
451
+ }
452
+
453
+ $probe = fetchPage($checkUrl, $cookies);
454
+ if ($probe['html'] && !isLoginPage($probe['html'])) {
455
+ return true;
456
+ }
457
+
458
+ return loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies);
459
+ }
460
+
274
461
  if ($loginUrl && $loginUser && $loginPass) {
275
462
  $loggedIn = loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies);
276
463
  if (!$loggedIn) {
@@ -289,6 +476,8 @@ $adjacency = [];
289
476
  $pageData = [];
290
477
 
291
478
  $totalPages = 0;
479
+ $keepAliveInterval = 120;
480
+ $lastKeepAliveAt = time();
292
481
 
293
482
  while (!empty($queue) && $totalPages < $maxPages) {
294
483
  [$url, $depth] = array_shift($queue);
@@ -302,33 +491,48 @@ while (!empty($queue) && $totalPages < $maxPages) {
302
491
  $totalPages++;
303
492
 
304
493
  echo "Crawling [$totalPages/$maxPages]: $url (depth: $depth)\n";
305
-
306
- $ch = curl_init();
307
- curl_setopt($ch, CURLOPT_URL, $url);
308
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
309
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
310
- curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
311
- curl_setopt($ch, CURLOPT_TIMEOUT, 30);
312
- curl_setopt($ch, CURLOPT_USERAGENT, 'Lwazi/1.0');
313
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
314
- curl_setopt($ch, CURLOPT_COOKIEFILE, $cookies);
315
-
316
- $html = curl_exec($ch);
317
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
318
- curl_close($ch);
494
+
495
+ if ($loginUrl && $loginUser && $loginPass) {
496
+ $now = time();
497
+ if (($now - $lastKeepAliveAt) >= $keepAliveInterval) {
498
+ ensureSessionAlive($rootUrl, $loginUrl, $loginUser, $loginPass, $cookies);
499
+ $lastKeepAliveAt = $now;
500
+ }
501
+ }
502
+
503
+ $response = fetchPage($url, $cookies);
504
+ $html = $response['html'];
505
+ $httpCode = $response['http_code'];
506
+ $finalUrl = $response['final_url'];
507
+ $headers = $response['headers'];
319
508
 
320
509
  if (!$html || $httpCode >= 400) {
321
510
  echo " -> Failed (HTTP $httpCode)\n";
322
511
  continue;
323
512
  }
324
513
 
325
- if (stripos($html, 'login') !== false && stripos($html, 'password') !== false) {
326
- echo " -> Login required, skipping\n";
327
- continue;
514
+ if ($html && isLoginPage($html)) {
515
+ if ($loginUrl && $loginUser && $loginPass) {
516
+ echo " -> Login required, re-authenticating and retrying\n";
517
+ $loggedIn = loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies);
518
+ if ($loggedIn) {
519
+ $response = fetchPage($url, $cookies);
520
+ $html = $response['html'];
521
+ $httpCode = $response['http_code'];
522
+ $finalUrl = $response['final_url'];
523
+ $headers = $response['headers'];
524
+ }
525
+ }
526
+
527
+ if (!$html || isLoginPage($html)) {
528
+ echo " -> Login required, skipping\n";
529
+ continue;
530
+ }
328
531
  }
329
532
 
330
533
  $title = extractTitle($html);
331
534
  $description = extractMeta($html, 'description');
535
+ $text = extractText($html);
332
536
  $links = extractLinks($html, $url);
333
537
 
334
538
  $nodes[$urlId] = [
@@ -342,6 +546,13 @@ while (!empty($queue) && $totalPages < $maxPages) {
342
546
  $pageData[$urlId] = [
343
547
  'html' => $html,
344
548
  'url' => $url,
549
+ 'final_url' => $finalUrl,
550
+ 'title' => $title,
551
+ 'description' => $description,
552
+ 'text' => $text,
553
+ 'headers' => $headers,
554
+ 'fetched_at' => date('c'),
555
+ 'http_code' => $httpCode,
345
556
  ];
346
557
 
347
558
  foreach ($links as $link) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lwazi",
3
- "version": "1.16.2",
3
+ "version": "1.16.4",
4
4
  "description": "Lwazi is an AI assistant for Laravel. Install with one command to add an AI assistant to your Laravel app.",
5
5
  "main": "bin/lwazi.js",
6
6
  "bin": {
package/uninstall CHANGED
@@ -62,11 +62,18 @@ if [ -f "$INDEX_FILE" ]; then
62
62
  php -r '
63
63
  $file = "public/index.php";
64
64
  $content = file_get_contents($file);
65
+ $hadNewline = str_ends_with($content, "\n");
65
66
  $content = preg_replace(
66
67
  "/\n*require __DIR__\.\"\/..\/lwazi\/auto_prepend\.php\";\n*/",
67
- "\n\n",
68
+ "\n",
68
69
  $content
69
70
  );
71
+ $content = preg_replace("/\n{3,}/", "\n\n", $content);
72
+ if ($hadNewline) {
73
+ $content = rtrim($content, "\n") . "\n";
74
+ } else {
75
+ $content = rtrim($content, "\n");
76
+ }
70
77
  file_put_contents($file, $content);
71
78
  '
72
79
  echo "Removed injection from public/index.php"
@@ -79,11 +86,18 @@ if [ -f "$PROJECT_DIR/index.php" ]; then
79
86
  php -r '
80
87
  $file = "index.php";
81
88
  $content = file_get_contents($file);
89
+ $hadNewline = str_ends_with($content, "\n");
82
90
  $content = preg_replace(
83
91
  "/\n*require __DIR__\.\"\/lwazi\/auto_prepend\.php\";\n*/",
84
- "\n\n",
92
+ "\n",
85
93
  $content
86
94
  );
95
+ $content = preg_replace("/\n{3,}/", "\n\n", $content);
96
+ if ($hadNewline) {
97
+ $content = rtrim($content, "\n") . "\n";
98
+ } else {
99
+ $content = rtrim($content, "\n");
100
+ }
87
101
  file_put_contents($file, $content);
88
102
  '
89
103
  echo "Removed injection from index.php"
@@ -104,6 +118,7 @@ php -r '
104
118
  $file = "composer.json";
105
119
  if (file_exists($file)) {
106
120
  $json = json_decode(file_get_contents($file), true);
121
+ $hadNewline = str_ends_with(file_get_contents($file), "\n");
107
122
  $changed = false;
108
123
 
109
124
  if (isset($json["require"]["lwazi/core"])) {
@@ -130,7 +145,11 @@ if (file_exists($file)) {
130
145
  }
131
146
 
132
147
  if ($changed) {
133
- file_put_contents($file, json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
148
+ $encoded = json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
149
+ if ($hadNewline) {
150
+ $encoded .= "\n";
151
+ }
152
+ file_put_contents($file, $encoded);
134
153
  echo "Removed lwazi from composer.json\n";
135
154
  }
136
155
  }
package/uninstall.bat CHANGED
@@ -22,6 +22,15 @@ echo.
22
22
  echo Uninstalling Lwazi...
23
23
  echo -------------------------------------------
24
24
 
25
+ REM Prefer PowerShell uninstaller if available for cleaner cleanup
26
+ where powershell >nul 2>&1
27
+ if %ERRORLEVEL%==0 (
28
+ if exist "%PROJECT_DIR%\uninstall.ps1" (
29
+ powershell -NoProfile -ExecutionPolicy Bypass -File "%PROJECT_DIR%\uninstall.ps1" -Force
30
+ exit /b %ERRORLEVEL%
31
+ )
32
+ )
33
+
25
34
  REM Remove lwazi folder
26
35
  if exist "%PROJECT_DIR%\lwazi" (
27
36
  rmdir /s /q "%PROJECT_DIR%\lwazi"
package/uninstall.ps1 CHANGED
@@ -7,6 +7,30 @@ param(
7
7
 
8
8
  $ErrorActionPreference = "Stop"
9
9
 
10
+ function Get-LineEnding {
11
+ param([string]$Content)
12
+ if ($Content -match "`r`n") { return "`r`n" }
13
+ return "`n"
14
+ }
15
+
16
+ function Write-FilePreserveNewline {
17
+ param(
18
+ [string]$Path,
19
+ [string]$Content,
20
+ [string]$OriginalContent
21
+ )
22
+ $lineEnding = Get-LineEnding -Content $OriginalContent
23
+ $hadNewline = $OriginalContent -match "(`r?`n)$"
24
+
25
+ if ($hadNewline) {
26
+ $Content = $Content.TrimEnd("`r", "`n") + $lineEnding
27
+ } else {
28
+ $Content = $Content.TrimEnd("`r", "`n")
29
+ }
30
+
31
+ [IO.File]::WriteAllText($Path, $Content)
32
+ }
33
+
10
34
  Write-Host "==========================================" -ForegroundColor Cyan
11
35
  Write-Host " Lwazi AI Assistant Uninstaller" -ForegroundColor Cyan
12
36
  Write-Host "==========================================" -ForegroundColor Cyan
@@ -28,16 +52,6 @@ Write-Host ""
28
52
  Write-Host "Uninstalling Lwazi..." -ForegroundColor Yellow
29
53
  Write-Host "-------------------------------------------"
30
54
 
31
- # Check if composer was used
32
- $composerUsed = $false
33
- $modeFile = "$ProjectDir\lwazi\.lwazi_mode"
34
- if (Test-Path $modeFile) {
35
- $modeContent = Get-Content $modeFile -Raw
36
- if ($modeContent -match "mode=true") {
37
- $composerUsed = $true
38
- }
39
- }
40
-
41
55
  # Remove lwazi folder
42
56
  if (Test-Path "$ProjectDir\lwazi") {
43
57
  Remove-Item -Recurse -Force "$ProjectDir\lwazi"
@@ -50,7 +64,8 @@ if (Test-Path "$ProjectDir\.env") {
50
64
  if ($envContent -match "LWAZI_") {
51
65
  $lines = Get-Content "$ProjectDir\.env"
52
66
  $newLines = $lines | Where-Object { $_ -notmatch "^LWAZI_" -and $_ -notmatch "^# Lwazi AI Configuration" }
53
- Set-Content "$ProjectDir\.env" -Value $newLines
67
+ $content = $newLines -join (Get-LineEnding -Content $envContent)
68
+ Write-FilePreserveNewline -Path "$ProjectDir\.env" -Content $content -OriginalContent $envContent
54
69
  Write-Host "Removed Lwazi config from .env" -ForegroundColor Green
55
70
  }
56
71
  }
@@ -62,7 +77,8 @@ if (Test-Path $userIni) {
62
77
  if ($userIniContent -match "auto_prepend_file.*lwazi") {
63
78
  $lines = Get-Content $userIni
64
79
  $newLines = $lines | Where-Object { $_ -notmatch "auto_prepend_file.*lwazi" }
65
- Set-Content $userIni -Value $newLines
80
+ $content = $newLines -join (Get-LineEnding -Content $userIniContent)
81
+ Write-FilePreserveNewline -Path $userIni -Content $content -OriginalContent $userIniContent
66
82
  Write-Host "Removed auto_prepend_file from .user.ini" -ForegroundColor Green
67
83
  }
68
84
  }
@@ -72,8 +88,9 @@ $indexFile = "$ProjectDir\public\index.php"
72
88
  if (Test-Path $indexFile) {
73
89
  $content = Get-Content $indexFile -Raw
74
90
  if ($content -match "lwazi/auto_prepend.php") {
91
+ $original = $content
75
92
  $content = $content -replace "`r?`n?require __DIR__`.\"/`../lwazi/auto_prepend.php`";`r?`n?", "`r`n"
76
- Set-Content -Path $indexFile -Value $content -NoNewline
93
+ Write-FilePreserveNewline -Path $indexFile -Content $content -OriginalContent $original
77
94
  Write-Host "Removed injection from public\index.php" -ForegroundColor Green
78
95
  }
79
96
  }
@@ -83,8 +100,9 @@ $rootIndexFile = "$ProjectDir\index.php"
83
100
  if (Test-Path $rootIndexFile) {
84
101
  $content = Get-Content $rootIndexFile -Raw
85
102
  if ($content -match "lwazi/auto_prepend.php") {
103
+ $original = $content
86
104
  $content = $content -replace "`r?`n?require __DIR__`.\"/lwazi/auto_prepend.php`";`r?`n?", "`r`n"
87
- Set-Content -Path $rootIndexFile -Value $content -NoNewline
105
+ Write-FilePreserveNewline -Path $rootIndexFile -Content $content -OriginalContent $original
88
106
  Write-Host "Removed injection from index.php" -ForegroundColor Green
89
107
  }
90
108
  }
@@ -96,46 +114,46 @@ if (Test-Path "$ProjectDir\artisan") {
96
114
  Write-Host "Cleared caches" -ForegroundColor Green
97
115
  }
98
116
 
99
- # Remove composer integration if it was used
100
- if ($composerUsed) {
101
- Write-Host "Cleaning up composer integration..." -ForegroundColor Yellow
102
-
103
- $composerJsonFile = "$ProjectDir\composer.json"
104
- if (Test-Path $composerJsonFile) {
105
- $composerJson = Get-Content $composerJsonFile | ConvertFrom-Json
106
- $changed = $false
107
-
108
- if ($composerJson.PSObject.Properties.Name -contains "require") {
109
- if ($composerJson.require.PSObject.Properties.Name -contains "lwazi/core") {
110
- $composerJson.require.PSObject.Properties.Remove("lwazi/core")
111
- $changed = $true
112
- Write-Host "Removed lwazi/core from composer.json" -ForegroundColor Green
113
- }
117
+ # Always clean up composer.json (lwazi might have added it even if mode wasn't saved)
118
+ Write-Host "Cleaning up composer.json..." -ForegroundColor Yellow
119
+
120
+ $composerJsonFile = "$ProjectDir\composer.json"
121
+ if (Test-Path $composerJsonFile) {
122
+ $composerJson = Get-Content $composerJsonFile | ConvertFrom-Json
123
+ $changed = $false
124
+
125
+ if ($composerJson.PSObject.Properties.Name -contains "require") {
126
+ if ($composerJson.require.PSObject.Properties.Name -contains "lwazi/core") {
127
+ $composerJson.require.PSObject.Properties.Remove("lwazi/core")
128
+ $changed = $true
129
+ Write-Host "Removed lwazi/core from composer.json" -ForegroundColor Green
114
130
  }
115
-
116
- if ($composerJson.PSObject.Properties.Name -contains "repositories" -and $composerJson.repositories) {
117
- $originalCount = $composerJson.repositories.Count
118
- $composerJson.repositories = $composerJson.repositories | Where-Object { $_.url -ne "lwazi" }
119
- if ($composerJson.repositories.Count -ne $originalCount) {
120
- $changed = $true
121
- }
122
- if ($composerJson.repositories.Count -eq 0) {
123
- $composerJson.PSObject.Properties.Remove("repositories")
124
- }
131
+ }
132
+
133
+ if ($composerJson.PSObject.Properties.Name -contains "repositories" -and $composerJson.repositories) {
134
+ $originalCount = $composerJson.repositories.Count
135
+ $composerJson.repositories = $composerJson.repositories | Where-Object { $_.url -ne "lwazi" }
136
+ if ($composerJson.repositories.Count -ne $originalCount) {
137
+ $changed = $true
125
138
  }
126
-
127
- if ($changed) {
128
- $composerJson | ConvertTo-Json -Depth 10 | Set-Content $composerJsonFile -Encoding UTF8
139
+ if ($composerJson.repositories.Count -eq 0) {
140
+ $composerJson.PSObject.Properties.Remove("repositories")
129
141
  }
130
142
  }
131
-
132
- # Run composer dump-autoload
133
- $composerCmd = Get-Command composer -ErrorAction SilentlyContinue
134
- if ($composerCmd) {
135
- composer dump-autoload 2>&1 | Out-Null
143
+
144
+ if ($changed) {
145
+ $original = Get-Content $composerJsonFile -Raw
146
+ $jsonText = $composerJson | ConvertTo-Json -Depth 10
147
+ Write-FilePreserveNewline -Path $composerJsonFile -Content $jsonText -OriginalContent $original
136
148
  }
137
149
  }
138
150
 
151
+ # Run composer dump-autoload
152
+ $composerCmd = Get-Command composer -ErrorAction SilentlyContinue
153
+ if ($composerCmd) {
154
+ composer dump-autoload 2>&1 | Out-Null
155
+ }
156
+
139
157
  Write-Host ""
140
158
  Write-Host "==========================================" -ForegroundColor Cyan
141
159
  Write-Host " Uninstallation Complete!" -ForegroundColor Cyan