mirror of https://github.com/garrytan/gstack.git
Merge add4d7f5ee into a3259400a3
This commit is contained in:
commit
07b1bce03e
39
setup
39
setup
|
|
@ -606,6 +606,44 @@ link_claude_root_skill_alias() {
|
||||||
_print_windows_copy_note_once
|
_print_windows_copy_note_once
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Claude skill bodies still reference ~/.claude/skills/gstack/ for runtime
|
||||||
|
# assets. If the checkout is installed as ~/.claude/skills/<other-name>, keep a
|
||||||
|
# runtime-only gstack alias so those paths resolve without rewriting generated
|
||||||
|
# SKILL.md files or dirtying the checkout.
|
||||||
|
ensure_claude_runtime_alias() {
|
||||||
|
local gstack_dir="$1"
|
||||||
|
local skills_dir="$2"
|
||||||
|
local install_name
|
||||||
|
local install_parent
|
||||||
|
local alias
|
||||||
|
local existing_real
|
||||||
|
local claude_skills_dir
|
||||||
|
|
||||||
|
claude_skills_dir="$HOME/.claude/skills"
|
||||||
|
[ "$skills_dir" = "$claude_skills_dir" ] || return 0
|
||||||
|
|
||||||
|
install_name="$(basename "$gstack_dir")"
|
||||||
|
[ "$install_name" = "gstack" ] && return 0
|
||||||
|
|
||||||
|
install_parent="$(dirname "$gstack_dir")"
|
||||||
|
[ "$install_parent" = "$skills_dir" ] || return 0
|
||||||
|
|
||||||
|
alias="$skills_dir/gstack"
|
||||||
|
if [ -d "$alias" ] && [ ! -L "$alias" ]; then
|
||||||
|
existing_real=$(cd "$alias" 2>/dev/null && pwd -P || echo "")
|
||||||
|
if [ -n "$existing_real" ] && [ "$existing_real" != "$gstack_dir" ]; then
|
||||||
|
log " warning: $alias already exists as a separate install; not replacing it"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -L "$alias" ] || [ ! -e "$alias" ]; then
|
||||||
|
_link_or_copy "$gstack_dir" "$alias"
|
||||||
|
log " linked runtime alias: $alias -> $gstack_dir"
|
||||||
|
_print_windows_copy_note_once
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# ─── Helper: remove old unprefixed Claude skill entries ───────────────────────
|
# ─── Helper: remove old unprefixed Claude skill entries ───────────────────────
|
||||||
# Migration: when switching from flat names to gstack- prefixed names,
|
# Migration: when switching from flat names to gstack- prefixed names,
|
||||||
# clean up stale symlinks or directories that point into the gstack directory.
|
# clean up stale symlinks or directories that point into the gstack directory.
|
||||||
|
|
@ -982,6 +1020,7 @@ fi
|
||||||
|
|
||||||
if [ "$INSTALL_CLAUDE" -eq 1 ]; then
|
if [ "$INSTALL_CLAUDE" -eq 1 ]; then
|
||||||
if [ "$SKILLS_BASENAME" = "skills" ]; then
|
if [ "$SKILLS_BASENAME" = "skills" ]; then
|
||||||
|
ensure_claude_runtime_alias "$INSTALL_GSTACK_DIR" "$INSTALL_SKILLS_DIR"
|
||||||
# Clean up stale symlinks from the opposite prefix mode
|
# Clean up stale symlinks from the opposite prefix mode
|
||||||
if [ "$SKILL_PREFIX" -eq 1 ]; then
|
if [ "$SKILL_PREFIX" -eq 1 ]; then
|
||||||
cleanup_old_claude_symlinks "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"
|
cleanup_old_claude_symlinks "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
import { describe, test, expect } from 'bun:test';
|
||||||
|
import { spawnSync } from 'child_process';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as os from 'os';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
const ROOT = path.resolve(import.meta.dir, '..');
|
||||||
|
const SETUP = fs.readFileSync(path.join(ROOT, 'setup'), 'utf-8');
|
||||||
|
|
||||||
|
function extractFunction(src: string, name: string): string {
|
||||||
|
const start = src.indexOf(`${name}() {`);
|
||||||
|
if (start < 0) throw new Error(`Could not locate ${name}() in setup`);
|
||||||
|
const end = src.indexOf('\n}\n', start);
|
||||||
|
if (end < 0) throw new Error(`Could not locate end of ${name}() in setup`);
|
||||||
|
return src.slice(start, end + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('setup: Claude runtime alias for non-gstack install dirs', () => {
|
||||||
|
test('setup calls the runtime alias helper before linking Claude skills', () => {
|
||||||
|
const helperIdx = SETUP.indexOf('ensure_claude_runtime_alias "$INSTALL_GSTACK_DIR" "$INSTALL_SKILLS_DIR"');
|
||||||
|
const linkIdx = SETUP.indexOf('link_claude_skill_dirs "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR"');
|
||||||
|
|
||||||
|
expect(helperIdx).toBeGreaterThan(-1);
|
||||||
|
expect(linkIdx).toBeGreaterThan(-1);
|
||||||
|
expect(helperIdx).toBeLessThan(linkIdx);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.skipIf(process.platform === 'win32')('helper creates skills/gstack -> install dir when install basename is not gstack', () => {
|
||||||
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-runtime-alias-'));
|
||||||
|
try {
|
||||||
|
const skillsDir = path.join(tmp, '.claude', 'skills');
|
||||||
|
const installDir = path.join(skillsDir, 'gstack-dev');
|
||||||
|
fs.mkdirSync(installDir, { recursive: true });
|
||||||
|
|
||||||
|
const linkHelper = extractFunction(SETUP, '_link_or_copy');
|
||||||
|
const noteHelper = extractFunction(SETUP, '_print_windows_copy_note_once');
|
||||||
|
const aliasHelper = extractFunction(SETUP, 'ensure_claude_runtime_alias');
|
||||||
|
const script = `
|
||||||
|
set -e
|
||||||
|
HOME='${tmp}'
|
||||||
|
IS_WINDOWS=0
|
||||||
|
QUIET=0
|
||||||
|
_WINDOWS_COPY_NOTE_PRINTED=0
|
||||||
|
log() { echo "$@"; }
|
||||||
|
${linkHelper}
|
||||||
|
${noteHelper}
|
||||||
|
${aliasHelper}
|
||||||
|
ensure_claude_runtime_alias '${installDir}' '${skillsDir}'
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = spawnSync('bash', ['-c', script], { encoding: 'utf-8' });
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
const alias = path.join(skillsDir, 'gstack');
|
||||||
|
expect(fs.lstatSync(alias).isSymbolicLink()).toBe(true);
|
||||||
|
expect(fs.readlinkSync(alias)).toBe(installDir);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tmp, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.skipIf(process.platform === 'win32')('helper ignores non-Claude skills directories', () => {
|
||||||
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-runtime-alias-'));
|
||||||
|
try {
|
||||||
|
const skillsDir = path.join(tmp, 'skills');
|
||||||
|
const installDir = path.join(skillsDir, 'gstack-dev');
|
||||||
|
fs.mkdirSync(installDir, { recursive: true });
|
||||||
|
|
||||||
|
const linkHelper = extractFunction(SETUP, '_link_or_copy');
|
||||||
|
const noteHelper = extractFunction(SETUP, '_print_windows_copy_note_once');
|
||||||
|
const aliasHelper = extractFunction(SETUP, 'ensure_claude_runtime_alias');
|
||||||
|
const script = `
|
||||||
|
set -e
|
||||||
|
HOME='${tmp}'
|
||||||
|
IS_WINDOWS=0
|
||||||
|
QUIET=0
|
||||||
|
_WINDOWS_COPY_NOTE_PRINTED=0
|
||||||
|
log() { echo "$@"; }
|
||||||
|
${linkHelper}
|
||||||
|
${noteHelper}
|
||||||
|
${aliasHelper}
|
||||||
|
ensure_claude_runtime_alias '${installDir}' '${skillsDir}'
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = spawnSync('bash', ['-c', script], { encoding: 'utf-8' });
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
expect(fs.existsSync(path.join(skillsDir, 'gstack'))).toBe(false);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tmp, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.skipIf(process.platform === 'win32')('helper leaves canonical skills/gstack installs alone', () => {
|
||||||
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-runtime-alias-'));
|
||||||
|
try {
|
||||||
|
const skillsDir = path.join(tmp, '.claude', 'skills');
|
||||||
|
const installDir = path.join(skillsDir, 'gstack');
|
||||||
|
fs.mkdirSync(installDir, { recursive: true });
|
||||||
|
|
||||||
|
const linkHelper = extractFunction(SETUP, '_link_or_copy');
|
||||||
|
const noteHelper = extractFunction(SETUP, '_print_windows_copy_note_once');
|
||||||
|
const aliasHelper = extractFunction(SETUP, 'ensure_claude_runtime_alias');
|
||||||
|
const script = `
|
||||||
|
set -e
|
||||||
|
HOME='${tmp}'
|
||||||
|
IS_WINDOWS=0
|
||||||
|
QUIET=0
|
||||||
|
_WINDOWS_COPY_NOTE_PRINTED=0
|
||||||
|
log() { echo "$@"; }
|
||||||
|
${linkHelper}
|
||||||
|
${noteHelper}
|
||||||
|
${aliasHelper}
|
||||||
|
ensure_claude_runtime_alias '${installDir}' '${skillsDir}'
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = spawnSync('bash', ['-c', script], { encoding: 'utf-8' });
|
||||||
|
expect(result.status).toBe(0);
|
||||||
|
expect(fs.lstatSync(installDir).isDirectory()).toBe(true);
|
||||||
|
expect(fs.lstatSync(installDir).isSymbolicLink()).toBe(false);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tmp, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue