264 lines
8.0 KiB
Bash
Executable File
264 lines
8.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
KIT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
MERGE_COPILOT_INSTRUCTIONS=0
|
|
DRY_RUN=0
|
|
PROJECT_ROOT=""
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: bootstrap/install.sh [PROJECT_ROOT] [--merge-copilot-instructions] [--dry-run]
|
|
|
|
Options:
|
|
--merge-copilot-instructions Opt-in: safely merge .github/copilot-instructions.md
|
|
--dry-run Print planned changes without modifying files
|
|
-h, --help Show this help message
|
|
EOF
|
|
}
|
|
|
|
log_action() {
|
|
local message="$1"
|
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
echo "[dry-run] $message"
|
|
else
|
|
echo "$message"
|
|
fi
|
|
}
|
|
|
|
run_or_dry() {
|
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
log_action "$*"
|
|
return 0
|
|
fi
|
|
"$@"
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--merge-copilot-instructions)
|
|
MERGE_COPILOT_INSTRUCTIONS=1
|
|
shift
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=1
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
-*)
|
|
echo "Unknown option: $1" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
if [[ -z "$PROJECT_ROOT" ]]; then
|
|
PROJECT_ROOT="$1"
|
|
shift
|
|
else
|
|
echo "Unexpected extra argument: $1" >&2
|
|
usage >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
PROJECT_ROOT="${PROJECT_ROOT:-$(pwd)}"
|
|
|
|
MANAGED_BEGIN='<!-- BEGIN: myagent-kit (managed - do not edit) -->'
|
|
MANAGED_END='<!-- END: myagent-kit -->'
|
|
PROJECT_BEGIN='<!-- BEGIN: project-specific (safe to edit) -->'
|
|
PROJECT_END='<!-- END: project-specific -->'
|
|
|
|
count_occurrence() {
|
|
local needle="$1"
|
|
local file="$2"
|
|
grep -cF "$needle" "$file" || true
|
|
}
|
|
|
|
build_managed_block() {
|
|
local kit_file="$1"
|
|
{
|
|
printf '%s\n' "$MANAGED_BEGIN"
|
|
cat "$kit_file"
|
|
printf '\n%s\n' "$MANAGED_END"
|
|
}
|
|
}
|
|
|
|
append_project_section_if_missing() {
|
|
local file="$1"
|
|
local project_begin_count
|
|
local project_end_count
|
|
|
|
project_begin_count="$(count_occurrence "$PROJECT_BEGIN" "$file")"
|
|
project_end_count="$(count_occurrence "$PROJECT_END" "$file")"
|
|
|
|
if [[ "$project_begin_count" -eq 0 && "$project_end_count" -eq 0 ]]; then
|
|
cat >> "$file" <<EOF
|
|
|
|
$PROJECT_BEGIN
|
|
<!-- Add host-project-specific Copilot rules here. -->
|
|
$PROJECT_END
|
|
EOF
|
|
elif [[ "$project_begin_count" -ne "$project_end_count" ]]; then
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
validate_merged_copilot_file() {
|
|
local file="$1"
|
|
local managed_begin_count
|
|
local managed_end_count
|
|
local project_begin_count
|
|
local project_end_count
|
|
|
|
managed_begin_count="$(count_occurrence "$MANAGED_BEGIN" "$file")"
|
|
managed_end_count="$(count_occurrence "$MANAGED_END" "$file")"
|
|
project_begin_count="$(count_occurrence "$PROJECT_BEGIN" "$file")"
|
|
project_end_count="$(count_occurrence "$PROJECT_END" "$file")"
|
|
|
|
[[ "$managed_begin_count" -eq 1 ]] || return 1
|
|
[[ "$managed_end_count" -eq 1 ]] || return 1
|
|
[[ "$project_begin_count" -eq 1 ]] || return 1
|
|
[[ "$project_end_count" -eq 1 ]] || return 1
|
|
}
|
|
|
|
merge_copilot_instructions() {
|
|
local kit_file="$KIT_ROOT/base/.github/copilot-instructions.md"
|
|
local host_file="$PROJECT_ROOT/.github/copilot-instructions.md"
|
|
local backup_file="$host_file.bak"
|
|
local tmp_file
|
|
local managed_block
|
|
local managed_begin_count
|
|
local managed_end_count
|
|
|
|
if [[ "$MERGE_COPILOT_INSTRUCTIONS" -ne 1 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if [[ ! -f "$kit_file" ]]; then
|
|
log_action "Skipped Copilot merge: kit template not found at $kit_file"
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
if [[ ! -f "$host_file" ]]; then
|
|
log_action "Would create and initialize $host_file with managed + project-specific sections"
|
|
return 0
|
|
fi
|
|
|
|
managed_begin_count="$(count_occurrence "$MANAGED_BEGIN" "$host_file")"
|
|
managed_end_count="$(count_occurrence "$MANAGED_END" "$host_file")"
|
|
|
|
log_action "Would back up $host_file to $host_file.bak"
|
|
if [[ "$managed_begin_count" -eq 1 && "$managed_end_count" -eq 1 ]]; then
|
|
log_action "Would update managed myagent-kit section in $host_file"
|
|
elif [[ "$managed_begin_count" -eq 0 && "$managed_end_count" -eq 0 ]]; then
|
|
log_action "Would append managed myagent-kit section to $host_file"
|
|
else
|
|
log_action "Would fail: malformed managed marker pair in $host_file"
|
|
return 1
|
|
fi
|
|
|
|
local project_begin_count
|
|
local project_end_count
|
|
project_begin_count="$(count_occurrence "$PROJECT_BEGIN" "$host_file")"
|
|
project_end_count="$(count_occurrence "$PROJECT_END" "$host_file")"
|
|
if [[ "$project_begin_count" -eq 0 && "$project_end_count" -eq 0 ]]; then
|
|
log_action "Would append project-specific editable section to $host_file"
|
|
elif [[ "$project_begin_count" -ne "$project_end_count" ]]; then
|
|
log_action "Would fail: malformed project-specific marker pair in $host_file"
|
|
return 1
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
managed_block="$(build_managed_block "$kit_file")"
|
|
tmp_file="$(mktemp)"
|
|
|
|
if [[ ! -f "$host_file" ]]; then
|
|
{
|
|
printf '%s\n' "$managed_block"
|
|
printf '\n%s\n' "$PROJECT_BEGIN"
|
|
printf '<!-- Add host-project-specific Copilot rules here. -->\n'
|
|
printf '%s\n' "$PROJECT_END"
|
|
} > "$host_file"
|
|
validate_merged_copilot_file "$host_file"
|
|
log_action "Created and initialized $host_file"
|
|
rm -f "$tmp_file"
|
|
return 0
|
|
fi
|
|
|
|
cp "$host_file" "$backup_file"
|
|
managed_begin_count="$(count_occurrence "$MANAGED_BEGIN" "$host_file")"
|
|
managed_end_count="$(count_occurrence "$MANAGED_END" "$host_file")"
|
|
|
|
if [[ "$managed_begin_count" -eq 1 && "$managed_end_count" -eq 1 ]]; then
|
|
awk \
|
|
-v begin="$MANAGED_BEGIN" \
|
|
-v end="$MANAGED_END" \
|
|
-v block="$managed_block" '
|
|
BEGIN {
|
|
in_block = 0
|
|
replaced = 0
|
|
}
|
|
$0 == begin {
|
|
if (!replaced) {
|
|
print block
|
|
replaced = 1
|
|
}
|
|
in_block = 1
|
|
next
|
|
}
|
|
$0 == end {
|
|
in_block = 0
|
|
next
|
|
}
|
|
!in_block {
|
|
print
|
|
}
|
|
END {
|
|
if (!replaced) {
|
|
print ""
|
|
print block
|
|
}
|
|
}
|
|
' "$host_file" > "$tmp_file"
|
|
elif [[ "$managed_begin_count" -eq 0 && "$managed_end_count" -eq 0 ]]; then
|
|
{
|
|
cat "$host_file"
|
|
printf '\n%s\n' "$managed_block"
|
|
} > "$tmp_file"
|
|
else
|
|
echo "Malformed managed marker pair in $host_file" >&2
|
|
cp "$backup_file" "$host_file"
|
|
rm -f "$tmp_file"
|
|
return 1
|
|
fi
|
|
|
|
mv "$tmp_file" "$host_file"
|
|
append_project_section_if_missing "$host_file"
|
|
validate_merged_copilot_file "$host_file"
|
|
log_action "Merged $host_file (backup: $backup_file)"
|
|
}
|
|
|
|
run_or_dry mkdir -p "$PROJECT_ROOT/.github/prompts" \
|
|
"$PROJECT_ROOT/.github/instructions" \
|
|
"$PROJECT_ROOT/.github/ISSUE_TEMPLATE"
|
|
|
|
run_or_dry cp "$KIT_ROOT/base/.github/prompts/"*.prompt.md "$PROJECT_ROOT/.github/prompts/"
|
|
run_or_dry cp "$KIT_ROOT/base/.github/instructions/"*.instructions.md "$PROJECT_ROOT/.github/instructions/"
|
|
run_or_dry cp "$KIT_ROOT/base/.github/ISSUE_TEMPLATE/"*.md "$PROJECT_ROOT/.github/ISSUE_TEMPLATE/"
|
|
if [[ -f "$KIT_ROOT/base/.github/pull_request_template.md" ]]; then
|
|
run_or_dry cp "$KIT_ROOT/base/.github/pull_request_template.md" "$PROJECT_ROOT/.github/pull_request_template.md"
|
|
fi
|
|
|
|
merge_copilot_instructions
|
|
|
|
log_action "Installed myagent-kit assets into $PROJECT_ROOT/.github"
|