lwazi 1.16.2 → 1.16.5

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,7 +225,176 @@ function extractCsrfToken(string $html): ?string {
204
225
  return null;
205
226
  }
206
227
 
207
- function loginAndGetCookies(string $loginUrl, string $username, string $password, string $cookies): bool {
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
+ $firstTextField = null;
302
+
303
+ if ($targetForm) {
304
+ $actionAttr = trim($targetForm->getAttribute('action'));
305
+ if ($actionAttr !== '') {
306
+ $action = normalizeUrl($actionAttr, $loginUrl);
307
+ }
308
+ $methodAttr = strtolower(trim($targetForm->getAttribute('method')));
309
+ if ($methodAttr !== '') {
310
+ $method = $methodAttr;
311
+ }
312
+
313
+ $inputs = $targetForm->getElementsByTagName('input');
314
+ foreach ($inputs as $input) {
315
+ $name = $input->getAttribute('name');
316
+ if ($name === '') {
317
+ continue;
318
+ }
319
+ $type = strtolower($input->getAttribute('type'));
320
+ $value = $input->getAttribute('value');
321
+ if ($type === '' || $type === 'text' || $type === 'email' || $type === 'password' || $type === 'hidden' || $type === 'checkbox' || $type === 'submit') {
322
+ if (!isset($fields[$name]) && $value !== '') {
323
+ $fields[$name] = $value;
324
+ } elseif ($type === 'hidden') {
325
+ $fields[$name] = $value;
326
+ }
327
+ }
328
+ if (in_array($type, ['text', 'email'], true)) {
329
+ if ($firstTextField === null) {
330
+ $firstTextField = $name;
331
+ }
332
+ if (preg_match('/email|user|username|login/i', $name)) {
333
+ $userField = $name;
334
+ }
335
+ }
336
+ if ($type === 'password' && $passField === null) {
337
+ $passField = $name;
338
+ }
339
+ }
340
+ }
341
+
342
+ if ($userField === null && $firstTextField !== null) {
343
+ $userField = $firstTextField;
344
+ }
345
+
346
+ return [
347
+ 'action' => $action,
348
+ 'method' => $method,
349
+ 'fields' => $fields,
350
+ 'user_field' => $userField,
351
+ 'pass_field' => $passField,
352
+ ];
353
+ }
354
+
355
+ function readPasswordMasked(string $prompt): string {
356
+ // Masked password input for POSIX terminals, with a safe fallback.
357
+ $sttyMode = null;
358
+ if (function_exists('shell_exec')) {
359
+ $sttyMode = shell_exec('stty -g 2>/dev/null');
360
+ if ($sttyMode) {
361
+ shell_exec('stty -echo -icanon min 1 time 0 2>/dev/null');
362
+ }
363
+ }
364
+
365
+ echo $prompt;
366
+ $password = '';
367
+
368
+ if ($sttyMode) {
369
+ while (true) {
370
+ $char = fread(STDIN, 1);
371
+ if ($char === false || $char === '') {
372
+ break;
373
+ }
374
+ if ($char === "\n" || $char === "\r") {
375
+ echo "\n";
376
+ break;
377
+ }
378
+ if ($char === "\x7f" || $char === "\x08") { // backspace/delete
379
+ if (strlen($password) > 0) {
380
+ $password = substr($password, 0, -1);
381
+ echo "\x08 \x08";
382
+ }
383
+ continue;
384
+ }
385
+ $password .= $char;
386
+ echo "*";
387
+ }
388
+ shell_exec('stty ' . $sttyMode . ' 2>/dev/null');
389
+ return $password;
390
+ }
391
+
392
+ // Fallback (no masking) if stty isn't available.
393
+ $line = fgets(STDIN);
394
+ return $line === false ? '' : rtrim($line, "\r\n");
395
+ }
396
+
397
+ function loginAndGetCookies(string $loginUrl, string $username, string $password, string $cookies, ?string $checkUrl = null): bool {
208
398
  echo "Attempting to login to $loginUrl...\n";
209
399
 
210
400
  $ch = curl_init();
@@ -224,32 +414,28 @@ function loginAndGetCookies(string $loginUrl, string $username, string $password
224
414
  echo "Failed to fetch login page\n";
225
415
  return false;
226
416
  }
227
-
417
+
418
+ $form = parseLoginForm($loginPage, $loginUrl);
228
419
  $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) {
420
+ $loginData = $form['fields'];
421
+
422
+ $userField = $form['user_field'] ?: 'email';
423
+ $passField = $form['pass_field'] ?: 'password';
424
+ $loginData[$userField] = $username;
425
+ $loginData[$passField] = $password;
426
+
427
+ if ($csrfToken && !isset($loginData['_token'])) {
246
428
  $loginData['_token'] = $csrfToken;
247
429
  }
248
430
 
249
431
  $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));
432
+ curl_setopt($ch, CURLOPT_URL, $form['action']);
433
+ curl_setopt($ch, CURLOPT_POST, ($form['method'] !== 'get'));
434
+ if ($form['method'] === 'get') {
435
+ curl_setopt($ch, CURLOPT_URL, $form['action'] . '?' . http_build_query($loginData));
436
+ } else {
437
+ curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($loginData));
438
+ }
253
439
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
254
440
  curl_setopt($ch, CURLOPT_COOKIEJAR, $cookies);
