jekyll-theme-zer0 0.22.21 → 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.
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+ # =========================================================================
3
+ # scripts/lib/install/fs.sh
4
+ # =========================================================================
5
+ # Filesystem helpers for install.sh: idempotent file/directory copy with
6
+ # automatic timestamped backups when destination already exists.
7
+ #
8
+ # Required globals (set by install.sh before sourcing/use):
9
+ # $TARGET_DIR — used to compute relative paths in log messages
10
+ #
11
+ # Functions exported:
12
+ # copy_file_with_backup SRC DEST
13
+ # copy_directory_with_backup SRC DEST
14
+ # =========================================================================
15
+
16
+ # Copy a file, backing up the destination if it already exists.
17
+ copy_file_with_backup() {
18
+ local src="$1"
19
+ local dest="$2"
20
+ local relative_path="${dest#${TARGET_DIR:-}/}"
21
+
22
+ # Create destination directory if it doesn't exist
23
+ mkdir -p "$(dirname "$dest")"
24
+
25
+ # Backup existing file if it exists
26
+ if [[ -f "$dest" ]]; then
27
+ local backup_file="${dest}.backup.$(date +%Y%m%d_%H%M%S)"
28
+ log_warning "File exists, creating backup: $relative_path -> ${backup_file##*/}"
29
+ cp "$dest" "$backup_file"
30
+ fi
31
+
32
+ # Copy the file
33
+ cp "$src" "$dest"
34
+ log_info "Copied: $relative_path"
35
+ }
36
+
37
+ # Copy a directory, backing up the destination if it already exists.
38
+ copy_directory_with_backup() {
39
+ local src="$1"
40
+ local dest="$2"
41
+ local relative_path="${dest#${TARGET_DIR:-}/}"
42
+
43
+ if [[ -d "$dest" ]]; then
44
+ local backup_dir="${dest}.backup.$(date +%Y%m%d_%H%M%S)"
45
+ log_warning "Directory exists, creating backup: $relative_path -> ${backup_dir##*/}"
46
+ cp -r "$dest" "$backup_dir"
47
+ rm -rf "$dest"
48
+ fi
49
+
50
+ cp -r "$src" "$dest"
51
+ log_info "Copied directory: $relative_path"
52
+ }
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+ # =========================================================================
3
+ # scripts/lib/install/logging.sh
4
+ # =========================================================================
5
+ # Logging shim for install.sh. Provides log_info / log_success /
6
+ # log_warning / log_error names used throughout install.sh while
7
+ # delegating to scripts/lib/common.sh primitives where available.
8
+ #
9
+ # WHY a shim instead of a direct sed-rename?
10
+ # - install.sh has ~300+ call sites to log_info/log_success/log_warning/log_error
11
+ # - Common.sh uses different verbs (info/success/warn/error) and `error` exits
12
+ # - A thin shim preserves behavior exactly while letting future code use
13
+ # either vocabulary.
14
+ #
15
+ # Source order requirement:
16
+ # This file must be sourced AFTER scripts/lib/common.sh so the
17
+ # colour variables (RED/GREEN/YELLOW/BLUE/NC) are defined.
18
+ #
19
+ # Compatibility: bash 3.2+ (macOS default)
20
+ # =========================================================================
21
+
22
+ # Define colours if common.sh wasn't sourced (safe defaults).
23
+ RED="${RED:-\033[0;31m}"
24
+ GREEN="${GREEN:-\033[0;32m}"
25
+ YELLOW="${YELLOW:-\033[1;33m}"
26
+ BLUE="${BLUE:-\033[0;34m}"
27
+ NC="${NC:-\033[0m}"
28
+
29
+ # install.sh-style logging — identical output to the original block.
30
+ log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
31
+ log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
32
+ log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
33
+ log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/lib/install/pages.sh
3
+ #
4
+ # Starter-page generator for jekyll-theme-zer0 installations.
5
+ # Replaces 8 near-identical create_*_page heredoc functions with a single
6
+ # manifest-driven renderer.
7
+ #
8
+ # Required env (from install.conf via config.sh):
9
+ # TARGET_DIR, THEME_NAME, THEME_DISPLAY_NAME, DEFAULT_URL, GITHUB_URL
10
+ #
11
+ # Required functions (from template.sh):
12
+ # create_from_template <template_relpath> <dest_abspath> <fallback_content>
13
+ #
14
+ # Public API:
15
+ # render_starter_pages - generate all default pages + admin
16
+ # render_admin_settings_pages - generate just the _about/settings/* pages
17
+
18
+ # ---------- Manifest --------------------------------------------------------
19
+ # Format (pipe-separated, no spaces around |):
20
+ # <template_relpath>|<dest_relpath>|<mkdir_relpath>|<fallback_func>
21
+ # Empty <mkdir_relpath> = no mkdir; empty <fallback_func> = no fallback.
22
+ _starter_pages_manifest() {
23
+ cat <<'MANIFEST'
24
+ pages/quickstart.md.template|pages/quickstart/index.md|pages/quickstart|_fallback_quickstart
25
+ pages/docs-index.md.template|pages/_docs/index.md|pages/_docs|_fallback_docs_index
26
+ pages/configuration.md.template|pages/_docs/configuration/index.md|pages/_docs/configuration|
27
+ pages/troubleshooting.md.template|pages/_docs/troubleshooting.md|pages/_docs|
28
+ pages/about.md.template|pages/_about/index.md|pages/_about|_fallback_about
29
+ pages/blog.md.template|pages/blog.md||_fallback_blog
30
+ MANIFEST
31
+ }
32
+
33
+ _admin_settings_pages() {
34
+ echo "theme config navigation collections analytics environment"
35
+ }
36
+
37
+ # ---------- Fallback content (only used when template missing) --------------
38
+ _fallback_quickstart() {
39
+ cat <<EOF
40
+ ---
41
+ layout: default
42
+ title: Quick Start
43
+ permalink: /quickstart/
44
+ ---
45
+
46
+ # Quick Start Guide
47
+
48
+ Get your site up and running in just a few minutes!
49
+
50
+ ## Prerequisites
51
+
52
+ Before you begin, make sure you have:
53
+
54
+ - **Docker Desktop** installed ([download](https://www.docker.com/products/docker-desktop))
55
+ - **Git** installed ([download](https://git-scm.com/))
56
+
57
+ ## 1. Start Development Server
58
+
59
+ ### Using Docker (Recommended)
60
+
61
+ \`\`\`bash
62
+ docker-compose up
63
+ \`\`\`
64
+
65
+ Your site will be available at **${DEFAULT_URL}**
66
+
67
+ ### Using Local Ruby
68
+
69
+ \`\`\`bash
70
+ bundle install
71
+ bundle exec jekyll serve
72
+ \`\`\`
73
+
74
+ ## 2. Customize Your Site
75
+
76
+ Edit \`_config.yml\` to personalize your site:
77
+
78
+ \`\`\`yaml
79
+ title: Your Site Title
80
+ description: Your site description
81
+ author: Your Name
82
+ \`\`\`
83
+
84
+ ## 3. Add Content
85
+
86
+ - Create posts in \`pages/_posts/\`
87
+ - Create documentation in \`pages/_docs/\`
88
+ - Add static pages in \`pages/\`
89
+
90
+ ## Next Steps
91
+
92
+ - [Read the Documentation](/docs/) - Learn about all features
93
+ - [Explore Configuration](/docs/configuration/) - Customize your site
94
+ - [Learn about Layouts](/docs/layouts/) - Understand page layouts
95
+
96
+ ---
97
+
98
+ Need help? Check the [troubleshooting guide](/docs/troubleshooting/) or [open an issue](${GITHUB_URL}/issues).
99
+ EOF
100
+ }
101
+
102
+ _fallback_docs_index() {
103
+ cat <<EOF
104
+ ---
105
+ layout: default
106
+ title: Documentation
107
+ permalink: /docs/
108
+ ---
109
+
110
+ # Documentation
111
+
112
+ Welcome to the ${THEME_NAME} theme documentation. Here you'll find everything you need to build and customize your Jekyll site.
113
+
114
+ ## Getting Started
115
+
116
+ <div class="row">
117
+ <div class="col-md-6 mb-3">
118
+
119
+ ### Installation
120
+
121
+ The theme supports multiple installation methods:
122
+
123
+ - **Docker** (Recommended) - Zero dependencies
124
+ - **Remote Theme** - For GitHub Pages
125
+ - **Gem** - Traditional Ruby installation
126
+
127
+ [View Installation Guide →](/quickstart/)
128
+
129
+ </div>
130
+ <div class="col-md-6 mb-3">
131
+
132
+ ### Configuration
133
+
134
+ Customize your site with \`_config.yml\`:
135
+
136
+ - Site title and description
137
+ - Navigation menus
138
+ - Social links
139
+ - Analytics integration
140
+
141
+ [View Configuration Guide →](/docs/configuration/)
142
+
143
+ </div>
144
+ </div>
145
+
146
+ ## Need Help?
147
+
148
+ - [Troubleshooting Guide](/docs/troubleshooting/)
149
+ - [GitHub Issues](${GITHUB_URL}/issues)
150
+ - [GitHub Discussions](${GITHUB_URL}/discussions)
151
+ EOF
152
+ }
153
+
154
+ _fallback_about() {
155
+ cat <<EOF
156
+ ---
157
+ layout: default
158
+ title: About
159
+ permalink: /about/
160
+ ---
161
+
162
+ # About This Site
163
+
164
+ This site is built with the **${THEME_DISPLAY_NAME}** - a professional Jekyll theme designed for GitHub Pages with Bootstrap 5.3.
165
+
166
+ ## Theme Features
167
+
168
+ - ✅ Bootstrap 5.3 integration
169
+ - ✅ Dark/Light mode toggle
170
+ - ✅ Docker support
171
+ - ✅ GitHub Pages compatible
172
+ - ✅ SEO optimized
173
+
174
+ ## Learn More
175
+
176
+ - [Theme Documentation](/docs/)
177
+ - [GitHub Repository](${GITHUB_URL})
178
+ - [Report an Issue](${GITHUB_URL}/issues)
179
+
180
+ ## Customizing This Page
181
+
182
+ Edit \`pages/_about/index.md\` to customize this page with your own content.
183
+ EOF
184
+ }
185
+
186
+ _fallback_blog() {
187
+ cat <<'EOF'
188
+ ---
189
+ layout: default
190
+ title: Blog
191
+ permalink: /blog/
192
+ ---
193
+
194
+ # Blog
195
+
196
+ Welcome to the blog. Create your first post to get started!
197
+
198
+ ## Creating Posts
199
+
200
+ Create markdown files in `pages/_posts/` with the format:
201
+
202
+ ```
203
+ YYYY-MM-DD-your-post-title.md
204
+ ```
205
+
206
+ ## Recent Posts
207
+
208
+ {% for post in site.posts limit:5 %}
209
+ - [{{ post.title }}]({{ post.url }}) - {{ post.date | date: "%B %d, %Y" }}
210
+ {% endfor %}
211
+
212
+ {% if site.posts.size == 0 %}
213
+ *No posts yet. Create your first post to see it here!*
214
+ {% endif %}
215
+ EOF
216
+ }
217
+
218
+ # ---------- Public renderers ------------------------------------------------
219
+ render_admin_settings_pages() {
220
+ local admin_dir="$TARGET_DIR/pages/_about/settings"
221
+ mkdir -p "$admin_dir"
222
+
223
+ log_info "Creating admin settings pages..."
224
+ local page
225
+ for page in $(_admin_settings_pages); do
226
+ create_from_template "pages/admin/${page}.md.template" "$admin_dir/${page}.md" ""
227
+ done
228
+ }
229
+
230
+ render_starter_pages() {
231
+ log_info "Creating essential starter pages..."
232
+ mkdir -p "$TARGET_DIR/pages"
233
+
234
+ local tmpl dest mkdir_rel fb_func fallback
235
+ while IFS='|' read -r tmpl dest mkdir_rel fb_func; do
236
+ [ -z "$tmpl" ] && continue
237
+ [ -n "$mkdir_rel" ] && mkdir -p "$TARGET_DIR/$mkdir_rel"
238
+ if [ -n "$fb_func" ] && declare -f "$fb_func" >/dev/null 2>&1; then
239
+ fallback="$("$fb_func")"
240
+ else
241
+ fallback=""
242
+ fi
243
+ create_from_template "$tmpl" "$TARGET_DIR/$dest" "$fallback"
244
+ done <<MANIFEST_EOF
245
+ $(_starter_pages_manifest)
246
+ MANIFEST_EOF
247
+
248
+ render_admin_settings_pages
249
+
250
+ log_success "Starter pages created"
251
+ }
252
+
253
+ # Backward-compatible aliases (legacy install.sh call sites)
254
+ create_starter_pages() { render_starter_pages "$@"; }
255
+ create_admin_pages() { render_admin_settings_pages "$@"; }
@@ -0,0 +1,71 @@
1
+ #!/bin/bash
2
+ # =========================================================================
3
+ # scripts/lib/install/platform.sh
4
+ # =========================================================================
5
+ # Platform / Ruby detection helpers used by install.sh.
6
+ #
7
+ # Bash 3.2-compatible — no `declare -A`, no `=~` capture groups.
8
+ #
9
+ # Functions exported:
10
+ # detect_os -> Darwin | Linux | CYGWIN | MINGW | unknown
11
+ # detect_ruby_version -> e.g. "2.6.8" or "none"
12
+ # ruby_version_lt_27 -> exit 0 when ruby < 2.7
13
+ # needs_macos_gemfile -> exit 0 when macOS + ruby < 2.7
14
+ # detect_platform -> auto | wsl | macos | linux | unknown
15
+ # =========================================================================
16
+
17
+ # Returns: Darwin | Linux | CYGWIN | MINGW | unknown
18
+ detect_os() {
19
+ uname -s 2>/dev/null || echo "unknown"
20
+ }
21
+
22
+ # Returns the ruby version string (e.g. "2.6.8"), or "none" if ruby is absent.
23
+ detect_ruby_version() {
24
+ if ! command -v ruby >/dev/null 2>&1; then
25
+ echo "none"
26
+ return
27
+ fi
28
+ # ruby --version prints: ruby 2.6.8p205 (2021-07-07 ...) [platform]
29
+ # We want "2.6.8" — strip the trailing pNNN patch indicator via sed.
30
+ ruby --version 2>/dev/null | awk '{print $2}' | sed 's/p[0-9]*//' | sed 's/-.*//' | tr -d '\r'
31
+ }
32
+
33
+ # Returns 0 (true) if the current Ruby version is < 2.7.0, 1 (false) otherwise.
34
+ # Uses awk so arithmetic is safe even with partial version strings.
35
+ ruby_version_lt_27() {
36
+ local ver
37
+ ver=$(detect_ruby_version)
38
+ [ "$ver" = "none" ] && return 1 # no ruby → don't apply macOS caps
39
+ awk -v ver="$ver" 'BEGIN {
40
+ n = split(ver, a, ".")
41
+ if (a[1]+0 == 2 && a[2]+0 < 7) exit 0
42
+ exit 1
43
+ }'
44
+ }
45
+
46
+ # Returns 0 (true) when running on macOS with system Ruby < 2.7.
47
+ # This is the condition that triggers use of Gemfile.macos.template.
48
+ needs_macos_gemfile() {
49
+ local os
50
+ os=$(detect_os)
51
+ [ "$os" = "Darwin" ] && ruby_version_lt_27
52
+ }
53
+
54
+ # Detect runtime platform. Honours $PLATFORM if explicitly set.
55
+ # Returns: macos | linux | wsl | unknown
56
+ detect_platform() {
57
+ if [[ "${PLATFORM:-auto}" != "auto" ]]; then
58
+ echo "$PLATFORM"
59
+ return
60
+ fi
61
+ # WSL detection (check before generic Linux)
62
+ if grep -qiE '(microsoft|wsl)' /proc/version 2>/dev/null; then
63
+ echo "wsl"
64
+ elif [[ "$(uname -s)" == "Darwin" ]]; then
65
+ echo "macos"
66
+ elif [[ "$(uname -s)" == "Linux" ]]; then
67
+ echo "linux"
68
+ else
69
+ echo "unknown"
70
+ fi
71
+ }
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/lib/install/profile.sh
3
+ #
4
+ # Minimal pure-bash YAML reader for templates/profiles/*.yml.
5
+ # Bash 3.2 compatible. Schema is intentionally simple (scalar key/value
6
+ # + flat string lists) so we don't need yq/python.
7
+ #
8
+ # Public API:
9
+ # profiles_dir <REPO_ROOT> — absolute path to profiles dir
10
+ # list_profile_names <REPO_ROOT> — newline-separated profile slugs
11
+ # profile_path <REPO_ROOT> <name> — absolute path to a profile yml
12
+ # profile_get_scalar <file> <key> — print a scalar value or empty
13
+ # profile_get_list <file> <key> — print list items, one per line
14
+ # profile_print_summary <file> — pretty-print full profile
15
+
16
+ profiles_dir() {
17
+ local repo_root="$1"
18
+ echo "$repo_root/templates/profiles"
19
+ }
20
+
21
+ list_profile_names() {
22
+ local repo_root="$1"
23
+ local dir
24
+ dir="$(profiles_dir "$repo_root")"
25
+ [ -d "$dir" ] || return 1
26
+ # List *.yml stems, sorted, excluding any leading-dot files
27
+ ( cd "$dir" && for f in *.yml; do
28
+ [ -e "$f" ] || continue
29
+ echo "${f%.yml}"
30
+ done ) | sort
31
+ }
32
+
33
+ profile_path() {
34
+ local repo_root="$1" name="$2"
35
+ local p
36
+ p="$(profiles_dir "$repo_root")/${name}.yml"
37
+ [ -f "$p" ] || return 1
38
+ echo "$p"
39
+ }
40
+
41
+ # Extract a scalar value: lines like `key: value`. Strips surrounding
42
+ # whitespace and quotes. Ignores list lines (starting with `-`).
43
+ profile_get_scalar() {
44
+ local file="$1" key="$2"
45
+ [ -f "$file" ] || return 1
46
+ awk -v k="$key" '
47
+ # match " key: value" or "key: value" at top level (no leading dash)
48
+ $0 ~ "^[[:space:]]*" k "[[:space:]]*:" {
49
+ sub("^[[:space:]]*" k "[[:space:]]*:[[:space:]]*", "")
50
+ # strip trailing comments
51
+ sub(/[[:space:]]+#.*$/, "")
52
+ # strip surrounding quotes
53
+ gsub(/^["'\'']|["'\'']$/, "")
54
+ print
55
+ exit
56
+ }
57
+ ' "$file"
58
+ }
59
+
60
+ # Extract a flat list under a key. Returns nothing for `key: []` or missing.
61
+ profile_get_list() {
62
+ local file="$1" key="$2"
63
+ [ -f "$file" ] || return 1
64
+ awk -v k="$key" '
65
+ BEGIN { inblock = 0 }
66
+ # Start: "key:" line with nothing (or just a comment) after it
67
+ $0 ~ "^[[:space:]]*" k "[[:space:]]*:[[:space:]]*(#.*)?$" {
68
+ inblock = 1
69
+ next
70
+ }
71
+ inblock {
72
+ # End block on next non-list, non-blank, non-indented line
73
+ if ($0 ~ /^[[:space:]]*-[[:space:]]+/) {
74
+ line = $0
75
+ sub(/^[[:space:]]*-[[:space:]]+/, "", line)
76
+ sub(/[[:space:]]+#.*$/, "", line)
77
+ gsub(/^["'\'']|["'\'']$/, "", line)
78
+ print line
79
+ next
80
+ }
81
+ if ($0 ~ /^[[:space:]]*#/) next
82
+ if ($0 ~ /^[[:space:]]*$/) next
83
+ inblock = 0
84
+ }
85
+ ' "$file"
86
+ }
87
+
88
+ profile_print_summary() {
89
+ local file="$1"
90
+ [ -f "$file" ] || { echo "(profile file not found: $file)" >&2; return 1; }
91
+ local name display desc legacy rec
92
+ name="$(profile_get_scalar "$file" name)"
93
+ display="$(profile_get_scalar "$file" display_name)"
94
+ desc="$(profile_get_scalar "$file" description)"
95
+ legacy="$(profile_get_scalar "$file" legacy_flag)"
96
+ rec="$(profile_get_scalar "$file" recommended_for)"
97
+ printf ' %-9s %s\n' "$name" "$display"
98
+ [ -n "$desc" ] && printf ' %s\n' "$desc"
99
+ [ -n "$legacy" ] && printf ' → install.sh %s\n' "$legacy"
100
+ [ -n "$rec" ] && printf ' For: %s\n' "$rec"
101
+ local includes excludes
102
+ includes="$(profile_get_list "$file" includes)"
103
+ excludes="$(profile_get_list "$file" excludes)"
104
+ if [ -n "$includes" ]; then
105
+ printf ' Includes:\n'
106
+ echo "$includes" | sed 's/^/ - /'
107
+ fi
108
+ if [ -n "$excludes" ]; then
109
+ printf ' Excludes:\n'
110
+ echo "$excludes" | sed 's/^/ - /'
111
+ fi
112
+ echo
113
+ }
@@ -0,0 +1,137 @@
1
+ #!/bin/bash
2
+ # =========================================================================
3
+ # scripts/lib/install/template.sh
4
+ # =========================================================================
5
+ # Template rendering for install.sh.
6
+ #
7
+ # Functions exported:
8
+ # render_template TEMPLATE_FILE [OUTPUT_FILE]
9
+ # Replace {{VAR_NAME}} placeholders. If OUTPUT_FILE omitted, writes
10
+ # to stdout.
11
+ #
12
+ # create_from_template TEMPLATE_REL OUTPUT_FILE [FALLBACK_CONTENT]
13
+ # Resolution order:
14
+ # 1. Local templates ($TEMPLATES_DIR/$TEMPLATE_REL)
15
+ # 2. Remote fetch from $GITHUB_RAW_URL/templates/$TEMPLATE_REL
16
+ # (only when REMOTE_INSTALL=true)
17
+ # 3. FALLBACK_CONTENT (literal string)
18
+ # Existing OUTPUT_FILE is preserved (skipped with a warning).
19
+ #
20
+ # templates_available
21
+ # Returns 0 when $TEMPLATES_DIR is set and points to an existing dir.
22
+ #
23
+ # Required globals (provided by install.sh / install.conf):
24
+ # THEME_NAME, THEME_GEM_NAME, THEME_DISPLAY_NAME,
25
+ # GITHUB_USER, GITHUB_REPO, GITHUB_URL, GITHUB_RAW_URL,
26
+ # DEFAULT_PORT, DEFAULT_URL,
27
+ # JEKYLL_VERSION, FFI_VERSION, WEBRICK_VERSION, COMMONMARKER_VERSION,
28
+ # GITHUB_PAGES_MAX_VERSION, COMMONMARKER_MACOS_VERSION,
29
+ # RUBY_MIN_VERSION_MACOS, INSTALL_MODE, REMOTE_INSTALL, TEMPLATES_DIR
30
+ #
31
+ # Optional globals (used when set):
32
+ # FORK_GITHUB_USER, FORK_SITE_NAME, FORK_AUTHOR, FORK_EMAIL,
33
+ # SITE_TITLE, SITE_DESCRIPTION, SITE_AUTHOR, SITE_EMAIL,
34
+ # REPOSITORY_NAME
35
+ # =========================================================================
36
+
37
+ # Render a template file, replacing {{VAR_NAME}} placeholders.
38
+ render_template() {
39
+ local template_file="$1"
40
+ local output_file="${2:-}"
41
+
42
+ if [[ ! -f "$template_file" ]]; then
43
+ return 1
44
+ fi
45
+
46
+ local content
47
+ content=$(cat "$template_file")
48
+
49
+ # Replace all known placeholders. Order matches the original install.sh
50
+ # implementation to guarantee identical output.
51
+ content=$(echo "$content" | sed \
52
+ -e "s|{{THEME_NAME}}|${THEME_NAME}|g" \
53
+ -e "s|{{THEME_GEM_NAME}}|${THEME_GEM_NAME}|g" \
54
+ -e "s|{{THEME_DISPLAY_NAME}}|${THEME_DISPLAY_NAME}|g" \
55
+ -e "s|{{GITHUB_USER}}|${FORK_GITHUB_USER:-$GITHUB_USER}|g" \
56
+ -e "s|{{GITHUB_REPO}}|${GITHUB_REPO}|g" \
57
+ -e "s|{{GITHUB_URL}}|${GITHUB_URL}|g" \
58
+ -e "s|{{GITHUB_RAW_URL}}|${GITHUB_RAW_URL}|g" \
59
+ -e "s|{{DEFAULT_PORT}}|${DEFAULT_PORT}|g" \
60
+ -e "s|{{DEFAULT_URL}}|${DEFAULT_URL}|g" \
61
+ -e "s|{{JEKYLL_VERSION}}|${JEKYLL_VERSION}|g" \
62
+ -e "s|{{FFI_VERSION}}|${FFI_VERSION}|g" \
63
+ -e "s|{{WEBRICK_VERSION}}|${WEBRICK_VERSION}|g" \
64
+ -e "s|{{COMMONMARKER_VERSION}}|${COMMONMARKER_VERSION}|g" \
65
+ -e "s|{{GITHUB_PAGES_MAX_VERSION}}|${GITHUB_PAGES_MAX_VERSION:-232}|g" \
66
+ -e "s|{{COMMONMARKER_MACOS_VERSION}}|${COMMONMARKER_MACOS_VERSION:-~> 0.23}|g" \
67
+ -e "s|{{RUBY_MIN_VERSION_MACOS}}|${RUBY_MIN_VERSION_MACOS:-2.6.0}|g" \
68
+ -e "s|{{SITE_TITLE}}|${FORK_SITE_NAME:-${SITE_TITLE:-My Jekyll Site}}|g" \
69
+ -e "s|{{SITE_DESCRIPTION}}|${SITE_DESCRIPTION:-A Jekyll site built with ${THEME_NAME}}|g" \
70
+ -e "s|{{SITE_AUTHOR}}|${FORK_AUTHOR:-${SITE_AUTHOR:-Site Author}}|g" \
71
+ -e "s|{{SITE_EMAIL}}|${FORK_EMAIL:-${SITE_EMAIL:-your@email.com}}|g" \
72
+ -e "s|{{CURRENT_DATE}}|$(date +%Y-%m-%d)|g" \
73
+ -e "s|{{CURRENT_YEAR}}|$(date +%Y)|g" \
74
+ -e "s|{{REPOSITORY_NAME}}|${REPOSITORY_NAME:-$THEME_NAME}|g" \
75
+ -e "s|{{RAW_GITHUB_URL}}|${GITHUB_RAW_URL}|g" \
76
+ -e "s|{{FORK_GITHUB_USER}}|${FORK_GITHUB_USER:-${GITHUB_USER}}|g" \
77
+ -e "s|{{INSTALL_MODE}}|${INSTALL_MODE:-full}|g" \
78
+ -e "s|{{GITHUB_PAGES_URL}}|https://${FORK_GITHUB_USER:-${GITHUB_USER}}.github.io/${REPOSITORY_NAME:-$THEME_NAME}|g")
79
+
80
+ if [[ -n "$output_file" ]]; then
81
+ mkdir -p "$(dirname "$output_file")"
82
+ echo "$content" > "$output_file"
83
+ else
84
+ echo "$content"
85
+ fi
86
+ }
87
+
88
+ # Create a file from template with automatic fallback to embedded content.
89
+ create_from_template() {
90
+ local template_path="$1"
91
+ local output_file="$2"
92
+ local fallback_content="${3:-}"
93
+
94
+ # Skip if output already exists
95
+ if [[ -f "$output_file" ]]; then
96
+ log_warning "$(basename "$output_file") already exists, skipping to preserve content"
97
+ return 0
98
+ fi
99
+
100
+ # Try local template first
101
+ if [[ -n "${TEMPLATES_DIR:-}" ]] && [[ -f "$TEMPLATES_DIR/$template_path" ]]; then
102
+ render_template "$TEMPLATES_DIR/$template_path" "$output_file"
103
+ log_info "Created $(basename "$output_file") from template"
104
+ return 0
105
+ fi
106
+
107
+ # Try to fetch from GitHub for remote installs
108
+ if [[ "${REMOTE_INSTALL:-false}" == "true" ]]; then
109
+ local remote_url="${GITHUB_RAW_URL}/templates/$template_path"
110
+ local remote_content
111
+ if remote_content=$(curl -fsSL "$remote_url" 2>/dev/null); then
112
+ local temp_file
113
+ temp_file=$(mktemp)
114
+ echo "$remote_content" > "$temp_file"
115
+ render_template "$temp_file" "$output_file"
116
+ rm -f "$temp_file"
117
+ log_info "Created $(basename "$output_file") from remote template"
118
+ return 0
119
+ fi
120
+ fi
121
+
122
+ # Use fallback content if provided
123
+ if [[ -n "$fallback_content" ]]; then
124
+ mkdir -p "$(dirname "$output_file")"
125
+ echo "$fallback_content" > "$output_file"
126
+ log_info "Created $(basename "$output_file") from fallback"
127
+ return 0
128
+ fi
129
+
130
+ log_warning "Could not create $(basename "$output_file") (no template or fallback)"
131
+ return 1
132
+ }
133
+
134
+ # Check if templates are available (TEMPLATES_DIR set + directory exists).
135
+ templates_available() {
136
+ [[ -n "${TEMPLATES_DIR:-}" ]] && [[ -d "$TEMPLATES_DIR" ]]
137
+ }