fss-link 1.5.7 → 1.6.0

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 (40) hide show
  1. package/README.md +1 -1
  2. package/bundle/fss-link.js +4785 -3183
  3. package/package.json +4 -1
  4. package/scripts/analyze-session-logs.sh +279 -0
  5. package/scripts/build.js +55 -0
  6. package/scripts/build_package.js +37 -0
  7. package/scripts/build_sandbox.js +195 -0
  8. package/scripts/build_vscode_companion.js +30 -0
  9. package/scripts/check-build-status.js +148 -0
  10. package/scripts/check-publish.js +101 -0
  11. package/scripts/clean.js +55 -0
  12. package/scripts/copy_bundle_assets.js +40 -0
  13. package/scripts/copy_files.js +56 -0
  14. package/scripts/create_alias.sh +39 -0
  15. package/scripts/emergency-kill-all-tests.sh +95 -0
  16. package/scripts/emergency-kill-vitest.sh +95 -0
  17. package/scripts/extract-session-logs.sh +202 -0
  18. package/scripts/generate-git-commit-info.js +71 -0
  19. package/scripts/get-previous-tag.js +213 -0
  20. package/scripts/get-release-version.js +119 -0
  21. package/scripts/index-session-logs.sh +173 -0
  22. package/scripts/install-linux.sh +294 -0
  23. package/scripts/install-macos.sh +343 -0
  24. package/scripts/install-windows.ps1 +427 -0
  25. package/scripts/local_telemetry.js +219 -0
  26. package/scripts/memory-monitor.sh +165 -0
  27. package/scripts/postinstall-message.js +31 -0
  28. package/scripts/prepare-package.js +51 -0
  29. package/scripts/process-session-log.py +302 -0
  30. package/scripts/quick-install.sh +195 -0
  31. package/scripts/sandbox_command.js +126 -0
  32. package/scripts/start.js +76 -0
  33. package/scripts/telemetry.js +85 -0
  34. package/scripts/telemetry_gcp.js +188 -0
  35. package/scripts/telemetry_utils.js +421 -0
  36. package/scripts/test-windows-paths.js +51 -0
  37. package/scripts/tests/get-release-version.test.js +110 -0
  38. package/scripts/tests/test-setup.ts +12 -0
  39. package/scripts/tests/vitest.config.ts +20 -0
  40. package/scripts/version.js +83 -0
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import os from 'os'; // Import os module
10
+
11
+ // --- Configuration ---
12
+ const cliPackageDir = path.resolve('packages', 'cli'); // Base directory for the CLI package
13
+ const buildTimestampPath = path.join(cliPackageDir, 'dist', '.last_build'); // Path to the timestamp file within the CLI package
14
+ const sourceDirs = [path.join(cliPackageDir, 'src')]; // Source directory within the CLI package
15
+ const filesToWatch = [
16
+ path.join(cliPackageDir, 'package.json'),
17
+ path.join(cliPackageDir, 'tsconfig.json'),
18
+ ]; // Specific files within the CLI package
19
+ const buildDir = path.join(cliPackageDir, 'dist'); // Build output directory within the CLI package
20
+ const warningsFilePath = path.join(os.tmpdir(), 'qwen-code-warnings.txt'); // Temp file for warnings
21
+ // ---------------------
22
+
23
+ function getMtime(filePath) {
24
+ try {
25
+ return fs.statSync(filePath).mtimeMs; // Use mtimeMs for higher precision
26
+ } catch (err) {
27
+ if (err.code === 'ENOENT') {
28
+ return null; // File doesn't exist
29
+ }
30
+ console.error(`Error getting stats for ${filePath}:`, err);
31
+ process.exit(1); // Exit on unexpected errors getting stats
32
+ }
33
+ }
34
+
35
+ function findSourceFiles(dir, allFiles = []) {
36
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
37
+ for (const entry of entries) {
38
+ const fullPath = path.join(dir, entry.name);
39
+ // Simple check to avoid recursing into node_modules or build dir itself
40
+ if (
41
+ entry.isDirectory() &&
42
+ entry.name !== 'node_modules' &&
43
+ fullPath !== buildDir
44
+ ) {
45
+ findSourceFiles(fullPath, allFiles);
46
+ } else if (entry.isFile()) {
47
+ allFiles.push(fullPath);
48
+ }
49
+ }
50
+ return allFiles;
51
+ }
52
+
53
+ console.log('Checking build status...');
54
+
55
+ // Clean up old warnings file before check
56
+ try {
57
+ if (fs.existsSync(warningsFilePath)) {
58
+ fs.unlinkSync(warningsFilePath);
59
+ }
60
+ } catch (err) {
61
+ console.warn(
62
+ `[Check Script] Warning: Could not delete previous warnings file: ${err.message}`,
63
+ );
64
+ }
65
+
66
+ const buildMtime = getMtime(buildTimestampPath);
67
+ if (!buildMtime) {
68
+ // If build is missing, write that as a warning and exit(0) so app can display it
69
+ const errorMessage = `ERROR: Build timestamp file (${path.relative(process.cwd(), buildTimestampPath)}) not found. Run \`npm run build\` first.`;
70
+ console.error(errorMessage); // Still log error here
71
+ try {
72
+ fs.writeFileSync(warningsFilePath, errorMessage);
73
+ } catch (writeErr) {
74
+ console.error(
75
+ `[Check Script] Error writing missing build warning file: ${writeErr.message}`,
76
+ );
77
+ }
78
+ process.exit(0); // Allow app to start and show the error
79
+ }
80
+
81
+ let newerSourceFileFound = false;
82
+ const warningMessages = []; // Collect warnings here
83
+ const allSourceFiles = [];
84
+
85
+ // Collect files from specified directories
86
+ sourceDirs.forEach((dir) => {
87
+ const dirPath = path.resolve(dir);
88
+ if (fs.existsSync(dirPath)) {
89
+ findSourceFiles(dirPath, allSourceFiles);
90
+ } else {
91
+ console.warn(`Warning: Source directory "${dir}" not found.`);
92
+ }
93
+ });
94
+
95
+ // Add specific files
96
+ filesToWatch.forEach((file) => {
97
+ const filePath = path.resolve(file);
98
+ if (fs.existsSync(filePath)) {
99
+ allSourceFiles.push(filePath);
100
+ } else {
101
+ console.warn(`Warning: Watched file "${file}" not found.`);
102
+ }
103
+ });
104
+
105
+ // Check modification times
106
+ for (const file of allSourceFiles) {
107
+ const sourceMtime = getMtime(file);
108
+ const relativePath = path.relative(process.cwd(), file);
109
+ const isNewer = sourceMtime && sourceMtime > buildMtime;
110
+
111
+ if (isNewer) {
112
+ const warning = `Warning: Source file "${relativePath}" has been modified since the last build.`;
113
+ console.warn(warning); // Keep console warning for script debugging
114
+ warningMessages.push(warning);
115
+ newerSourceFileFound = true;
116
+ // break; // Uncomment to stop checking after the first newer file
117
+ }
118
+ }
119
+
120
+ if (newerSourceFileFound) {
121
+ const finalWarning =
122
+ '\nRun "npm run build" to incorporate changes before starting.';
123
+ warningMessages.push(finalWarning);
124
+ console.warn(finalWarning);
125
+
126
+ // Write warnings to the temp file
127
+ try {
128
+ fs.writeFileSync(warningsFilePath, warningMessages.join('\n'));
129
+ // Removed debug log
130
+ } catch (err) {
131
+ console.error(`[Check Script] Error writing warnings file: ${err.message}`);
132
+ // Proceed without writing, app won't show warnings
133
+ }
134
+ } else {
135
+ console.log('Build is up-to-date.');
136
+ // Ensure no stale warning file exists if build is ok
137
+ try {
138
+ if (fs.existsSync(warningsFilePath)) {
139
+ fs.unlinkSync(warningsFilePath);
140
+ }
141
+ } catch (err) {
142
+ console.warn(
143
+ `[Check Script] Warning: Could not delete previous warnings file: ${err.message}`,
144
+ );
145
+ }
146
+ }
147
+
148
+ process.exit(0); // Always exit successfully so the app starts
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Pre-publish safety check for FSS Link
4
+ * Ensures we're publishing from the correct location with a valid bundle
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const projectRoot = path.resolve(__dirname, '..');
13
+
14
+ console.log('🔍 FSS Link Pre-Publish Safety Check');
15
+ console.log('=====================================\n');
16
+
17
+ let errors = 0;
18
+
19
+ // Check 1: Publishing from project root, not packages/
20
+ if (process.cwd().includes('packages')) {
21
+ console.error('❌ ERROR: Must publish from project root, not packages/!');
22
+ console.error(' Current directory: ' + process.cwd());
23
+ console.error(' Run: cd ' + projectRoot + ' && npm publish\n');
24
+ errors++;
25
+ } else {
26
+ console.log('✅ Publishing from project root');
27
+ }
28
+
29
+ // Check 2: Bundle exists
30
+ const bundlePath = path.join(projectRoot, 'bundle/fss-link.js');
31
+ if (!fs.existsSync(bundlePath)) {
32
+ console.error('❌ ERROR: bundle/fss-link.js not found');
33
+ console.error(' Run: npm run bundle\n');
34
+ errors++;
35
+ } else {
36
+ console.log('✅ Bundle exists');
37
+
38
+ // Check 3: Bundle is executable
39
+ const stats = fs.statSync(bundlePath);
40
+ if (!(stats.mode & 0o111)) {
41
+ console.error('❌ ERROR: Bundle is not executable');
42
+ console.error(' Run: chmod +x bundle/fss-link.js\n');
43
+ errors++;
44
+ } else {
45
+ console.log('✅ Bundle is executable');
46
+ }
47
+
48
+ // Check 4: Bundle has shebang
49
+ const firstLine = fs.readFileSync(bundlePath, 'utf8').split('\n')[0];
50
+ if (firstLine !== '#!/usr/bin/env node') {
51
+ console.error('❌ ERROR: Bundle missing shebang line');
52
+ console.error(' Expected: #!/usr/bin/env node');
53
+ console.error(' Got: ' + firstLine + '\n');
54
+ errors++;
55
+ } else {
56
+ console.log('✅ Bundle has correct shebang');
57
+ }
58
+
59
+ // Check 5: Bundle size is reasonable (> 1MB)
60
+ const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
61
+ if (stats.size < 1024 * 1024) {
62
+ console.error(`❌ ERROR: Bundle suspiciously small (${sizeMB} MB)`);
63
+ console.error(' Expected: > 1 MB');
64
+ console.error(' This may indicate an incomplete build\n');
65
+ errors++;
66
+ } else {
67
+ console.log(`✅ Bundle size reasonable (${sizeMB} MB)`);
68
+ }
69
+ }
70
+
71
+ // Check 6: Version in bundle matches package.json
72
+ const pkgPath = path.join(projectRoot, 'package.json');
73
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
74
+ if (fs.existsSync(bundlePath)) {
75
+ const bundleContent = fs.readFileSync(bundlePath, 'utf8');
76
+ // Check for version in various formats: "version":"1.2.8", version = "1.2.8", etc.
77
+ const versionPatterns = [
78
+ `"version":"${pkg.version}"`,
79
+ `version = "${pkg.version}"`,
80
+ `'version':'${pkg.version}'`,
81
+ ];
82
+ const hasVersion = versionPatterns.some(pattern => bundleContent.includes(pattern));
83
+
84
+ if (!hasVersion) {
85
+ console.error(`❌ ERROR: Bundle version mismatch`);
86
+ console.error(` package.json: ${pkg.version}`);
87
+ console.error(` Bundle may have different version - rebuild with: npm run bundle\n`);
88
+ errors++;
89
+ } else {
90
+ console.log(`✅ Bundle version matches package.json (${pkg.version})`);
91
+ }
92
+ }
93
+
94
+ console.log('\n=====================================');
95
+ if (errors > 0) {
96
+ console.error(`❌ ${errors} error(s) found - publish blocked\n`);
97
+ process.exit(1);
98
+ } else {
99
+ console.log('✅ All checks passed - safe to publish\n');
100
+ process.exit(0);
101
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ //
8
+ // Licensed under the Apache License, Version 2.0 (the "License");
9
+ // you may not use this file except in compliance with the License.
10
+ // You may obtain a copy of the License at
11
+ //
12
+ // http://www.apache.org/licenses/LICENSE-2.0
13
+ //
14
+ // Unless required by applicable law or agreed to in writing, software
15
+ // distributed under the License is distributed on an "AS IS" BASIS,
16
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ // See the License for the specific language governing permissions and
18
+ // limitations under the License.
19
+
20
+ import { rmSync, readFileSync } from 'fs';
21
+ import { dirname, join } from 'path';
22
+ import { fileURLToPath } from 'url';
23
+ import { globSync } from 'glob';
24
+
25
+ const __dirname = dirname(fileURLToPath(import.meta.url));
26
+ const root = join(__dirname, '..');
27
+
28
+ // remove npm install/build artifacts
29
+ rmSync(join(root, 'node_modules'), { recursive: true, force: true });
30
+ rmSync(join(root, 'bundle'), { recursive: true, force: true });
31
+ rmSync(join(root, 'packages/cli/src/generated/'), {
32
+ recursive: true,
33
+ force: true,
34
+ });
35
+ const RMRF_OPTIONS = { recursive: true, force: true };
36
+ rmSync(join(root, 'bundle'), RMRF_OPTIONS);
37
+ // Dynamically clean dist directories in all workspaces
38
+ const rootPackageJson = JSON.parse(
39
+ readFileSync(join(root, 'package.json'), 'utf-8'),
40
+ );
41
+ for (const workspace of rootPackageJson.workspaces) {
42
+ const packages = globSync(join(workspace, 'package.json'), { cwd: root });
43
+ for (const pkgPath of packages) {
44
+ const pkgDir = dirname(join(root, pkgPath));
45
+ rmSync(join(pkgDir, 'dist'), RMRF_OPTIONS);
46
+ }
47
+ }
48
+
49
+ // Clean up vsix files in vscode-ide-companion
50
+ const vsixFiles = globSync('packages/vscode-ide-companion/*.vsix', {
51
+ cwd: root,
52
+ });
53
+ for (const vsixFile of vsixFiles) {
54
+ rmSync(join(root, vsixFile), RMRF_OPTIONS);
55
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ //
8
+ // Licensed under the Apache License, Version 2.0 (the "License");
9
+ // you may not use this file except in compliance with the License.
10
+ // You may obtain a copy of the License at
11
+ //
12
+ // http://www.apache.org/licenses/LICENSE-2.0
13
+ //
14
+ // Unless required by applicable law or agreed to in writing, software
15
+ // distributed under the License is distributed on an "AS IS" BASIS,
16
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ // See the License for the specific language governing permissions and
18
+ // limitations under the License.
19
+
20
+ import { copyFileSync, existsSync, mkdirSync } from 'fs';
21
+ import { dirname, join, basename } from 'path';
22
+ import { fileURLToPath } from 'url';
23
+ import { glob } from 'glob';
24
+
25
+ const __dirname = dirname(fileURLToPath(import.meta.url));
26
+ const root = join(__dirname, '..');
27
+ const bundleDir = join(root, 'bundle');
28
+
29
+ // Create the bundle directory if it doesn't exist
30
+ if (!existsSync(bundleDir)) {
31
+ mkdirSync(bundleDir);
32
+ }
33
+
34
+ // Find and copy all .sb files from packages to the root of the bundle directory
35
+ const sbFiles = glob.sync('packages/**/*.sb', { cwd: root });
36
+ for (const file of sbFiles) {
37
+ copyFileSync(join(root, file), join(bundleDir, basename(file)));
38
+ }
39
+
40
+ console.log('Assets copied to bundle/');
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @license
5
+ * Copyright 2025 Google LLC
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+
9
+ // Copyright 2025 Google LLC
10
+ //
11
+ // Licensed under the Apache License, Version 2.0 (the "License");
12
+ // you may not use this file except in compliance with the License.
13
+ // You may obtain a copy of the License at
14
+ //
15
+ // http://www.apache.org/licenses/LICENSE-2.0
16
+ //
17
+ // Unless required by applicable law or agreed to in writing, software
18
+ // distributed under the License is distributed on an "AS IS" BASIS,
19
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ // See the License for the specific language governing permissions and
21
+ // limitations under the License.
22
+
23
+ import fs from 'fs';
24
+ import path from 'path';
25
+
26
+ const sourceDir = path.join('src');
27
+ const targetDir = path.join('dist', 'src');
28
+
29
+ const extensionsToCopy = ['.md', '.json', '.sb'];
30
+
31
+ function copyFilesRecursive(source, target) {
32
+ if (!fs.existsSync(target)) {
33
+ fs.mkdirSync(target, { recursive: true });
34
+ }
35
+
36
+ const items = fs.readdirSync(source, { withFileTypes: true });
37
+
38
+ for (const item of items) {
39
+ const sourcePath = path.join(source, item.name);
40
+ const targetPath = path.join(target, item.name);
41
+
42
+ if (item.isDirectory()) {
43
+ copyFilesRecursive(sourcePath, targetPath);
44
+ } else if (extensionsToCopy.includes(path.extname(item.name))) {
45
+ fs.copyFileSync(sourcePath, targetPath);
46
+ }
47
+ }
48
+ }
49
+
50
+ if (!fs.existsSync(sourceDir)) {
51
+ console.error(`Source directory ${sourceDir} not found.`);
52
+ process.exit(1);
53
+ }
54
+
55
+ copyFilesRecursive(sourceDir, targetDir);
56
+ console.log('Successfully copied files.');
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # This script creates an alias for the Gemini CLI
5
+
6
+ # Determine the project directory
7
+ PROJECT_DIR=$(cd "$(dirname "$0")/.." && pwd)
8
+ ALIAS_COMMAND="alias qwen='node "${PROJECT_DIR}/scripts/start.js"'"
9
+
10
+ # Detect shell and set config file path
11
+ if [[ "${SHELL}" == *"/bash" ]]; then
12
+ CONFIG_FILE="${HOME}/.bashrc"
13
+ elif [[ "${SHELL}" == *"/zsh" ]]; then
14
+ CONFIG_FILE="${HOME}/.zshrc"
15
+ else
16
+ echo "Unsupported shell. Only bash and zsh are supported."
17
+ exit 1
18
+ fi
19
+
20
+ echo "This script will add the following alias to your shell configuration file (${CONFIG_FILE}):"
21
+ echo " ${ALIAS_COMMAND}"
22
+ echo ""
23
+
24
+ # Check if the alias already exists
25
+ if grep -q "alias qwen=" "${CONFIG_FILE}"; then
26
+ echo "A 'qwen' alias already exists in ${CONFIG_FILE}. No changes were made."
27
+ exit 0
28
+ fi
29
+
30
+ read -p "Do you want to proceed? (y/n) " -n 1 -r
31
+ echo ""
32
+ if [[ "${REPLY}" =~ ^[Yy]$ ]]; then
33
+ echo "${ALIAS_COMMAND}" >> "${CONFIG_FILE}"
34
+ echo ""
35
+ echo "Alias added to ${CONFIG_FILE}."
36
+ echo "Please run 'source ${CONFIG_FILE}' or open a new terminal to use the 'qwen' command."
37
+ else
38
+ echo "Aborted. No changes were made."
39
+ fi
@@ -0,0 +1,95 @@
1
+ #!/bin/bash
2
+
3
+ # 🚨 NUCLEAR OPTION: Kill ALL test-related processes
4
+ # Purpose: Emergency system recovery when tests cripple the PC
5
+ # Usage: ./scripts/emergency-kill-all-tests.sh
6
+
7
+ set -e
8
+
9
+ RED='\033[0;31m'
10
+ YELLOW='\033[1;33m'
11
+ GREEN='\033[0;32m'
12
+ NC='\033[0m'
13
+
14
+ echo -e "${RED}💣 NUCLEAR TEST KILLER - EMERGENCY SYSTEM RECOVERY${NC}"
15
+ echo -e "${YELLOW}⚠️ This will kill ALL Node.js test processes immediately${NC}"
16
+ echo ""
17
+
18
+ # Find all test-related processes
19
+ echo "Scanning for dangerous processes..."
20
+
21
+ # More comprehensive process detection
22
+ VITEST_PIDS=$(pgrep -f "vitest" 2>/dev/null || true)
23
+ NODE_TEST_PIDS=$(pgrep -f "node.*test" 2>/dev/null || true)
24
+ JEST_PIDS=$(pgrep -f "jest" 2>/dev/null || true)
25
+ MOCHA_PIDS=$(pgrep -f "mocha" 2>/dev/null || true)
26
+ NODE_FSS_PIDS=$(pgrep -f "node.*fss-link" 2>/dev/null || true)
27
+
28
+ ALL_PIDS="${VITEST_PIDS} ${NODE_TEST_PIDS} ${JEST_PIDS} ${MOCHA_PIDS} ${NODE_FSS_PIDS}"
29
+
30
+ if [ -z "$(echo $ALL_PIDS | tr -d ' ')" ]; then
31
+ echo -e "${GREEN}✅ No test processes found${NC}"
32
+ exit 0
33
+ fi
34
+
35
+ echo -e "${RED}🎯 TARGETS IDENTIFIED:${NC}"
36
+ echo "$ALL_PIDS" | tr ' ' '\n' | while read -r pid; do
37
+ if [ -n "$pid" ] && [ "$pid" != " " ]; then
38
+ ps -p "$pid" -o pid,%cpu,%mem,cmd 2>/dev/null || true
39
+ fi
40
+ done
41
+
42
+ echo ""
43
+ echo -e "${YELLOW}⚡ IMMEDIATE FORCE KILL - NO MERCY${NC}"
44
+ echo "Killing all processes with SIGKILL..."
45
+
46
+ # Nuclear option - kill everything immediately
47
+ for pid in $ALL_PIDS; do
48
+ if [ -n "$pid" ] && [ "$pid" != " " ]; then
49
+ kill -KILL "$pid" 2>/dev/null && echo "💀 KILLED PID $pid" || true
50
+ fi
51
+ done
52
+
53
+ # Also kill any node processes using excessive memory (>1GB)
54
+ echo ""
55
+ echo -e "${YELLOW}🔍 Checking for memory hogs (>1GB)...${NC}"
56
+ ps aux --no-headers | awk '$6 > 1000000 && /node/ {print $2, $6/1024 "MB", $11}' | while read -r line; do
57
+ if [ -n "$line" ]; then
58
+ pid=$(echo "$line" | awk '{print $1}')
59
+ echo -e "${RED}Found memory hog: $line${NC}"
60
+ kill -KILL "$pid" 2>/dev/null && echo "💀 KILLED memory hog PID $pid" || true
61
+ fi
62
+ done
63
+
64
+ sleep 2
65
+
66
+ # Final verification
67
+ echo ""
68
+ echo -e "${GREEN}🔍 Final system check...${NC}"
69
+ SURVIVORS=$(pgrep -f "vitest\|jest\|mocha\|node.*test" 2>/dev/null || true)
70
+ if [ -n "$SURVIVORS" ]; then
71
+ echo -e "${RED}❌ SURVIVORS DETECTED:${NC}"
72
+ ps -p $SURVIVORS -o pid,%cpu,%mem,cmd 2>/dev/null || true
73
+ echo ""
74
+ echo -e "${YELLOW}Manual intervention required:${NC}"
75
+ echo "sudo kill -9 $SURVIVORS"
76
+ else
77
+ echo -e "${GREEN}🎉 MISSION ACCOMPLISHED - All test processes eliminated${NC}"
78
+ fi
79
+
80
+ # System recovery commands
81
+ echo ""
82
+ echo -e "${GREEN}🚑 SYSTEM RECOVERY PROTOCOL:${NC}"
83
+ echo "1. Clear memory caches:"
84
+ echo " sync && echo 3 | sudo tee /proc/sys/vm/drop_caches"
85
+ echo ""
86
+ echo "2. Check available memory:"
87
+ echo " free -h"
88
+ echo ""
89
+ echo "3. Check system load:"
90
+ echo " uptime"
91
+ echo ""
92
+ echo "4. Monitor for zombies:"
93
+ echo " ps aux | grep defunct"
94
+ echo ""
95
+ echo -e "${YELLOW}System should be responsive now. Monitor for 30 seconds before resuming work.${NC}"
@@ -0,0 +1,95 @@
1
+ #!/bin/bash
2
+
3
+ # 🚨 EMERGENCY VITEST KILLER - System Recovery Script
4
+ # Purpose: Kill all vitest processes when they consume excessive memory
5
+ # Usage: ./scripts/emergency-kill-vitest.sh [--force]
6
+
7
+ set -e
8
+
9
+ # Colors for output
10
+ RED='\033[0;31m'
11
+ YELLOW='\033[1;33m'
12
+ GREEN='\033[0;32m'
13
+ NC='\033[0m' # No Color
14
+
15
+ echo -e "${RED}🚨 EMERGENCY VITEST KILLER ACTIVATED${NC}"
16
+ echo "Scanning for vitest processes..."
17
+
18
+ # Find all vitest-related processes
19
+ VITEST_PIDS=$(pgrep -f "vitest\|node.*test" 2>/dev/null || true)
20
+ NODE_TEST_PIDS=$(pgrep -f "node.*\.test\." 2>/dev/null || true)
21
+ ALL_PIDS="${VITEST_PIDS} ${NODE_TEST_PIDS}"
22
+
23
+ if [ -z "$ALL_PIDS" ]; then
24
+ echo -e "${GREEN}✅ No vitest processes found running${NC}"
25
+ exit 0
26
+ fi
27
+
28
+ echo -e "${YELLOW}Found processes to kill:${NC}"
29
+ echo "$ALL_PIDS" | tr ' ' '\n' | while read -r pid; do
30
+ if [ -n "$pid" ]; then
31
+ ps -p "$pid" -o pid,ppid,%cpu,%mem,cmd 2>/dev/null || true
32
+ fi
33
+ done
34
+
35
+ # Check for --force flag
36
+ if [ "$1" = "--force" ]; then
37
+ echo -e "${RED}🔥 FORCE MODE: Killing immediately...${NC}"
38
+ else
39
+ echo ""
40
+ echo -e "${YELLOW}⚠️ WARNING: This will kill ALL vitest and node test processes${NC}"
41
+ echo "Press ENTER to continue, or Ctrl+C to cancel..."
42
+ read -r
43
+ fi
44
+
45
+ # Kill processes gracefully first (SIGTERM)
46
+ echo -e "${YELLOW}🔄 Attempting graceful shutdown (SIGTERM)...${NC}"
47
+ for pid in $ALL_PIDS; do
48
+ if [ -n "$pid" ]; then
49
+ kill -TERM "$pid" 2>/dev/null || true
50
+ echo "Sent SIGTERM to PID $pid"
51
+ fi
52
+ done
53
+
54
+ # Wait 3 seconds for graceful shutdown
55
+ echo "Waiting 3 seconds for graceful shutdown..."
56
+ sleep 3
57
+
58
+ # Check what's still running
59
+ REMAINING_PIDS=$(pgrep -f "vitest\|node.*test" 2>/dev/null || true)
60
+ if [ -n "$REMAINING_PIDS" ]; then
61
+ echo -e "${RED}💀 Force killing remaining processes (SIGKILL)...${NC}"
62
+ for pid in $REMAINING_PIDS; do
63
+ if [ -n "$pid" ]; then
64
+ kill -KILL "$pid" 2>/dev/null || true
65
+ echo "Sent SIGKILL to PID $pid"
66
+ fi
67
+ done
68
+ else
69
+ echo -e "${GREEN}✅ All processes terminated gracefully${NC}"
70
+ fi
71
+
72
+ # Final verification
73
+ sleep 1
74
+ FINAL_CHECK=$(pgrep -f "vitest\|node.*test" 2>/dev/null || true)
75
+ if [ -n "$FINAL_CHECK" ]; then
76
+ echo -e "${RED}❌ Some processes still running:${NC}"
77
+ echo "$FINAL_CHECK" | tr ' ' '\n' | while read -r pid; do
78
+ if [ -n "$pid" ]; then
79
+ ps -p "$pid" -o pid,ppid,%cpu,%mem,cmd 2>/dev/null || true
80
+ fi
81
+ done
82
+ echo ""
83
+ echo -e "${YELLOW}💡 You may need to run: sudo kill -9 <PID>${NC}"
84
+ else
85
+ echo -e "${GREEN}🎉 SUCCESS: All vitest processes killed${NC}"
86
+ fi
87
+
88
+ # Memory recovery suggestions
89
+ echo ""
90
+ echo -e "${YELLOW}🔧 Memory Recovery Suggestions:${NC}"
91
+ echo "1. Run: sync && echo 3 | sudo tee /proc/sys/vm/drop_caches"
92
+ echo "2. Check memory: free -h"
93
+ echo "3. Check for zombie processes: ps aux | grep defunct"
94
+ echo ""
95
+ echo -e "${GREEN}Emergency kill complete!${NC}"