255
441
  curl_setopt($ch, CURLOPT_COOKIEFILE, $cookies);
@@ -257,22 +443,47 @@ function loginAndGetCookies(string $loginUrl, string $username, string $password
257
443
  curl_setopt($ch, CURLOPT_TIMEOUT, 30);
258
444
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
259
445
  curl_setopt($ch, CURLOPT_USERAGENT, 'Lwazi/1.0');
446
+ curl_setopt($ch, CURLOPT_REFERER, $loginUrl);
447
+ if ($csrfToken) {
448
+ curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-CSRF-TOKEN: ' . $csrfToken]);
449
+ }
260
450
 
261
451
  $result = curl_exec($ch);
262
452
  $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
263
453
  curl_close($ch);
264
454
 
265
- if ($httpCode >= 200 && $httpCode < 400) {
455
+ if ($httpCode >= 200 && $httpCode < 400 && $result && !isLoginPage($result)) {
266
456
  echo "Login successful!\n";
267
457
  return true;
268
458
  }
269
-
459
+
460
+ if ($checkUrl) {
461
+ $probe = fetchPage($checkUrl, $cookies);
462
+ if ($probe['html'] && !isLoginPage($probe['html'])) {
463
+ echo "Login successful!\n";
464
+ return true;
465
+ }
466
+ }
467
+
270
468
  echo "Login may have failed (HTTP $httpCode)\n";
271
469
  return false;
272
470
  }
273
471
 
472
+ function ensureSessionAlive(string $checkUrl, string $loginUrl, string $loginUser, string $loginPass, string $cookies): bool {
473
+ if (!$loginUrl || !$loginUser || !$loginPass) {
474
+ return false;
475
+ }
476
+
477
+ $probe = fetchPage($checkUrl, $cookies);
478
+ if ($probe['html'] && !isLoginPage($probe['html'])) {
479
+ return true;
480
+ }
481
+
482
+ return loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies);
483
+ }
484
+
274
485
  if ($loginUrl && $loginUser && $loginPass) {
275
- $loggedIn = loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies);
486
+ $loggedIn = loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies, $rootUrl);
276
487
  if (!$loggedIn) {
277
488
  echo "Warning: Login may have failed. Continuing anyway...\n";
278
489
  }
@@ -289,6 +500,8 @@ $adjacency = [];
289
500
  $pageData = [];
290
501
 
291
502
  $totalPages = 0;
503
+ $keepAliveInterval = 120;
504
+ $lastKeepAliveAt = time();
292
505
 
293
506
  while (!empty($queue) && $totalPages < $maxPages) {
294
507
  [$url, $depth] = array_shift($queue);
@@ -302,33 +515,48 @@ while (!empty($queue) && $totalPages < $maxPages) {
302
515
  $totalPages++;
303
516
 
304
517
  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);
518
+
519
+ if ($loginUrl && $loginUser && $loginPass) {
520
+ $now = time();
521
+ if (($now - $lastKeepAliveAt) >= $keepAliveInterval) {
522
+ ensureSessionAlive($rootUrl, $loginUrl, $loginUser, $loginPass, $cookies);
523
+ $lastKeepAliveAt = $now;
524
+ }
525
+ }
526
+
527
+ $response = fetchPage($url, $cookies);
528
+ $html = $response['html'];
529
+ $httpCode = $response['http_code'];
530
+ $finalUrl = $response['final_url'];
531
+ $headers = $response['headers'];
319
532
 
320
533
  if (!$html || $httpCode >= 400) {
321
534
  echo " -> Failed (HTTP $httpCode)\n";
322
535
  continue;
323
536
  }
324
537
 
325
- if (stripos($html, 'login') !== false && stripos($html, 'password') !== false) {
326
- echo " -> Login required, skipping\n";
327
- continue;
538
+ if ($html && isLoginPage($html)) {
539
+ if ($loginUrl && $loginUser && $loginPass) {
540
+ echo " -> Login required, re-authenticating and retrying\n";
541
+ $loggedIn = loginAndGetCookies($loginUrl, $loginUser, $loginPass, $cookies, $rootUrl);
542
+ if ($loggedIn) {
543
+ $response = fetchPage($url, $cookies);
544
+ $html = $response['html'];
545
+ $httpCode = $response['http_code'];
546
+ $finalUrl = $response['final_url'];
547
+ $headers = $response['headers'];
548
+ }
549
+ }
550
+
551
+ if (!$html || isLoginPage($html)) {
552
+ echo " -> Login required, skipping\n";
553
+ continue;
554
+ }
328
555
  }
329
556
 
330
557
  $title = extractTitle($html);
331
558
  $description = extractMeta($html, 'description');
559
+ $text = extractText($html);
332
560
  $links = extractLinks($html, $url);
333
561
 
334
562
  $nodes[$urlId] = [
@@ -342,6 +570,13 @@ while (!empty($queue) && $totalPages < $maxPages) {
342
570
  $pageData[$urlId] = [
343
571
  'html' => $html,
344
572
  'url' => $url,
573
+ 'final_url' => $finalUrl,
574
+ 'title' => $title,
575
+ 'description' => $description,
576
+ 'text' => $text,
577
+ 'headers' => $headers,
578
+ 'fetched_at' => date('c'),
579
+ 'http_code' => $httpCode,
345
580
  ];
346
581
 
347
582
  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.5",
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