jekyll-theme-zer0 0.22.20 → 0.22.22
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +74 -4
- data/README.md +325 -40
- data/_data/README.md +1 -0
- data/_data/roadmap.yml +215 -0
- data/scripts/bin/install +717 -0
- data/scripts/bin/test +45 -2
- data/scripts/generate-roadmap.rb +200 -0
- data/scripts/generate-roadmap.sh +21 -0
- data/scripts/lib/install/README.md +63 -0
- data/scripts/lib/install/agents.sh +166 -0
- data/scripts/lib/install/ai/diagnose.sh +199 -0
- data/scripts/lib/install/ai/openai.sh +233 -0
- data/scripts/lib/install/ai/suggest.sh +182 -0
- data/scripts/lib/install/ai/wizard.sh +160 -0
- data/scripts/lib/install/config.sh +56 -0
- data/scripts/lib/install/deploy/README.md +52 -0
- data/scripts/lib/install/deploy/azure-swa.sh +50 -0
- data/scripts/lib/install/deploy/docker-prod.sh +71 -0
- data/scripts/lib/install/deploy/github-pages.sh +44 -0
- data/scripts/lib/install/deploy/registry.sh +190 -0
- data/scripts/lib/install/doctor.sh +301 -0
- data/scripts/lib/install/fs.sh +52 -0
- data/scripts/lib/install/logging.sh +33 -0
- data/scripts/lib/install/pages.sh +255 -0
- data/scripts/lib/install/platform.sh +71 -0
- data/scripts/lib/install/profile.sh +113 -0
- data/scripts/lib/install/template.sh +137 -0
- data/scripts/lib/install/upgrade.sh +184 -0
- data/scripts/lib/install/wizard_interactive.sh +189 -0
- metadata +27 -2
data/scripts/bin/install
ADDED
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/bin/install
|
|
3
|
+
#
|
|
4
|
+
# Canonical CLI entrypoint for jekyll-theme-zer0 installer.
|
|
5
|
+
# Sibling of scripts/bin/{build,release,test}.
|
|
6
|
+
#
|
|
7
|
+
# This is the Phase 2 dispatcher. Subcommands progressively delegate to
|
|
8
|
+
# scripts/lib/install/* modules. Until each subcommand's backing module
|
|
9
|
+
# lands (Phases 3-6), most subcommands print a clear "not yet wired"
|
|
10
|
+
# notice and exit non-zero.
|
|
11
|
+
#
|
|
12
|
+
# The legacy install.sh remains the supported entrypoint for `init`
|
|
13
|
+
# (the default subcommand). This shim translates the new subcommand
|
|
14
|
+
# surface into legacy flags where possible.
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
20
|
+
LEGACY_INSTALL="$REPO_ROOT/install.sh"
|
|
21
|
+
LIB_DIR="$REPO_ROOT/scripts/lib/install"
|
|
22
|
+
|
|
23
|
+
# Source minimal logging if available; otherwise inline shim.
|
|
24
|
+
if [[ -f "$LIB_DIR/logging.sh" ]]; then
|
|
25
|
+
# shellcheck source=../lib/install/logging.sh
|
|
26
|
+
source "$LIB_DIR/logging.sh"
|
|
27
|
+
else
|
|
28
|
+
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
|
29
|
+
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
|
30
|
+
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
|
31
|
+
log_warning() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
32
|
+
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Source profile loader (Phase 3). Falls back to a minimal stub when missing.
|
|
36
|
+
if [[ -f "$LIB_DIR/profile.sh" ]]; then
|
|
37
|
+
# shellcheck source=../lib/install/profile.sh
|
|
38
|
+
source "$LIB_DIR/profile.sh"
|
|
39
|
+
_HAS_PROFILE_LOADER=1
|
|
40
|
+
else
|
|
41
|
+
_HAS_PROFILE_LOADER=0
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Source deploy registry (Phase 4). Optional — list-targets/deploy degrade
|
|
45
|
+
# to a static help message when missing.
|
|
46
|
+
if [[ -f "$LIB_DIR/deploy/registry.sh" ]]; then
|
|
47
|
+
# shellcheck source=../lib/install/deploy/registry.sh
|
|
48
|
+
source "$LIB_DIR/deploy/registry.sh"
|
|
49
|
+
_HAS_DEPLOY_REGISTRY=1
|
|
50
|
+
else
|
|
51
|
+
_HAS_DEPLOY_REGISTRY=0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# Source agents copier (Phase 5). Optional.
|
|
55
|
+
if [[ -f "$LIB_DIR/agents.sh" ]]; then
|
|
56
|
+
# shellcheck source=../lib/install/agents.sh
|
|
57
|
+
source "$LIB_DIR/agents.sh"
|
|
58
|
+
_HAS_AGENTS_LIB=1
|
|
59
|
+
else
|
|
60
|
+
_HAS_AGENTS_LIB=0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Source AI modules (Phase 5). All optional and gated behind explicit flags.
|
|
64
|
+
_HAS_AI_LIBS=0
|
|
65
|
+
if [[ -f "$LIB_DIR/ai/openai.sh" ]]; then
|
|
66
|
+
# shellcheck source=../lib/install/ai/openai.sh
|
|
67
|
+
source "$LIB_DIR/ai/openai.sh"
|
|
68
|
+
[[ -f "$LIB_DIR/ai/wizard.sh" ]] && source "$LIB_DIR/ai/wizard.sh"
|
|
69
|
+
[[ -f "$LIB_DIR/ai/diagnose.sh" ]] && source "$LIB_DIR/ai/diagnose.sh"
|
|
70
|
+
[[ -f "$LIB_DIR/ai/suggest.sh" ]] && source "$LIB_DIR/ai/suggest.sh"
|
|
71
|
+
_HAS_AI_LIBS=1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# Source Phase 6 modules (doctor / upgrade / non-AI wizard). All optional.
|
|
75
|
+
_HAS_DOCTOR_LIB=0
|
|
76
|
+
[[ -f "$LIB_DIR/doctor.sh" ]] && { source "$LIB_DIR/doctor.sh"; _HAS_DOCTOR_LIB=1; }
|
|
77
|
+
_HAS_UPGRADE_LIB=0
|
|
78
|
+
[[ -f "$LIB_DIR/upgrade.sh" ]] && { source "$LIB_DIR/upgrade.sh"; _HAS_UPGRADE_LIB=1; }
|
|
79
|
+
_HAS_WIZARD_INTERACTIVE_LIB=0
|
|
80
|
+
[[ -f "$LIB_DIR/wizard_interactive.sh" ]] && { source "$LIB_DIR/wizard_interactive.sh"; _HAS_WIZARD_INTERACTIVE_LIB=1; }
|
|
81
|
+
|
|
82
|
+
# ---------- Help text -------------------------------------------------------
|
|
83
|
+
show_usage() {
|
|
84
|
+
cat <<'EOF'
|
|
85
|
+
🚀 jekyll-theme-zer0 installer (scripts/bin/install)
|
|
86
|
+
|
|
87
|
+
USAGE:
|
|
88
|
+
./scripts/bin/install <subcommand> [options] [target-dir]
|
|
89
|
+
|
|
90
|
+
SUBCOMMANDS:
|
|
91
|
+
init [target] Install / scaffold a site (default subcommand)
|
|
92
|
+
Maps to legacy install.sh with --full / --minimal /
|
|
93
|
+
--fork / --github / --remote selected via --profile.
|
|
94
|
+
|
|
95
|
+
wizard [target] Interactive guided setup. Add --ai for OpenAI-
|
|
96
|
+
powered config generation (requires OPENAI_API_KEY).
|
|
97
|
+
deploy <target> Configure a deployment target
|
|
98
|
+
(github-pages | azure-swa | docker-prod). Comma-
|
|
99
|
+
separate to install multiple in one run.
|
|
100
|
+
Use --ai-suggest to ask for a recommendation first.
|
|
101
|
+
diagnose [target] Analyze the last `jekyll build` output for known
|
|
102
|
+
errors. Add --ai for OpenAI-powered patch suggestions.
|
|
103
|
+
doctor [target] Environment + site health check (platform tools,
|
|
104
|
+
Docker, Ruby, gh CLI; --ai for OpenAI connectivity)
|
|
105
|
+
upgrade [target] Re-run installer over an existing site (refreshes
|
|
106
|
+
agent files + flags drifted workflows; never touches
|
|
107
|
+
user content). Use --from <ver> to override detection.
|
|
108
|
+
list-profiles Print available install profiles
|
|
109
|
+
list-targets Print available deploy targets
|
|
110
|
+
agents [target] Install AI agent guidance files (AGENTS.md +
|
|
111
|
+
.github/copilot-instructions.md + instructions/
|
|
112
|
+
+ prompts/). Add --cursor / --claude / --aider /
|
|
113
|
+
--all to include extra integrations.
|
|
114
|
+
version Print theme version
|
|
115
|
+
help Show this help
|
|
116
|
+
|
|
117
|
+
GLOBAL OPTIONS:
|
|
118
|
+
-p, --profile <name> Profile to apply: minimal|full|fork|github|remote
|
|
119
|
+
(default: full)
|
|
120
|
+
-f, --force Skip safety prompts
|
|
121
|
+
-n, --dry-run Print actions without executing (when supported)
|
|
122
|
+
-v, --verbose Enable verbose logging
|
|
123
|
+
-h, --help Show help for a subcommand
|
|
124
|
+
|
|
125
|
+
EXAMPLES:
|
|
126
|
+
./scripts/bin/install init # full install in CWD
|
|
127
|
+
./scripts/bin/install init --profile minimal /tmp/demo
|
|
128
|
+
./scripts/bin/install init --profile fork
|
|
129
|
+
./scripts/bin/install agents
|
|
130
|
+
./scripts/bin/install version
|
|
131
|
+
|
|
132
|
+
NOTES:
|
|
133
|
+
• This is the Phase 2 dispatcher. Subcommands marked "Phase N" emit a
|
|
134
|
+
clear notice and exit non-zero until that phase lands.
|
|
135
|
+
• The legacy install.sh remains the supported low-level entrypoint and
|
|
136
|
+
is invoked by `init` under the hood.
|
|
137
|
+
|
|
138
|
+
EOF
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# ---------- Helpers ---------------------------------------------------------
|
|
142
|
+
require_legacy_install() {
|
|
143
|
+
if [[ ! -x "$LEGACY_INSTALL" ]]; then
|
|
144
|
+
log_error "Cannot find executable install.sh at: $LEGACY_INSTALL"
|
|
145
|
+
exit 1
|
|
146
|
+
fi
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
print_phase_stub() {
|
|
150
|
+
local name="$1" phase="$2"
|
|
151
|
+
log_warning "Subcommand '$name' is not yet wired (planned for $phase)."
|
|
152
|
+
log_info "Track progress in scripts/lib/install/README.md (Roadmap)."
|
|
153
|
+
exit 2
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
print_version() {
|
|
157
|
+
local vfile="$REPO_ROOT/lib/jekyll-theme-zer0/version.rb"
|
|
158
|
+
if [[ -f "$vfile" ]]; then
|
|
159
|
+
# Extract the VERSION = "x.y.z" line
|
|
160
|
+
local v
|
|
161
|
+
v="$(grep -E 'VERSION\s*=' "$vfile" | head -n1 | sed -E 's/.*"([^"]+)".*/\1/')"
|
|
162
|
+
echo "jekyll-theme-zer0 ${v:-unknown}"
|
|
163
|
+
else
|
|
164
|
+
echo "jekyll-theme-zer0 (version file not found)"
|
|
165
|
+
fi
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
print_agents_index() {
|
|
169
|
+
local agents_file="$REPO_ROOT/AGENTS.md"
|
|
170
|
+
if [[ -f "$agents_file" ]]; then
|
|
171
|
+
log_info "AGENTS.md (cross-tool entry point):"
|
|
172
|
+
echo " $agents_file"
|
|
173
|
+
echo
|
|
174
|
+
log_info "Detailed Copilot instructions:"
|
|
175
|
+
echo " $REPO_ROOT/.github/copilot-instructions.md"
|
|
176
|
+
echo
|
|
177
|
+
log_info "File-scoped instruction sets:"
|
|
178
|
+
if [[ -d "$REPO_ROOT/.github/instructions" ]]; then
|
|
179
|
+
ls "$REPO_ROOT/.github/instructions"/*.instructions.md 2>/dev/null \
|
|
180
|
+
| sed "s#$REPO_ROOT/# #"
|
|
181
|
+
fi
|
|
182
|
+
else
|
|
183
|
+
log_warning "AGENTS.md not found at $agents_file"
|
|
184
|
+
fi
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# Run the `agents` subcommand: copy agent guidance into a target dir.
|
|
188
|
+
# Falls back to print_agents_index if no target given (or `--index` flag).
|
|
189
|
+
run_agents() {
|
|
190
|
+
local target="" with_cursor="" with_claude="" with_aider="" with_all="" force="" want_index=0
|
|
191
|
+
while [[ $# -gt 0 ]]; do
|
|
192
|
+
case "$1" in
|
|
193
|
+
--cursor) with_cursor="--cursor" ;;
|
|
194
|
+
--claude) with_claude="--claude" ;;
|
|
195
|
+
--aider) with_aider="--aider" ;;
|
|
196
|
+
--all) with_all="--all" ;;
|
|
197
|
+
-f|--force) force="--force" ;;
|
|
198
|
+
--index) want_index=1 ;;
|
|
199
|
+
-h|--help)
|
|
200
|
+
cat <<EOF
|
|
201
|
+
Usage: ./scripts/bin/install agents [target-dir] [options]
|
|
202
|
+
|
|
203
|
+
Copies AI agent guidance files from this theme into TARGET-DIR (defaults to CWD):
|
|
204
|
+
- AGENTS.md
|
|
205
|
+
- .github/copilot-instructions.md
|
|
206
|
+
- .github/instructions/*.md
|
|
207
|
+
- .github/prompts/*.md
|
|
208
|
+
|
|
209
|
+
Options:
|
|
210
|
+
--cursor Also copy .cursor/commands/*.md
|
|
211
|
+
--claude Also write CLAUDE.md stub
|
|
212
|
+
--aider Also write .aider.conf.yml
|
|
213
|
+
--all Shortcut for --cursor --claude --aider
|
|
214
|
+
--force, -f Overwrite existing files (default: skip + warn)
|
|
215
|
+
--index Print the agent guidance index (legacy behavior) and exit
|
|
216
|
+
|
|
217
|
+
Examples:
|
|
218
|
+
./scripts/bin/install agents # Copy core set into CWD
|
|
219
|
+
./scripts/bin/install agents /tmp/site --all
|
|
220
|
+
./scripts/bin/install agents --index # Just print where files live
|
|
221
|
+
EOF
|
|
222
|
+
exit 0 ;;
|
|
223
|
+
-*) log_error "Unknown option: $1"; exit 1 ;;
|
|
224
|
+
*)
|
|
225
|
+
if [[ -z "$target" ]]; then target="$1"
|
|
226
|
+
else log_error "Unexpected argument: $1"; exit 1
|
|
227
|
+
fi ;;
|
|
228
|
+
esac
|
|
229
|
+
shift
|
|
230
|
+
done
|
|
231
|
+
|
|
232
|
+
if [[ "$want_index" = "1" ]]; then
|
|
233
|
+
print_agents_index
|
|
234
|
+
return 0
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
if [[ "$_HAS_AGENTS_LIB" != "1" ]]; then
|
|
238
|
+
log_error "agents.sh module not found at $LIB_DIR/agents.sh"
|
|
239
|
+
exit 1
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
[[ -z "$target" ]] && target="$PWD"
|
|
243
|
+
agents_install "$target" "$REPO_ROOT" $with_cursor $with_claude $with_aider $with_all $force
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# Run the `wizard` subcommand. With --ai, delegates to wizard_ai_run.
|
|
247
|
+
# Without --ai, currently still routes to legacy install.sh --full as a
|
|
248
|
+
# scaffolding fallback (Phase 5 ships AI path; non-AI wizard is Phase 6).
|
|
249
|
+
run_wizard() {
|
|
250
|
+
local use_ai=0 auto_accept=0 target=""
|
|
251
|
+
while [[ $# -gt 0 ]]; do
|
|
252
|
+
case "$1" in
|
|
253
|
+
--ai) use_ai=1 ;;
|
|
254
|
+
--auto-accept) auto_accept=1 ;;
|
|
255
|
+
-h|--help)
|
|
256
|
+
cat <<EOF
|
|
257
|
+
Usage: ./scripts/bin/install wizard [target-dir] [--ai] [--auto-accept]
|
|
258
|
+
|
|
259
|
+
--ai Use OpenAI to generate site title, description, navigation,
|
|
260
|
+
and a welcome-post outline (requires OPENAI_API_KEY).
|
|
261
|
+
--auto-accept Skip confirmation prompts (for CI use).
|
|
262
|
+
|
|
263
|
+
Without --ai, runs an interactive prompt-based scaffold (no network calls).
|
|
264
|
+
The wizard auto-skips the doctor preflight when dispatching to 'init'.
|
|
265
|
+
EOF
|
|
266
|
+
exit 0 ;;
|
|
267
|
+
-*) log_error "Unknown option: $1"; exit 1 ;;
|
|
268
|
+
*)
|
|
269
|
+
if [[ -z "$target" ]]; then target="$1"
|
|
270
|
+
else log_error "Unexpected argument: $1"; exit 1
|
|
271
|
+
fi ;;
|
|
272
|
+
esac
|
|
273
|
+
shift
|
|
274
|
+
done
|
|
275
|
+
[[ -z "$target" ]] && target="$PWD"
|
|
276
|
+
|
|
277
|
+
if [[ "$use_ai" = "1" ]]; then
|
|
278
|
+
if [[ "$_HAS_AI_LIBS" != "1" ]]; then
|
|
279
|
+
log_error "AI modules not found at $LIB_DIR/ai/"
|
|
280
|
+
exit 1
|
|
281
|
+
fi
|
|
282
|
+
local extra=""
|
|
283
|
+
[[ "$auto_accept" = "1" ]] && extra="--auto-accept"
|
|
284
|
+
if wizard_ai_run "$target" "$REPO_ROOT" $extra; then
|
|
285
|
+
return 0
|
|
286
|
+
fi
|
|
287
|
+
log_warning "AI wizard failed — falling back to interactive scaffold."
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
# Non-AI interactive wizard (Phase 6). Falls back to legacy behavior
|
|
291
|
+
# if the module is missing.
|
|
292
|
+
if [[ "$_HAS_WIZARD_INTERACTIVE_LIB" = "1" ]]; then
|
|
293
|
+
local extra=""
|
|
294
|
+
[[ "$auto_accept" = "1" ]] && extra="--auto-accept"
|
|
295
|
+
wizard_interactive_run "$target" "$REPO_ROOT" $extra
|
|
296
|
+
return $?
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
log_warning "Interactive wizard module not available; running 'init --profile full' instead."
|
|
300
|
+
run_init --profile full "$target"
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# Run the `diagnose` subcommand.
|
|
304
|
+
run_diagnose() {
|
|
305
|
+
if [[ "$_HAS_AI_LIBS" != "1" ]]; then
|
|
306
|
+
log_error "Diagnose modules not found at $LIB_DIR/ai/"
|
|
307
|
+
exit 1
|
|
308
|
+
fi
|
|
309
|
+
local args=() target=""
|
|
310
|
+
while [[ $# -gt 0 ]]; do
|
|
311
|
+
case "$1" in
|
|
312
|
+
-h|--help)
|
|
313
|
+
cat <<EOF
|
|
314
|
+
Usage: ./scripts/bin/install diagnose [target-dir] [--log <file>] [--ai] [--auto-accept]
|
|
315
|
+
|
|
316
|
+
Pattern-matches a Jekyll build log against a curated list of known errors.
|
|
317
|
+
With --ai, also sends a sanitized log + _config.yml + Gemfile to OpenAI for
|
|
318
|
+
a proposed fix (requires OPENAI_API_KEY).
|
|
319
|
+
|
|
320
|
+
Options:
|
|
321
|
+
--log <file> Use an existing log file instead of running 'jekyll build'.
|
|
322
|
+
--ai Enable AI analysis (opt-in; sanitized).
|
|
323
|
+
--auto-accept Skip the API-call confirmation prompt.
|
|
324
|
+
|
|
325
|
+
Examples:
|
|
326
|
+
./scripts/bin/install diagnose
|
|
327
|
+
./scripts/bin/install diagnose --log build.log --ai
|
|
328
|
+
EOF
|
|
329
|
+
exit 0 ;;
|
|
330
|
+
--log)
|
|
331
|
+
args+=("--log" "${2:-}")
|
|
332
|
+
shift ;;
|
|
333
|
+
--ai|--auto-accept) args+=("$1") ;;
|
|
334
|
+
-*) log_error "Unknown option: $1"; exit 1 ;;
|
|
335
|
+
*)
|
|
336
|
+
if [[ -z "$target" ]]; then target="$1"
|
|
337
|
+
else args+=("$1")
|
|
338
|
+
fi ;;
|
|
339
|
+
esac
|
|
340
|
+
shift
|
|
341
|
+
done
|
|
342
|
+
[[ -z "$target" ]] && target="$PWD"
|
|
343
|
+
diagnose_run "$target" "$REPO_ROOT" ${args[@]+"${args[@]}"}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
# Run the `doctor` subcommand (Phase 6).
|
|
347
|
+
run_doctor() {
|
|
348
|
+
if [[ "$_HAS_DOCTOR_LIB" != "1" ]]; then
|
|
349
|
+
log_error "doctor.sh module not found at $LIB_DIR/doctor.sh"
|
|
350
|
+
exit 1
|
|
351
|
+
fi
|
|
352
|
+
local target="" args=()
|
|
353
|
+
while [[ $# -gt 0 ]]; do
|
|
354
|
+
case "$1" in
|
|
355
|
+
-h|--help)
|
|
356
|
+
cat <<EOF
|
|
357
|
+
Usage: ./scripts/bin/install doctor [target-dir] [--ai] [--quiet] [--json]
|
|
358
|
+
|
|
359
|
+
Runs platform + tooling + site health checks. Exits 0 when no FAIL,
|
|
360
|
+
1 otherwise. Use --ai to also probe OpenAI API connectivity (opt-in;
|
|
361
|
+
honors ZER0_NO_AI=1 kill-switch).
|
|
362
|
+
|
|
363
|
+
Options:
|
|
364
|
+
--ai Include OpenAI connectivity check (requires OPENAI_API_KEY)
|
|
365
|
+
--quiet Suppress section headers (still prints summary)
|
|
366
|
+
--json Emit a machine-readable {"pass":N,"warn":N,"fail":N} summary
|
|
367
|
+
EOF
|
|
368
|
+
exit 0 ;;
|
|
369
|
+
--ai|--quiet|--json) args+=("$1") ;;
|
|
370
|
+
-*) log_error "Unknown option: $1"; exit 1 ;;
|
|
371
|
+
*)
|
|
372
|
+
if [[ -z "$target" ]]; then target="$1"
|
|
373
|
+
else log_error "Unexpected argument: $1"; exit 1
|
|
374
|
+
fi ;;
|
|
375
|
+
esac
|
|
376
|
+
shift
|
|
377
|
+
done
|
|
378
|
+
[[ -z "$target" ]] && target="$PWD"
|
|
379
|
+
doctor_run "$target" "$REPO_ROOT" ${args[@]+"${args[@]}"}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
# Run the `upgrade` subcommand (Phase 6).
|
|
383
|
+
run_upgrade() {
|
|
384
|
+
if [[ "$_HAS_UPGRADE_LIB" != "1" ]]; then
|
|
385
|
+
log_error "upgrade.sh module not found at $LIB_DIR/upgrade.sh"
|
|
386
|
+
exit 1
|
|
387
|
+
fi
|
|
388
|
+
local target="" args=()
|
|
389
|
+
while [[ $# -gt 0 ]]; do
|
|
390
|
+
case "$1" in
|
|
391
|
+
-h|--help)
|
|
392
|
+
cat <<EOF
|
|
393
|
+
Usage: ./scripts/bin/install upgrade [target-dir] [--from <version>]
|
|
394
|
+
[--force] [--dry-run] [--auto-accept]
|
|
395
|
+
|
|
396
|
+
Re-runs the installer over an existing site. Refreshes AI agent files
|
|
397
|
+
(always additive), flags drifted .github/workflows/, and writes a
|
|
398
|
+
.zer0-installed marker. Never touches user content under pages/, _posts/,
|
|
399
|
+
_drafts/.
|
|
400
|
+
|
|
401
|
+
Options:
|
|
402
|
+
--from <ver> Override detected installed version
|
|
403
|
+
-f, --force Re-run even if already on the latest version
|
|
404
|
+
-n, --dry-run Show what would change without writing files
|
|
405
|
+
--auto-accept Skip the interactive confirmation prompt
|
|
406
|
+
EOF
|
|
407
|
+
exit 0 ;;
|
|
408
|
+
--from)
|
|
409
|
+
args+=("--from" "${2:-}"); shift ;;
|
|
410
|
+
-f|--force|-n|--dry-run|--auto-accept) args+=("$1") ;;
|
|
411
|
+
-*) log_error "Unknown option: $1"; exit 1 ;;
|
|
412
|
+
*)
|
|
413
|
+
if [[ -z "$target" ]]; then target="$1"
|
|
414
|
+
else log_error "Unexpected argument: $1"; exit 1
|
|
415
|
+
fi ;;
|
|
416
|
+
esac
|
|
417
|
+
shift
|
|
418
|
+
done
|
|
419
|
+
[[ -z "$target" ]] && target="$PWD"
|
|
420
|
+
upgrade_run "$target" "$REPO_ROOT" ${args[@]+"${args[@]}"}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
list_profiles() {
|
|
424
|
+
if [[ "$_HAS_PROFILE_LOADER" == "1" ]] && [[ -d "$(profiles_dir "$REPO_ROOT")" ]]; then
|
|
425
|
+
echo "Available install profiles (from templates/profiles/):"
|
|
426
|
+
echo
|
|
427
|
+
local p path
|
|
428
|
+
while IFS= read -r p; do
|
|
429
|
+
[ -z "$p" ] && continue
|
|
430
|
+
path="$(profile_path "$REPO_ROOT" "$p")" || continue
|
|
431
|
+
profile_print_summary "$path"
|
|
432
|
+
done < <(list_profile_names "$REPO_ROOT")
|
|
433
|
+
return
|
|
434
|
+
fi
|
|
435
|
+
cat <<EOF
|
|
436
|
+
Available install profiles (Phase 3 — profile loader not available, showing static map):
|
|
437
|
+
|
|
438
|
+
minimal Smallest viable site (Gemfile, _config.yml, index.md)
|
|
439
|
+
→ install.sh --minimal
|
|
440
|
+
full Default. All starter pages, navigation, admin settings, wizard
|
|
441
|
+
→ install.sh --full
|
|
442
|
+
fork Fork-friendly install: cleans creator content, seeds welcome post
|
|
443
|
+
→ install.sh --fork
|
|
444
|
+
github GitHub Pages remote_theme installation
|
|
445
|
+
→ install.sh --github
|
|
446
|
+
remote Bootstrap from raw.githubusercontent.com (curl|bash style)
|
|
447
|
+
→ install.sh --remote
|
|
448
|
+
EOF
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
list_targets() {
|
|
452
|
+
if [[ "$_HAS_DEPLOY_REGISTRY" == "1" ]]; then
|
|
453
|
+
echo "Available deploy targets:"
|
|
454
|
+
echo
|
|
455
|
+
local t
|
|
456
|
+
for t in $DEPLOY_TARGETS_LIST; do
|
|
457
|
+
deploy_print_summary "$t" "$REPO_ROOT"
|
|
458
|
+
done
|
|
459
|
+
echo "Usage: ./scripts/bin/install deploy <target>[,<target>...] [target-dir]"
|
|
460
|
+
return
|
|
461
|
+
fi
|
|
462
|
+
cat <<EOF
|
|
463
|
+
Available deploy targets (Phase 4 — registry not available, showing static map):
|
|
464
|
+
|
|
465
|
+
github-pages GitHub Pages with peaceiris/actions-gh-pages workflow
|
|
466
|
+
azure-swa Azure Static Web Apps (workflow + staticwebapp.config.json)
|
|
467
|
+
docker-prod Self-hosted production Docker image (Ruby builder + nginx:alpine)
|
|
468
|
+
EOF
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
# Translate `deploy <target>[,<target>...] [dir]` into registry calls.
|
|
472
|
+
run_deploy() {
|
|
473
|
+
if [[ "$_HAS_DEPLOY_REGISTRY" != "1" ]]; then
|
|
474
|
+
log_error "Deploy registry not available at $LIB_DIR/deploy/registry.sh"
|
|
475
|
+
exit 1
|
|
476
|
+
fi
|
|
477
|
+
|
|
478
|
+
local targets_arg=""
|
|
479
|
+
local target_dir=""
|
|
480
|
+
local force=0
|
|
481
|
+
local ai_suggest=0
|
|
482
|
+
local auto_accept=0
|
|
483
|
+
|
|
484
|
+
while [[ $# -gt 0 ]]; do
|
|
485
|
+
case "$1" in
|
|
486
|
+
-f|--force) force=1; shift ;;
|
|
487
|
+
--ai-suggest) ai_suggest=1; shift ;;
|
|
488
|
+
--auto-accept) auto_accept=1; shift ;;
|
|
489
|
+
-h|--help)
|
|
490
|
+
cat <<EOF
|
|
491
|
+
Usage: ./scripts/bin/install deploy <target>[,<target>...] [target-dir]
|
|
492
|
+
|
|
493
|
+
Targets:
|
|
494
|
+
github-pages, azure-swa, docker-prod
|
|
495
|
+
|
|
496
|
+
Options:
|
|
497
|
+
-f, --force Overwrite existing files (default: skip + warn)
|
|
498
|
+
--ai-suggest Recommend a target before installing (rule-based;
|
|
499
|
+
add OPENAI_API_KEY for AI-assisted rationale)
|
|
500
|
+
--auto-accept Skip API-call confirmation prompts
|
|
501
|
+
-h, --help Show this help
|
|
502
|
+
|
|
503
|
+
Examples:
|
|
504
|
+
./scripts/bin/install deploy github-pages
|
|
505
|
+
./scripts/bin/install deploy --ai-suggest /tmp/site
|
|
506
|
+
./scripts/bin/install deploy github-pages,docker-prod /tmp/site
|
|
507
|
+
EOF
|
|
508
|
+
exit 0
|
|
509
|
+
;;
|
|
510
|
+
-*)
|
|
511
|
+
log_error "Unknown option: $1"
|
|
512
|
+
exit 1
|
|
513
|
+
;;
|
|
514
|
+
*)
|
|
515
|
+
if [[ -z "$targets_arg" ]]; then
|
|
516
|
+
targets_arg="$1"
|
|
517
|
+
elif [[ -z "$target_dir" ]]; then
|
|
518
|
+
target_dir="$1"
|
|
519
|
+
else
|
|
520
|
+
log_error "Unexpected argument: $1"
|
|
521
|
+
exit 1
|
|
522
|
+
fi
|
|
523
|
+
shift
|
|
524
|
+
;;
|
|
525
|
+
esac
|
|
526
|
+
done
|
|
527
|
+
|
|
528
|
+
# AI-suggest mode: recommend a target, then either install or just print.
|
|
529
|
+
if [[ "$ai_suggest" = "1" ]]; then
|
|
530
|
+
if [[ "$_HAS_AI_LIBS" != "1" ]]; then
|
|
531
|
+
log_error "AI modules not found at $LIB_DIR/ai/"
|
|
532
|
+
exit 1
|
|
533
|
+
fi
|
|
534
|
+
# If no target arg, treat the first positional as the dir instead.
|
|
535
|
+
if [[ -z "$target_dir" ]] && [[ -n "$targets_arg" ]] && [[ -d "$targets_arg" ]]; then
|
|
536
|
+
target_dir="$targets_arg"
|
|
537
|
+
targets_arg=""
|
|
538
|
+
fi
|
|
539
|
+
[[ -z "$target_dir" ]] && target_dir="$PWD"
|
|
540
|
+
|
|
541
|
+
local ai_args=()
|
|
542
|
+
# Use OpenAI path only if key present; otherwise rule-based (no flag).
|
|
543
|
+
[[ -n "${OPENAI_API_KEY:-}" ]] && ai_args+=("--ai")
|
|
544
|
+
[[ "$auto_accept" = "1" ]] && ai_args+=("--auto-accept")
|
|
545
|
+
|
|
546
|
+
local recommended
|
|
547
|
+
if ! recommended="$(suggest_deploy_target "$target_dir" "$REPO_ROOT" ${ai_args[@]+"${ai_args[@]}"})"; then
|
|
548
|
+
log_error "Suggestion failed."
|
|
549
|
+
exit 1
|
|
550
|
+
fi
|
|
551
|
+
echo
|
|
552
|
+
log_info "Recommended target: $recommended"
|
|
553
|
+
if [[ -z "$targets_arg" ]]; then
|
|
554
|
+
log_info "Re-run with the slug to install: ./scripts/bin/install deploy $recommended ${target_dir}"
|
|
555
|
+
return 0
|
|
556
|
+
fi
|
|
557
|
+
# If user also provided an explicit slug, honor that.
|
|
558
|
+
fi
|
|
559
|
+
|
|
560
|
+
if [[ -z "$targets_arg" ]]; then
|
|
561
|
+
log_error "No deploy target specified. Run 'install list-targets' to see options."
|
|
562
|
+
exit 1
|
|
563
|
+
fi
|
|
564
|
+
|
|
565
|
+
[[ -z "$target_dir" ]] && target_dir="$PWD"
|
|
566
|
+
[[ "$force" = "1" ]] && export DEPLOY_FORCE=1
|
|
567
|
+
|
|
568
|
+
# Default site name from target dir for placeholder substitution.
|
|
569
|
+
[[ -z "${DEPLOY_SITE_NAME:-}" ]] && export DEPLOY_SITE_NAME="$(basename "$target_dir")"
|
|
570
|
+
|
|
571
|
+
local IFS=','
|
|
572
|
+
# Capture into a positional array, then restore IFS before dispatching
|
|
573
|
+
# so registry helpers (which split space-separated lists) work correctly.
|
|
574
|
+
set -- $targets_arg
|
|
575
|
+
unset IFS
|
|
576
|
+
local t fail=0
|
|
577
|
+
for t in "$@"; do
|
|
578
|
+
[ -z "$t" ] && continue
|
|
579
|
+
deploy_run_target "$t" "$target_dir" "$REPO_ROOT" || fail=1
|
|
580
|
+
echo
|
|
581
|
+
done
|
|
582
|
+
[ "$fail" = "0" ] || exit 1
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
# Translate `init` subcommand options into legacy install.sh flags.
|
|
586
|
+
run_init() {
|
|
587
|
+
require_legacy_install
|
|
588
|
+
local profile="full"
|
|
589
|
+
local target=""
|
|
590
|
+
local extra_args=()
|
|
591
|
+
local skip_doctor=0
|
|
592
|
+
|
|
593
|
+
while [[ $# -gt 0 ]]; do
|
|
594
|
+
case "$1" in
|
|
595
|
+
-p|--profile)
|
|
596
|
+
profile="${2:-}"
|
|
597
|
+
if [[ -z "$profile" ]]; then
|
|
598
|
+
log_error "--profile requires a value"
|
|
599
|
+
exit 1
|
|
600
|
+
fi
|
|
601
|
+
shift 2
|
|
602
|
+
;;
|
|
603
|
+
--skip-doctor)
|
|
604
|
+
skip_doctor=1
|
|
605
|
+
shift
|
|
606
|
+
;;
|
|
607
|
+
-h|--help)
|
|
608
|
+
cat <<EOF
|
|
609
|
+
Usage: ./scripts/bin/install init [options] [target-dir]
|
|
610
|
+
|
|
611
|
+
-p, --profile <name> minimal | full | fork | github | remote (default: full)
|
|
612
|
+
--skip-doctor Skip the doctor preflight (faster but no env validation)
|
|
613
|
+
-h, --help Show this help
|
|
614
|
+
|
|
615
|
+
Any other flags are forwarded to install.sh as-is.
|
|
616
|
+
EOF
|
|
617
|
+
exit 0
|
|
618
|
+
;;
|
|
619
|
+
--)
|
|
620
|
+
shift
|
|
621
|
+
extra_args+=("$@")
|
|
622
|
+
break
|
|
623
|
+
;;
|
|
624
|
+
-*)
|
|
625
|
+
extra_args+=("$1")
|
|
626
|
+
shift
|
|
627
|
+
;;
|
|
628
|
+
*)
|
|
629
|
+
if [[ -z "$target" ]]; then
|
|
630
|
+
target="$1"
|
|
631
|
+
else
|
|
632
|
+
extra_args+=("$1")
|
|
633
|
+
fi
|
|
634
|
+
shift
|
|
635
|
+
;;
|
|
636
|
+
esac
|
|
637
|
+
done
|
|
638
|
+
|
|
639
|
+
local profile_flag=""
|
|
640
|
+
|
|
641
|
+
# Prefer the profile YAML for legacy_flag resolution when available.
|
|
642
|
+
if [[ "$_HAS_PROFILE_LOADER" == "1" ]]; then
|
|
643
|
+
local profile_file
|
|
644
|
+
if profile_file="$(profile_path "$REPO_ROOT" "$profile" 2>/dev/null)"; then
|
|
645
|
+
profile_flag="$(profile_get_scalar "$profile_file" legacy_flag)"
|
|
646
|
+
fi
|
|
647
|
+
fi
|
|
648
|
+
|
|
649
|
+
if [[ -z "$profile_flag" ]]; then
|
|
650
|
+
# Fallback to the static map (also used when profile.sh is absent).
|
|
651
|
+
case "$profile" in
|
|
652
|
+
minimal) profile_flag="--minimal" ;;
|
|
653
|
+
full) profile_flag="--full" ;;
|
|
654
|
+
fork) profile_flag="--fork" ;;
|
|
655
|
+
github) profile_flag="--github" ;;
|
|
656
|
+
remote) profile_flag="--remote" ;;
|
|
657
|
+
*)
|
|
658
|
+
log_error "Unknown profile: $profile (use list-profiles to see options)"
|
|
659
|
+
exit 1
|
|
660
|
+
;;
|
|
661
|
+
esac
|
|
662
|
+
fi
|
|
663
|
+
|
|
664
|
+
log_info "Dispatching to install.sh ($profile profile, $profile_flag)${target:+ → $target}"
|
|
665
|
+
|
|
666
|
+
# Phase 6: doctor preflight. Quick (--quiet) sanity check before
|
|
667
|
+
# invoking the heavy installer. Skipped on --skip-doctor or when the
|
|
668
|
+
# module is unavailable. FAILs are surfaced but never block install
|
|
669
|
+
# (user can still proceed; doctor is advisory at this stage).
|
|
670
|
+
if [[ "$skip_doctor" != "1" ]] && [[ "$_HAS_DOCTOR_LIB" = "1" ]]; then
|
|
671
|
+
local doctor_target="${target:-$PWD}"
|
|
672
|
+
log_info "Running doctor preflight (skip with --skip-doctor)..."
|
|
673
|
+
if ! doctor_run "$doctor_target" "$REPO_ROOT" --quiet; then
|
|
674
|
+
log_warning "Doctor reported FAILs — proceeding anyway. Re-run 'install doctor' for details."
|
|
675
|
+
fi
|
|
676
|
+
echo
|
|
677
|
+
fi
|
|
678
|
+
|
|
679
|
+
if [[ -n "$target" ]]; then
|
|
680
|
+
exec "$LEGACY_INSTALL" "$profile_flag" ${extra_args[@]+"${extra_args[@]}"} "$target"
|
|
681
|
+
else
|
|
682
|
+
exec "$LEGACY_INSTALL" "$profile_flag" ${extra_args[@]+"${extra_args[@]}"}
|
|
683
|
+
fi
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
# ---------- Dispatcher ------------------------------------------------------
|
|
687
|
+
main() {
|
|
688
|
+
if [[ $# -eq 0 ]]; then
|
|
689
|
+
show_usage
|
|
690
|
+
exit 0
|
|
691
|
+
fi
|
|
692
|
+
|
|
693
|
+
local subcmd="$1"
|
|
694
|
+
shift || true
|
|
695
|
+
|
|
696
|
+
case "$subcmd" in
|
|
697
|
+
init) run_init "$@" ;;
|
|
698
|
+
wizard) run_wizard "$@" ;;
|
|
699
|
+
deploy) run_deploy "$@" ;;
|
|
700
|
+
diagnose) run_diagnose "$@" ;;
|
|
701
|
+
doctor) run_doctor "$@" ;;
|
|
702
|
+
upgrade) run_upgrade "$@" ;;
|
|
703
|
+
list-profiles) list_profiles ;;
|
|
704
|
+
list-targets) list_targets ;;
|
|
705
|
+
agents) run_agents "$@" ;;
|
|
706
|
+
version|--version|-V) print_version ;;
|
|
707
|
+
help|--help|-h) show_usage ;;
|
|
708
|
+
*)
|
|
709
|
+
log_error "Unknown subcommand: $subcmd"
|
|
710
|
+
echo
|
|
711
|
+
show_usage
|
|
712
|
+
exit 1
|
|
713
|
+
;;
|
|
714
|
+
esac
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
main "$@"
|