|
1 | 1 | #!/bin/bash |
2 | | -# sync.sh - Fetch centralized scripts, commands, and rules from esolia.github |
| 2 | +# sync.sh — Bootstrap wrapper for sync.ts |
| 3 | +# |
| 4 | +# This is a thin wrapper. The real logic and file lists live in sync.ts. |
3 | 5 | # |
4 | 6 | # Usage: |
5 | 7 | # curl -sSfL https://raw.githubusercontent.com/eSolia/.github/main/scripts/sync.sh | bash |
6 | | -# ./scripts/shared/sync.sh # Re-sync (after initial fetch) |
7 | | -# ./scripts/shared/sync.sh --check # Check if scripts are up-to-date |
8 | | -# ./scripts/shared/sync.sh --ref v1.0.0 # Pin to a specific tag/SHA |
9 | | -# ./scripts/shared/sync.sh --scripts-only # Only sync scripts (skip commands/rules) |
10 | | -# |
11 | | -# Downloads centralized scripts into scripts/shared/, shared Claude commands |
12 | | -# into .claude/commands/, and shared rules into .claude/rules/. |
| 8 | +# ./scripts/shared/sync.sh # Re-sync (delegates to sync.ts) |
| 9 | +# ./scripts/shared/sync.sh --check # Check for updates |
| 10 | +# ./scripts/shared/sync.sh --ref v1.0.0 # Pin to a specific tag/SHA |
13 | 11 |
|
14 | 12 | set -e |
15 | 13 |
|
16 | 14 | # ════════════════════════════════════════════════════════════════════════════ |
17 | | -# Configuration |
18 | | -# ════════════════════════════════════════════════════════════════════════════ |
19 | | - |
20 | | -REPO_OWNER="eSolia" |
21 | | -REPO_NAME=".github" |
22 | | -DEFAULT_REF="main" |
23 | | -REF="$DEFAULT_REF" |
24 | | -CHECK_ONLY=false |
25 | | -SCRIPTS_ONLY=false |
26 | | - |
27 | | -# Scripts to sync (source path in esolia.github -> local path under scripts/shared/) |
28 | | -SYNC_SCRIPTS=( |
29 | | - "scripts/lib/common.sh:lib/common.sh" |
30 | | - "scripts/bump-version.sh:bump-version.sh" |
31 | | - "scripts/update-wrangler.sh:update-wrangler.sh" |
32 | | - "scripts/audit-backpressure.sh:audit-backpressure.sh" |
33 | | - "scripts/asvs-check.ts:asvs-check.ts" |
34 | | - "scripts/sync.sh:sync.sh" |
35 | | - "scripts/sync.ts:sync.ts" |
36 | | - "scripts/submit-bing.mts:submit-bing.mts" |
37 | | - "scripts/cross-post-devto.mts:cross-post-devto.mts" |
38 | | - "scripts/cross-post-qiita.mts:cross-post-qiita.mts" |
39 | | -) |
40 | | - |
41 | | -# Shared commands to sync (source -> .claude/commands/) |
42 | | -SYNC_COMMANDS=( |
43 | | - ".claude/shared-commands/backpressure-review.md:backpressure-review.md" |
44 | | - ".claude/shared-commands/seo-setup.md:seo-setup.md" |
45 | | - ".claude/shared-commands/seo-report.md:seo-report.md" |
46 | | - ".claude/shared-commands/checkpoint.md:checkpoint.md" |
47 | | - ".claude/shared-commands/commit-style.md:commit-style.md" |
48 | | - ".claude/shared-commands/dev/d1-health.md:dev/d1-health.md" |
49 | | - ".claude/shared-commands/dev/preflight.md:dev/preflight.md" |
50 | | - ".claude/shared-commands/dev/svelte-review.md:dev/svelte-review.md" |
51 | | - ".claude/shared-commands/security/audit-github-actions.md:security/audit-github-actions.md" |
52 | | - ".claude/shared-commands/security/harden-github-org.md:security/harden-github-org.md" |
53 | | - ".claude/shared-commands/standards/check.md:standards/check.md" |
54 | | - ".claude/shared-commands/standards/list.md:standards/list.md" |
55 | | - ".claude/shared-commands/standards/search.md:standards/search.md" |
56 | | - ".claude/shared-commands/standards/writing.md:standards/writing.md" |
57 | | -) |
58 | | - |
59 | | -# Shared rules to sync (source -> .claude/rules/) |
60 | | -SYNC_RULES=( |
61 | | - ".claude/shared-rules/backpressure-verify.md:backpressure-verify.md" |
62 | | - ".claude/shared-rules/d1-maintenance.md:d1-maintenance.md" |
63 | | - ".claude/shared-rules/mermaid-diagrams.md:mermaid-diagrams.md" |
64 | | -) |
65 | | - |
66 | | -# Colors (inline — can't source common.sh before it's downloaded) |
67 | | -RED='\033[0;31m' |
68 | | -GREEN='\033[0;32m' |
69 | | -YELLOW='\033[1;33m' |
70 | | -BLUE='\033[0;34m' |
71 | | -CYAN='\033[0;36m' |
72 | | -BOLD='\033[1m' |
73 | | -NC='\033[0m' |
74 | | - |
75 | | -print_step() { echo -e "${BLUE}==>${NC} $1"; } |
76 | | -print_success() { echo -e "${GREEN} ✓${NC} $1"; } |
77 | | -print_warning() { echo -e "${YELLOW} ⚠${NC} $1"; } |
78 | | -print_error() { echo -e "${RED} ✗${NC} $1"; } |
79 | | -print_info() { echo -e "${CYAN} ℹ${NC} $1"; } |
80 | | - |
81 | | -# ════════════════════════════════════════════════════════════════════════════ |
82 | | -# Parse Arguments |
83 | | -# ════════════════════════════════════════════════════════════════════════════ |
84 | | - |
85 | | -while [[ $# -gt 0 ]]; do |
86 | | - case "$1" in |
87 | | - --ref) REF="$2"; shift 2 ;; |
88 | | - --check) CHECK_ONLY=true; shift ;; |
89 | | - --scripts-only) SCRIPTS_ONLY=true; shift ;; |
90 | | - --help|-h) |
91 | | - echo "Usage: $0 [--ref <tag-or-sha>] [--check] [--scripts-only]" |
92 | | - echo "" |
93 | | - echo "Options:" |
94 | | - echo " --ref <ref> Git ref to fetch from (default: main)" |
95 | | - echo " --check Check if local scripts match remote (exit 1 if stale)" |
96 | | - echo " --scripts-only Only sync scripts (skip commands and rules)" |
97 | | - echo " --help Show this help" |
98 | | - exit 0 |
99 | | - ;; |
100 | | - *) |
101 | | - print_error "Unknown option: $1" |
102 | | - exit 1 |
103 | | - ;; |
104 | | - esac |
105 | | -done |
106 | | - |
107 | | -# ════════════════════════════════════════════════════════════════════════════ |
108 | | -# Find project root |
| 15 | +# Find project root and shared directory |
109 | 16 | # ════════════════════════════════════════════════════════════════════════════ |
110 | 17 |
|
111 | 18 | PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) |
112 | 19 | SHARED_DIR="$PROJECT_ROOT/scripts/shared" |
113 | | -SCRIPTVERSION_FILE="$SHARED_DIR/.scriptversion" |
114 | | - |
115 | | -# ════════════════════════════════════════════════════════════════════════════ |
116 | | -# Check mode |
117 | | -# ════════════════════════════════════════════════════════════════════════════ |
118 | | - |
119 | | -if [ "$CHECK_ONLY" = true ]; then |
120 | | - if [ ! -f "$SCRIPTVERSION_FILE" ]; then |
121 | | - print_error "No .scriptversion found — scripts have never been synced" |
122 | | - exit 1 |
123 | | - fi |
124 | | - |
125 | | - LOCAL_COMMIT=$(grep '^commit=' "$SCRIPTVERSION_FILE" 2>/dev/null | cut -d= -f2) |
126 | | - LOCAL_FETCHED=$(grep '^fetched=' "$SCRIPTVERSION_FILE" 2>/dev/null | cut -d= -f2) |
127 | | - LOCAL_REF=$(grep '^ref=' "$SCRIPTVERSION_FILE" 2>/dev/null | cut -d= -f2) |
128 | | - |
129 | | - print_info "Local: commit=$LOCAL_COMMIT ref=$LOCAL_REF fetched=$LOCAL_FETCHED" |
130 | | - |
131 | | - # Fetch remote HEAD for the ref |
132 | | - REMOTE_COMMIT=$(git ls-remote "https://github.com/$REPO_OWNER/$REPO_NAME.git" "$LOCAL_REF" 2>/dev/null | head -1 | cut -f1) |
133 | | - |
134 | | - if [ -z "$REMOTE_COMMIT" ]; then |
135 | | - print_warning "Could not fetch remote HEAD (network issue?)" |
136 | | - exit 1 |
137 | | - fi |
138 | | - |
139 | | - print_info "Remote: commit=$REMOTE_COMMIT ref=$LOCAL_REF" |
140 | | - |
141 | | - if [ "$LOCAL_COMMIT" = "$REMOTE_COMMIT" ]; then |
142 | | - print_success "Scripts are up-to-date" |
143 | | - exit 0 |
144 | | - else |
145 | | - print_warning "Scripts are stale — run sync.sh to update" |
146 | | - exit 1 |
147 | | - fi |
148 | | -fi |
| 20 | +SYNC_TS="$SHARED_DIR/sync.ts" |
149 | 21 |
|
150 | 22 | # ════════════════════════════════════════════════════════════════════════════ |
151 | | -# Sync mode |
| 23 | +# Bootstrap: download sync.ts if it doesn't exist yet |
152 | 24 | # ════════════════════════════════════════════════════════════════════════════ |
153 | 25 |
|
154 | | -echo "" |
155 | | -echo -e "${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}" |
156 | | -echo -e "${BOLD}║ Sync from eSolia/.github ║${NC}" |
157 | | -echo -e "${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}" |
158 | | -echo "" |
159 | | -print_info "Source: $REPO_OWNER/$REPO_NAME@$REF" |
160 | | -print_info "Target: $PROJECT_ROOT" |
161 | | -echo "" |
162 | | - |
163 | | -BASE_URL="https://raw.githubusercontent.com/$REPO_OWNER/$REPO_NAME/$REF" |
164 | | - |
165 | | -# Helper: download a list of files |
166 | | -download_files() { |
167 | | - local list_name=$1 |
168 | | - local target_dir="$2" |
169 | | - local label="$3" |
170 | | - |
171 | | - eval "local entries=(\"\${${list_name}[@]}\")" |
172 | | - for entry in "${entries[@]}"; do |
173 | | - src="${entry%%:*}" |
174 | | - dest="${entry##*:}" |
175 | | - url="$BASE_URL/$src" |
176 | | - target="$target_dir/$dest" |
177 | | - |
178 | | - # Ensure parent directory exists |
179 | | - mkdir -p "$(dirname "$target")" |
180 | | - |
181 | | - if curl -sSfL "$url" -o "$target" 2>/dev/null; then |
182 | | - # Make .sh files executable |
183 | | - [[ "$target" == *.sh ]] && chmod +x "$target" |
184 | | - print_success "$dest" |
185 | | - else |
186 | | - print_error "Failed to download $src" |
187 | | - exit 1 |
188 | | - fi |
189 | | - done |
190 | | -} |
191 | | - |
192 | | -# ── 1. Sync scripts ── |
193 | | -mkdir -p "$SHARED_DIR/lib" |
194 | | -print_step "Downloading scripts" |
195 | | -download_files SYNC_SCRIPTS "$SHARED_DIR" "scripts" |
196 | | -echo "" |
197 | | - |
198 | | -# ── 2. Sync commands and rules (unless --scripts-only) ── |
199 | | -if [ "$SCRIPTS_ONLY" = false ]; then |
200 | | - COMMANDS_DIR="$PROJECT_ROOT/.claude/commands" |
201 | | - RULES_DIR="$PROJECT_ROOT/.claude/rules" |
202 | | - |
203 | | - if [ ${#SYNC_COMMANDS[@]} -gt 0 ]; then |
204 | | - mkdir -p "$COMMANDS_DIR" |
205 | | - print_step "Syncing shared commands to .claude/commands/" |
206 | | - download_files SYNC_COMMANDS "$COMMANDS_DIR" "commands" |
207 | | - echo "" |
208 | | - fi |
209 | | - |
210 | | - if [ ${#SYNC_RULES[@]} -gt 0 ]; then |
211 | | - mkdir -p "$RULES_DIR" |
212 | | - print_step "Syncing shared rules to .claude/rules/" |
213 | | - download_files SYNC_RULES "$RULES_DIR" "rules" |
214 | | - echo "" |
215 | | - fi |
| 26 | +if [ ! -f "$SYNC_TS" ]; then |
| 27 | + echo "Bootstrapping: downloading sync.ts from eSolia/.github ..." |
| 28 | + mkdir -p "$SHARED_DIR" |
| 29 | + curl -sSfL "https://raw.githubusercontent.com/eSolia/.github/main/scripts/sync.ts" -o "$SYNC_TS" |
216 | 30 | fi |
217 | 31 |
|
218 | 32 | # ════════════════════════════════════════════════════════════════════════════ |
219 | | -# Write .scriptversion |
220 | | -# ════════════════════════════════════════════════════════════════════════════ |
221 | | - |
222 | | -REMOTE_COMMIT=$(git ls-remote "https://github.com/$REPO_OWNER/$REPO_NAME.git" "$REF" 2>/dev/null | head -1 | cut -f1) |
223 | | -REMOTE_COMMIT="${REMOTE_COMMIT:-unknown}" |
224 | | -FETCH_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) |
225 | | - |
226 | | -cat > "$SCRIPTVERSION_FILE" << EOF |
227 | | -commit=$REMOTE_COMMIT |
228 | | -fetched=$FETCH_TIME |
229 | | -ref=$REF |
230 | | -EOF |
231 | | - |
232 | | -print_success "Wrote .scriptversion (commit=${REMOTE_COMMIT:0:12})" |
233 | | -echo "" |
234 | | - |
235 | | -# ════════════════════════════════════════════════════════════════════════════ |
236 | | -# Create thin wrappers (only if they don't already exist) |
| 33 | +# Require Node.js |
237 | 34 | # ════════════════════════════════════════════════════════════════════════════ |
238 | 35 |
|
239 | | -SCRIPTS_DIR="$PROJECT_ROOT/scripts" |
240 | | - |
241 | | -create_wrapper() { |
242 | | - local name="$1" |
243 | | - local wrapper="$SCRIPTS_DIR/$name" |
244 | | - |
245 | | - if [ -f "$wrapper" ]; then |
246 | | - # Check if it's already a wrapper (contains "shared/" reference) |
247 | | - if grep -q 'shared/' "$wrapper" 2>/dev/null; then |
248 | | - print_info "$name wrapper already exists" |
249 | | - return |
250 | | - fi |
251 | | - # It's a local script — back it up |
252 | | - local backup="${wrapper}.local-backup" |
253 | | - cp "$wrapper" "$backup" |
254 | | - print_warning "Backed up existing $name to ${name}.local-backup" |
255 | | - fi |
256 | | - |
257 | | - cat > "$wrapper" << WRAPPER |
258 | | -#!/bin/bash |
259 | | -# Wrapper — delegates to centralized script from esolia.github |
260 | | -# To update: ./scripts/shared/sync.sh |
261 | | -SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)" |
262 | | -exec "\$SCRIPT_DIR/shared/$name" "\$@" |
263 | | -WRAPPER |
264 | | - |
265 | | - chmod +x "$wrapper" |
266 | | - print_success "Created wrapper: scripts/$name" |
267 | | -} |
268 | | - |
269 | | -print_step "Setting up wrappers" |
270 | | -create_wrapper "bump-version.sh" |
271 | | -create_wrapper "update-wrangler.sh" |
272 | | -create_wrapper "audit-backpressure.sh" |
273 | | -echo "" |
274 | | - |
275 | | -# ════════════════════════════════════════════════════════════════════════════ |
276 | | -# Add shared dir to .gitignore if not already there |
277 | | -# ════════════════════════════════════════════════════════════════════════════ |
278 | | - |
279 | | -GITIGNORE="$PROJECT_ROOT/.gitignore" |
280 | | -if [ -f "$GITIGNORE" ]; then |
281 | | - if ! grep -qF 'scripts/shared/' "$GITIGNORE" 2>/dev/null; then |
282 | | - print_step "Adding scripts/shared/ to .gitignore" |
283 | | - echo "" >> "$GITIGNORE" |
284 | | - echo "# Centralized scripts fetched from esolia.github" >> "$GITIGNORE" |
285 | | - echo "scripts/shared/" >> "$GITIGNORE" |
286 | | - print_success "Updated .gitignore" |
287 | | - echo "" |
288 | | - fi |
289 | | -else |
290 | | - print_info "No .gitignore found — consider adding scripts/shared/ to it" |
291 | | - echo "" |
| 36 | +if ! command -v node &>/dev/null; then |
| 37 | + echo "Error: Node.js is required. Install it from https://nodejs.org/" >&2 |
| 38 | + exit 1 |
292 | 39 | fi |
293 | 40 |
|
294 | 41 | # ════════════════════════════════════════════════════════════════════════════ |
295 | | -# Summary |
| 42 | +# Delegate to sync.ts |
296 | 43 | # ════════════════════════════════════════════════════════════════════════════ |
297 | 44 |
|
298 | | -echo -e "${BOLD}── Sync Complete ──────────────────────────────────────────────${NC}" |
299 | | -echo "" |
300 | | -echo " Synced from: $REPO_OWNER/$REPO_NAME@$REF" |
301 | | -echo " Commit: ${REMOTE_COMMIT:0:12}" |
302 | | -echo "" |
303 | | -echo " Scripts:" |
304 | | -echo " ./scripts/bump-version.sh <version> # Bump version + QC" |
305 | | -echo " ./scripts/bump-version.sh --qc-only # QC checks only" |
306 | | -echo " ./scripts/update-wrangler.sh # Update wrangler" |
307 | | -echo " ./scripts/audit-backpressure.sh # Backpressure audit" |
308 | | -echo " npx tsx scripts/shared/asvs-check.ts # ASVS compliance check" |
309 | | -echo " npx tsx scripts/shared/submit-bing.mts # Bing URL submission" |
310 | | -if [ "$SCRIPTS_ONLY" = false ]; then |
311 | | - echo "" |
312 | | - echo " Commands (in .claude/commands/):" |
313 | | - echo " /backpressure-review # SvelteKit quality review" |
314 | | - echo " /seo-setup # SEO checklist + setup" |
315 | | - echo " /checkpoint # Save session checkpoint" |
316 | | - echo " /commit-style # Conventional commit reference" |
317 | | - echo " /dev:d1-health # D1 database health audit" |
318 | | - echo " /dev:preflight # Show preflight checks" |
319 | | - echo " /dev:svelte-review # Svelte 5 best practices review" |
320 | | - echo " /security:audit-github-actions # GitHub Actions security audit" |
321 | | - echo " /security:harden-github-org # GitHub org hardening" |
322 | | - echo " /standards:check # Review code against standards" |
323 | | - echo " /standards:list # List all eSolia standards" |
324 | | - echo " /standards:search # Search standards by keyword" |
325 | | - echo " /standards:writing # Review content against writing guides" |
326 | | - echo "" |
327 | | - echo " Rules (in .claude/rules/):" |
328 | | - echo " backpressure-verify # Auto-verify after code changes" |
329 | | - echo " d1-maintenance # D1 database best practices" |
330 | | - echo " mermaid-diagrams # Compact diagram styling" |
331 | | -fi |
332 | | -echo "" |
333 | | -echo " Maintenance:" |
334 | | -echo " ./scripts/shared/sync.sh # Re-sync (bash)" |
335 | | -echo " npx tsx scripts/shared/sync.ts # Re-sync (cross-platform)" |
336 | | -echo " ./scripts/shared/sync.sh --check # Check for updates" |
337 | | -echo "" |
| 45 | +exec npx tsx "$SYNC_TS" "$@" |
0 commit comments