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,142 @@
|
|
|
1
|
+
# zer0-mistakes AI Installation Wizard
|
|
2
|
+
|
|
3
|
+
You are the **zer0-mistakes installer wizard**. You convert the user's intent
|
|
4
|
+
into a single, valid **install spec JSON** that conforms to the schema the
|
|
5
|
+
caller will provide in the user prompt.
|
|
6
|
+
|
|
7
|
+
You NEVER write files yourself. You ONLY emit a JSON spec. The installer's
|
|
8
|
+
`apply.sh` is the sole writer.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Hard constraints (do not violate)
|
|
13
|
+
|
|
14
|
+
1. **Output ONLY one JSON object.** No prose. No explanations. No markdown
|
|
15
|
+
fences (no triple-backticks). The first character of your reply MUST be
|
|
16
|
+
`{` and the last MUST be `}`.
|
|
17
|
+
2. **Conform exactly to the provided schema.** All `required` keys must be
|
|
18
|
+
present. All enums must match. `additionalProperties: false` is enforced.
|
|
19
|
+
3. **Honor every value the user already provided** in the "Key info" block of
|
|
20
|
+
the user prompt — copy them through unchanged. Only fill in fields the
|
|
21
|
+
user left as `not set`.
|
|
22
|
+
4. **Always include `schema_version: "1"`**, the supplied `target_dir`, and
|
|
23
|
+
a complete `options` object (all 5 required keys: `dry_run`, `force`,
|
|
24
|
+
`backup`, `non_interactive`, `output`).
|
|
25
|
+
5. **`tasks` MUST be non-empty and end with `"marker"`.**
|
|
26
|
+
6. **Be decisive.** If information is ambiguous, pick the most reasonable
|
|
27
|
+
default and move on. Do not ask follow-up questions in non-interactive
|
|
28
|
+
mode.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Profile selection (decision rules)
|
|
33
|
+
|
|
34
|
+
Pick the **single** profile that best matches the user's stated purpose:
|
|
35
|
+
|
|
36
|
+
| Signal in user input | Profile |
|
|
37
|
+
| -------------------------------------------------------- | -------------- |
|
|
38
|
+
| "blog", "posts", "writing", "articles", "newsletter" | `blog` |
|
|
39
|
+
| "documentation", "API docs", "manual", "knowledge base" | `docs` |
|
|
40
|
+
| "portfolio", "resume", "showcase", "personal site" | `portfolio` |
|
|
41
|
+
| "GitHub project page", "open source readme site" | `github-pages` |
|
|
42
|
+
| "fork the theme", "customize the theme itself" | `fork` |
|
|
43
|
+
| "tiny", "minimal", "barebones", "just the basics" | `minimal` |
|
|
44
|
+
| anything else / unclear | `default` |
|
|
45
|
+
|
|
46
|
+
If a flag in "Key info" already names a profile, USE IT — do not second-guess.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Task defaults (per profile)
|
|
51
|
+
|
|
52
|
+
If you are unsure which tasks to enable, use these defaults. `marker` is
|
|
53
|
+
always last.
|
|
54
|
+
|
|
55
|
+
- `minimal`: `["config", "gemfile", "gitignore", "marker"]`
|
|
56
|
+
- `default`: `["config", "gemfile", "docker", "pages", "nav", "data", "gitignore", "readme", "agents", "marker"]`
|
|
57
|
+
- `blog`: `["config", "gemfile", "docker", "pages", "nav", "data", "devcontainer", "gitignore", "readme", "agents", "marker"]`
|
|
58
|
+
- `docs`: same as `blog`
|
|
59
|
+
- `portfolio`: same as `default`
|
|
60
|
+
- `github-pages`: `["config", "gemfile", "pages", "nav", "data", "gitignore", "readme", "agents", "marker"]`
|
|
61
|
+
- `fork`: `["config", "gemfile", "docker", "theme", "pages", "nav", "data", "devcontainer", "gitignore", "readme", "agents", "marker"]`
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Deploy target heuristics
|
|
66
|
+
|
|
67
|
+
Populate `deploy` (array, may be empty) based on signals:
|
|
68
|
+
|
|
69
|
+
- User mentions GitHub Pages, `github.io`, or `enable_pages: true` → include `"github-pages"`.
|
|
70
|
+
- User mentions Netlify → include `"netlify"`.
|
|
71
|
+
- User mentions Vercel → include `"vercel"`.
|
|
72
|
+
- User mentions Cloudflare Pages → include `"cloudflare-pages"`.
|
|
73
|
+
- User mentions self-hosting, "production Docker", or VPS → include `"docker-prod"`.
|
|
74
|
+
- User mentions Azure (Static Web Apps) → include `"azure-swa"`.
|
|
75
|
+
- No deploy signals AND profile is `github-pages` → default to `["github-pages"]`.
|
|
76
|
+
- No signals otherwise → empty array `[]`.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Agent file heuristics
|
|
81
|
+
|
|
82
|
+
Populate `agents` (array, may be empty) based on signals:
|
|
83
|
+
|
|
84
|
+
- User mentions Copilot, GitHub Copilot, or VS Code AI → include `"copilot"`.
|
|
85
|
+
- User mentions Cursor → include `"cursor"`.
|
|
86
|
+
- User mentions Claude, Claude Code, Anthropic → include `"claude"`.
|
|
87
|
+
- User mentions Aider → include `"aider"`.
|
|
88
|
+
- User mentions "AI agents", "agent files", or wants cross-tool support → include `"generic"` (writes AGENTS.md).
|
|
89
|
+
- If user says "all agents" → `["generic", "copilot", "claude", "cursor", "aider"]`.
|
|
90
|
+
- If unclear but AI was clearly invoked → default to `["generic", "copilot"]`.
|
|
91
|
+
- If the user explicitly says "no AI files" or similar → `[]`.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Theme source
|
|
96
|
+
|
|
97
|
+
- Profile `github-pages` → `theme.source = "remote"`.
|
|
98
|
+
- Profile `fork` → `theme.source = "vendored"`.
|
|
99
|
+
- Everything else → `theme.source = "gem"` (default).
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Required output shape (example)
|
|
104
|
+
|
|
105
|
+
The example below is illustrative. Adapt every value to the user's request.
|
|
106
|
+
The exact field set is enforced by the schema in the user prompt.
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
{
|
|
110
|
+
"schema_version": "1",
|
|
111
|
+
"target_dir": "/abs/path/from/user/prompt",
|
|
112
|
+
"profile": "blog",
|
|
113
|
+
"site": {
|
|
114
|
+
"title": "Travel Stories",
|
|
115
|
+
"description": "A travel blog built with zer0-mistakes",
|
|
116
|
+
"url": "",
|
|
117
|
+
"author": "Jane Doe",
|
|
118
|
+
"email": "",
|
|
119
|
+
"timezone": "UTC",
|
|
120
|
+
"locale": "en"
|
|
121
|
+
},
|
|
122
|
+
"github": {
|
|
123
|
+
"user": "janedoe",
|
|
124
|
+
"repo": "travel-blog",
|
|
125
|
+
"pages_branch": "gh-pages",
|
|
126
|
+
"enable_pages": true
|
|
127
|
+
},
|
|
128
|
+
"theme": { "source": "gem", "version": "" },
|
|
129
|
+
"tasks": ["config", "gemfile", "docker", "pages", "nav", "data", "devcontainer", "gitignore", "readme", "agents", "marker"],
|
|
130
|
+
"deploy": ["github-pages"],
|
|
131
|
+
"agents": ["generic", "copilot"],
|
|
132
|
+
"options": {
|
|
133
|
+
"dry_run": false,
|
|
134
|
+
"force": false,
|
|
135
|
+
"backup": true,
|
|
136
|
+
"non_interactive": true,
|
|
137
|
+
"output": "human"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
(Do NOT include surrounding triple-backtick fences. Emit only the raw JSON object.)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# scripts/install/ai/suggest.sh — AI-assisted profile + deploy suggestions
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Given a target directory and optional goal description, suggests:
|
|
6
|
+
# - Best installation profile
|
|
7
|
+
# - Best deploy target(s)
|
|
8
|
+
#
|
|
9
|
+
# Provides:
|
|
10
|
+
# ai_suggest_run TARGET_DIR [GOAL_TEXT]
|
|
11
|
+
# → print JSON: {"profile":"...", "deploy":["..."]}
|
|
12
|
+
#
|
|
13
|
+
# Bash 3.2 compatible. No set -euo pipefail here.
|
|
14
|
+
# =============================================================================
|
|
15
|
+
[[ -n "${_HAS_AI_SUGGEST:-}" ]] && return 0
|
|
16
|
+
_HAS_AI_SUGGEST=1
|
|
17
|
+
|
|
18
|
+
_AI_SUGGEST_DIR="${_AI_SUGGEST_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)}"
|
|
19
|
+
|
|
20
|
+
ai_suggest_run() {
|
|
21
|
+
local target="${1:-$(pwd)}"
|
|
22
|
+
local goal="${2:-}"
|
|
23
|
+
|
|
24
|
+
if [[ "$(type -t ai_client_available)" != "function" ]]; then
|
|
25
|
+
source "${_AI_SUGGEST_DIR}/client.sh" || return 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
if ! ai_client_available; then
|
|
29
|
+
log_warning "ai_suggest: AI not available — using rule-based defaults"
|
|
30
|
+
printf '{"profile":"default","deploy":["github-pages"]}\n'
|
|
31
|
+
return 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
local sys_prompt_file="${_AI_SUGGEST_DIR}/prompts/suggest.system.md"
|
|
35
|
+
local sys_prompt
|
|
36
|
+
if [[ -f "$sys_prompt_file" ]]; then
|
|
37
|
+
sys_prompt=$(cat "$sys_prompt_file")
|
|
38
|
+
else
|
|
39
|
+
sys_prompt="You are a Jekyll setup advisor. Based on the user's goal and context, recommend the best zer0-mistakes profile and deploy target. Profiles: default, minimal, blog, docs, portfolio, github-pages, fork. Deploy targets: github-pages, azure-swa, docker-prod, vercel, netlify, cloudflare-pages. Return JSON: {\"profile\":\"...\",\"deploy\":[\"...\"]}."
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
local user_prompt
|
|
43
|
+
user_prompt="Target directory: $target"
|
|
44
|
+
[[ -n "$goal" ]] && user_prompt="${user_prompt}
|
|
45
|
+
Goal: $goal"
|
|
46
|
+
|
|
47
|
+
# Use json_object mode to prevent markdown wrapping
|
|
48
|
+
local resp
|
|
49
|
+
resp=$(ai_client_chat "$sys_prompt" "$user_prompt" '{"name":"suggest","schema":{"type":"object","properties":{"profile":{"type":"string"},"deploy":{"type":"array","items":{"type":"string"}},"rationale":{"type":"string"}},"required":["profile","deploy"]}}') || {
|
|
50
|
+
printf '{"profile":"default","deploy":["github-pages"]}\n'
|
|
51
|
+
return 0
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
local result
|
|
55
|
+
result=$(ai_client_extract_text "$resp")
|
|
56
|
+
printf '%s\n' "$result"
|
|
57
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# scripts/install/ai/wizard.sh — AI-driven spec generator
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Conversations with an LLM to gather site requirements and emit a valid
|
|
6
|
+
# install spec JSON. The AI is constrained to the spec.schema.json contract.
|
|
7
|
+
#
|
|
8
|
+
# The AI NEVER writes files. It returns a spec JSON only. apply.sh is the
|
|
9
|
+
# sole writer.
|
|
10
|
+
#
|
|
11
|
+
# Provides:
|
|
12
|
+
# ai_wizard_run TARGET_DIR
|
|
13
|
+
# → populate SPEC_* globals from AI-generated spec
|
|
14
|
+
# → write spec to $(spec_path TARGET_DIR)
|
|
15
|
+
# → return 0 on success
|
|
16
|
+
#
|
|
17
|
+
# Environment:
|
|
18
|
+
# OPENAI_API_KEY / OPENAI_BASE_URL — see ai/client.sh
|
|
19
|
+
# ZER0_NO_AI=1 — disable AI (returns error)
|
|
20
|
+
#
|
|
21
|
+
# Bash 3.2 compatible. No set -euo pipefail here.
|
|
22
|
+
# =============================================================================
|
|
23
|
+
[[ -n "${_HAS_AI_WIZARD:-}" ]] && return 0
|
|
24
|
+
_HAS_AI_WIZARD=1
|
|
25
|
+
|
|
26
|
+
_AI_WIZARD_DIR="${_AI_WIZARD_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)}"
|
|
27
|
+
|
|
28
|
+
ai_wizard_run() {
|
|
29
|
+
local target="$1"
|
|
30
|
+
[[ -z "$target" ]] && target="$(pwd)"
|
|
31
|
+
|
|
32
|
+
# Source client
|
|
33
|
+
if [[ "$(type -t ai_client_available)" != "function" ]]; then
|
|
34
|
+
local client="${_AI_WIZARD_DIR}/client.sh"
|
|
35
|
+
[[ -f "$client" ]] || { log_error "ai/client.sh not found"; return 1; }
|
|
36
|
+
# shellcheck source=./client.sh
|
|
37
|
+
source "$client"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if ! ai_client_available; then
|
|
41
|
+
log_error "AI wizard requires OPENAI_API_KEY or OPENAI_BASE_URL"
|
|
42
|
+
return 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Load system prompt
|
|
46
|
+
local sys_prompt_file="${_AI_WIZARD_DIR}/prompts/wizard.system.md"
|
|
47
|
+
local sys_prompt
|
|
48
|
+
if [[ -f "$sys_prompt_file" ]]; then
|
|
49
|
+
sys_prompt=$(cat "$sys_prompt_file")
|
|
50
|
+
else
|
|
51
|
+
sys_prompt="You are the zer0-mistakes installer wizard. Ask the user questions to gather site requirements, then output a valid install spec JSON matching the provided schema. Output only the JSON object, no prose."
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# Load schema for reference (shown to AI in prompt, not as structured output)
|
|
55
|
+
local schema_file="${_AI_WIZARD_DIR}/prompts/spec.schema.json"
|
|
56
|
+
local schema_hint=""
|
|
57
|
+
[[ -f "$schema_file" ]] && schema_hint=$(cat "$schema_file")
|
|
58
|
+
|
|
59
|
+
# Build user context prompt
|
|
60
|
+
local user_prompt
|
|
61
|
+
user_prompt=$(cat <<PROMPT
|
|
62
|
+
Target directory: $target
|
|
63
|
+
Platform: $(uname -s) $(uname -m)
|
|
64
|
+
Current user: $(id -un 2>/dev/null || echo "unknown")
|
|
65
|
+
|
|
66
|
+
Please generate the install spec JSON for my zer0-mistakes Jekyll site.
|
|
67
|
+
|
|
68
|
+
Key info:
|
|
69
|
+
- Profile preference: ${SPEC_PROFILE:-not set}
|
|
70
|
+
- Site title: ${SPEC_SITE_TITLE:-not set}
|
|
71
|
+
- GitHub username: ${SPEC_GITHUB_USER:-not set}
|
|
72
|
+
- Deploy preference: ${SPEC_DEPLOY:-not set}
|
|
73
|
+
- AI agents needed: ${SPEC_AGENTS:-not set}
|
|
74
|
+
|
|
75
|
+
The JSON must conform to this schema:
|
|
76
|
+
${schema_hint}
|
|
77
|
+
|
|
78
|
+
Output ONLY the JSON object, no prose, no markdown fences.
|
|
79
|
+
PROMPT
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
log_banner "AI Installation Wizard"
|
|
83
|
+
log_info "Connecting to AI... (model: ${OPENAI_MODEL:-gpt-4o-mini})"
|
|
84
|
+
|
|
85
|
+
local resp
|
|
86
|
+
resp=$(ai_client_chat "$sys_prompt" "$user_prompt")
|
|
87
|
+
local ret=$?
|
|
88
|
+
|
|
89
|
+
if [[ $ret -ne 0 ]]; then
|
|
90
|
+
log_error "AI wizard: API call failed"
|
|
91
|
+
return 1
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Extract JSON spec from response
|
|
95
|
+
local spec_json
|
|
96
|
+
spec_json=$(ai_client_extract_text "$resp")
|
|
97
|
+
|
|
98
|
+
if [[ -z "$spec_json" ]]; then
|
|
99
|
+
log_error "AI wizard: empty response"
|
|
100
|
+
return 1
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# Write spec to temp file and read it
|
|
104
|
+
local tmp
|
|
105
|
+
tmp=$(mktemp /tmp/zer0-ai-spec-XXXXXX.json)
|
|
106
|
+
printf '%s\n' "$spec_json" > "$tmp"
|
|
107
|
+
|
|
108
|
+
if spec_validate "$tmp"; then
|
|
109
|
+
log_success "AI generated a valid spec"
|
|
110
|
+
# Populate globals
|
|
111
|
+
spec_read "$tmp"
|
|
112
|
+
# Override target dir (AI may not know the exact local path)
|
|
113
|
+
SPEC_TARGET_DIR="$target"
|
|
114
|
+
export SPEC_TARGET_DIR
|
|
115
|
+
# If AI omitted deploy/agents, fall back to profile defaults so the
|
|
116
|
+
# user still gets sensible files. CLI flags then win below.
|
|
117
|
+
if [[ -z "${SPEC_DEPLOY:-}" || -z "${SPEC_AGENTS:-}" ]]; then
|
|
118
|
+
local _profile_file="${TEMPLATES_DIR:-}/profiles/${SPEC_PROFILE:-default}.yml"
|
|
119
|
+
if [[ -f "$_profile_file" && "$(type -t plan_load_profile)" == "function" ]]; then
|
|
120
|
+
local _saved_deploy="$SPEC_DEPLOY"
|
|
121
|
+
local _saved_agents="$SPEC_AGENTS"
|
|
122
|
+
SPEC_DEPLOY=""; SPEC_AGENTS=""
|
|
123
|
+
plan_load_profile "$_profile_file" 2>/dev/null || true
|
|
124
|
+
[[ -n "$_saved_deploy" ]] && SPEC_DEPLOY="$_saved_deploy"
|
|
125
|
+
[[ -n "$_saved_agents" ]] && SPEC_AGENTS="$_saved_agents"
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
# Re-apply CLI flags so user-provided --site-title, --github-user, etc.
|
|
129
|
+
# always win over AI guesses.
|
|
130
|
+
if [[ "$(type -t plan_apply_flags)" == "function" ]]; then
|
|
131
|
+
plan_apply_flags
|
|
132
|
+
fi
|
|
133
|
+
# Apply platform defaults
|
|
134
|
+
plan_apply_platform
|
|
135
|
+
# Record AI metadata
|
|
136
|
+
SPEC_AI_USED=true
|
|
137
|
+
SPEC_AI_PROVIDER="${OPENAI_PROVIDER:-openai}"
|
|
138
|
+
SPEC_AI_MODEL="${OPENAI_MODEL:-gpt-4o-mini}"
|
|
139
|
+
export SPEC_AI_USED SPEC_AI_PROVIDER SPEC_AI_MODEL
|
|
140
|
+
# Write final spec
|
|
141
|
+
spec_write "$(spec_path "$target")"
|
|
142
|
+
rm -f "$tmp"
|
|
143
|
+
return 0
|
|
144
|
+
else
|
|
145
|
+
log_error "AI returned an invalid spec. Review: $tmp"
|
|
146
|
+
log_warning "Falling back to interactive wizard..."
|
|
147
|
+
rm -f "$tmp"
|
|
148
|
+
return 1
|
|
149
|
+
fi
|
|
150
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# scripts/install/apply.sh — Execute an install spec
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# apply.sh is THE ONLY WRITER in the installer. All tasks call fs.sh and
|
|
6
|
+
# template.sh — never write directly.
|
|
7
|
+
#
|
|
8
|
+
# Provides:
|
|
9
|
+
# apply_run SPEC_FILE
|
|
10
|
+
# Read spec, run doctor (unless skip_doctor), execute task sequence,
|
|
11
|
+
# write marker on success.
|
|
12
|
+
#
|
|
13
|
+
# apply_task TASK_NAME TARGET_DIR
|
|
14
|
+
# Source and execute a single task from scripts/install/tasks/TASK.sh
|
|
15
|
+
#
|
|
16
|
+
# Bash 3.2 compatible. No set -euo pipefail here.
|
|
17
|
+
# =============================================================================
|
|
18
|
+
[[ -n "${_HAS_APPLY_LIB:-}" ]] && return 0
|
|
19
|
+
_HAS_APPLY_LIB=1
|
|
20
|
+
|
|
21
|
+
# Directory where task modules live
|
|
22
|
+
_APPLY_TASKS_DIR="${_APPLY_TASKS_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)/tasks}"
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# apply_task TASK TARGET_DIR — source and run one task
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
apply_task() {
|
|
28
|
+
local task="$1"
|
|
29
|
+
local target="$2"
|
|
30
|
+
local task_file="${_APPLY_TASKS_DIR}/${task}.sh"
|
|
31
|
+
|
|
32
|
+
if [[ ! -f "$task_file" ]]; then
|
|
33
|
+
log_warning "apply_task: task not found: $task (skipping)"
|
|
34
|
+
return 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
log_step "Task: $task"
|
|
38
|
+
# shellcheck source=/dev/null
|
|
39
|
+
source "$task_file"
|
|
40
|
+
|
|
41
|
+
local fn_name="task_${task}_run"
|
|
42
|
+
if [[ "$(type -t "$fn_name")" != "function" ]]; then
|
|
43
|
+
log_error "apply_task: task file $task_file does not define $fn_name"
|
|
44
|
+
log_step_done
|
|
45
|
+
return 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
"$fn_name" "$target"
|
|
49
|
+
local ret=$?
|
|
50
|
+
log_step_done
|
|
51
|
+
|
|
52
|
+
return $ret
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# apply_run SPEC_FILE
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
apply_run() {
|
|
59
|
+
local spec_file="$1"
|
|
60
|
+
|
|
61
|
+
if [[ ! -f "$spec_file" ]]; then
|
|
62
|
+
log_error "apply_run: spec file not found: $spec_file"
|
|
63
|
+
return 1
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# Load spec into SPEC_* globals
|
|
67
|
+
spec_read "$spec_file"
|
|
68
|
+
|
|
69
|
+
local target="${SPEC_TARGET_DIR:-}"
|
|
70
|
+
if [[ -z "$target" ]]; then
|
|
71
|
+
log_error "apply_run: spec.target_dir is empty"
|
|
72
|
+
return 1
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Sync option globals → fs.sh and prompt.sh
|
|
76
|
+
[[ "$SPEC_OPT_DRY_RUN" == "true" ]] && _FS_DRY_RUN=1 || _FS_DRY_RUN=0
|
|
77
|
+
[[ "$SPEC_OPT_FORCE" == "true" ]] && _FS_FORCE=1 || _FS_FORCE=0
|
|
78
|
+
[[ "$SPEC_OPT_BACKUP" == "false" ]] && _FS_BACKUP=0 || _FS_BACKUP=1
|
|
79
|
+
[[ "$SPEC_OPT_VERBOSE" == "true" ]] && _LOG_VERBOSE=1 || _LOG_VERBOSE=0
|
|
80
|
+
[[ "$SPEC_OPT_NON_INTERACTIVE" == "true" ]] && _PROMPT_NON_INTERACTIVE=1 || _PROMPT_NON_INTERACTIVE=0
|
|
81
|
+
[[ "$SPEC_OPT_AUTO_ACCEPT" == "true" ]] && _PROMPT_AUTO_ACCEPT=1 || _PROMPT_AUTO_ACCEPT=0
|
|
82
|
+
[[ "$SPEC_OPT_OUTPUT" == "json" ]] && _LOG_OUTPUT="json" || _LOG_OUTPUT="human"
|
|
83
|
+
|
|
84
|
+
export _FS_DRY_RUN _FS_FORCE _FS_BACKUP _LOG_VERBOSE \
|
|
85
|
+
_PROMPT_NON_INTERACTIVE _PROMPT_AUTO_ACCEPT _LOG_OUTPUT
|
|
86
|
+
|
|
87
|
+
# Propagate spec → template globals
|
|
88
|
+
SITE_TITLE="${SPEC_SITE_TITLE:-My Jekyll Site}"
|
|
89
|
+
SITE_DESCRIPTION="${SPEC_SITE_DESCRIPTION:-}"
|
|
90
|
+
SITE_AUTHOR="${SPEC_SITE_AUTHOR:-}"
|
|
91
|
+
SITE_EMAIL="${SPEC_SITE_EMAIL:-}"
|
|
92
|
+
SITE_URL="${SPEC_SITE_URL:-}"
|
|
93
|
+
SITE_TIMEZONE="${SPEC_SITE_TIMEZONE:-UTC}"
|
|
94
|
+
SITE_LOCALE="${SPEC_SITE_LOCALE:-en}"
|
|
95
|
+
GITHUB_USER="${SPEC_GITHUB_USER:-}"
|
|
96
|
+
GITHUB_REPO="${SPEC_GITHUB_REPO:-}"
|
|
97
|
+
GITHUB_PAGES_BRANCH="${SPEC_GITHUB_PAGES_BRANCH:-gh-pages}"
|
|
98
|
+
REPOSITORY_NAME="${SPEC_GITHUB_REPO:-${REPOSITORY_NAME:-}}"
|
|
99
|
+
THEME_SOURCE="${SPEC_THEME_SOURCE:-gem}"
|
|
100
|
+
INSTALL_PROFILE="${SPEC_PROFILE:-default}"
|
|
101
|
+
export SITE_TITLE SITE_DESCRIPTION SITE_AUTHOR SITE_EMAIL SITE_URL \
|
|
102
|
+
SITE_TIMEZONE SITE_LOCALE GITHUB_USER GITHUB_REPO \
|
|
103
|
+
GITHUB_PAGES_BRANCH REPOSITORY_NAME THEME_SOURCE INSTALL_PROFILE
|
|
104
|
+
|
|
105
|
+
log_banner "zer0-mistakes installer — applying spec"
|
|
106
|
+
log_info "Target : $target"
|
|
107
|
+
log_info "Profile: ${SPEC_PROFILE:-default}"
|
|
108
|
+
log_info "Tasks : ${SPEC_TASKS}"
|
|
109
|
+
[[ -n "${SPEC_DEPLOY:-}" ]] && log_info "Deploy : $SPEC_DEPLOY"
|
|
110
|
+
[[ -n "${SPEC_AGENTS:-}" ]] && log_info "Agents : $SPEC_AGENTS"
|
|
111
|
+
[[ "$_FS_DRY_RUN" == "1" ]] && log_warning "DRY RUN — no files will be written"
|
|
112
|
+
|
|
113
|
+
# Ensure target directory exists
|
|
114
|
+
if [[ "$(type -t fs_ensure_dir)" == "function" ]]; then
|
|
115
|
+
fs_ensure_dir "$target"
|
|
116
|
+
else
|
|
117
|
+
mkdir -p "$target"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# Run doctor unless skipped
|
|
121
|
+
if [[ "$SPEC_OPT_SKIP_DOCTOR" != "true" ]]; then
|
|
122
|
+
if [[ "$(type -t doctor_run)" == "function" ]]; then
|
|
123
|
+
doctor_run "$target" || true # warn but don't abort
|
|
124
|
+
fi
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# Execute tasks in order
|
|
128
|
+
local failed_tasks=""
|
|
129
|
+
local task
|
|
130
|
+
for task in ${SPEC_TASKS}; do
|
|
131
|
+
apply_task "$task" "$target" || {
|
|
132
|
+
log_error "Task failed: $task"
|
|
133
|
+
failed_tasks="${failed_tasks} $task"
|
|
134
|
+
}
|
|
135
|
+
done
|
|
136
|
+
|
|
137
|
+
# Deploy plugins
|
|
138
|
+
for task in ${SPEC_DEPLOY:-}; do
|
|
139
|
+
apply_task "deploy_${task}" "$target" || \
|
|
140
|
+
log_warning "Deploy task failed: $task (continuing)"
|
|
141
|
+
done
|
|
142
|
+
|
|
143
|
+
# Agent files
|
|
144
|
+
if [[ -n "${SPEC_AGENTS:-}" ]]; then
|
|
145
|
+
apply_task "agents" "$target" || \
|
|
146
|
+
log_warning "Agents task failed (continuing)"
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
if [[ -n "$failed_tasks" ]]; then
|
|
150
|
+
log_error "Some tasks failed:$failed_tasks"
|
|
151
|
+
return 1
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
log_success "All tasks completed for: $target"
|
|
155
|
+
return 0
|
|
156
|
+
}
|