jekyll-theme-zer0 1.8.2 → 1.9.1
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 +33 -3
- 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/github-setup.sh +0 -0
- 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 +49 -6
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# scripts/install/spec.sh — Install spec JSON I/O and validation
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# The spec is a JSON document that fully describes an installer run.
|
|
6
|
+
# This module handles creating, reading, writing, and validating specs
|
|
7
|
+
# without requiring jq (uses a pure-bash fallback for basic reads).
|
|
8
|
+
#
|
|
9
|
+
# Provides:
|
|
10
|
+
# spec_default → print a minimal valid spec to stdout
|
|
11
|
+
# spec_write FILE → write current spec env-vars to FILE as JSON
|
|
12
|
+
# spec_read FILE → export spec env-vars from FILE
|
|
13
|
+
# spec_validate FILE → validate FILE against schema; print errors
|
|
14
|
+
# spec_get FILE KEY → print value for KEY (dot-notation: site.title)
|
|
15
|
+
# spec_hash FILE → print sha256 of spec content
|
|
16
|
+
# spec_path TARGET → canonical spec file path for TARGET dir
|
|
17
|
+
#
|
|
18
|
+
# Spec globals (populated by spec_read / plan.sh):
|
|
19
|
+
# SPEC_SCHEMA_VERSION SPEC_TARGET_DIR SPEC_PROFILE
|
|
20
|
+
# SPEC_SITE_TITLE SPEC_SITE_DESCRIPTION SPEC_SITE_URL
|
|
21
|
+
# SPEC_SITE_AUTHOR SPEC_SITE_EMAIL SPEC_SITE_TIMEZONE SPEC_SITE_LOCALE
|
|
22
|
+
# SPEC_GITHUB_USER SPEC_GITHUB_REPO SPEC_GITHUB_PAGES_BRANCH SPEC_GITHUB_ENABLE_PAGES
|
|
23
|
+
# SPEC_THEME_SOURCE SPEC_THEME_VERSION
|
|
24
|
+
# SPEC_TASKS (space-separated)
|
|
25
|
+
# SPEC_DEPLOY (space-separated)
|
|
26
|
+
# SPEC_AGENTS (space-separated)
|
|
27
|
+
# SPEC_OPT_DRY_RUN SPEC_OPT_FORCE SPEC_OPT_BACKUP
|
|
28
|
+
# SPEC_OPT_NON_INTERACTIVE SPEC_OPT_OUTPUT SPEC_OPT_AUTO_ACCEPT
|
|
29
|
+
# SPEC_OPT_SKIP_DOCTOR SPEC_OPT_VERBOSE
|
|
30
|
+
# SPEC_AI_USED SPEC_AI_PROVIDER SPEC_AI_MODEL
|
|
31
|
+
#
|
|
32
|
+
# Bash 3.2 compatible. No set -euo pipefail here.
|
|
33
|
+
# =============================================================================
|
|
34
|
+
[[ -n "${_HAS_SPEC_LIB:-}" ]] && return 0
|
|
35
|
+
_HAS_SPEC_LIB=1
|
|
36
|
+
|
|
37
|
+
# Relative path inside target dir where the spec is stored
|
|
38
|
+
SPEC_FILE_NAME=".zer0/install.spec.json"
|
|
39
|
+
|
|
40
|
+
spec_path() {
|
|
41
|
+
local target_dir="$1"
|
|
42
|
+
echo "${target_dir}/${SPEC_FILE_NAME}"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# spec_default — print a minimal valid spec with sensible defaults
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
spec_default() {
|
|
49
|
+
cat <<'SPEC'
|
|
50
|
+
{
|
|
51
|
+
"schema_version": "1",
|
|
52
|
+
"target_dir": "",
|
|
53
|
+
"profile": "default",
|
|
54
|
+
"site": {
|
|
55
|
+
"title": "My Jekyll Site",
|
|
56
|
+
"description": "A Jekyll site built with zer0-mistakes",
|
|
57
|
+
"url": "",
|
|
58
|
+
"author": "Site Author",
|
|
59
|
+
"email": "",
|
|
60
|
+
"timezone": "UTC",
|
|
61
|
+
"locale": "en"
|
|
62
|
+
},
|
|
63
|
+
"github": {
|
|
64
|
+
"user": "",
|
|
65
|
+
"repo": "",
|
|
66
|
+
"pages_branch": "gh-pages",
|
|
67
|
+
"enable_pages": false
|
|
68
|
+
},
|
|
69
|
+
"theme": {
|
|
70
|
+
"source": "gem",
|
|
71
|
+
"version": ""
|
|
72
|
+
},
|
|
73
|
+
"tasks": ["config","gemfile","docker","pages","nav","data","gitignore","readme","marker"],
|
|
74
|
+
"deploy": [],
|
|
75
|
+
"agents": [],
|
|
76
|
+
"options": {
|
|
77
|
+
"dry_run": false,
|
|
78
|
+
"force": false,
|
|
79
|
+
"backup": true,
|
|
80
|
+
"non_interactive": false,
|
|
81
|
+
"output": "human",
|
|
82
|
+
"auto_accept": false,
|
|
83
|
+
"skip_doctor": false,
|
|
84
|
+
"verbose": false
|
|
85
|
+
},
|
|
86
|
+
"ai": {
|
|
87
|
+
"used": false,
|
|
88
|
+
"provider": "openai",
|
|
89
|
+
"model": "",
|
|
90
|
+
"tokens_estimated": 0,
|
|
91
|
+
"spec_hash": ""
|
|
92
|
+
},
|
|
93
|
+
"scrape": {
|
|
94
|
+
"source_url": "",
|
|
95
|
+
"depth": 2,
|
|
96
|
+
"max_pages": 25,
|
|
97
|
+
"out_dir": "",
|
|
98
|
+
"include_nav": true
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
SPEC
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
# spec_write FILE — serialise current SPEC_* globals to JSON
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
spec_write() {
|
|
108
|
+
local out_file="$1"
|
|
109
|
+
local dry_run="${_FS_DRY_RUN:-0}"
|
|
110
|
+
|
|
111
|
+
# Build tasks array from space-separated SPEC_TASKS
|
|
112
|
+
local tasks_json=""
|
|
113
|
+
local t
|
|
114
|
+
for t in ${SPEC_TASKS:-config gemfile docker pages nav data gitignore readme marker}; do
|
|
115
|
+
tasks_json="${tasks_json}\"${t}\","
|
|
116
|
+
done
|
|
117
|
+
tasks_json="[${tasks_json%,}]"
|
|
118
|
+
|
|
119
|
+
local deploy_json=""
|
|
120
|
+
for t in ${SPEC_DEPLOY:-}; do
|
|
121
|
+
deploy_json="${deploy_json}\"${t}\","
|
|
122
|
+
done
|
|
123
|
+
deploy_json="[${deploy_json%,}]"
|
|
124
|
+
|
|
125
|
+
local agents_json=""
|
|
126
|
+
for t in ${SPEC_AGENTS:-}; do
|
|
127
|
+
agents_json="${agents_json}\"${t}\","
|
|
128
|
+
done
|
|
129
|
+
agents_json="[${agents_json%,}]"
|
|
130
|
+
|
|
131
|
+
local json
|
|
132
|
+
json=$(cat <<JSON
|
|
133
|
+
{
|
|
134
|
+
"schema_version": "1",
|
|
135
|
+
"target_dir": "${SPEC_TARGET_DIR:-}",
|
|
136
|
+
"profile": "${SPEC_PROFILE:-default}",
|
|
137
|
+
"site": {
|
|
138
|
+
"title": "${SPEC_SITE_TITLE:-My Jekyll Site}",
|
|
139
|
+
"description": "${SPEC_SITE_DESCRIPTION:-A Jekyll site built with zer0-mistakes}",
|
|
140
|
+
"url": "${SPEC_SITE_URL:-}",
|
|
141
|
+
"author": "${SPEC_SITE_AUTHOR:-Site Author}",
|
|
142
|
+
"email": "${SPEC_SITE_EMAIL:-}",
|
|
143
|
+
"timezone": "${SPEC_SITE_TIMEZONE:-UTC}",
|
|
144
|
+
"locale": "${SPEC_SITE_LOCALE:-en}"
|
|
145
|
+
},
|
|
146
|
+
"github": {
|
|
147
|
+
"user": "${SPEC_GITHUB_USER:-}",
|
|
148
|
+
"repo": "${SPEC_GITHUB_REPO:-}",
|
|
149
|
+
"pages_branch": "${SPEC_GITHUB_PAGES_BRANCH:-gh-pages}",
|
|
150
|
+
"enable_pages": ${SPEC_GITHUB_ENABLE_PAGES:-false}
|
|
151
|
+
},
|
|
152
|
+
"theme": {
|
|
153
|
+
"source": "${SPEC_THEME_SOURCE:-gem}",
|
|
154
|
+
"version": "${SPEC_THEME_VERSION:-}"
|
|
155
|
+
},
|
|
156
|
+
"tasks": ${tasks_json},
|
|
157
|
+
"deploy": ${deploy_json},
|
|
158
|
+
"agents": ${agents_json},
|
|
159
|
+
"options": {
|
|
160
|
+
"dry_run": ${SPEC_OPT_DRY_RUN:-false},
|
|
161
|
+
"force": ${SPEC_OPT_FORCE:-false},
|
|
162
|
+
"backup": ${SPEC_OPT_BACKUP:-true},
|
|
163
|
+
"non_interactive": ${SPEC_OPT_NON_INTERACTIVE:-false},
|
|
164
|
+
"output": "${SPEC_OPT_OUTPUT:-human}",
|
|
165
|
+
"auto_accept": ${SPEC_OPT_AUTO_ACCEPT:-false},
|
|
166
|
+
"skip_doctor": ${SPEC_OPT_SKIP_DOCTOR:-false},
|
|
167
|
+
"verbose": ${SPEC_OPT_VERBOSE:-false}
|
|
168
|
+
},
|
|
169
|
+
"ai": {
|
|
170
|
+
"used": ${SPEC_AI_USED:-false},
|
|
171
|
+
"provider": "${SPEC_AI_PROVIDER:-openai}",
|
|
172
|
+
"model": "${SPEC_AI_MODEL:-}",
|
|
173
|
+
"tokens_estimated": ${SPEC_AI_TOKENS:-0},
|
|
174
|
+
"spec_hash": ""
|
|
175
|
+
},
|
|
176
|
+
"scrape": {
|
|
177
|
+
"source_url": "${SPEC_SCRAPE_SOURCE_URL:-}",
|
|
178
|
+
"depth": ${SPEC_SCRAPE_DEPTH:-2},
|
|
179
|
+
"max_pages": ${SPEC_SCRAPE_MAX_PAGES:-25},
|
|
180
|
+
"out_dir": "${SPEC_SCRAPE_OUT_DIR:-}",
|
|
181
|
+
"include_nav": ${SPEC_SCRAPE_INCLUDE_NAV:-true}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
JSON
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if [[ "$dry_run" == "1" ]]; then
|
|
188
|
+
log_debug "[dry-run] would write spec: $out_file"
|
|
189
|
+
return 0
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
if [[ "$(type -t fs_ensure_dir)" == "function" ]]; then
|
|
193
|
+
fs_ensure_dir "$(dirname "$out_file")"
|
|
194
|
+
else
|
|
195
|
+
mkdir -p "$(dirname "$out_file")"
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
printf '%s\n' "$json" > "$out_file"
|
|
199
|
+
log_debug "Spec written: $out_file"
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# ---------------------------------------------------------------------------
|
|
203
|
+
# spec_read FILE — parse JSON spec → export SPEC_* globals
|
|
204
|
+
# Use jq when available; fall back to pure-bash awk parser for core fields.
|
|
205
|
+
# ---------------------------------------------------------------------------
|
|
206
|
+
spec_read() {
|
|
207
|
+
local in_file="$1"
|
|
208
|
+
if [[ ! -f "$in_file" ]]; then
|
|
209
|
+
log_error "spec_read: file not found: $in_file"
|
|
210
|
+
return 1
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
if command -v jq >/dev/null 2>&1; then
|
|
214
|
+
_spec_read_jq "$in_file"
|
|
215
|
+
else
|
|
216
|
+
_spec_read_awk "$in_file"
|
|
217
|
+
fi
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# jq-powered reader
|
|
221
|
+
_spec_read_jq() {
|
|
222
|
+
local f="$1"
|
|
223
|
+
SPEC_SCHEMA_VERSION=$(jq -r '.schema_version // "1"' "$f")
|
|
224
|
+
SPEC_TARGET_DIR=$(jq -r '.target_dir // ""' "$f")
|
|
225
|
+
SPEC_PROFILE=$(jq -r '.profile // "default"' "$f")
|
|
226
|
+
SPEC_SITE_TITLE=$(jq -r '.site.title // "My Jekyll Site"' "$f")
|
|
227
|
+
SPEC_SITE_DESCRIPTION=$(jq -r '.site.description // ""' "$f")
|
|
228
|
+
SPEC_SITE_URL=$(jq -r '.site.url // ""' "$f")
|
|
229
|
+
SPEC_SITE_AUTHOR=$(jq -r '.site.author // ""' "$f")
|
|
230
|
+
SPEC_SITE_EMAIL=$(jq -r '.site.email // ""' "$f")
|
|
231
|
+
SPEC_SITE_TIMEZONE=$(jq -r '.site.timezone // "UTC"' "$f")
|
|
232
|
+
SPEC_SITE_LOCALE=$(jq -r '.site.locale // "en"' "$f")
|
|
233
|
+
SPEC_GITHUB_USER=$(jq -r '.github.user // ""' "$f")
|
|
234
|
+
SPEC_GITHUB_REPO=$(jq -r '.github.repo // ""' "$f")
|
|
235
|
+
SPEC_GITHUB_PAGES_BRANCH=$(jq -r '.github.pages_branch // "gh-pages"' "$f")
|
|
236
|
+
SPEC_GITHUB_ENABLE_PAGES=$(jq -r '.github.enable_pages // false' "$f")
|
|
237
|
+
SPEC_THEME_SOURCE=$(jq -r '.theme.source // "gem"' "$f")
|
|
238
|
+
SPEC_THEME_VERSION=$(jq -r '.theme.version // ""' "$f")
|
|
239
|
+
SPEC_TASKS=$(jq -r '.tasks // [] | join(" ")' "$f")
|
|
240
|
+
SPEC_DEPLOY=$(jq -r '.deploy // [] | join(" ")' "$f")
|
|
241
|
+
SPEC_AGENTS=$(jq -r '.agents // [] | join(" ")' "$f")
|
|
242
|
+
SPEC_OPT_DRY_RUN=$(jq -r '.options.dry_run // false' "$f")
|
|
243
|
+
SPEC_OPT_FORCE=$(jq -r '.options.force // false' "$f")
|
|
244
|
+
SPEC_OPT_BACKUP=$(jq -r '.options.backup // true' "$f")
|
|
245
|
+
SPEC_OPT_NON_INTERACTIVE=$(jq -r '.options.non_interactive // false' "$f")
|
|
246
|
+
SPEC_OPT_OUTPUT=$(jq -r '.options.output // "human"' "$f")
|
|
247
|
+
SPEC_OPT_AUTO_ACCEPT=$(jq -r '.options.auto_accept // false' "$f")
|
|
248
|
+
SPEC_OPT_SKIP_DOCTOR=$(jq -r '.options.skip_doctor // false' "$f")
|
|
249
|
+
SPEC_OPT_VERBOSE=$(jq -r '.options.verbose // false' "$f")
|
|
250
|
+
SPEC_AI_USED=$(jq -r '.ai.used // false' "$f")
|
|
251
|
+
SPEC_AI_PROVIDER=$(jq -r '.ai.provider // "openai"' "$f")
|
|
252
|
+
SPEC_AI_MODEL=$(jq -r '.ai.model // ""' "$f")
|
|
253
|
+
SPEC_AI_TOKENS=$(jq -r '.ai.tokens_estimated // 0' "$f")
|
|
254
|
+
SPEC_SCRAPE_SOURCE_URL=$(jq -r '.scrape.source_url // ""' "$f")
|
|
255
|
+
SPEC_SCRAPE_DEPTH=$(jq -r '.scrape.depth // 2' "$f")
|
|
256
|
+
SPEC_SCRAPE_MAX_PAGES=$(jq -r '.scrape.max_pages // 25' "$f")
|
|
257
|
+
SPEC_SCRAPE_OUT_DIR=$(jq -r '.scrape.out_dir // ""' "$f")
|
|
258
|
+
SPEC_SCRAPE_INCLUDE_NAV=$(jq -r '.scrape.include_nav // true' "$f")
|
|
259
|
+
export SPEC_SCHEMA_VERSION SPEC_TARGET_DIR SPEC_PROFILE \
|
|
260
|
+
SPEC_SITE_TITLE SPEC_SITE_DESCRIPTION SPEC_SITE_URL \
|
|
261
|
+
SPEC_SITE_AUTHOR SPEC_SITE_EMAIL SPEC_SITE_TIMEZONE SPEC_SITE_LOCALE \
|
|
262
|
+
SPEC_GITHUB_USER SPEC_GITHUB_REPO SPEC_GITHUB_PAGES_BRANCH SPEC_GITHUB_ENABLE_PAGES \
|
|
263
|
+
SPEC_THEME_SOURCE SPEC_THEME_VERSION \
|
|
264
|
+
SPEC_TASKS SPEC_DEPLOY SPEC_AGENTS \
|
|
265
|
+
SPEC_OPT_DRY_RUN SPEC_OPT_FORCE SPEC_OPT_BACKUP \
|
|
266
|
+
SPEC_OPT_NON_INTERACTIVE SPEC_OPT_OUTPUT SPEC_OPT_AUTO_ACCEPT \
|
|
267
|
+
SPEC_OPT_SKIP_DOCTOR SPEC_OPT_VERBOSE \
|
|
268
|
+
SPEC_AI_USED SPEC_AI_PROVIDER SPEC_AI_MODEL SPEC_AI_TOKENS \
|
|
269
|
+
SPEC_SCRAPE_SOURCE_URL SPEC_SCRAPE_DEPTH SPEC_SCRAPE_MAX_PAGES \
|
|
270
|
+
SPEC_SCRAPE_OUT_DIR SPEC_SCRAPE_INCLUDE_NAV
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
# Minimal awk-based JSON reader for when jq is absent.
|
|
274
|
+
# Handles flat and one-level-deep scalar fields. Arrays are returned
|
|
275
|
+
# as space-separated values.
|
|
276
|
+
_spec_read_awk() {
|
|
277
|
+
local f="$1"
|
|
278
|
+
# Helper: extract a value for a given key pattern
|
|
279
|
+
_awk_get() {
|
|
280
|
+
local key="$1"
|
|
281
|
+
local default="${2:-}"
|
|
282
|
+
awk -v k="$key" '
|
|
283
|
+
$0 ~ "\"" k "\"[[:space:]]*:[[:space:]]*\"" {
|
|
284
|
+
# string value
|
|
285
|
+
match($0, "\"" k "\"[[:space:]]*:[[:space:]]*\"([^\"]*)", a)
|
|
286
|
+
# fallback awk for bash 3.2 (no match capture groups in some builds)
|
|
287
|
+
line = $0
|
|
288
|
+
sub(".*\"" k "\"[[:space:]]*:[[:space:]]*\"", "", line)
|
|
289
|
+
sub("\".*", "", line)
|
|
290
|
+
print line
|
|
291
|
+
found=1
|
|
292
|
+
exit
|
|
293
|
+
}
|
|
294
|
+
$0 ~ "\"" k "\"[[:space:]]*:[[:space:]]*[0-9tf]" {
|
|
295
|
+
# boolean/number value
|
|
296
|
+
line = $0
|
|
297
|
+
sub(".*\"" k "\"[[:space:]]*:[[:space:]]*", "", line)
|
|
298
|
+
sub("[,}].*", "", line)
|
|
299
|
+
gsub(/[[:space:]]/, "", line)
|
|
300
|
+
print line
|
|
301
|
+
found=1
|
|
302
|
+
exit
|
|
303
|
+
}
|
|
304
|
+
END { if (!found) print "" }
|
|
305
|
+
' "$f" || echo "$default"
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
SPEC_SCHEMA_VERSION=$(_awk_get "schema_version" "1")
|
|
309
|
+
SPEC_TARGET_DIR=$(_awk_get "target_dir" "")
|
|
310
|
+
SPEC_PROFILE=$(_awk_get "profile" "default")
|
|
311
|
+
SPEC_SITE_TITLE=$(_awk_get "title" "My Jekyll Site")
|
|
312
|
+
SPEC_SITE_AUTHOR=$(_awk_get "author" "")
|
|
313
|
+
SPEC_SITE_EMAIL=$(_awk_get "email" "")
|
|
314
|
+
SPEC_SITE_TIMEZONE=$(_awk_get "timezone" "UTC")
|
|
315
|
+
SPEC_GITHUB_USER=$(_awk_get "user" "")
|
|
316
|
+
SPEC_GITHUB_REPO=$(_awk_get "repo" "")
|
|
317
|
+
SPEC_GITHUB_PAGES_BRANCH=$(_awk_get "pages_branch" "gh-pages")
|
|
318
|
+
SPEC_THEME_SOURCE=$(_awk_get "source" "gem")
|
|
319
|
+
SPEC_THEME_VERSION=$(_awk_get "version" "")
|
|
320
|
+
SPEC_OPT_OUTPUT=$(_awk_get "output" "human")
|
|
321
|
+
|
|
322
|
+
# Arrays: extract list items from JSON arrays
|
|
323
|
+
SPEC_TASKS=$(awk '
|
|
324
|
+
/"tasks"/ { found=1 }
|
|
325
|
+
found && /\[/ { inlist=1 }
|
|
326
|
+
inlist && /"[a-z]/ {
|
|
327
|
+
val = $0
|
|
328
|
+
gsub(/^[^"]*"/, "", val)
|
|
329
|
+
gsub(/".*/, "", val)
|
|
330
|
+
printf "%s ", val
|
|
331
|
+
}
|
|
332
|
+
inlist && /\]/ { exit }
|
|
333
|
+
' "$f" | sed 's/[[:space:]]*$//')
|
|
334
|
+
|
|
335
|
+
SPEC_DEPLOY=$(awk '
|
|
336
|
+
/"deploy"/ { found=1 }
|
|
337
|
+
found && /\[/ { inlist=1 }
|
|
338
|
+
inlist && /"[a-z]/ {
|
|
339
|
+
val = $0
|
|
340
|
+
gsub(/^[^"]*"/, "", val)
|
|
341
|
+
gsub(/".*/, "", val)
|
|
342
|
+
printf "%s ", val
|
|
343
|
+
}
|
|
344
|
+
inlist && /\]/ { exit }
|
|
345
|
+
' "$f" | sed 's/[[:space:]]*$//')
|
|
346
|
+
|
|
347
|
+
SPEC_AGENTS=$(awk '
|
|
348
|
+
/"agents"/ { found=1 }
|
|
349
|
+
found && /\[/ { inlist=1 }
|
|
350
|
+
inlist && /"[a-z]/ {
|
|
351
|
+
val = $0
|
|
352
|
+
gsub(/^[^"]*"/, "", val)
|
|
353
|
+
gsub(/".*/, "", val)
|
|
354
|
+
printf "%s ", val
|
|
355
|
+
}
|
|
356
|
+
inlist && /\]/ { exit }
|
|
357
|
+
' "$f" | sed 's/[[:space:]]*$//')
|
|
358
|
+
|
|
359
|
+
# Fallback defaults for fields we couldn't parse without jq
|
|
360
|
+
SPEC_SITE_DESCRIPTION="${SPEC_SITE_DESCRIPTION:-A Jekyll site built with zer0-mistakes}"
|
|
361
|
+
SPEC_SITE_URL="${SPEC_SITE_URL:-}"
|
|
362
|
+
SPEC_SITE_LOCALE="${SPEC_SITE_LOCALE:-en}"
|
|
363
|
+
SPEC_GITHUB_ENABLE_PAGES="${SPEC_GITHUB_ENABLE_PAGES:-false}"
|
|
364
|
+
SPEC_OPT_DRY_RUN="${SPEC_OPT_DRY_RUN:-false}"
|
|
365
|
+
SPEC_OPT_FORCE="${SPEC_OPT_FORCE:-false}"
|
|
366
|
+
SPEC_OPT_BACKUP="${SPEC_OPT_BACKUP:-true}"
|
|
367
|
+
SPEC_OPT_NON_INTERACTIVE="${SPEC_OPT_NON_INTERACTIVE:-false}"
|
|
368
|
+
SPEC_OPT_AUTO_ACCEPT="${SPEC_OPT_AUTO_ACCEPT:-false}"
|
|
369
|
+
SPEC_OPT_SKIP_DOCTOR="${SPEC_OPT_SKIP_DOCTOR:-false}"
|
|
370
|
+
SPEC_OPT_VERBOSE="${SPEC_OPT_VERBOSE:-false}"
|
|
371
|
+
SPEC_AI_USED="${SPEC_AI_USED:-false}"
|
|
372
|
+
SPEC_AI_PROVIDER="${SPEC_AI_PROVIDER:-openai}"
|
|
373
|
+
SPEC_AI_MODEL="${SPEC_AI_MODEL:-}"
|
|
374
|
+
SPEC_AI_TOKENS="${SPEC_AI_TOKENS:-0}"
|
|
375
|
+
SPEC_SCRAPE_SOURCE_URL="${SPEC_SCRAPE_SOURCE_URL:-$(awk '/"source_url"/ { gsub(/.*"source_url"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); print; exit }' "$f" 2>/dev/null)}"
|
|
376
|
+
SPEC_SCRAPE_DEPTH="${SPEC_SCRAPE_DEPTH:-2}"
|
|
377
|
+
SPEC_SCRAPE_MAX_PAGES="${SPEC_SCRAPE_MAX_PAGES:-25}"
|
|
378
|
+
SPEC_SCRAPE_OUT_DIR="${SPEC_SCRAPE_OUT_DIR:-}"
|
|
379
|
+
SPEC_SCRAPE_INCLUDE_NAV="${SPEC_SCRAPE_INCLUDE_NAV:-true}"
|
|
380
|
+
|
|
381
|
+
export SPEC_SCHEMA_VERSION SPEC_TARGET_DIR SPEC_PROFILE \
|
|
382
|
+
SPEC_SITE_TITLE SPEC_SITE_DESCRIPTION SPEC_SITE_URL \
|
|
383
|
+
SPEC_SITE_AUTHOR SPEC_SITE_EMAIL SPEC_SITE_TIMEZONE SPEC_SITE_LOCALE \
|
|
384
|
+
SPEC_GITHUB_USER SPEC_GITHUB_REPO SPEC_GITHUB_PAGES_BRANCH SPEC_GITHUB_ENABLE_PAGES \
|
|
385
|
+
SPEC_THEME_SOURCE SPEC_THEME_VERSION \
|
|
386
|
+
SPEC_TASKS SPEC_DEPLOY SPEC_AGENTS \
|
|
387
|
+
SPEC_OPT_DRY_RUN SPEC_OPT_FORCE SPEC_OPT_BACKUP \
|
|
388
|
+
SPEC_OPT_NON_INTERACTIVE SPEC_OPT_OUTPUT SPEC_OPT_AUTO_ACCEPT \
|
|
389
|
+
SPEC_OPT_SKIP_DOCTOR SPEC_OPT_VERBOSE \
|
|
390
|
+
SPEC_AI_USED SPEC_AI_PROVIDER SPEC_AI_MODEL SPEC_AI_TOKENS \
|
|
391
|
+
SPEC_SCRAPE_SOURCE_URL SPEC_SCRAPE_DEPTH SPEC_SCRAPE_MAX_PAGES \
|
|
392
|
+
SPEC_SCRAPE_OUT_DIR SPEC_SCRAPE_INCLUDE_NAV
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
# ---------------------------------------------------------------------------
|
|
396
|
+
# spec_validate FILE — lightweight validation (no jq required)
|
|
397
|
+
# ---------------------------------------------------------------------------
|
|
398
|
+
spec_validate() {
|
|
399
|
+
local f="$1"
|
|
400
|
+
local errors=0
|
|
401
|
+
|
|
402
|
+
if [[ ! -f "$f" ]]; then
|
|
403
|
+
log_error "spec_validate: file not found: $f"
|
|
404
|
+
return 1
|
|
405
|
+
fi
|
|
406
|
+
|
|
407
|
+
# Check schema_version is present
|
|
408
|
+
if ! grep -q '"schema_version"' "$f"; then
|
|
409
|
+
log_error "spec_validate: missing required field: schema_version"
|
|
410
|
+
errors=$(( errors + 1 ))
|
|
411
|
+
fi
|
|
412
|
+
|
|
413
|
+
# Check target_dir is present and non-empty
|
|
414
|
+
if ! grep -q '"target_dir"[[:space:]]*:[[:space:]]*"[^"]' "$f"; then
|
|
415
|
+
log_error "spec_validate: target_dir is missing or empty"
|
|
416
|
+
errors=$(( errors + 1 ))
|
|
417
|
+
fi
|
|
418
|
+
|
|
419
|
+
# Check profile is a known value
|
|
420
|
+
local profile
|
|
421
|
+
if command -v jq >/dev/null 2>&1; then
|
|
422
|
+
profile=$(jq -r '.profile // ""' "$f")
|
|
423
|
+
else
|
|
424
|
+
profile=$(awk '/"profile"/ { gsub(/.*"profile"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); print; exit }' "$f")
|
|
425
|
+
fi
|
|
426
|
+
case "$profile" in
|
|
427
|
+
default|minimal|blog|docs|portfolio|github-pages|fork) ;;
|
|
428
|
+
*)
|
|
429
|
+
log_error "spec_validate: unknown profile: '$profile'"
|
|
430
|
+
errors=$(( errors + 1 ))
|
|
431
|
+
;;
|
|
432
|
+
esac
|
|
433
|
+
|
|
434
|
+
# Check theme.source
|
|
435
|
+
local theme_src
|
|
436
|
+
if command -v jq >/dev/null 2>&1; then
|
|
437
|
+
theme_src=$(jq -r '.theme.source // ""' "$f")
|
|
438
|
+
else
|
|
439
|
+
theme_src=$(awk '/"source"/ { gsub(/.*"source"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); print; exit }' "$f")
|
|
440
|
+
fi
|
|
441
|
+
case "$theme_src" in
|
|
442
|
+
gem|remote|vendored) ;;
|
|
443
|
+
*)
|
|
444
|
+
log_error "spec_validate: invalid theme.source: '$theme_src'"
|
|
445
|
+
errors=$(( errors + 1 ))
|
|
446
|
+
;;
|
|
447
|
+
esac
|
|
448
|
+
|
|
449
|
+
if [[ $errors -eq 0 ]]; then
|
|
450
|
+
log_debug "spec_validate: OK — $f"
|
|
451
|
+
return 0
|
|
452
|
+
fi
|
|
453
|
+
return 1
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
# ---------------------------------------------------------------------------
|
|
457
|
+
# spec_get FILE KEY — print the value for a dot-notation key
|
|
458
|
+
# spec_get spec.json site.title
|
|
459
|
+
# ---------------------------------------------------------------------------
|
|
460
|
+
spec_get() {
|
|
461
|
+
local f="$1"
|
|
462
|
+
local key="$2"
|
|
463
|
+
if command -v jq >/dev/null 2>&1; then
|
|
464
|
+
jq -r ".${key} // \"\"" "$f"
|
|
465
|
+
else
|
|
466
|
+
# Fallback: load into SPEC_* and echo the matching var
|
|
467
|
+
spec_read "$f"
|
|
468
|
+
local var_name
|
|
469
|
+
var_name=$(echo "SPEC_${key}" | tr '.' '_' | tr '[:lower:]' '[:upper:]')
|
|
470
|
+
eval "echo \"\${${var_name}:-}\""
|
|
471
|
+
fi
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
# ---------------------------------------------------------------------------
|
|
475
|
+
# spec_hash FILE — sha256 hex of the file content
|
|
476
|
+
# ---------------------------------------------------------------------------
|
|
477
|
+
spec_hash() {
|
|
478
|
+
local f="$1"
|
|
479
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
480
|
+
sha256sum "$f" | awk '{print $1}'
|
|
481
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
482
|
+
shasum -a 256 "$f" | awk '{print $1}'
|
|
483
|
+
else
|
|
484
|
+
echo "unknown"
|
|
485
|
+
fi
|
|
486
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# scripts/install/tasks/_registry.sh — Task discovery and metadata
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Describes the canonical task graph: dependencies, conditions, descriptions.
|
|
6
|
+
# Sourced by apply.sh and diff.sh.
|
|
7
|
+
#
|
|
8
|
+
# Provides:
|
|
9
|
+
# task_list_all → print all task names, one per line
|
|
10
|
+
# task_description TASK → print one-line description
|
|
11
|
+
# task_depends TASK → print space-separated dependency names
|
|
12
|
+
#
|
|
13
|
+
# Bash 3.2 compatible. No set -euo pipefail here.
|
|
14
|
+
# =============================================================================
|
|
15
|
+
[[ -n "${_HAS_TASK_REGISTRY:-}" ]] && return 0
|
|
16
|
+
_HAS_TASK_REGISTRY=1
|
|
17
|
+
|
|
18
|
+
# Ordered canonical task list (deps must appear before dependents)
|
|
19
|
+
_TASK_ALL="config gemfile docker theme pages scrape nav data devcontainer agents gitignore readme marker"
|
|
20
|
+
|
|
21
|
+
# One-line descriptions
|
|
22
|
+
_task_desc_config="Write _config.yml and _config_dev.yml"
|
|
23
|
+
_task_desc_gemfile="Write Gemfile (variant: gem|remote|macos)"
|
|
24
|
+
_task_desc_docker="Write docker-compose.yml and docker/Dockerfile"
|
|
25
|
+
_task_desc_theme="Copy _layouts _includes _sass assets (vendor|local)"
|
|
26
|
+
_task_desc_pages="Generate starter pages from templates"
|
|
27
|
+
_task_desc_scrape="Import content from an existing website (spec.scrape.source_url)"
|
|
28
|
+
_task_desc_nav="Generate _data/navigation/ from template"
|
|
29
|
+
_task_desc_data="Generate _data/authors.yml and seed data"
|
|
30
|
+
_task_desc_devcontainer="Write .devcontainer/devcontainer.json"
|
|
31
|
+
_task_desc_agents="Write AI agent files (AGENTS.md, CLAUDE.md, etc.)"
|
|
32
|
+
_task_desc_gitignore="Write .gitignore"
|
|
33
|
+
_task_desc_readme="Write INSTALLATION.md and README seed"
|
|
34
|
+
_task_desc_marker="Write .zer0-installed marker + persist spec"
|
|
35
|
+
|
|
36
|
+
# Dependencies (space-separated, empty = no deps)
|
|
37
|
+
_task_deps_config=""
|
|
38
|
+
_task_deps_gemfile="config"
|
|
39
|
+
_task_deps_docker="config"
|
|
40
|
+
_task_deps_theme=""
|
|
41
|
+
_task_deps_pages="config"
|
|
42
|
+
_task_deps_scrape="config pages"
|
|
43
|
+
_task_deps_nav="config"
|
|
44
|
+
_task_deps_data="config"
|
|
45
|
+
_task_deps_devcontainer=""
|
|
46
|
+
_task_deps_agents=""
|
|
47
|
+
_task_deps_gitignore=""
|
|
48
|
+
_task_deps_readme=""
|
|
49
|
+
_task_deps_marker="config gemfile"
|
|
50
|
+
|
|
51
|
+
task_list_all() {
|
|
52
|
+
echo "$_TASK_ALL" | tr ' ' '\n'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
task_description() {
|
|
56
|
+
local task="$1"
|
|
57
|
+
local var="_task_desc_${task}"
|
|
58
|
+
eval "echo \"\${${var}:-unknown task: $task}\""
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
task_depends() {
|
|
62
|
+
local task="$1"
|
|
63
|
+
local var="_task_deps_${task}"
|
|
64
|
+
eval "echo \"\${${var}:-}\""
|
|
65
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# scripts/install/tasks/agents.sh — Write AI agent files
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Writes agent-specific files based on SPEC_AGENTS (space-separated list):
|
|
6
|
+
# copilot → .github/copilot-instructions.md
|
|
7
|
+
# claude → CLAUDE.md
|
|
8
|
+
# cursor → .cursor/rules/zer0.mdc
|
|
9
|
+
# aider → .aider.conf.yml
|
|
10
|
+
# generic → AGENTS.md (always written as baseline)
|
|
11
|
+
#
|
|
12
|
+
# Bash 3.2 compatible. No set -euo pipefail here.
|
|
13
|
+
# =============================================================================
|
|
14
|
+
[[ -n "${_HAS_TASK_AGENTS:-}" ]] && return 0
|
|
15
|
+
_HAS_TASK_AGENTS=1
|
|
16
|
+
|
|
17
|
+
task_agents_run() {
|
|
18
|
+
local target="$1"
|
|
19
|
+
local agents="${SPEC_AGENTS:-}"
|
|
20
|
+
|
|
21
|
+
[[ -z "$agents" ]] && { log_debug "agents task: no agents configured (skipping)"; return 0; }
|
|
22
|
+
|
|
23
|
+
log_info "Writing AI agent files..."
|
|
24
|
+
|
|
25
|
+
local agent
|
|
26
|
+
for agent in $agents; do
|
|
27
|
+
case "$agent" in
|
|
28
|
+
generic)
|
|
29
|
+
tmpl_apply "agents/AGENTS.md.template" "${target}/AGENTS.md"
|
|
30
|
+
;;
|
|
31
|
+
copilot)
|
|
32
|
+
fs_ensure_dir "${target}/.github"
|
|
33
|
+
tmpl_apply "agents/copilot-instructions.md.template" \
|
|
34
|
+
"${target}/.github/copilot-instructions.md"
|
|
35
|
+
;;
|
|
36
|
+
claude)
|
|
37
|
+
tmpl_apply "agents/CLAUDE.md.template" "${target}/CLAUDE.md"
|
|
38
|
+
;;
|
|
39
|
+
cursor)
|
|
40
|
+
fs_ensure_dir "${target}/.cursor/rules"
|
|
41
|
+
tmpl_apply "agents/cursor-rule.mdc.template" \
|
|
42
|
+
"${target}/.cursor/rules/zer0.mdc"
|
|
43
|
+
;;
|
|
44
|
+
aider)
|
|
45
|
+
tmpl_apply "agents/aider.conf.yml.template" \
|
|
46
|
+
"${target}/.aider.conf.yml"
|
|
47
|
+
;;
|
|
48
|
+
all)
|
|
49
|
+
# Re-invoke with each known agent
|
|
50
|
+
SPEC_AGENTS="generic copilot claude cursor aider" task_agents_run "$target"
|
|
51
|
+
return $?
|
|
52
|
+
;;
|
|
53
|
+
*)
|
|
54
|
+
log_warning "agents task: unknown agent '$agent' (skipping)"
|
|
55
|
+
;;
|
|
56
|
+
esac
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
log_success "Agent files written"
|
|
60
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# scripts/install/tasks/config.sh — Write Jekyll config files
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Writes: _config.yml, _config_dev.yml (if profile != minimal/github-pages)
|
|
6
|
+
# All writes go through template.sh::tmpl_apply → fs.sh.
|
|
7
|
+
# Bash 3.2 compatible. No set -euo pipefail here.
|
|
8
|
+
# =============================================================================
|
|
9
|
+
[[ -n "${_HAS_TASK_CONFIG:-}" ]] && return 0
|
|
10
|
+
_HAS_TASK_CONFIG=1
|
|
11
|
+
|
|
12
|
+
task_config_run() {
|
|
13
|
+
local target="$1"
|
|
14
|
+
|
|
15
|
+
log_info "Writing Jekyll configuration..."
|
|
16
|
+
|
|
17
|
+
# Choose template variant based on theme.source
|
|
18
|
+
local config_tmpl
|
|
19
|
+
case "${SPEC_THEME_SOURCE:-${THEME_SOURCE:-gem}}" in
|
|
20
|
+
remote) config_tmpl="config/_config.remote.yml.template" ;;
|
|
21
|
+
fork) config_tmpl="config/_config.fork.yml.template" ;;
|
|
22
|
+
*) config_tmpl="config/_config.starter.yml.template" ;;
|
|
23
|
+
esac
|
|
24
|
+
|
|
25
|
+
tmpl_apply "$config_tmpl" "${target}/_config.yml"
|
|
26
|
+
|
|
27
|
+
# Dev config — skip for github-pages (remote_theme) and minimal profiles
|
|
28
|
+
local profile="${SPEC_PROFILE:-default}"
|
|
29
|
+
case "$profile" in
|
|
30
|
+
minimal|github-pages) ;;
|
|
31
|
+
*)
|
|
32
|
+
tmpl_apply "config/_config_dev.yml.template" "${target}/_config_dev.yml"
|
|
33
|
+
;;
|
|
34
|
+
esac
|
|
35
|
+
|
|
36
|
+
log_success "Jekyll configuration written"
|
|
37
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# scripts/install/tasks/data.sh — Generate seed data files
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Writes: _data/authors.yml
|
|
6
|
+
# Bash 3.2 compatible. No set -euo pipefail here.
|
|
7
|
+
# =============================================================================
|
|
8
|
+
[[ -n "${_HAS_TASK_DATA:-}" ]] && return 0
|
|
9
|
+
_HAS_TASK_DATA=1
|
|
10
|
+
|
|
11
|
+
task_data_run() {
|
|
12
|
+
local target="$1"
|
|
13
|
+
|
|
14
|
+
log_info "Writing seed data..."
|
|
15
|
+
fs_ensure_dir "${target}/_data"
|
|
16
|
+
tmpl_apply "data/authors.yml.template" "${target}/_data/authors.yml"
|
|
17
|
+
log_success "Seed data written"
|
|
18
|
+
}
|