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.
- package/README.md +1 -1
- package/bundle/fss-link.js +4785 -3183
- package/package.json +4 -1
- package/scripts/analyze-session-logs.sh +279 -0
- package/scripts/build.js +55 -0
- package/scripts/build_package.js +37 -0
- package/scripts/build_sandbox.js +195 -0
- package/scripts/build_vscode_companion.js +30 -0
- package/scripts/check-build-status.js +148 -0
- package/scripts/check-publish.js +101 -0
- package/scripts/clean.js +55 -0
- package/scripts/copy_bundle_assets.js +40 -0
- package/scripts/copy_files.js +56 -0
- package/scripts/create_alias.sh +39 -0
- package/scripts/emergency-kill-all-tests.sh +95 -0
- package/scripts/emergency-kill-vitest.sh +95 -0
- package/scripts/extract-session-logs.sh +202 -0
- package/scripts/generate-git-commit-info.js +71 -0
- package/scripts/get-previous-tag.js +213 -0
- package/scripts/get-release-version.js +119 -0
- package/scripts/index-session-logs.sh +173 -0
- package/scripts/install-linux.sh +294 -0
- package/scripts/install-macos.sh +343 -0
- package/scripts/install-windows.ps1 +427 -0
- package/scripts/local_telemetry.js +219 -0
- package/scripts/memory-monitor.sh +165 -0
- package/scripts/postinstall-message.js +31 -0
- package/scripts/prepare-package.js +51 -0
- package/scripts/process-session-log.py +302 -0
- package/scripts/quick-install.sh +195 -0
- package/scripts/sandbox_command.js +126 -0
- package/scripts/start.js +76 -0
- package/scripts/telemetry.js +85 -0
- package/scripts/telemetry_gcp.js +188 -0
- package/scripts/telemetry_utils.js +421 -0
- package/scripts/test-windows-paths.js +51 -0
- package/scripts/tests/get-release-version.test.js +110 -0
- package/scripts/tests/test-setup.ts +12 -0
- package/scripts/tests/vitest.config.ts +20 -0
- package/scripts/version.js +83 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# FSS Link Quick Install Script
|
|
4
|
+
# Universal installer that detects OS and runs appropriate installation
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
# Colors for output
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
GREEN='\033[0;32m'
|
|
11
|
+
YELLOW='\033[1;33m'
|
|
12
|
+
BLUE='\033[0;34m'
|
|
13
|
+
NC='\033[0m'
|
|
14
|
+
|
|
15
|
+
log_info() {
|
|
16
|
+
echo -e "${BLUE}[INFO]${NC} $1"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
log_success() {
|
|
20
|
+
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
log_warning() {
|
|
24
|
+
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
log_error() {
|
|
28
|
+
echo -e "${RED}[ERROR]${NC} $1"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Detect operating system
|
|
32
|
+
detect_os() {
|
|
33
|
+
case "$(uname -s)" in
|
|
34
|
+
Darwin)
|
|
35
|
+
OS="macos"
|
|
36
|
+
;;
|
|
37
|
+
Linux)
|
|
38
|
+
OS="linux"
|
|
39
|
+
;;
|
|
40
|
+
CYGWIN*|MINGW32*|MSYS*|MINGW*)
|
|
41
|
+
OS="windows"
|
|
42
|
+
;;
|
|
43
|
+
*)
|
|
44
|
+
log_error "Unsupported operating system: $(uname -s)"
|
|
45
|
+
exit 1
|
|
46
|
+
;;
|
|
47
|
+
esac
|
|
48
|
+
|
|
49
|
+
log_info "Detected OS: $OS"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Download installation script
|
|
53
|
+
download_installer() {
|
|
54
|
+
local script_name="install-${OS}.sh"
|
|
55
|
+
local script_url="https://raw.githubusercontent.com/FSSCoding/fss-link/main/scripts/${script_name}"
|
|
56
|
+
|
|
57
|
+
log_info "Downloading $script_name..."
|
|
58
|
+
|
|
59
|
+
if command -v curl &> /dev/null; then
|
|
60
|
+
curl -fsSL "$script_url" -o "$script_name"
|
|
61
|
+
elif command -v wget &> /dev/null; then
|
|
62
|
+
wget -q "$script_url" -O "$script_name"
|
|
63
|
+
else
|
|
64
|
+
log_error "Neither curl nor wget found. Please install one of them."
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if [ -f "$script_name" ]; then
|
|
69
|
+
chmod +x "$script_name"
|
|
70
|
+
log_success "Downloaded $script_name"
|
|
71
|
+
return 0
|
|
72
|
+
else
|
|
73
|
+
log_error "Failed to download $script_name"
|
|
74
|
+
return 1
|
|
75
|
+
fi
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Run the appropriate installer
|
|
79
|
+
run_installer() {
|
|
80
|
+
local script_name="install-${OS}.sh"
|
|
81
|
+
|
|
82
|
+
if [ "$OS" = "windows" ]; then
|
|
83
|
+
# For Windows, we need PowerShell
|
|
84
|
+
log_info "For Windows installation, please run:"
|
|
85
|
+
log_info "PowerShell -ExecutionPolicy Bypass -File install-windows.ps1"
|
|
86
|
+
return 0
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
log_info "Running $script_name..."
|
|
90
|
+
./"$script_name" "$@"
|
|
91
|
+
|
|
92
|
+
# Clean up
|
|
93
|
+
rm -f "$script_name"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# NPM fallback installation
|
|
97
|
+
npm_install() {
|
|
98
|
+
log_info "Attempting direct NPM installation..."
|
|
99
|
+
|
|
100
|
+
if command -v npm &> /dev/null; then
|
|
101
|
+
npm install -g fss-link
|
|
102
|
+
|
|
103
|
+
if command -v fss-link &> /dev/null; then
|
|
104
|
+
log_success "FSS Link installed via NPM"
|
|
105
|
+
log_info "Run 'fss-link --help' to get started"
|
|
106
|
+
return 0
|
|
107
|
+
else
|
|
108
|
+
log_error "NPM installation succeeded but fss-link command not found"
|
|
109
|
+
return 1
|
|
110
|
+
fi
|
|
111
|
+
else
|
|
112
|
+
log_error "NPM not found. Please install Node.js 20+ first."
|
|
113
|
+
return 1
|
|
114
|
+
fi
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Main function
|
|
118
|
+
main() {
|
|
119
|
+
echo "🚀 FSS Link Universal Quick Installer"
|
|
120
|
+
echo "====================================="
|
|
121
|
+
echo ""
|
|
122
|
+
|
|
123
|
+
# Handle help flag
|
|
124
|
+
if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
|
|
125
|
+
cat << EOF
|
|
126
|
+
FSS Link Universal Quick Installer
|
|
127
|
+
|
|
128
|
+
Usage: $0 [options]
|
|
129
|
+
|
|
130
|
+
Options:
|
|
131
|
+
--help, -h Show this help message
|
|
132
|
+
--npm-only Skip OS-specific installer, use NPM directly
|
|
133
|
+
--version, -v Show script version
|
|
134
|
+
|
|
135
|
+
This script will:
|
|
136
|
+
1. Detect your operating system
|
|
137
|
+
2. Download the appropriate installation script
|
|
138
|
+
3. Run the OS-specific installer
|
|
139
|
+
4. Verify the installation
|
|
140
|
+
|
|
141
|
+
Supported Systems:
|
|
142
|
+
- Linux (Ubuntu, Debian, CentOS, Fedora, Arch, openSUSE)
|
|
143
|
+
- macOS (10.15+)
|
|
144
|
+
- Windows (10+)
|
|
145
|
+
|
|
146
|
+
Requirements:
|
|
147
|
+
- Node.js 20.0.0 or higher
|
|
148
|
+
- Build tools for native dependencies
|
|
149
|
+
- Internet connection for downloading
|
|
150
|
+
|
|
151
|
+
Examples:
|
|
152
|
+
curl -fsSL https://raw.githubusercontent.com/FSSCoding/fss-link/main/scripts/quick-install.sh | bash
|
|
153
|
+
bash quick-install.sh --npm-only
|
|
154
|
+
EOF
|
|
155
|
+
exit 0
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# Handle version flag
|
|
159
|
+
if [ "${1:-}" = "--version" ] || [ "${1:-}" = "-v" ]; then
|
|
160
|
+
echo "FSS Link Universal Quick Installer v1.0.0"
|
|
161
|
+
exit 0
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# Handle NPM-only installation
|
|
165
|
+
if [ "${1:-}" = "--npm-only" ]; then
|
|
166
|
+
npm_install
|
|
167
|
+
exit $?
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# Detect OS
|
|
171
|
+
detect_os
|
|
172
|
+
|
|
173
|
+
# Try downloading and running OS-specific installer
|
|
174
|
+
if download_installer; then
|
|
175
|
+
run_installer "$@"
|
|
176
|
+
else
|
|
177
|
+
log_warning "OS-specific installer download failed, trying NPM installation..."
|
|
178
|
+
npm_install
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
# Final verification
|
|
182
|
+
if command -v fss-link &> /dev/null; then
|
|
183
|
+
VERSION=$(fss-link --version 2>/dev/null || echo "unknown")
|
|
184
|
+
log_success "🎉 FSS Link installation complete!"
|
|
185
|
+
log_info "Version: $VERSION"
|
|
186
|
+
log_info "Run 'fss-link --help' to get started"
|
|
187
|
+
else
|
|
188
|
+
log_error "Installation may have failed - fss-link command not found"
|
|
189
|
+
log_info "Try running: npm install -g fss-link"
|
|
190
|
+
exit 1
|
|
191
|
+
fi
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# Run main function with all arguments
|
|
195
|
+
main "$@"
|
|
@@ -0,0 +1,126 @@
|
|
|
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 { execSync } from 'child_process';
|
|
21
|
+
import { existsSync, readFileSync } from 'fs';
|
|
22
|
+
import { join, dirname } from 'path';
|
|
23
|
+
import stripJsonComments from 'strip-json-comments';
|
|
24
|
+
import os from 'os';
|
|
25
|
+
import yargs from 'yargs';
|
|
26
|
+
import { hideBin } from 'yargs/helpers';
|
|
27
|
+
import dotenv from 'dotenv';
|
|
28
|
+
|
|
29
|
+
const argv = yargs(hideBin(process.argv)).option('q', {
|
|
30
|
+
alias: 'quiet',
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
default: false,
|
|
33
|
+
}).argv;
|
|
34
|
+
|
|
35
|
+
let fssLinkSandbox = process.env.FSS_LINK_SANDBOX;
|
|
36
|
+
|
|
37
|
+
if (!fssLinkSandbox) {
|
|
38
|
+
const userSettingsFile = join(os.homedir(), '.fss-link', 'settings.json');
|
|
39
|
+
if (existsSync(userSettingsFile)) {
|
|
40
|
+
const settings = JSON.parse(
|
|
41
|
+
stripJsonComments(readFileSync(userSettingsFile, 'utf-8')),
|
|
42
|
+
);
|
|
43
|
+
if (settings.sandbox) {
|
|
44
|
+
fssLinkSandbox = settings.sandbox;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!fssLinkSandbox) {
|
|
50
|
+
let currentDir = process.cwd();
|
|
51
|
+
while (true) {
|
|
52
|
+
const fssLinkEnv = join(currentDir, '.fss-link', '.env');
|
|
53
|
+
const regularEnv = join(currentDir, '.env');
|
|
54
|
+
if (existsSync(fssLinkEnv)) {
|
|
55
|
+
dotenv.config({ path: fssLinkEnv, quiet: true });
|
|
56
|
+
break;
|
|
57
|
+
} else if (existsSync(regularEnv)) {
|
|
58
|
+
dotenv.config({ path: regularEnv, quiet: true });
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
const parentDir = dirname(currentDir);
|
|
62
|
+
if (parentDir === currentDir) {
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
currentDir = parentDir;
|
|
66
|
+
}
|
|
67
|
+
fssLinkSandbox = process.env.FSS_LINK_SANDBOX;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fssLinkSandbox = (fssLinkSandbox || '').toLowerCase();
|
|
71
|
+
|
|
72
|
+
const commandExists = (cmd) => {
|
|
73
|
+
const checkCommand = os.platform() === 'win32' ? 'where' : 'command -v';
|
|
74
|
+
try {
|
|
75
|
+
execSync(`${checkCommand} ${cmd}`, { stdio: 'ignore' });
|
|
76
|
+
return true;
|
|
77
|
+
} catch {
|
|
78
|
+
if (os.platform() === 'win32') {
|
|
79
|
+
try {
|
|
80
|
+
execSync(`${checkCommand} ${cmd}.exe`, { stdio: 'ignore' });
|
|
81
|
+
return true;
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
let command = '';
|
|
91
|
+
if (['1', 'true'].includes(fssLinkSandbox)) {
|
|
92
|
+
if (commandExists('docker')) {
|
|
93
|
+
command = 'docker';
|
|
94
|
+
} else if (commandExists('podman')) {
|
|
95
|
+
command = 'podman';
|
|
96
|
+
} else {
|
|
97
|
+
console.error(
|
|
98
|
+
'ERROR: install docker or podman or specify command in FSS_LINK_SANDBOX',
|
|
99
|
+
);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
} else if (fssLinkSandbox && !['0', 'false'].includes(fssLinkSandbox)) {
|
|
103
|
+
if (commandExists(fssLinkSandbox)) {
|
|
104
|
+
command = fssLinkSandbox;
|
|
105
|
+
} else {
|
|
106
|
+
console.error(
|
|
107
|
+
`ERROR: missing sandbox command '${fssLinkSandbox}' (from FSS_LINK_SANDBOX)`,
|
|
108
|
+
);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
if (os.platform() === 'darwin' && process.env.SEATBELT_PROFILE !== 'none') {
|
|
113
|
+
if (commandExists('sandbox-exec')) {
|
|
114
|
+
command = 'sandbox-exec';
|
|
115
|
+
} else {
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!argv.q) {
|
|
124
|
+
console.log(command);
|
|
125
|
+
}
|
|
126
|
+
process.exit(0);
|
package/scripts/start.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
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 { spawn, execSync } from 'child_process';
|
|
21
|
+
import { dirname, join } from 'path';
|
|
22
|
+
import { fileURLToPath } from 'url';
|
|
23
|
+
import { readFileSync } from 'fs';
|
|
24
|
+
|
|
25
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const root = join(__dirname, '..');
|
|
27
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));
|
|
28
|
+
|
|
29
|
+
// check build status, write warnings to file for app to display if needed
|
|
30
|
+
execSync('node ./scripts/check-build-status.js', {
|
|
31
|
+
stdio: 'inherit',
|
|
32
|
+
cwd: root,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const nodeArgs = [];
|
|
36
|
+
let sandboxCommand = undefined;
|
|
37
|
+
try {
|
|
38
|
+
sandboxCommand = execSync('node scripts/sandbox_command.js', {
|
|
39
|
+
cwd: root,
|
|
40
|
+
})
|
|
41
|
+
.toString()
|
|
42
|
+
.trim();
|
|
43
|
+
} catch {
|
|
44
|
+
// ignore
|
|
45
|
+
}
|
|
46
|
+
// if debugging is enabled and sandboxing is disabled, use --inspect-brk flag
|
|
47
|
+
// note with sandboxing this flag is passed to the binary inside the sandbox
|
|
48
|
+
// inside sandbox SANDBOX should be set and sandbox_command.js should fail
|
|
49
|
+
if (process.env.DEBUG && !sandboxCommand) {
|
|
50
|
+
if (process.env.SANDBOX) {
|
|
51
|
+
const port = process.env.DEBUG_PORT || '9229';
|
|
52
|
+
nodeArgs.push(`--inspect-brk=0.0.0.0:${port}`);
|
|
53
|
+
} else {
|
|
54
|
+
nodeArgs.push('--inspect-brk');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
nodeArgs.push(join(root, 'packages', 'cli'));
|
|
59
|
+
nodeArgs.push(...process.argv.slice(2));
|
|
60
|
+
|
|
61
|
+
const env = {
|
|
62
|
+
...process.env,
|
|
63
|
+
CLI_VERSION: pkg.version,
|
|
64
|
+
DEV: 'true',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (process.env.DEBUG) {
|
|
68
|
+
// If this is not set, the debugger will pause on the outer process rather
|
|
69
|
+
// than the relaunched process making it harder to debug.
|
|
70
|
+
env.GEMINI_CLI_NO_RELAUNCH = 'true';
|
|
71
|
+
}
|
|
72
|
+
const child = spawn('node', nodeArgs, { stdio: 'inherit', env });
|
|
73
|
+
|
|
74
|
+
child.on('close', (code) => {
|
|
75
|
+
process.exit(code);
|
|
76
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
* Copyright 2025 Google LLC
|
|
6
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execSync } from 'child_process';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { existsSync, readFileSync } from 'fs';
|
|
12
|
+
|
|
13
|
+
const projectRoot = join(import.meta.dirname, '..');
|
|
14
|
+
|
|
15
|
+
const SETTINGS_DIRECTORY_NAME = '.fss-link';
|
|
16
|
+
const USER_SETTINGS_DIR = join(
|
|
17
|
+
process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || '',
|
|
18
|
+
SETTINGS_DIRECTORY_NAME,
|
|
19
|
+
);
|
|
20
|
+
const USER_SETTINGS_PATH = join(USER_SETTINGS_DIR, 'settings.json');
|
|
21
|
+
const WORKSPACE_SETTINGS_PATH = join(
|
|
22
|
+
projectRoot,
|
|
23
|
+
SETTINGS_DIRECTORY_NAME,
|
|
24
|
+
'settings.json',
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
let settingsTarget = undefined;
|
|
28
|
+
|
|
29
|
+
function loadSettingsValue(filePath) {
|
|
30
|
+
try {
|
|
31
|
+
if (existsSync(filePath)) {
|
|
32
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
33
|
+
const jsonContent = content.replace(/\/\/[^\n]*/g, '');
|
|
34
|
+
const settings = JSON.parse(jsonContent);
|
|
35
|
+
return settings.telemetry?.target;
|
|
36
|
+
}
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.warn(
|
|
39
|
+
`⚠️ Warning: Could not parse settings file at ${filePath}: ${e.message}`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
settingsTarget = loadSettingsValue(WORKSPACE_SETTINGS_PATH);
|
|
46
|
+
|
|
47
|
+
if (!settingsTarget) {
|
|
48
|
+
settingsTarget = loadSettingsValue(USER_SETTINGS_PATH);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let target = settingsTarget || 'local';
|
|
52
|
+
const allowedTargets = ['local', 'gcp'];
|
|
53
|
+
|
|
54
|
+
const targetArg = process.argv.find((arg) => arg.startsWith('--target='));
|
|
55
|
+
if (targetArg) {
|
|
56
|
+
const potentialTarget = targetArg.split('=')[1];
|
|
57
|
+
if (allowedTargets.includes(potentialTarget)) {
|
|
58
|
+
target = potentialTarget;
|
|
59
|
+
console.log(`⚙️ Using command-line target: ${target}`);
|
|
60
|
+
} else {
|
|
61
|
+
console.error(
|
|
62
|
+
`🛑 Error: Invalid target '${potentialTarget}'. Allowed targets are: ${allowedTargets.join(', ')}.`,
|
|
63
|
+
);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
} else if (settingsTarget) {
|
|
67
|
+
console.log(
|
|
68
|
+
`⚙️ Using telemetry target from settings.json: ${settingsTarget}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const scriptPath = join(
|
|
73
|
+
projectRoot,
|
|
74
|
+
'scripts',
|
|
75
|
+
target === 'gcp' ? 'telemetry_gcp.js' : 'local_telemetry.js',
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
console.log(`🚀 Running telemetry script for target: ${target}.`);
|
|
80
|
+
execSync(`node ${scriptPath}`, { stdio: 'inherit', cwd: projectRoot });
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error(`🛑 Failed to run telemetry script for target: ${target}`);
|
|
83
|
+
console.error(error);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
* Copyright 2025 Google LLC
|
|
6
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import { spawn, execSync } from 'child_process';
|
|
12
|
+
import {
|
|
13
|
+
OTEL_DIR,
|
|
14
|
+
BIN_DIR,
|
|
15
|
+
fileExists,
|
|
16
|
+
waitForPort,
|
|
17
|
+
ensureBinary,
|
|
18
|
+
manageTelemetrySettings,
|
|
19
|
+
registerCleanup,
|
|
20
|
+
} from './telemetry_utils.js';
|
|
21
|
+
|
|
22
|
+
const OTEL_CONFIG_FILE = path.join(OTEL_DIR, 'collector-gcp.yaml');
|
|
23
|
+
const OTEL_LOG_FILE = path.join(OTEL_DIR, 'collector-gcp.log');
|
|
24
|
+
|
|
25
|
+
const getOtelConfigContent = (projectId) => `
|
|
26
|
+
receivers:
|
|
27
|
+
otlp:
|
|
28
|
+
protocols:
|
|
29
|
+
grpc:
|
|
30
|
+
endpoint: "localhost:4317"
|
|
31
|
+
processors:
|
|
32
|
+
batch:
|
|
33
|
+
timeout: 1s
|
|
34
|
+
exporters:
|
|
35
|
+
googlecloud:
|
|
36
|
+
project: "${projectId}"
|
|
37
|
+
metric:
|
|
38
|
+
prefix: "custom.googleapis.com/gemini_cli"
|
|
39
|
+
log:
|
|
40
|
+
default_log_name: "gemini_cli"
|
|
41
|
+
debug:
|
|
42
|
+
verbosity: detailed
|
|
43
|
+
service:
|
|
44
|
+
telemetry:
|
|
45
|
+
logs:
|
|
46
|
+
level: "debug"
|
|
47
|
+
metrics:
|
|
48
|
+
level: "none"
|
|
49
|
+
pipelines:
|
|
50
|
+
traces:
|
|
51
|
+
receivers: [otlp]
|
|
52
|
+
processors: [batch]
|
|
53
|
+
exporters: [googlecloud]
|
|
54
|
+
metrics:
|
|
55
|
+
receivers: [otlp]
|
|
56
|
+
processors: [batch]
|
|
57
|
+
exporters: [googlecloud, debug]
|
|
58
|
+
logs:
|
|
59
|
+
receivers: [otlp]
|
|
60
|
+
processors: [batch]
|
|
61
|
+
exporters: [googlecloud, debug]
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
async function main() {
|
|
65
|
+
console.log('✨ Starting Local Telemetry Exporter for Google Cloud ✨');
|
|
66
|
+
|
|
67
|
+
let collectorProcess;
|
|
68
|
+
let collectorLogFd;
|
|
69
|
+
|
|
70
|
+
const originalSandboxSetting = manageTelemetrySettings(
|
|
71
|
+
true,
|
|
72
|
+
'http://localhost:4317',
|
|
73
|
+
'gcp',
|
|
74
|
+
);
|
|
75
|
+
registerCleanup(
|
|
76
|
+
() => [collectorProcess].filter((p) => p), // Function to get processes
|
|
77
|
+
() => [collectorLogFd].filter((fd) => fd), // Function to get FDs
|
|
78
|
+
originalSandboxSetting,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const projectId = process.env.OTLP_GOOGLE_CLOUD_PROJECT;
|
|
82
|
+
if (!projectId) {
|
|
83
|
+
console.error(
|
|
84
|
+
'🛑 Error: OTLP_GOOGLE_CLOUD_PROJECT environment variable is not exported.',
|
|
85
|
+
);
|
|
86
|
+
console.log(
|
|
87
|
+
' Please set it to your Google Cloud Project ID and try again.',
|
|
88
|
+
);
|
|
89
|
+
console.log(' `export OTLP_GOOGLE_CLOUD_PROJECT=your-project-id`');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
console.log(`✅ Using OTLP Google Cloud Project ID: ${projectId}`);
|
|
93
|
+
|
|
94
|
+
console.log('\n🔑 Please ensure you are authenticated with Google Cloud:');
|
|
95
|
+
console.log(
|
|
96
|
+
' - Run `gcloud auth application-default login` OR ensure `GOOGLE_APPLICATION_CREDENTIALS` environment variable points to a valid service account key.',
|
|
97
|
+
);
|
|
98
|
+
console.log(
|
|
99
|
+
' - The account needs "Cloud Trace Agent", "Monitoring Metric Writer", and "Logs Writer" roles.',
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (!fileExists(BIN_DIR)) fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
103
|
+
|
|
104
|
+
const otelcolPath = await ensureBinary(
|
|
105
|
+
'otelcol-contrib',
|
|
106
|
+
'open-telemetry/opentelemetry-collector-releases',
|
|
107
|
+
(version, platform, arch, ext) =>
|
|
108
|
+
`otelcol-contrib_${version}_${platform}_${arch}.${ext}`,
|
|
109
|
+
'otelcol-contrib',
|
|
110
|
+
false, // isJaeger = false
|
|
111
|
+
).catch((e) => {
|
|
112
|
+
console.error(`🛑 Error getting otelcol-contrib: ${e.message}`);
|
|
113
|
+
return null;
|
|
114
|
+
});
|
|
115
|
+
if (!otelcolPath) process.exit(1);
|
|
116
|
+
|
|
117
|
+
console.log('🧹 Cleaning up old processes and logs...');
|
|
118
|
+
try {
|
|
119
|
+
execSync('pkill -f "otelcol-contrib"');
|
|
120
|
+
console.log('✅ Stopped existing otelcol-contrib process.');
|
|
121
|
+
} catch (_e) {
|
|
122
|
+
/* no-op */
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
fs.unlinkSync(OTEL_LOG_FILE);
|
|
126
|
+
console.log('✅ Deleted old GCP collector log.');
|
|
127
|
+
} catch (e) {
|
|
128
|
+
if (e.code !== 'ENOENT') console.error(e);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!fileExists(OTEL_DIR)) fs.mkdirSync(OTEL_DIR, { recursive: true });
|
|
132
|
+
fs.writeFileSync(OTEL_CONFIG_FILE, getOtelConfigContent(projectId));
|
|
133
|
+
console.log(`📄 Wrote OTEL collector config to ${OTEL_CONFIG_FILE}`);
|
|
134
|
+
|
|
135
|
+
console.log(`🚀 Starting OTEL collector for GCP... Logs: ${OTEL_LOG_FILE}`);
|
|
136
|
+
collectorLogFd = fs.openSync(OTEL_LOG_FILE, 'a');
|
|
137
|
+
collectorProcess = spawn(otelcolPath, ['--config', OTEL_CONFIG_FILE], {
|
|
138
|
+
stdio: ['ignore', collectorLogFd, collectorLogFd],
|
|
139
|
+
env: { ...process.env },
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
console.log(
|
|
143
|
+
`⏳ Waiting for OTEL collector to start (PID: ${collectorProcess.pid})...`,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
await waitForPort(4317);
|
|
148
|
+
console.log(`✅ OTEL collector started successfully on port 4317.`);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error(`🛑 Error: OTEL collector failed to start on port 4317.`);
|
|
151
|
+
console.error(err.message);
|
|
152
|
+
if (collectorProcess && collectorProcess.pid) {
|
|
153
|
+
process.kill(collectorProcess.pid, 'SIGKILL');
|
|
154
|
+
}
|
|
155
|
+
if (fileExists(OTEL_LOG_FILE)) {
|
|
156
|
+
console.error('📄 OTEL Collector Log Output:');
|
|
157
|
+
console.error(fs.readFileSync(OTEL_LOG_FILE, 'utf-8'));
|
|
158
|
+
}
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
collectorProcess.on('error', (err) => {
|
|
163
|
+
console.error(`${collectorProcess.spawnargs[0]} process error:`, err);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
console.log(`\n✨ Local OTEL collector for GCP is running.`);
|
|
168
|
+
console.log(
|
|
169
|
+
'\n🚀 To send telemetry, run the Gemini CLI in a separate terminal window.',
|
|
170
|
+
);
|
|
171
|
+
console.log(`\n📄 Collector logs are being written to: ${OTEL_LOG_FILE}`);
|
|
172
|
+
console.log(
|
|
173
|
+
`📄 Tail collector logs in another terminal: tail -f ${OTEL_LOG_FILE}`,
|
|
174
|
+
);
|
|
175
|
+
console.log(`\n📊 View your telemetry data in Google Cloud Console:`);
|
|
176
|
+
console.log(
|
|
177
|
+
` - Logs: https://console.cloud.google.com/logs/query;query=logName%3D%22projects%2F${projectId}%2Flogs%2Fgemini_cli%22?project=${projectId}`,
|
|
178
|
+
);
|
|
179
|
+
console.log(
|
|
180
|
+
` - Metrics: https://console.cloud.google.com/monitoring/metrics-explorer?project=${projectId}`,
|
|
181
|
+
);
|
|
182
|
+
console.log(
|
|
183
|
+
` - Traces: https://console.cloud.google.com/traces/list?project=${projectId}`,
|
|
184
|
+
);
|
|
185
|
+
console.log(`\nPress Ctrl+C to exit.`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
main();
|