jekyll-theme-zer0 1.8.2 → 1.9.0
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 +26 -0
- data/README.md +98 -7
- data/_data/content_statistics.yml +253 -251
- data/_includes/components/nav-export.html +61 -0
- data/_includes/components/nav-overview.html +54 -0
- data/scripts/bin/install +52 -705
- data/scripts/install/README.md +162 -0
- data/scripts/install/ai/client.sh +164 -0
- data/scripts/install/ai/diagnose.sh +81 -0
- data/scripts/install/ai/prompts/diagnose.system.md +42 -0
- data/scripts/install/ai/prompts/spec.schema.json +129 -0
- data/scripts/install/ai/prompts/suggest.system.md +43 -0
- data/scripts/install/ai/prompts/wizard.system.md +142 -0
- data/scripts/install/ai/suggest.sh +57 -0
- data/scripts/install/ai/wizard.sh +150 -0
- data/scripts/install/apply.sh +156 -0
- data/scripts/install/cli.sh +561 -0
- data/scripts/install/diff.sh +128 -0
- data/scripts/install/doctor.sh +168 -0
- data/scripts/install/fs.sh +138 -0
- data/scripts/install/log.sh +119 -0
- data/scripts/install/plan.sh +299 -0
- data/scripts/install/platform.sh +122 -0
- data/scripts/install/prompt.sh +124 -0
- data/scripts/install/repair.sh +45 -0
- data/scripts/install/scrape.sh +535 -0
- data/scripts/install/scrape_html.py +764 -0
- data/scripts/install/spec.sh +486 -0
- data/scripts/install/tasks/_registry.sh +65 -0
- data/scripts/install/tasks/agents.sh +60 -0
- data/scripts/install/tasks/config.sh +37 -0
- data/scripts/install/tasks/data.sh +18 -0
- data/scripts/install/tasks/deploy_azure-swa.sh +17 -0
- data/scripts/install/tasks/deploy_docker-prod.sh +21 -0
- data/scripts/install/tasks/deploy_github-pages.sh +18 -0
- data/scripts/install/tasks/devcontainer.sh +26 -0
- data/scripts/install/tasks/docker.sh +29 -0
- data/scripts/install/tasks/gemfile.sh +42 -0
- data/scripts/install/tasks/gitignore.sh +26 -0
- data/scripts/install/tasks/marker.sh +46 -0
- data/scripts/install/tasks/nav.sh +18 -0
- data/scripts/install/tasks/pages.sh +61 -0
- data/scripts/install/tasks/readme.sh +27 -0
- data/scripts/install/tasks/scrape.sh +348 -0
- data/scripts/install/template.sh +138 -0
- data/scripts/install/tui.sh +110 -0
- data/scripts/install/upgrade.sh +49 -0
- data/scripts/lib/install/template.sh +1 -0
- metadata +45 -2
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# scripts/install/plan.sh — Build an install spec from inputs
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Accepts: profile, CLI flags, environment variables, detected platform.
|
|
6
|
+
# Produces: populated SPEC_* globals (see spec.sh for the full list).
|
|
7
|
+
# The three front-ends (flags, tui.sh, ai/wizard.sh) all call plan_build.
|
|
8
|
+
#
|
|
9
|
+
# Provides:
|
|
10
|
+
# plan_build TARGET_DIR [PROFILE]
|
|
11
|
+
# Merge layers (profile → env-var overrides → CLI flags → platform
|
|
12
|
+
# defaults) into SPEC_* globals. Does NOT write any files.
|
|
13
|
+
#
|
|
14
|
+
# plan_load_profile PROFILE_FILE
|
|
15
|
+
# Read a templates/profiles/*.yml file into SPEC_* globals
|
|
16
|
+
# (lower priority than explicit flag overrides).
|
|
17
|
+
#
|
|
18
|
+
# plan_apply_flags
|
|
19
|
+
# Copy flag globals (_FLAG_*) set by cli.sh into SPEC_* globals.
|
|
20
|
+
#
|
|
21
|
+
# plan_apply_platform
|
|
22
|
+
# Auto-detect SPEC_THEME_SOURCE if not set:
|
|
23
|
+
# - Docker available → gem
|
|
24
|
+
# - GitHub Pages mode → remote
|
|
25
|
+
# - fallback → gem
|
|
26
|
+
#
|
|
27
|
+
# plan_print [FILE]
|
|
28
|
+
# Print the spec to stdout or FILE as formatted JSON.
|
|
29
|
+
#
|
|
30
|
+
# Bash 3.2 compatible. No set -euo pipefail here.
|
|
31
|
+
# =============================================================================
|
|
32
|
+
[[ -n "${_HAS_PLAN_LIB:-}" ]] && return 0
|
|
33
|
+
_HAS_PLAN_LIB=1
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# plan_load_profile PROFILE_FILE
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
plan_load_profile() {
|
|
39
|
+
local profile_file="$1"
|
|
40
|
+
[[ -f "$profile_file" ]] || return 0
|
|
41
|
+
|
|
42
|
+
# Read scalar fields
|
|
43
|
+
local val
|
|
44
|
+
# profile_get_scalar is provided by profile.sh if sourced; fall back to awk
|
|
45
|
+
_plan_yaml_scalar() {
|
|
46
|
+
local file="$1" key="$2"
|
|
47
|
+
awk -v k="$key" '
|
|
48
|
+
$0 ~ "^[[:space:]]*" k "[[:space:]]*:" {
|
|
49
|
+
sub("^[[:space:]]*" k "[[:space:]]*:[[:space:]]*", "")
|
|
50
|
+
sub(/[[:space:]]+#.*$/, "")
|
|
51
|
+
gsub(/^["'"'"']|["'"'"']$/, "")
|
|
52
|
+
print; exit
|
|
53
|
+
}
|
|
54
|
+
' "$file"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
val=$(_plan_yaml_scalar "$profile_file" "profile")
|
|
58
|
+
[[ -n "$val" ]] && SPEC_PROFILE="$val"
|
|
59
|
+
|
|
60
|
+
val=$(_plan_yaml_scalar "$profile_file" "theme_source")
|
|
61
|
+
[[ -n "$val" ]] && SPEC_THEME_SOURCE="$val"
|
|
62
|
+
|
|
63
|
+
val=$(_plan_yaml_scalar "$profile_file" "theme_version")
|
|
64
|
+
[[ -n "$val" ]] && SPEC_THEME_VERSION="$val"
|
|
65
|
+
|
|
66
|
+
val=$(_plan_yaml_scalar "$profile_file" "site_title")
|
|
67
|
+
[[ -n "$val" ]] && : "${SPEC_SITE_TITLE:=$val}"
|
|
68
|
+
|
|
69
|
+
val=$(_plan_yaml_scalar "$profile_file" "site_description")
|
|
70
|
+
[[ -n "$val" ]] && : "${SPEC_SITE_DESCRIPTION:=$val}"
|
|
71
|
+
|
|
72
|
+
val=$(_plan_yaml_scalar "$profile_file" "site_timezone")
|
|
73
|
+
[[ -n "$val" ]] && : "${SPEC_SITE_TIMEZONE:=$val}"
|
|
74
|
+
|
|
75
|
+
val=$(_plan_yaml_scalar "$profile_file" "github_pages_branch")
|
|
76
|
+
[[ -n "$val" ]] && : "${SPEC_GITHUB_PAGES_BRANCH:=$val}"
|
|
77
|
+
|
|
78
|
+
# Tasks list from profile (space-separated after join)
|
|
79
|
+
local tasks
|
|
80
|
+
tasks=$(awk '
|
|
81
|
+
/^[[:space:]]*tasks[[:space:]]*:/ { found=1; next }
|
|
82
|
+
found && /^[[:space:]]*-[[:space:]]+/ {
|
|
83
|
+
line=$0; sub(/^[[:space:]]*-[[:space:]]+/, "", line)
|
|
84
|
+
gsub(/[[:space:]]/, "", line)
|
|
85
|
+
printf "%s ", line
|
|
86
|
+
}
|
|
87
|
+
found && !/^[[:space:]]*-/ && NF { exit }
|
|
88
|
+
' "$profile_file" | sed 's/[[:space:]]*$//')
|
|
89
|
+
[[ -n "$tasks" ]] && SPEC_TASKS="$tasks"
|
|
90
|
+
|
|
91
|
+
# Deploy list from profile (accepts `deploy:` or `deploy_targets:`)
|
|
92
|
+
local deploy
|
|
93
|
+
deploy=$(awk '
|
|
94
|
+
/^[[:space:]]*deploy(_targets)?[[:space:]]*:/ { found=1; next }
|
|
95
|
+
found && /^[[:space:]]*-[[:space:]]+/ {
|
|
96
|
+
line=$0; sub(/^[[:space:]]*-[[:space:]]+/, "", line)
|
|
97
|
+
gsub(/[[:space:]]/, "", line)
|
|
98
|
+
printf "%s ", line
|
|
99
|
+
}
|
|
100
|
+
found && !/^[[:space:]]*-/ && NF { exit }
|
|
101
|
+
' "$profile_file" | sed 's/[[:space:]]*$//')
|
|
102
|
+
[[ -n "$deploy" ]] && : "${SPEC_DEPLOY:=$deploy}"
|
|
103
|
+
|
|
104
|
+
# Agents list from profile (accepts top-level `agents:` or nested `ai_features.agent_files:`)
|
|
105
|
+
local agents
|
|
106
|
+
agents=$(awk '
|
|
107
|
+
/^[[:space:]]*agents[[:space:]]*:/ { in_agents=1; in_af=0; next }
|
|
108
|
+
/^[[:space:]]*ai_features[[:space:]]*:/ { in_af_block=1; next }
|
|
109
|
+
in_af_block && /^[[:space:]]*agent_files[[:space:]]*:/ {
|
|
110
|
+
in_af=1
|
|
111
|
+
# Inline flow form: agent_files: [foo, bar]
|
|
112
|
+
line=$0; sub(/^[^\[]*\[/, "", line); sub(/\].*$/, "", line)
|
|
113
|
+
gsub(/[[:space:],]+/, " ", line)
|
|
114
|
+
if (length(line) > 0 && line !~ /^[[:space:]]*$/) printf "%s ", line
|
|
115
|
+
next
|
|
116
|
+
}
|
|
117
|
+
(in_agents || in_af) && /^[[:space:]]*-[[:space:]]+/ {
|
|
118
|
+
line=$0; sub(/^[[:space:]]*-[[:space:]]+/, "", line)
|
|
119
|
+
gsub(/[[:space:]]/, "", line)
|
|
120
|
+
printf "%s ", line
|
|
121
|
+
next
|
|
122
|
+
}
|
|
123
|
+
(in_agents || in_af) && /^[^[:space:]-]/ { in_agents=0; in_af=0 }
|
|
124
|
+
/^[^[:space:]]/ { in_af_block=0 }
|
|
125
|
+
' "$profile_file" | sed 's/[[:space:]]*$//')
|
|
126
|
+
[[ -n "$agents" ]] && : "${SPEC_AGENTS:=$agents}"
|
|
127
|
+
return 0
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
# plan_apply_flags — copy _FLAG_* set by cli.sh → SPEC_* (highest priority)
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
plan_apply_flags() {
|
|
134
|
+
# Strings — only override when flag was explicitly set (non-empty)
|
|
135
|
+
[[ -n "${_FLAG_PROFILE:-}" ]] && SPEC_PROFILE="$_FLAG_PROFILE"
|
|
136
|
+
[[ -n "${_FLAG_SITE_TITLE:-}" ]] && SPEC_SITE_TITLE="$_FLAG_SITE_TITLE"
|
|
137
|
+
[[ -n "${_FLAG_SITE_DESC:-}" ]] && SPEC_SITE_DESCRIPTION="$_FLAG_SITE_DESC"
|
|
138
|
+
[[ -n "${_FLAG_SITE_URL:-}" ]] && SPEC_SITE_URL="$_FLAG_SITE_URL"
|
|
139
|
+
[[ -n "${_FLAG_SITE_AUTHOR:-}" ]] && SPEC_SITE_AUTHOR="$_FLAG_SITE_AUTHOR"
|
|
140
|
+
[[ -n "${_FLAG_SITE_EMAIL:-}" ]] && SPEC_SITE_EMAIL="$_FLAG_SITE_EMAIL"
|
|
141
|
+
[[ -n "${_FLAG_GITHUB_USER:-}" ]] && SPEC_GITHUB_USER="$_FLAG_GITHUB_USER"
|
|
142
|
+
[[ -n "${_FLAG_GITHUB_REPO:-}" ]] && SPEC_GITHUB_REPO="$_FLAG_GITHUB_REPO"
|
|
143
|
+
[[ -n "${_FLAG_THEME_SOURCE:-}" ]] && SPEC_THEME_SOURCE="$_FLAG_THEME_SOURCE"
|
|
144
|
+
[[ -n "${_FLAG_DEPLOY:-}" ]] && SPEC_DEPLOY="$_FLAG_DEPLOY"
|
|
145
|
+
[[ -n "${_FLAG_AGENTS:-}" ]] && SPEC_AGENTS="$_FLAG_AGENTS"
|
|
146
|
+
[[ -n "${_FLAG_TASKS:-}" ]] && SPEC_TASKS="$_FLAG_TASKS"
|
|
147
|
+
|
|
148
|
+
# Booleans — flags are set to "1" when present
|
|
149
|
+
[[ "${_FLAG_DRY_RUN:-0}" == "1" ]] && SPEC_OPT_DRY_RUN=true
|
|
150
|
+
[[ "${_FLAG_FORCE:-0}" == "1" ]] && SPEC_OPT_FORCE=true
|
|
151
|
+
[[ "${_FLAG_NO_BACKUP:-0}" == "1" ]] && SPEC_OPT_BACKUP=false
|
|
152
|
+
[[ "${_FLAG_NON_INTERACTIVE:-0}" == "1" ]] && SPEC_OPT_NON_INTERACTIVE=true
|
|
153
|
+
[[ "${_FLAG_AUTO_ACCEPT:-0}" == "1" ]] && SPEC_OPT_AUTO_ACCEPT=true
|
|
154
|
+
[[ "${_FLAG_SKIP_DOCTOR:-0}" == "1" ]] && SPEC_OPT_SKIP_DOCTOR=true
|
|
155
|
+
[[ "${_FLAG_VERBOSE:-0}" == "1" ]] && SPEC_OPT_VERBOSE=true
|
|
156
|
+
[[ -n "${_FLAG_OUTPUT:-}" ]] && SPEC_OPT_OUTPUT="$_FLAG_OUTPUT"
|
|
157
|
+
|
|
158
|
+
# Scrape flags — when --scrape URL is given, register the scrape task
|
|
159
|
+
# so apply.sh runs it (after pages so it can overlay).
|
|
160
|
+
if [[ -n "${_FLAG_SCRAPE_URL:-}" ]]; then
|
|
161
|
+
SPEC_SCRAPE_SOURCE_URL="$_FLAG_SCRAPE_URL"
|
|
162
|
+
[[ -n "${_FLAG_SCRAPE_DEPTH:-}" ]] && SPEC_SCRAPE_DEPTH="$_FLAG_SCRAPE_DEPTH"
|
|
163
|
+
[[ -n "${_FLAG_SCRAPE_MAX_PAGES:-}" ]] && SPEC_SCRAPE_MAX_PAGES="$_FLAG_SCRAPE_MAX_PAGES"
|
|
164
|
+
case " ${SPEC_TASKS:-} " in
|
|
165
|
+
*" scrape "*) ;;
|
|
166
|
+
*) SPEC_TASKS="${SPEC_TASKS:-} scrape" ;;
|
|
167
|
+
esac
|
|
168
|
+
fi
|
|
169
|
+
return 0
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# ---------------------------------------------------------------------------
|
|
173
|
+
# plan_apply_platform — fill platform-dependent defaults when not set
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
plan_apply_platform() {
|
|
176
|
+
# Theme source heuristic
|
|
177
|
+
if [[ -z "${SPEC_THEME_SOURCE:-}" ]]; then
|
|
178
|
+
case "${SPEC_PROFILE:-default}" in
|
|
179
|
+
github-pages) SPEC_THEME_SOURCE="remote" ;;
|
|
180
|
+
*) SPEC_THEME_SOURCE="gem" ;;
|
|
181
|
+
esac
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# Ensure github.repo defaults to REPOSITORY_NAME from env when set
|
|
185
|
+
if [[ -z "${SPEC_GITHUB_REPO:-}" && -n "${REPOSITORY_NAME:-}" ]]; then
|
|
186
|
+
SPEC_GITHUB_REPO="$REPOSITORY_NAME"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# Devcontainer task auto-include for github-pages profile
|
|
190
|
+
if [[ "${SPEC_PROFILE:-}" == "github-pages" && -n "${SPEC_TASKS:-}" ]]; then
|
|
191
|
+
case "$SPEC_TASKS" in
|
|
192
|
+
*devcontainer*) ;;
|
|
193
|
+
*) SPEC_TASKS="$SPEC_TASKS devcontainer" ;;
|
|
194
|
+
esac
|
|
195
|
+
fi
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
# plan_build TARGET_DIR [PROFILE]
|
|
200
|
+
# Orchestrates: set target_dir, resolve profile file, load it, apply flags,
|
|
201
|
+
# apply platform defaults.
|
|
202
|
+
# ---------------------------------------------------------------------------
|
|
203
|
+
plan_build() {
|
|
204
|
+
local target_dir="$1"
|
|
205
|
+
local profile_hint="${2:-${_FLAG_PROFILE:-default}}"
|
|
206
|
+
|
|
207
|
+
# Canonical absolute path
|
|
208
|
+
case "$target_dir" in
|
|
209
|
+
/*) ;;
|
|
210
|
+
*) target_dir="$(pwd)/${target_dir}" ;;
|
|
211
|
+
esac
|
|
212
|
+
|
|
213
|
+
# Seed required fields
|
|
214
|
+
SPEC_TARGET_DIR="$target_dir"
|
|
215
|
+
SPEC_PROFILE="${profile_hint}"
|
|
216
|
+
|
|
217
|
+
# Set global template variable mirrors used by template.sh
|
|
218
|
+
SITE_TITLE="${SPEC_SITE_TITLE:-${SITE_TITLE:-My Jekyll Site}}"
|
|
219
|
+
INSTALL_PROFILE="$SPEC_PROFILE"
|
|
220
|
+
|
|
221
|
+
# Locate profile file
|
|
222
|
+
local profile_file=""
|
|
223
|
+
if [[ -n "${TEMPLATES_DIR:-}" ]]; then
|
|
224
|
+
profile_file="${TEMPLATES_DIR}/profiles/${SPEC_PROFILE}.yml"
|
|
225
|
+
if [[ ! -f "$profile_file" ]]; then
|
|
226
|
+
log_warning "Profile not found: ${SPEC_PROFILE} — using defaults"
|
|
227
|
+
profile_file=""
|
|
228
|
+
fi
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
# Layer 1: Profile defaults
|
|
232
|
+
[[ -n "$profile_file" ]] && plan_load_profile "$profile_file"
|
|
233
|
+
|
|
234
|
+
# Layer 2: Environment variable overrides (ZER0_SITE_* etc.)
|
|
235
|
+
[[ -n "${ZER0_SITE_TITLE:-}" ]] && SPEC_SITE_TITLE="$ZER0_SITE_TITLE"
|
|
236
|
+
[[ -n "${ZER0_SITE_AUTHOR:-}" ]] && SPEC_SITE_AUTHOR="$ZER0_SITE_AUTHOR"
|
|
237
|
+
[[ -n "${ZER0_SITE_EMAIL:-}" ]] && SPEC_SITE_EMAIL="$ZER0_SITE_EMAIL"
|
|
238
|
+
[[ -n "${ZER0_GITHUB_USER:-}" ]] && SPEC_GITHUB_USER="$ZER0_GITHUB_USER"
|
|
239
|
+
[[ -n "${ZER0_GITHUB_REPO:-}" ]] && SPEC_GITHUB_REPO="$ZER0_GITHUB_REPO"
|
|
240
|
+
|
|
241
|
+
# Layer 3: CLI flags (highest priority)
|
|
242
|
+
plan_apply_flags
|
|
243
|
+
|
|
244
|
+
# Layer 4: Platform defaults
|
|
245
|
+
plan_apply_platform
|
|
246
|
+
|
|
247
|
+
# Ensure tasks list is never empty
|
|
248
|
+
if [[ -z "${SPEC_TASKS:-}" ]]; then
|
|
249
|
+
SPEC_TASKS="config gemfile docker pages nav data gitignore readme marker"
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
# Propagate spec values to template.sh globals
|
|
253
|
+
SITE_TITLE="${SPEC_SITE_TITLE:-My Jekyll Site}"
|
|
254
|
+
SITE_DESCRIPTION="${SPEC_SITE_DESCRIPTION:-}"
|
|
255
|
+
SITE_AUTHOR="${SPEC_SITE_AUTHOR:-}"
|
|
256
|
+
SITE_EMAIL="${SPEC_SITE_EMAIL:-}"
|
|
257
|
+
SITE_URL="${SPEC_SITE_URL:-}"
|
|
258
|
+
SITE_TIMEZONE="${SPEC_SITE_TIMEZONE:-UTC}"
|
|
259
|
+
SITE_LOCALE="${SPEC_SITE_LOCALE:-en}"
|
|
260
|
+
GITHUB_USER="${SPEC_GITHUB_USER:-}"
|
|
261
|
+
GITHUB_REPO="${SPEC_GITHUB_REPO:-}"
|
|
262
|
+
GITHUB_PAGES_BRANCH="${SPEC_GITHUB_PAGES_BRANCH:-gh-pages}"
|
|
263
|
+
THEME_SOURCE="${SPEC_THEME_SOURCE:-gem}"
|
|
264
|
+
REPOSITORY_NAME="${SPEC_GITHUB_REPO:-${REPOSITORY_NAME:-}}"
|
|
265
|
+
|
|
266
|
+
export SPEC_TARGET_DIR SPEC_PROFILE SPEC_TASKS SPEC_DEPLOY SPEC_AGENTS \
|
|
267
|
+
SPEC_SITE_TITLE SPEC_SITE_DESCRIPTION SPEC_SITE_URL \
|
|
268
|
+
SPEC_SITE_AUTHOR SPEC_SITE_EMAIL SPEC_SITE_TIMEZONE SPEC_SITE_LOCALE \
|
|
269
|
+
SPEC_GITHUB_USER SPEC_GITHUB_REPO SPEC_GITHUB_PAGES_BRANCH \
|
|
270
|
+
SPEC_GITHUB_ENABLE_PAGES SPEC_THEME_SOURCE SPEC_THEME_VERSION \
|
|
271
|
+
SPEC_OPT_DRY_RUN SPEC_OPT_FORCE SPEC_OPT_BACKUP \
|
|
272
|
+
SPEC_OPT_NON_INTERACTIVE SPEC_OPT_OUTPUT SPEC_OPT_AUTO_ACCEPT \
|
|
273
|
+
SPEC_OPT_SKIP_DOCTOR SPEC_OPT_VERBOSE \
|
|
274
|
+
SITE_TITLE SITE_DESCRIPTION SITE_AUTHOR SITE_EMAIL SITE_URL \
|
|
275
|
+
SITE_TIMEZONE SITE_LOCALE GITHUB_USER GITHUB_REPO \
|
|
276
|
+
GITHUB_PAGES_BRANCH THEME_SOURCE REPOSITORY_NAME INSTALL_PROFILE
|
|
277
|
+
|
|
278
|
+
log_debug "plan_build: target=$SPEC_TARGET_DIR profile=$SPEC_PROFILE tasks='$SPEC_TASKS'"
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
# ---------------------------------------------------------------------------
|
|
282
|
+
# plan_print [FILE] — print the spec (uses spec_write to a tmp then cat)
|
|
283
|
+
# ---------------------------------------------------------------------------
|
|
284
|
+
plan_print() {
|
|
285
|
+
local out="${1:-}"
|
|
286
|
+
# Temporarily disable dry-run so spec_write actually writes
|
|
287
|
+
local saved_dry="${_FS_DRY_RUN:-0}"
|
|
288
|
+
_FS_DRY_RUN=0
|
|
289
|
+
if [[ -n "$out" ]]; then
|
|
290
|
+
spec_write "$out"
|
|
291
|
+
else
|
|
292
|
+
local tmp
|
|
293
|
+
tmp=$(mktemp /tmp/zer0-plan-XXXXXX.json)
|
|
294
|
+
spec_write "$tmp"
|
|
295
|
+
cat "$tmp"
|
|
296
|
+
rm -f "$tmp"
|
|
297
|
+
fi
|
|
298
|
+
_FS_DRY_RUN="$saved_dry"
|
|
299
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# scripts/install/platform.sh — Platform & dependency detection
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Provides:
|
|
6
|
+
# platform_detect_os → Darwin | Linux | CYGWIN | MINGW | unknown
|
|
7
|
+
# platform_detect_runtime → macos | linux | wsl | windows | unknown
|
|
8
|
+
# platform_detect_ruby → "X.Y.Z" or "none"
|
|
9
|
+
# platform_ruby_lt_27 → returns 0 (true) when ruby < 2.7
|
|
10
|
+
# platform_needs_macos_gemfile → returns 0 when macOS + ruby < 2.7
|
|
11
|
+
# platform_detect_docker → "yes" | "no"
|
|
12
|
+
# platform_detect_gh → "yes" | "no"
|
|
13
|
+
# platform_detect_git → "yes" | "no"
|
|
14
|
+
# platform_detect_jq → "yes" | "no"
|
|
15
|
+
# platform_detect_bundler → "X.Y.Z" or "none"
|
|
16
|
+
# platform_summary → emit a JSON-compatible summary object
|
|
17
|
+
#
|
|
18
|
+
# Bash 3.2 compatible. No declare -A. No set -euo pipefail here.
|
|
19
|
+
# =============================================================================
|
|
20
|
+
[[ -n "${_HAS_PLATFORM_LIB:-}" ]] && return 0
|
|
21
|
+
_HAS_PLATFORM_LIB=1
|
|
22
|
+
|
|
23
|
+
platform_detect_os() {
|
|
24
|
+
uname -s 2>/dev/null || echo "unknown"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Returns: macos | linux | wsl | windows | unknown
|
|
28
|
+
platform_detect_runtime() {
|
|
29
|
+
if [[ "${PLATFORM:-auto}" != "auto" ]]; then
|
|
30
|
+
echo "$PLATFORM"
|
|
31
|
+
return
|
|
32
|
+
fi
|
|
33
|
+
if grep -qiE '(microsoft|wsl)' /proc/version 2>/dev/null; then
|
|
34
|
+
echo "wsl"
|
|
35
|
+
return
|
|
36
|
+
fi
|
|
37
|
+
local os
|
|
38
|
+
os=$(platform_detect_os)
|
|
39
|
+
case "$os" in
|
|
40
|
+
Darwin) echo "macos" ;;
|
|
41
|
+
Linux) echo "linux" ;;
|
|
42
|
+
CYGWIN*|MINGW*) echo "windows" ;;
|
|
43
|
+
*) echo "unknown" ;;
|
|
44
|
+
esac
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Returns ruby version "X.Y.Z" or "none"
|
|
48
|
+
platform_detect_ruby() {
|
|
49
|
+
if ! command -v ruby >/dev/null 2>&1; then
|
|
50
|
+
echo "none"
|
|
51
|
+
return
|
|
52
|
+
fi
|
|
53
|
+
# Wrap in subshell at / to avoid Gemfile.lock interference
|
|
54
|
+
( cd / && ruby --version 2>/dev/null ) \
|
|
55
|
+
| awk '{print $2}' \
|
|
56
|
+
| sed 's/p[0-9]*//' \
|
|
57
|
+
| sed 's/-.*//' \
|
|
58
|
+
| tr -d '\r\n'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Returns 0 (true) when ruby < 2.7
|
|
62
|
+
platform_ruby_lt_27() {
|
|
63
|
+
local ver
|
|
64
|
+
ver=$(platform_detect_ruby)
|
|
65
|
+
[ "$ver" = "none" ] && return 1
|
|
66
|
+
awk -v ver="$ver" 'BEGIN {
|
|
67
|
+
n = split(ver, a, ".")
|
|
68
|
+
if (n >= 2 && a[1]+0 == 2 && a[2]+0 < 7) exit 0
|
|
69
|
+
exit 1
|
|
70
|
+
}'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Returns 0 when macOS + system ruby < 2.7 (needs special Gemfile caps)
|
|
74
|
+
platform_needs_macos_gemfile() {
|
|
75
|
+
local os
|
|
76
|
+
os=$(platform_detect_os)
|
|
77
|
+
[ "$os" = "Darwin" ] && platform_ruby_lt_27
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Returns "X.Y.Z" or "none"
|
|
81
|
+
platform_detect_bundler() {
|
|
82
|
+
if ! command -v bundler >/dev/null 2>&1 && ! command -v bundle >/dev/null 2>&1; then
|
|
83
|
+
echo "none"
|
|
84
|
+
return
|
|
85
|
+
fi
|
|
86
|
+
( cd / && bundle --version 2>/dev/null ) \
|
|
87
|
+
| awk '{print $NF}' \
|
|
88
|
+
| tr -d '\r\n'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
platform_detect_docker() {
|
|
92
|
+
if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
|
|
93
|
+
echo "yes"
|
|
94
|
+
else
|
|
95
|
+
echo "no"
|
|
96
|
+
fi
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
platform_detect_gh() {
|
|
100
|
+
command -v gh >/dev/null 2>&1 && echo "yes" || echo "no"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
platform_detect_git() {
|
|
104
|
+
command -v git >/dev/null 2>&1 && echo "yes" || echo "no"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
platform_detect_jq() {
|
|
108
|
+
command -v jq >/dev/null 2>&1 && echo "yes" || echo "no"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Emit a compact single-line summary of detected platform (JSON-compatible)
|
|
112
|
+
platform_summary() {
|
|
113
|
+
printf '{"os":"%s","runtime":"%s","ruby":"%s","bundler":"%s","docker":"%s","gh":"%s","git":"%s","jq":"%s"}\n' \
|
|
114
|
+
"$(platform_detect_os)" \
|
|
115
|
+
"$(platform_detect_runtime)" \
|
|
116
|
+
"$(platform_detect_ruby)" \
|
|
117
|
+
"$(platform_detect_bundler)" \
|
|
118
|
+
"$(platform_detect_docker)" \
|
|
119
|
+
"$(platform_detect_gh)" \
|
|
120
|
+
"$(platform_detect_git)" \
|
|
121
|
+
"$(platform_detect_jq)"
|
|
122
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# scripts/install/prompt.sh — Interactive TTY prompts
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Provides:
|
|
6
|
+
# prompt_ask VAR_NAME QUESTION [DEFAULT]
|
|
7
|
+
# Read a string answer into VAR_NAME. Prints default in brackets.
|
|
8
|
+
# Returns default if user enters blank.
|
|
9
|
+
# No-ops (uses DEFAULT) when _PROMPT_NON_INTERACTIVE=1.
|
|
10
|
+
#
|
|
11
|
+
# prompt_confirm QUESTION [DEFAULT_Y]
|
|
12
|
+
# Ask a y/N question. Returns 0 for yes, 1 for no.
|
|
13
|
+
# DEFAULT_Y="y" makes the default yes.
|
|
14
|
+
# No-ops returning yes when _PROMPT_AUTO_ACCEPT=1.
|
|
15
|
+
#
|
|
16
|
+
# prompt_select VAR_NAME QUESTION OPTION1 [OPTION2 ...]
|
|
17
|
+
# Present a numbered menu. Sets VAR_NAME to chosen option.
|
|
18
|
+
# No-ops (uses first option) when _PROMPT_NON_INTERACTIVE=1.
|
|
19
|
+
#
|
|
20
|
+
# Globals:
|
|
21
|
+
# _PROMPT_NON_INTERACTIVE — "1" → never read, use defaults
|
|
22
|
+
# _PROMPT_AUTO_ACCEPT — "1" → confirm always returns yes
|
|
23
|
+
#
|
|
24
|
+
# Bash 3.2 compatible. No set -euo pipefail here.
|
|
25
|
+
# =============================================================================
|
|
26
|
+
[[ -n "${_HAS_PROMPT_LIB:-}" ]] && return 0
|
|
27
|
+
_HAS_PROMPT_LIB=1
|
|
28
|
+
|
|
29
|
+
_PROMPT_NON_INTERACTIVE="${_PROMPT_NON_INTERACTIVE:-0}"
|
|
30
|
+
_PROMPT_AUTO_ACCEPT="${_PROMPT_AUTO_ACCEPT:-0}"
|
|
31
|
+
|
|
32
|
+
# Prompt for a string value.
|
|
33
|
+
# Usage: prompt_ask VARNAME "Question text" ["default value"]
|
|
34
|
+
prompt_ask() {
|
|
35
|
+
local _var_name="$1"
|
|
36
|
+
local _question="$2"
|
|
37
|
+
local _default="${3:-}"
|
|
38
|
+
local _answer=""
|
|
39
|
+
|
|
40
|
+
if [[ "${_PROMPT_NON_INTERACTIVE:-0}" == "1" ]]; then
|
|
41
|
+
# Non-interactive: use default or fail if required
|
|
42
|
+
if [[ -z "$_default" ]]; then
|
|
43
|
+
log_error "prompt_ask: required value '$_var_name' has no default in non-interactive mode"
|
|
44
|
+
return 1
|
|
45
|
+
fi
|
|
46
|
+
_answer="$_default"
|
|
47
|
+
else
|
|
48
|
+
if [[ -n "$_default" ]]; then
|
|
49
|
+
printf "${_LOG_BLUE:-}?${_LOG_NC:-} %s [%s]: " "$_question" "$_default" >&2
|
|
50
|
+
else
|
|
51
|
+
printf "${_LOG_BLUE:-}?${_LOG_NC:-} %s: " "$_question" >&2
|
|
52
|
+
fi
|
|
53
|
+
read -r _answer </dev/tty
|
|
54
|
+
_answer="${_answer:-$_default}"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Assign to the named variable (bash 3.2 compatible — no nameref)
|
|
58
|
+
eval "${_var_name}=\$_answer"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Ask a yes/no confirmation.
|
|
62
|
+
# Usage: prompt_confirm "Question?" ["y"|"n"] → returns 0=yes 1=no
|
|
63
|
+
prompt_confirm() {
|
|
64
|
+
local _question="$1"
|
|
65
|
+
local _default="${2:-n}"
|
|
66
|
+
|
|
67
|
+
if [[ "${_PROMPT_AUTO_ACCEPT:-0}" == "1" || "${_PROMPT_NON_INTERACTIVE:-0}" == "1" ]]; then
|
|
68
|
+
return 0
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
local _prompt
|
|
72
|
+
if [[ "$_default" == "y" || "$_default" == "Y" ]]; then
|
|
73
|
+
_prompt="[Y/n]"
|
|
74
|
+
else
|
|
75
|
+
_prompt="[y/N]"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
local _answer=""
|
|
79
|
+
printf "${_LOG_BLUE:-}?${_LOG_NC:-} %s %s: " "$_question" "$_prompt" >&2
|
|
80
|
+
read -r _answer </dev/tty
|
|
81
|
+
_answer="${_answer:-$_default}"
|
|
82
|
+
|
|
83
|
+
case "$_answer" in
|
|
84
|
+
[yY][eE][sS]|[yY]) return 0 ;;
|
|
85
|
+
*) return 1 ;;
|
|
86
|
+
esac
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Present a numbered menu.
|
|
90
|
+
# Usage: prompt_select VARNAME "Question?" opt1 opt2 opt3
|
|
91
|
+
prompt_select() {
|
|
92
|
+
local _var_name="$1"
|
|
93
|
+
local _question="$2"
|
|
94
|
+
shift 2
|
|
95
|
+
local _options=("$@")
|
|
96
|
+
local _count=${#_options[@]}
|
|
97
|
+
|
|
98
|
+
if [[ "${_PROMPT_NON_INTERACTIVE:-0}" == "1" ]]; then
|
|
99
|
+
eval "${_var_name}=\${_options[0]}"
|
|
100
|
+
return 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
printf "${_LOG_BLUE:-}?${_LOG_NC:-} %s\n" "$_question" >&2
|
|
104
|
+
local _i=1
|
|
105
|
+
while [[ $_i -le $_count ]]; do
|
|
106
|
+
printf " %d) %s\n" "$_i" "${_options[$(( _i - 1 ))]}" >&2
|
|
107
|
+
_i=$(( _i + 1 ))
|
|
108
|
+
done
|
|
109
|
+
|
|
110
|
+
local _choice=""
|
|
111
|
+
while true; do
|
|
112
|
+
printf " Enter 1-%d [1]: " "$_count" >&2
|
|
113
|
+
read -r _choice </dev/tty
|
|
114
|
+
_choice="${_choice:-1}"
|
|
115
|
+
if [[ "$_choice" =~ ^[0-9]+$ ]] && \
|
|
116
|
+
[[ "$_choice" -ge 1 ]] && \
|
|
117
|
+
[[ "$_choice" -le "$_count" ]]; then
|
|
118
|
+
break
|
|
119
|
+
fi
|
|
120
|
+
printf " Invalid choice. Try again.\n" >&2
|
|
121
|
+
done
|
|
122
|
+
|
|
123
|
+
eval "${_var_name}=\${_options[$(( _choice - 1 ))]}"
|
|
124
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# scripts/install/repair.sh — Re-apply spec to fix drift
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Compares spec intent against disk state (diff.sh), then re-applies
|
|
6
|
+
# only the tasks whose output files are missing or changed.
|
|
7
|
+
#
|
|
8
|
+
# Provides:
|
|
9
|
+
# repair_run TARGET_DIR
|
|
10
|
+
#
|
|
11
|
+
# Bash 3.2 compatible. No set -euo pipefail here.
|
|
12
|
+
# =============================================================================
|
|
13
|
+
[[ -n "${_HAS_REPAIR_LIB:-}" ]] && return 0
|
|
14
|
+
_HAS_REPAIR_LIB=1
|
|
15
|
+
|
|
16
|
+
repair_run() {
|
|
17
|
+
local target="${1:-$(pwd)}"
|
|
18
|
+
local spec_file
|
|
19
|
+
spec_file="$(spec_path "$target")"
|
|
20
|
+
|
|
21
|
+
if [[ ! -f "$spec_file" ]]; then
|
|
22
|
+
log_error "repair: no spec found at $spec_file"
|
|
23
|
+
return 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
log_info "Checking for drift at: $target"
|
|
27
|
+
|
|
28
|
+
# Run diff (non-destructive) — prints what needs repair
|
|
29
|
+
diff_spec "$spec_file"
|
|
30
|
+
local diff_ret=$?
|
|
31
|
+
|
|
32
|
+
if [[ $diff_ret -eq 0 ]]; then
|
|
33
|
+
log_success "No drift detected — nothing to repair."
|
|
34
|
+
return 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
log_info "Drift detected. Re-applying spec..."
|
|
38
|
+
spec_read "$spec_file"
|
|
39
|
+
|
|
40
|
+
# Force-rewrite everything to fix drift
|
|
41
|
+
_FS_FORCE=1
|
|
42
|
+
export _FS_FORCE
|
|
43
|
+
|
|
44
|
+
apply_run "$spec_file"
|
|
45
|
+
}
|