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,202 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# FSS Link Session Log Extractor
|
|
3
|
+
# Extracts and organizes session logs from FSS Link checkpoint files
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
# Configuration
|
|
8
|
+
FSS_LINK_TMP_DIR="${HOME}/.fss-link/tmp"
|
|
9
|
+
OUTPUT_DIR="/MASTERFOLDER/Projects/fss-link/fss-live-testing/session-logs"
|
|
10
|
+
RAW_DIR="${OUTPUT_DIR}/raw"
|
|
11
|
+
PROCESSED_DIR="${OUTPUT_DIR}/processed"
|
|
12
|
+
|
|
13
|
+
# Colors for output
|
|
14
|
+
RED='\033[0;31m'
|
|
15
|
+
GREEN='\033[0;32m'
|
|
16
|
+
YELLOW='\033[1;33m'
|
|
17
|
+
BLUE='\033[0;34m'
|
|
18
|
+
NC='\033[0m' # No Color
|
|
19
|
+
|
|
20
|
+
# Usage information
|
|
21
|
+
usage() {
|
|
22
|
+
cat << EOF
|
|
23
|
+
Usage: $0 [OPTIONS]
|
|
24
|
+
|
|
25
|
+
Extract FSS Link session logs and prepare them for RAG indexing.
|
|
26
|
+
|
|
27
|
+
OPTIONS:
|
|
28
|
+
-d, --days DAYS Extract logs from last N days (default: 7)
|
|
29
|
+
-a, --all Extract all available logs
|
|
30
|
+
-t, --tool TOOL Filter by tool name (e.g., grep, read, write)
|
|
31
|
+
-s, --session ID Extract specific session by ID
|
|
32
|
+
-l, --list List available sessions
|
|
33
|
+
-h, --help Show this help message
|
|
34
|
+
|
|
35
|
+
EXAMPLES:
|
|
36
|
+
$0 --days 1 # Extract logs from last 24 hours
|
|
37
|
+
$0 --tool grep # Extract only grep-related sessions
|
|
38
|
+
$0 --session 05e09634... # Extract specific session
|
|
39
|
+
$0 --list # List all available sessions
|
|
40
|
+
|
|
41
|
+
OUTPUT:
|
|
42
|
+
Raw logs: ${RAW_DIR}/
|
|
43
|
+
Processed logs: ${PROCESSED_DIR}/
|
|
44
|
+
Analysis: ${OUTPUT_DIR}/analysis/
|
|
45
|
+
EOF
|
|
46
|
+
exit 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Parse arguments
|
|
50
|
+
DAYS=7
|
|
51
|
+
EXTRACT_ALL=false
|
|
52
|
+
TOOL_FILTER=""
|
|
53
|
+
SESSION_ID=""
|
|
54
|
+
LIST_ONLY=false
|
|
55
|
+
|
|
56
|
+
while [[ $# -gt 0 ]]; do
|
|
57
|
+
case $1 in
|
|
58
|
+
-d|--days)
|
|
59
|
+
DAYS="$2"
|
|
60
|
+
shift 2
|
|
61
|
+
;;
|
|
62
|
+
-a|--all)
|
|
63
|
+
EXTRACT_ALL=true
|
|
64
|
+
shift
|
|
65
|
+
;;
|
|
66
|
+
-t|--tool)
|
|
67
|
+
TOOL_FILTER="$2"
|
|
68
|
+
shift 2
|
|
69
|
+
;;
|
|
70
|
+
-s|--session)
|
|
71
|
+
SESSION_ID="$2"
|
|
72
|
+
shift 2
|
|
73
|
+
;;
|
|
74
|
+
-l|--list)
|
|
75
|
+
LIST_ONLY=true
|
|
76
|
+
shift
|
|
77
|
+
;;
|
|
78
|
+
-h|--help)
|
|
79
|
+
usage
|
|
80
|
+
;;
|
|
81
|
+
*)
|
|
82
|
+
echo -e "${RED}Unknown option: $1${NC}"
|
|
83
|
+
usage
|
|
84
|
+
;;
|
|
85
|
+
esac
|
|
86
|
+
done
|
|
87
|
+
|
|
88
|
+
# Ensure output directories exist
|
|
89
|
+
mkdir -p "${RAW_DIR}" "${PROCESSED_DIR}" "${OUTPUT_DIR}/analysis"
|
|
90
|
+
|
|
91
|
+
# List available sessions
|
|
92
|
+
list_sessions() {
|
|
93
|
+
echo -e "${BLUE}=== Available FSS Link Sessions ===${NC}\n"
|
|
94
|
+
|
|
95
|
+
if [[ ! -d "${FSS_LINK_TMP_DIR}" ]]; then
|
|
96
|
+
echo -e "${RED}FSS Link tmp directory not found: ${FSS_LINK_TMP_DIR}${NC}"
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
find "${FSS_LINK_TMP_DIR}" -name "checkpoint*.json" -type f | while read -r checkpoint; do
|
|
101
|
+
session_dir=$(dirname "${checkpoint}")
|
|
102
|
+
session_id=$(basename "${session_dir}")
|
|
103
|
+
file_size=$(du -h "${checkpoint}" | cut -f1)
|
|
104
|
+
mod_time=$(stat -c '%y' "${checkpoint}" 2>/dev/null || stat -f '%Sm' "${checkpoint}")
|
|
105
|
+
|
|
106
|
+
# Try to extract some context from the file
|
|
107
|
+
context=$(jq -r '.messages[2].parts[0].text // "Unknown"' "${checkpoint}" 2>/dev/null | head -c 100)
|
|
108
|
+
|
|
109
|
+
echo -e "${GREEN}Session:${NC} ${session_id}"
|
|
110
|
+
echo -e " ${YELLOW}File:${NC} ${checkpoint}"
|
|
111
|
+
echo -e " ${YELLOW}Size:${NC} ${file_size}"
|
|
112
|
+
echo -e " ${YELLOW}Modified:${NC} ${mod_time}"
|
|
113
|
+
echo -e " ${YELLOW}Context:${NC} ${context}..."
|
|
114
|
+
echo ""
|
|
115
|
+
done
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Extract specific session
|
|
119
|
+
extract_session() {
|
|
120
|
+
local session_path="$1"
|
|
121
|
+
local session_id="$2"
|
|
122
|
+
local output_name="$3"
|
|
123
|
+
|
|
124
|
+
echo -e "${BLUE}Extracting session: ${session_id}${NC}"
|
|
125
|
+
|
|
126
|
+
# Copy raw log
|
|
127
|
+
cp "${session_path}" "${RAW_DIR}/${output_name}.json"
|
|
128
|
+
echo -e "${GREEN}✓${NC} Raw log saved: ${RAW_DIR}/${output_name}.json"
|
|
129
|
+
|
|
130
|
+
# Process log with Python script
|
|
131
|
+
python3 /MASTERFOLDER/Projects/fss-link/scripts/process-session-log.py \
|
|
132
|
+
"${session_path}" \
|
|
133
|
+
"${PROCESSED_DIR}/${output_name}.md"
|
|
134
|
+
|
|
135
|
+
if [[ $? -eq 0 ]]; then
|
|
136
|
+
echo -e "${GREEN}✓${NC} Processed log saved: ${PROCESSED_DIR}/${output_name}.md"
|
|
137
|
+
else
|
|
138
|
+
echo -e "${RED}✗${NC} Failed to process session log"
|
|
139
|
+
return 1
|
|
140
|
+
fi
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Main execution
|
|
144
|
+
main() {
|
|
145
|
+
if [[ "${LIST_ONLY}" == "true" ]]; then
|
|
146
|
+
list_sessions
|
|
147
|
+
exit 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
echo -e "${BLUE}=== FSS Link Session Log Extractor ===${NC}\n"
|
|
151
|
+
|
|
152
|
+
# Specific session extraction
|
|
153
|
+
if [[ -n "${SESSION_ID}" ]]; then
|
|
154
|
+
session_path="${FSS_LINK_TMP_DIR}/${SESSION_ID}/checkpoint-fileRead.json"
|
|
155
|
+
if [[ ! -f "${session_path}" ]]; then
|
|
156
|
+
echo -e "${RED}Session not found: ${SESSION_ID}${NC}"
|
|
157
|
+
exit 1
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
timestamp=$(date +%Y%m%d-%H%M%S)
|
|
161
|
+
extract_session "${session_path}" "${SESSION_ID}" "session-${timestamp}"
|
|
162
|
+
exit 0
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
# Extract sessions based on filters
|
|
166
|
+
local count=0
|
|
167
|
+
find "${FSS_LINK_TMP_DIR}" -name "checkpoint*.json" -type f | while read -r checkpoint; do
|
|
168
|
+
session_dir=$(dirname "${checkpoint}")
|
|
169
|
+
session_id=$(basename "${session_dir}")
|
|
170
|
+
|
|
171
|
+
# Apply time filter
|
|
172
|
+
if [[ "${EXTRACT_ALL}" == "false" ]]; then
|
|
173
|
+
# Check if file was modified in last N days
|
|
174
|
+
if [[ $(find "${checkpoint}" -mtime "-${DAYS}" | wc -l) -eq 0 ]]; then
|
|
175
|
+
continue
|
|
176
|
+
fi
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# Apply tool filter
|
|
180
|
+
if [[ -n "${TOOL_FILTER}" ]]; then
|
|
181
|
+
if ! grep -qi "${TOOL_FILTER}" "${checkpoint}" 2>/dev/null; then
|
|
182
|
+
continue
|
|
183
|
+
fi
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# Extract this session
|
|
187
|
+
mod_date=$(stat -c '%Y' "${checkpoint}" 2>/dev/null || stat -f '%m' "${checkpoint}")
|
|
188
|
+
formatted_date=$(date -d "@${mod_date}" +%Y%m%d-%H%M%S 2>/dev/null || date -r "${mod_date}" +%Y%m%d-%H%M%S)
|
|
189
|
+
output_name="session-${formatted_date}-${session_id:0:8}"
|
|
190
|
+
|
|
191
|
+
extract_session "${checkpoint}" "${session_id}" "${output_name}"
|
|
192
|
+
((count++))
|
|
193
|
+
done
|
|
194
|
+
|
|
195
|
+
echo -e "\n${GREEN}Extraction complete: ${count} sessions processed${NC}"
|
|
196
|
+
echo -e "${YELLOW}Next steps:${NC}"
|
|
197
|
+
echo -e " 1. Review processed logs in: ${PROCESSED_DIR}/"
|
|
198
|
+
echo -e " 2. Index with RAG: cd ${OUTPUT_DIR} && rag-index create processed/"
|
|
199
|
+
echo -e " 3. Search logs: rag fss-link-sessions 'your query'"
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
main
|
|
@@ -0,0 +1,71 @@
|
|
|
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, mkdirSync, writeFileSync } from 'fs';
|
|
22
|
+
import { dirname, join, relative } from 'path';
|
|
23
|
+
import { fileURLToPath } from 'url';
|
|
24
|
+
import { readPackageUp } from 'read-package-up';
|
|
25
|
+
|
|
26
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const root = join(__dirname, '..');
|
|
28
|
+
const scriptPath = relative(root, fileURLToPath(import.meta.url));
|
|
29
|
+
const generatedCliDir = join(root, 'packages/cli/src/generated');
|
|
30
|
+
const cliGitCommitFile = join(generatedCliDir, 'git-commit.ts');
|
|
31
|
+
const generatedCoreDir = join(root, 'packages/core/src/generated');
|
|
32
|
+
const coreGitCommitFile = join(generatedCoreDir, 'git-commit.ts');
|
|
33
|
+
let gitCommitInfo = 'N/A';
|
|
34
|
+
let cliVersion = 'UNKNOWN';
|
|
35
|
+
|
|
36
|
+
if (!existsSync(generatedCliDir)) {
|
|
37
|
+
mkdirSync(generatedCliDir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!existsSync(generatedCoreDir)) {
|
|
41
|
+
mkdirSync(generatedCoreDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const gitHash = execSync('git rev-parse --short HEAD', {
|
|
46
|
+
encoding: 'utf-8',
|
|
47
|
+
}).trim();
|
|
48
|
+
if (gitHash) {
|
|
49
|
+
gitCommitInfo = gitHash;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const result = await readPackageUp();
|
|
53
|
+
cliVersion = result?.packageJson?.version ?? 'UNKNOWN';
|
|
54
|
+
} catch {
|
|
55
|
+
// ignore
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const fileContent = `/**
|
|
59
|
+
* @license
|
|
60
|
+
* Copyright ${new Date().getFullYear()} Google LLC
|
|
61
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
// This file is auto-generated by the build script (${scriptPath})
|
|
65
|
+
// Do not edit this file manually.
|
|
66
|
+
export const GIT_COMMIT_INFO = '${gitCommitInfo}';
|
|
67
|
+
export const CLI_VERSION = '${cliVersion}';
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
writeFileSync(cliGitCommitFile, fileContent);
|
|
71
|
+
writeFileSync(coreGitCommitFile, fileContent);
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Determines the correct previous tag for release notes generation.
|
|
11
|
+
* This function handles the complexity of mixed tag types (regular releases vs nightly releases).
|
|
12
|
+
*
|
|
13
|
+
* @param {string} currentTag - The current release tag (e.g., "v0.1.23")
|
|
14
|
+
* @returns {string|null} - The previous tag to compare against, or null if no suitable tag found
|
|
15
|
+
*/
|
|
16
|
+
export function getPreviousTag(currentTag) {
|
|
17
|
+
try {
|
|
18
|
+
// Parse the current tag to understand its type
|
|
19
|
+
const currentTagInfo = parseTag(currentTag);
|
|
20
|
+
if (!currentTagInfo) {
|
|
21
|
+
console.error(`Invalid current tag format: ${currentTag}`);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Find the appropriate previous tag based on the current tag type
|
|
26
|
+
let previousTag = null;
|
|
27
|
+
|
|
28
|
+
if (currentTagInfo.isNightly) {
|
|
29
|
+
// For nightly releases, find the last stable release
|
|
30
|
+
previousTag = findLastStableTag(currentTagInfo);
|
|
31
|
+
} else {
|
|
32
|
+
// For stable releases, find the previous stable release
|
|
33
|
+
previousTag = findPreviousStableTag(currentTagInfo);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return previousTag;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('Error getting previous tag:', error.message);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parses a tag string to extract version information and type
|
|
45
|
+
*/
|
|
46
|
+
function parseTag(tag) {
|
|
47
|
+
// Remove 'v' prefix if present
|
|
48
|
+
const cleanTag = tag.startsWith('v') ? tag.substring(1) : tag;
|
|
49
|
+
|
|
50
|
+
// Match pattern: X.Y.Z or X.Y.Z-prerelease
|
|
51
|
+
const match = cleanTag.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
|
|
52
|
+
if (!match) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const [, major, minor, patch, prerelease] = match;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
original: tag,
|
|
60
|
+
major: parseInt(major),
|
|
61
|
+
minor: parseInt(minor),
|
|
62
|
+
patch: parseInt(patch),
|
|
63
|
+
prerelease: prerelease || null,
|
|
64
|
+
isNightly: prerelease && prerelease.startsWith('nightly'),
|
|
65
|
+
isPreview: prerelease && prerelease.startsWith('preview'),
|
|
66
|
+
version: `${major}.${minor}.${patch}`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Finds the last stable tag for a nightly release
|
|
72
|
+
* Assumes version numbers are incremental and checks backwards from current version
|
|
73
|
+
*/
|
|
74
|
+
function findLastStableTag(currentTagInfo) {
|
|
75
|
+
// For nightly releases, find the stable version of the same version number first
|
|
76
|
+
const baseVersion = `v${currentTagInfo.version}`;
|
|
77
|
+
|
|
78
|
+
// Check if the stable version of the current version exists
|
|
79
|
+
if (tagExists(baseVersion)) {
|
|
80
|
+
return baseVersion;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// If not, look for the previous stable versions by decrementing version numbers
|
|
84
|
+
let { major, minor, patch } = currentTagInfo;
|
|
85
|
+
|
|
86
|
+
// Try decrementing patch version first
|
|
87
|
+
while (patch > 0) {
|
|
88
|
+
patch--;
|
|
89
|
+
const candidateTag = `v${major}.${minor}.${patch}`;
|
|
90
|
+
if (tagExists(candidateTag)) {
|
|
91
|
+
return candidateTag;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Try decrementing minor version
|
|
96
|
+
while (minor > 0) {
|
|
97
|
+
minor--;
|
|
98
|
+
patch = 999; // Start from a high patch number and work backwards
|
|
99
|
+
while (patch >= 0) {
|
|
100
|
+
const candidateTag = `v${major}.${minor}.${patch}`;
|
|
101
|
+
if (tagExists(candidateTag)) {
|
|
102
|
+
return candidateTag;
|
|
103
|
+
}
|
|
104
|
+
patch--;
|
|
105
|
+
// Don't check too many patch versions to avoid infinite loops
|
|
106
|
+
if (patch < 0) break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Try decrementing major version
|
|
111
|
+
while (major > 0) {
|
|
112
|
+
major--;
|
|
113
|
+
minor = 999; // Start from a high minor number and work backwards
|
|
114
|
+
while (minor >= 0) {
|
|
115
|
+
patch = 999;
|
|
116
|
+
while (patch >= 0) {
|
|
117
|
+
const candidateTag = `v${major}.${minor}.${patch}`;
|
|
118
|
+
if (tagExists(candidateTag)) {
|
|
119
|
+
return candidateTag;
|
|
120
|
+
}
|
|
121
|
+
patch--;
|
|
122
|
+
if (patch < 0) break;
|
|
123
|
+
}
|
|
124
|
+
minor--;
|
|
125
|
+
if (minor < 0) break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Finds the previous stable tag for a stable release
|
|
134
|
+
* Assumes version numbers are incremental and checks backwards from current version
|
|
135
|
+
*/
|
|
136
|
+
function findPreviousStableTag(currentTagInfo) {
|
|
137
|
+
let { major, minor, patch } = currentTagInfo;
|
|
138
|
+
|
|
139
|
+
// Try decrementing patch version first
|
|
140
|
+
while (patch > 0) {
|
|
141
|
+
patch--;
|
|
142
|
+
const candidateTag = `v${major}.${minor}.${patch}`;
|
|
143
|
+
if (tagExists(candidateTag)) {
|
|
144
|
+
return candidateTag;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Try decrementing minor version
|
|
149
|
+
while (minor > 0) {
|
|
150
|
+
minor--;
|
|
151
|
+
patch = 999; // Start from a high patch number and work backwards
|
|
152
|
+
while (patch >= 0) {
|
|
153
|
+
const candidateTag = `v${major}.${minor}.${patch}`;
|
|
154
|
+
if (tagExists(candidateTag)) {
|
|
155
|
+
return candidateTag;
|
|
156
|
+
}
|
|
157
|
+
patch--;
|
|
158
|
+
// Don't check too many patch versions to avoid infinite loops
|
|
159
|
+
if (patch < 0) break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Try decrementing major version
|
|
164
|
+
while (major > 0) {
|
|
165
|
+
major--;
|
|
166
|
+
minor = 999; // Start from a high minor number and work backwards
|
|
167
|
+
while (minor >= 0) {
|
|
168
|
+
patch = 999;
|
|
169
|
+
while (patch >= 0) {
|
|
170
|
+
const candidateTag = `v${major}.${minor}.${patch}`;
|
|
171
|
+
if (tagExists(candidateTag)) {
|
|
172
|
+
return candidateTag;
|
|
173
|
+
}
|
|
174
|
+
patch--;
|
|
175
|
+
if (patch < 0) break;
|
|
176
|
+
}
|
|
177
|
+
minor--;
|
|
178
|
+
if (minor < 0) break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Checks if a git tag exists
|
|
187
|
+
*/
|
|
188
|
+
function tagExists(tag) {
|
|
189
|
+
try {
|
|
190
|
+
execSync(`git rev-parse --verify ${tag}`, { stdio: 'ignore' });
|
|
191
|
+
return true;
|
|
192
|
+
} catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// CLI usage
|
|
198
|
+
if (process.argv[1] === new URL(import.meta.url).pathname) {
|
|
199
|
+
const currentTag = process.argv[2];
|
|
200
|
+
|
|
201
|
+
if (!currentTag) {
|
|
202
|
+
console.error('Usage: node get-previous-tag.js <current-tag>');
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const previousTag = getPreviousTag(currentTag);
|
|
207
|
+
if (previousTag) {
|
|
208
|
+
console.log(previousTag);
|
|
209
|
+
} else {
|
|
210
|
+
console.error('No suitable previous tag found');
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
function getPackageVersion() {
|
|
12
|
+
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
|
|
13
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
14
|
+
return packageJson.version;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function incrementPatchVersion(version) {
|
|
18
|
+
const parts = version.split('.');
|
|
19
|
+
const major = parseInt(parts[0]);
|
|
20
|
+
const minor = parseInt(parts[1]);
|
|
21
|
+
const patch = parseInt(parts[2].split('-')[0]); // Handle pre-release versions
|
|
22
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getLatestNightlyCount() {
|
|
26
|
+
try {
|
|
27
|
+
// Try to get the latest nightly tag from git to determine the counter
|
|
28
|
+
const currentVersion = getPackageVersion();
|
|
29
|
+
const nextVersion = incrementPatchVersion(currentVersion);
|
|
30
|
+
const tags = execSync(`git tag -l "v${nextVersion}-nightly.*"`)
|
|
31
|
+
.toString()
|
|
32
|
+
.trim();
|
|
33
|
+
|
|
34
|
+
if (!tags) return 0;
|
|
35
|
+
|
|
36
|
+
const nightlyTags = tags.split('\n').filter(Boolean);
|
|
37
|
+
const counts = nightlyTags.map((tag) => {
|
|
38
|
+
const match = tag.match(/nightly\.(\d+)$/);
|
|
39
|
+
return match ? parseInt(match[1]) : 0;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return Math.max(...counts, -1) + 1;
|
|
43
|
+
} catch (_error) {
|
|
44
|
+
// If we can't get tags, start from 0
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getNightlyTagName() {
|
|
50
|
+
const version = getPackageVersion();
|
|
51
|
+
const nextVersion = incrementPatchVersion(version);
|
|
52
|
+
const nightlyCount = getLatestNightlyCount();
|
|
53
|
+
|
|
54
|
+
return `v${nextVersion}-nightly.${nightlyCount}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getReleaseVersion() {
|
|
58
|
+
const isNightly = process.env.IS_NIGHTLY === 'true';
|
|
59
|
+
const manualVersion = process.env.MANUAL_VERSION;
|
|
60
|
+
|
|
61
|
+
let releaseTag;
|
|
62
|
+
|
|
63
|
+
if (isNightly) {
|
|
64
|
+
console.error('Calculating next nightly version...');
|
|
65
|
+
releaseTag = getNightlyTagName();
|
|
66
|
+
} else if (manualVersion) {
|
|
67
|
+
console.error(`Using manual version: ${manualVersion}`);
|
|
68
|
+
releaseTag = manualVersion;
|
|
69
|
+
} else {
|
|
70
|
+
throw new Error(
|
|
71
|
+
'Error: No version specified and this is not a nightly release.',
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!releaseTag) {
|
|
76
|
+
throw new Error('Error: Version could not be determined.');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!releaseTag.startsWith('v')) {
|
|
80
|
+
console.error("Version is missing 'v' prefix. Prepending it.");
|
|
81
|
+
releaseTag = `v${releaseTag}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (releaseTag.includes('+')) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
'Error: Versions with build metadata (+) are not supported for releases. Please use a pre-release version (e.g., v1.2.3-alpha.4) instead.',
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!releaseTag.match(/^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$/)) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
'Error: Version must be in the format vX.Y.Z or vX.Y.Z-prerelease',
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const releaseVersion = releaseTag.substring(1);
|
|
97
|
+
let npmTag = 'latest';
|
|
98
|
+
if (releaseVersion.includes('-')) {
|
|
99
|
+
const prereleasePart = releaseVersion.split('-')[1];
|
|
100
|
+
npmTag = prereleasePart.split('.')[0];
|
|
101
|
+
|
|
102
|
+
// Ensure nightly releases use 'nightly' tag, not 'latest'
|
|
103
|
+
if (npmTag === 'nightly') {
|
|
104
|
+
npmTag = 'nightly';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { releaseTag, releaseVersion, npmTag };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (process.argv[1] === new URL(import.meta.url).pathname) {
|
|
112
|
+
try {
|
|
113
|
+
const versions = getReleaseVersion();
|
|
114
|
+
console.log(JSON.stringify(versions));
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error(error.message);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|