mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-20 11:19:56 +08:00
fix: split update check cache TTL + add --force flag
UP_TO_DATE cache now expires after 60 min (was 720 min / 12 hours). UPGRADE_AVAILABLE keeps 720 min TTL to keep nagging. --force flag deletes cache before checking, used by /gstack-upgrade standalone invocation to always get a fresh result from GitHub. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,11 @@ SNOOZE_FILE="$STATE_DIR/update-snoozed"
|
|||||||
VERSION_FILE="$GSTACK_DIR/VERSION"
|
VERSION_FILE="$GSTACK_DIR/VERSION"
|
||||||
REMOTE_URL="${GSTACK_REMOTE_URL:-https://raw.githubusercontent.com/garrytan/gstack/main/VERSION}"
|
REMOTE_URL="${GSTACK_REMOTE_URL:-https://raw.githubusercontent.com/garrytan/gstack/main/VERSION}"
|
||||||
|
|
||||||
|
# ─── Force flag (busts cache for standalone /gstack-upgrade) ──
|
||||||
|
if [ "${1:-}" = "--force" ]; then
|
||||||
|
rm -f "$CACHE_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
# ─── Step 0: Check if updates are disabled ────────────────────
|
# ─── Step 0: Check if updates are disabled ────────────────────
|
||||||
_UC=$("$GSTACK_DIR/bin/gstack-config" get update_check 2>/dev/null || true)
|
_UC=$("$GSTACK_DIR/bin/gstack-config" get update_check 2>/dev/null || true)
|
||||||
if [ "$_UC" = "false" ]; then
|
if [ "$_UC" = "false" ]; then
|
||||||
@@ -97,24 +102,27 @@ if [ -f "$MARKER_FILE" ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ─── Step 3: Check cache freshness (12h = 720 min) ──────────
|
# ─── Step 3: Check cache freshness ──────────────────────────
|
||||||
|
# UP_TO_DATE: 60 min TTL (detect new releases quickly)
|
||||||
|
# UPGRADE_AVAILABLE: 720 min TTL (keep nagging)
|
||||||
if [ -f "$CACHE_FILE" ]; then
|
if [ -f "$CACHE_FILE" ]; then
|
||||||
# Cache is fresh if modified within 720 minutes
|
CACHED="$(cat "$CACHE_FILE" 2>/dev/null || true)"
|
||||||
STALE=$(find "$CACHE_FILE" -mmin +720 2>/dev/null || true)
|
case "$CACHED" in
|
||||||
if [ -z "$STALE" ]; then
|
UP_TO_DATE*) CACHE_TTL=60 ;;
|
||||||
# Cache is fresh — read it
|
UPGRADE_AVAILABLE*) CACHE_TTL=720 ;;
|
||||||
CACHED="$(cat "$CACHE_FILE" 2>/dev/null || true)"
|
*) CACHE_TTL=0 ;; # corrupt → force re-fetch
|
||||||
|
esac
|
||||||
|
|
||||||
|
STALE=$(find "$CACHE_FILE" -mmin +$CACHE_TTL 2>/dev/null || true)
|
||||||
|
if [ -z "$STALE" ] && [ "$CACHE_TTL" -gt 0 ]; then
|
||||||
case "$CACHED" in
|
case "$CACHED" in
|
||||||
UP_TO_DATE*)
|
UP_TO_DATE*)
|
||||||
# Verify local version still matches cached version
|
|
||||||
CACHED_VER="$(echo "$CACHED" | awk '{print $2}')"
|
CACHED_VER="$(echo "$CACHED" | awk '{print $2}')"
|
||||||
if [ "$CACHED_VER" = "$LOCAL" ]; then
|
if [ "$CACHED_VER" = "$LOCAL" ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
# Local version changed — fall through to re-check
|
|
||||||
;;
|
;;
|
||||||
UPGRADE_AVAILABLE*)
|
UPGRADE_AVAILABLE*)
|
||||||
# Verify local version still matches cached old version
|
|
||||||
CACHED_OLD="$(echo "$CACHED" | awk '{print $2}')"
|
CACHED_OLD="$(echo "$CACHED" | awk '{print $2}')"
|
||||||
if [ "$CACHED_OLD" = "$LOCAL" ]; then
|
if [ "$CACHED_OLD" = "$LOCAL" ]; then
|
||||||
CACHED_NEW="$(echo "$CACHED" | awk '{print $3}')"
|
CACHED_NEW="$(echo "$CACHED" | awk '{print $3}')"
|
||||||
@@ -124,7 +132,6 @@ if [ -f "$CACHE_FILE" ]; then
|
|||||||
echo "$CACHED"
|
echo "$CACHED"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
# Local version changed (manual upgrade?) — fall through to re-check
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
||||||
import { mkdtempSync, writeFileSync, rmSync, existsSync, readFileSync, mkdirSync, symlinkSync } from 'fs';
|
import { mkdtempSync, writeFileSync, rmSync, existsSync, readFileSync, mkdirSync, symlinkSync, utimesSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { tmpdir } from 'os';
|
import { tmpdir } from 'os';
|
||||||
|
|
||||||
@@ -16,8 +16,8 @@ const SCRIPT = join(import.meta.dir, '..', '..', 'bin', 'gstack-update-check');
|
|||||||
let gstackDir: string;
|
let gstackDir: string;
|
||||||
let stateDir: string;
|
let stateDir: string;
|
||||||
|
|
||||||
function run(extraEnv: Record<string, string> = {}) {
|
function run(extraEnv: Record<string, string> = {}, args: string[] = []) {
|
||||||
const result = Bun.spawnSync(['bash', SCRIPT], {
|
const result = Bun.spawnSync(['bash', SCRIPT, ...args], {
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
GSTACK_DIR: gstackDir,
|
GSTACK_DIR: gstackDir,
|
||||||
@@ -412,4 +412,56 @@ describe('gstack-update-check', () => {
|
|||||||
expect(exitCode).toBe(0);
|
expect(exitCode).toBe(0);
|
||||||
expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
|
expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── --force flag tests ──────────────────────────────────────
|
||||||
|
|
||||||
|
test('--force busts fresh UP_TO_DATE cache', () => {
|
||||||
|
writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
|
||||||
|
writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.4.0\n');
|
||||||
|
writeFileSync(join(stateDir, 'last-update-check'), 'UP_TO_DATE 0.3.3');
|
||||||
|
|
||||||
|
// Without --force: cache hit, silent
|
||||||
|
const cached = run();
|
||||||
|
expect(cached.stdout).toBe('');
|
||||||
|
|
||||||
|
// With --force: cache busted, re-fetches, finds upgrade
|
||||||
|
const forced = run({}, ['--force']);
|
||||||
|
expect(forced.exitCode).toBe(0);
|
||||||
|
expect(forced.stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('--force busts fresh UPGRADE_AVAILABLE cache', () => {
|
||||||
|
writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
|
||||||
|
writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.3.3\n');
|
||||||
|
writeFileSync(join(stateDir, 'last-update-check'), 'UPGRADE_AVAILABLE 0.3.3 0.4.0');
|
||||||
|
|
||||||
|
// Without --force: cache hit, outputs stale upgrade
|
||||||
|
const cached = run();
|
||||||
|
expect(cached.stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
|
||||||
|
|
||||||
|
// With --force: cache busted, re-fetches, now up to date
|
||||||
|
const forced = run({}, ['--force']);
|
||||||
|
expect(forced.exitCode).toBe(0);
|
||||||
|
expect(forced.stdout).toBe('');
|
||||||
|
const cache = readFileSync(join(stateDir, 'last-update-check'), 'utf-8');
|
||||||
|
expect(cache).toContain('UP_TO_DATE');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Split TTL tests ─────────────────────────────────────────
|
||||||
|
|
||||||
|
test('UP_TO_DATE cache expires after 60 min (not 720)', () => {
|
||||||
|
writeFileSync(join(gstackDir, 'VERSION'), '0.3.3\n');
|
||||||
|
writeFileSync(join(gstackDir, 'REMOTE_VERSION'), '0.4.0\n');
|
||||||
|
writeFileSync(join(stateDir, 'last-update-check'), 'UP_TO_DATE 0.3.3');
|
||||||
|
|
||||||
|
// Set cache mtime to 90 minutes ago (past 60-min TTL)
|
||||||
|
const ninetyMinAgo = new Date(Date.now() - 90 * 60 * 1000);
|
||||||
|
const cachePath = join(stateDir, 'last-update-check');
|
||||||
|
utimesSync(cachePath, ninetyMinAgo, ninetyMinAgo);
|
||||||
|
|
||||||
|
// Cache should be stale at 60-min TTL, re-fetches and finds upgrade
|
||||||
|
const { exitCode, stdout } = run();
|
||||||
|
expect(exitCode).toBe(0);
|
||||||
|
expect(stdout).toBe('UPGRADE_AVAILABLE 0.3.3 0.4.0');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user