Rustelo/templates/website-leptos/justfile
2026-07-18 19:57:03 +01:00

881 lines
33 KiB
Makefile

# Set shell for commands
set shell := ["bash", "-c"]
project_root := justfile_directory()
# Load environment variables from .env file if it exists
set dotenv-load := true
[doc("Show available recipes")]
default:
@just --list
# =============================================================================
# LAMINA LAYER BUILDS
# =============================================================================
lamina_root := justfile_directory() + "/../lamina"
libre_forge_root := justfile_directory() + "/../workspaces/libre-forge"
lian_build_root := justfile_directory() + "/../lian-build"
secrets_base := justfile_directory() + "/../workspaces/libre-daoshi"
rustelo_root := justfile_directory() + "/{{RUSTELO_ROOT}}"
strops_root := justfile_directory() + "/../stratumiops"
# Build and push: no arg or "website" → build this project via fleet; layer name → delegate to lamina
# Pass log=/path/file.log to capture output to a file instead of printing to screen.
build-push-remote layer="website" log="":
#!/usr/bin/env nu
if "{{ layer }}" != "website" {
just -f "{{ lamina_root }}/justfile" build-push-remote {{ layer }}
return
}
let forge = "{{ libre_forge_root }}"
let kage = ($forge | path join "infra/control-plane/.kage")
let sc_file = ($forge | path join "infra/control-plane/secrets/sccache.sops.yaml")
let nk_file = ($forge | path join "infra/control-plane/secrets/nkeys.sops.yaml")
let handler = ($forge | path join "scripts/provision-relay.nu")
for p in [$kage, $sc_file, $nk_file] {
if not ($p | path exists) { error make { msg: $"missing: ($p)" } }
}
let sc_raw = (with-env { SOPS_AGE_KEY_FILE: $kage } {
do { ^sops --decrypt --output-type json $sc_file } | complete
})
if $sc_raw.exit_code != 0 { error make { msg: $"sops sccache: ($sc_raw.stderr | str trim)" } }
let sc = ($sc_raw.stdout | from json)
let nk_raw = (with-env { SOPS_AGE_KEY_FILE: $kage } {
do { ^sops --decrypt --extract '["forge_control_seed"]' $nk_file } | complete
})
if $nk_raw.exit_code != 0 { error make { msg: $"sops nkeys: ($nk_raw.stderr | str trim)" } }
let nk_seed = ($nk_raw.stdout | str trim)
let image_tag = (open "{{ justfile_directory() }}/Cargo.toml" | get workspace.package.version)
let log_file = if ("{{ log }}" != "") { "{{ log }}" } else { null }
if $log_file != null { print $" lian-build log: ($log_file)" }
let ctx = (^mktemp -d | str trim)
print $" assembling context: ($ctx)"
^rsync -a --delete "--exclude=.git/" "--exclude=target/" "--exclude=node_modules/" "--exclude=.coder/" "{{ justfile_directory() }}/" $"($ctx)/"
^rsync -a --delete "--exclude=.git/" "--exclude=target/" "{{ rustelo_root }}/" $"($ctx)/rustelo/"
if ("{{ strops_root }}" | path exists) {
^rsync -a --delete "--exclude=.git/" "--exclude=target/" "{{ strops_root }}/" $"($ctx)/stratumiops/"
}
# Write version-patched project.ncl into lian_build_root so Nickel finds it by logical name.
# build_directives.ncl does `import "jpl-project.ncl"` — resolved via LIAN_BUILD_NICKEL_IMPORT_PATH
# which already points to lian_build_root. No path changes needed; file is deleted on exit.
let proj_override = $"{{ lian_build_root }}/jpl-project.ncl"
open "{{ justfile_directory() }}/provisioning/project.ncl"
| str replace 'image_tag = "latest"' $'image_tag = "($image_tag)"'
| save -f $proj_override
let dir_raw = (do {
^nickel export --import-path "{{ lian_build_root }}" "{{ justfile_directory() }}/lian-build/build_directives.ncl"
} | complete)
if $dir_raw.exit_code != 0 { rm -f $proj_override; error make { msg: $"nickel export directives: ($dir_raw.stderr | str trim)" } }
let fleet_nats_url = ($dir_raw.stdout | from json | get adapter.nats_url)
# nats CLI requires the NKey seed as a file (--nkey), not as $NATS_SEED string.
let nkey_file = (^mktemp | str trim)
$nk_seed | save -f $nkey_file
let relay_job = (job spawn --description "provision-relay" {
^nats --server $fleet_nats_url --nkey $nkey_file reply "fleet.libre-forge.ops.provision.>" --command $"nu ($handler)"
})
print $" provision-relay: job ($relay_job)"
print $" daemon logs: ssh libre-daoshi-0 'k0s kubectl -n fleet-system logs -l app=fleet-daemon -f'"
let daoshi_kage = ("{{ secrets_base }}" | path join "infra/libre-daoshi/.kage")
try {
with-env {
AWS_ACCESS_KEY_ID: $sc.access_key_id,
AWS_SECRET_ACCESS_KEY: $sc.secret_access_key,
NATS_NKEY_SEED: $nk_seed,
LIAN_BUILD_NICKEL_IMPORT_PATH: "{{ lian_build_root }}",
SOPS_AGE_KEY_FILE: $daoshi_kage,
} {
if $log_file != null {
^lian-build build --directives "{{ justfile_directory() }}/lian-build/build_directives.ncl" --context $ctx --secrets-base "{{ secrets_base }}" o+e> $log_file
} else {
^lian-build build --directives "{{ justfile_directory() }}/lian-build/build_directives.ncl" --context $ctx --secrets-base "{{ secrets_base }}"
}
}
} catch { |e|
job kill $relay_job
rm -f $nkey_file $proj_override
rm -rf $ctx
let hint = if $log_file != null { $" — see ($log_file)" } else { "" }
error make { msg: $"lian-build failed($hint)\n($e.msg)" }
}
job kill $relay_job
rm -f $nkey_file $proj_override
rm -rf $ctx
# Build server binary only: WASM is compiled locally then sent as part of the context.
# wasm=<path> override the WASM artifacts dir (default: target/site/).
# If absent and no override is given, builds WASM locally first.
# log=<path> capture lian-build output to a file instead of printing to screen.
build-push-server wasm="" log="":
#!/usr/bin/env nu
let forge = "{{ libre_forge_root }}"
let kage = ($forge | path join "infra/control-plane/.kage")
let sc_file = ($forge | path join "infra/control-plane/secrets/sccache.sops.yaml")
let nk_file = ($forge | path join "infra/control-plane/secrets/nkeys.sops.yaml")
let handler = ($forge | path join "scripts/provision-relay.nu")
for p in [$kage, $sc_file, $nk_file] {
if not ($p | path exists) { error make { msg: $"missing: ($p)" } }
}
let sc_raw = (with-env { SOPS_AGE_KEY_FILE: $kage } {
do { ^sops --decrypt --output-type json $sc_file } | complete
})
if $sc_raw.exit_code != 0 { error make { msg: $"sops sccache: ($sc_raw.stderr | str trim)" } }
let sc = ($sc_raw.stdout | from json)
let nk_raw = (with-env { SOPS_AGE_KEY_FILE: $kage } {
do { ^sops --decrypt --extract '["forge_control_seed"]' $nk_file } | complete
})
if $nk_raw.exit_code != 0 { error make { msg: $"sops nkeys: ($nk_raw.stderr | str trim)" } }
let nk_seed = ($nk_raw.stdout | str trim)
let image_tag = (open "{{ justfile_directory() }}/Cargo.toml" | get workspace.package.version)
let wasm_site = if ("{{ wasm }}" != "") {
"{{ wasm }}"
} else {
let target_dir = (
$env.CARGO_TARGET_DIR? | default (
let cfg = "{{ justfile_directory() }}/.cargo/config.toml"
if ($cfg | path exists) {
(open $cfg).build?."target-dir"? | default "{{ justfile_directory() }}/target"
} else {
"{{ justfile_directory() }}/target"
}
)
)
$target_dir | path join "site"
}
if not ($wasm_site | path exists) {
print " building WASM frontend locally (no artifacts at ($wasm_site))..."
with-env {
SITE_CONFIG_PATH: "{{ justfile_directory() }}/site/config/index.ncl",
NICKEL_IMPORT_PATH: $"{{ rustelo_root }}/resources/nickel:{{ justfile_directory() }}/site/config",
} {
^cargo leptos build --frontend-only --release
}
}
let log_file = if ("{{ log }}" != "") { "{{ log }}" } else { null }
if $log_file != null { print $" lian-build log: ($log_file)" }
let ctx = (^mktemp -d | str trim)
print $" assembling context: ($ctx)"
^rsync -a --delete "--exclude=.git/" "--exclude=target/" "--exclude=node_modules/" "--exclude=.coder/" "{{ justfile_directory() }}/" $"($ctx)/"
^rsync -a --delete "--exclude=.git/" "--exclude=target/" "{{ rustelo_root }}/" $"($ctx)/rustelo/"
if ("{{ strops_root }}" | path exists) {
^rsync -a --delete "--exclude=.git/" "--exclude=target/" "{{ strops_root }}/" $"($ctx)/stratumiops/"
}
# Inject WASM artifacts into context as wasm-site/
^rsync -a --delete $"($wasm_site)/" $"($ctx)/wasm-site/"
print $" wasm-site: ($wasm_site)"
# Write version-patched project.ncl into lian_build_root — same approach as build-push-remote.
let proj_override = $"{{ lian_build_root }}/jpl-project.ncl"
open "{{ justfile_directory() }}/provisioning/project.ncl"
| str replace 'image_tag = "latest"' $'image_tag = "($image_tag)"'
| save -f $proj_override
let dir_raw = (do {
^nickel export --import-path "{{ lian_build_root }}" "{{ justfile_directory() }}/lian-build/build_directives_server.ncl"
} | complete)
if $dir_raw.exit_code != 0 { rm -f $proj_override; error make { msg: $"nickel export directives: ($dir_raw.stderr | str trim)" } }
let fleet_nats_url = ($dir_raw.stdout | from json | get adapter.nats_url)
let nkey_file = (^mktemp | str trim)
$nk_seed | save -f $nkey_file
let relay_job = (job spawn --description "provision-relay" {
^nats --server $fleet_nats_url --nkey $nkey_file reply "fleet.libre-forge.ops.provision.>" --command $"nu ($handler)"
})
print $" provision-relay: job ($relay_job)"
print $" daemon logs: ssh libre-daoshi-0 'k0s kubectl -n fleet-system logs -l app=fleet-daemon -f'"
let daoshi_kage = ("{{ secrets_base }}" | path join "infra/libre-daoshi/.kage")
try {
with-env {
AWS_ACCESS_KEY_ID: $sc.access_key_id,
AWS_SECRET_ACCESS_KEY: $sc.secret_access_key,
NATS_NKEY_SEED: $nk_seed,
LIAN_BUILD_NICKEL_IMPORT_PATH: "{{ lian_build_root }}",
SOPS_AGE_KEY_FILE: $daoshi_kage,
} {
if $log_file != null {
^lian-build build --directives "{{ justfile_directory() }}/lian-build/build_directives_server.ncl" --context $ctx --secrets-base "{{ secrets_base }}" o+e> $log_file
} else {
^lian-build build --directives "{{ justfile_directory() }}/lian-build/build_directives_server.ncl" --context $ctx --secrets-base "{{ secrets_base }}"
}
}
} catch { |e|
job kill $relay_job
rm -f $nkey_file $proj_override
rm -rf $ctx
let hint = if $log_file != null { $" — see ($log_file)" } else { "" }
error make { msg: $"lian-build failed($hint)\n($e.msg)" }
}
job kill $relay_job
rm -f $nkey_file $proj_override
rm -rf $ctx
libre_wuji_root := justfile_directory() + "/../workspaces/secrets-base"
# Register this project into a workspace repo, injecting the current Cargo.toml version as image_tag.
# workspace=<path> override the target workspace root (default: secrets-base sibling).
[doc("Write versioned appserv/builds stubs into the workspace repo")]
register workspace="" workspace_name="secrets-base":
#!/usr/bin/env nu
let image_tag = (open "{{ justfile_directory() }}/Cargo.toml" | get workspace.package.version)
let ws = if ("{{ workspace }}" | is-not-empty) { "{{ workspace }}" } else { "{{ libre_wuji_root }}" }
if not ($ws | path exists) { error make { msg: $"workspace not found: ($ws)" } }
print $"[register] image_tag: ($image_tag), workspace: ($ws)"
^nu "{{ justfile_directory() }}/provisioning/register.nu" --workspace $ws --workspace-name "{{ workspace_name }}" --image-tag $image_tag
# =============================================================================
# MODULE IMPORTS
# =============================================================================
mod dev "justfiles/dev.just"
mod build "justfiles/build.just"
mod database "justfiles/database.just"
mod docs "justfiles/docs.just"
mod test "justfiles/test.just"
mod utils "justfiles/utils.just"
mod tools "justfiles/tools.just"
mod helptext "justfiles/helptext.just"
mod cache "justfiles/cache.just"
mod ui "justfiles/ui.just"
# =============================================================================
# ALIASES
# =============================================================================
alias a := list-alias
alias b := build-dev
alias bp := build-prod
alias s := dev-full
alias sp := serve-prod
alias sc := serve-cms
alias cb := content-build
alias oa := dev::organize-artifacts
alias sbl := server-browser-logs
alias bl := browser-logs
alias t := test-run
alias d := start
alias df := dev-fast
alias dc := dev-coordinated
alias dm := dev-minimal
alias ba := build-artifacts
alias bs := build-status
alias ca := clean-build-artifacts
alias cs := cache::cs
alias cc := cache::cache-clean
alias cca := cache::cache-clean
# alias dev := start # Conflicts with module name
# alias h := help # Defined as recipe below
alias ha := help-all
alias o := overview
alias c := cross-build
alias pt := page-tester
alias pr := pages-report
alias pn := page-new
alias sh := show
# Tools aliases
alias ta := build-tools-analyze
alias tm := build-tools-manage
alias tg := build-tools-generate-page
alias ts := build-tools-status
# Navigation Testing aliases
alias nts := dev::with-nav-test # Nav Test Start
alias nt := dev::nav-test-route # Nav Test route
alias ns := dev::nav-test-sequence # Nav test Sequence
alias nv := dev::nav-test-validate # Nav Validate
alias nb := dev::nav-test-bench # Nav Bench
alias na := dev::nav-test-all # Nav All tests
alias nc := dev::nav-quick-check # Nav Check (CI/CD)
alias nd := dev::nav-dashboard # Nav Dashboard
alias nh := dev::nav-help # Nav Help
# Quick nav test aliases for common routes
alias nt-home := nav-test-home # Test home route
alias nt-services := nav-test-services # Test services route
alias nt-contact := nav-test-contact # Test contact route
alias nt-blog := nav-test-blog # Test blog route
alias nt-es := nav-test-es # Test Spanish home
alias nt-contactar := nav-test-contactar # Test Spanish contact
# UI Management aliases
alias routes := ui::routes-list
alias pages := ui::pages-list
alias components := ui::components-list
# =============================================================================
# CORE COMMANDS (Backward Compatibility & Common Tasks)
# =============================================================================
list-alias:
@echo "🔗 List of aliases:"
@cat {{justfile()}} | grep "^alias" | sed 's/^alias / /g'
# Show comprehensive system overview
overview:
@just utils::overview
# Show information about specified topic (try: info, status, config, overview, aliases, modules, help)
show *TOPIC:
#!/usr/bin/env bash
TOPIC="{{TOPIC}}"
if [ -z "$TOPIC" ]; then
echo "📋 Available topics to show:"
echo " info - Project information"
echo " status - Application health status"
echo " config - Configuration settings"
echo " overview - System overview"
echo " aliases - List of command aliases"
echo " modules - Available modules"
echo " build-tools - Build tools system"
echo " help - Help system"
echo ""
echo "Usage: just show <topic> or just sh <topic>"
exit 0
fi
case "$TOPIC" in
"info"|"information") just utils::info ;;
"status"|"health") just utils::health ;;
"config"|"configuration") just utils::config ;;
"overview"|"system") just utils::overview ;;
"aliases"|"alias") just list-alias ;;
"modules"|"module") just helptext::modules ;;
"build-tools"|"tools"|"tool") just helptext::tools ;;
"help") just help ;;
*)
echo "📋 Available topics to show:"
echo " info - Project information"
echo " status - Application health status"
echo " config - Configuration settings"
echo " overview - System overview"
echo " aliases - List of command aliases"
echo " modules - Available modules"
echo " build-tools - Build tools system"
echo " help - Help system"
echo ""
echo "Usage: just show <topic> or just sh <topic>"
;;
esac
# =============================================================================
# PRIMARY WORKFLOW COMMANDS (Unified Interface)
# =============================================================================
# Start development server (main entry point)
start:
@just dev::serve
# Backward compatibility: redirect 'dev' to 'start'
dev-start:
@echo "💡 Note: 'just dev' has been renamed to 'just start'"
@echo "🔄 Redirecting to development server..."
@just start
# Start development server with full features
dev-full:
@just dev::full
# Build for development
build-dev:
@just build::dev
# Build for production
build-prod:
@just build::prod
# Smoke-test the htmx-ssr surface after a build (full page / fragment / theme / lang)
htmx-smoke base="http://127.0.0.1:3030":
@set -e; BASE="{{base}}"; \
echo "→ full page must include <!DOCTYPE> and /assets/htmx/htmx.min.js"; \
curl -fsS "$BASE/" | grep -q "<!DOCTYPE" || (echo "FAIL: missing DOCTYPE"; exit 1); \
echo "→ fragment must omit <!DOCTYPE> and <html>"; \
resp=$(curl -fsS -H "HX-Request: true" "$BASE/"); \
echo "$resp" | grep -qv "<!DOCTYPE" || (echo "FAIL: fragment contains DOCTYPE"; exit 1); \
echo "→ theme toggle returns 200 and Set-Cookie: theme=…"; \
curl -fsSI -X POST -H "Cookie: theme=light" "$BASE/api/htmx/theme/toggle" \
| grep -i "^set-cookie: theme=" || (echo "FAIL: theme toggle Set-Cookie missing"; exit 1); \
echo "→ unknown lang returns 400 with HX-Retarget"; \
curl -fsSI -X POST -H "HX-Request: true" "$BASE/api/htmx/lang/zz" -o /dev/null -w "%{http_code} %{header_HX-Retarget}\n" \
| grep -q "400" || (echo "FAIL: bad lang did not 400"; exit 1); \
echo "✅ htmx-ssr smoke checks passed"
# Switch rendering profile and rebuild. Without args: shows current state and prompts.
switch-rendering profile="":
#!/usr/bin/env nu
let ncl = "site/config/rendering.ncl"
let profiles = [
{ name: "leptos-hydration", label: "Leptos hydration — SSR + WASM bundle, full reactive client" },
{ name: "htmx-ssr", label: "htmx-ssr — SSR only, no WASM, htmx interactivity" },
]
# Read current profile from rendering.ncl
let current = (
open $ncl
| parse --regex 'default_profile = "(?P<p>[^"]+)"'
| get p?.0?
| default "unknown"
)
let chosen = if ("{{ profile }}" == "") {
print ""
print $" Current rendering profile: (ansi yellow_bold)($current)(ansi reset)"
print ""
for i in ($profiles | enumerate) {
let marker = if $i.item.name == $current { $"(ansi green)▶(ansi reset)" } else { " " }
print $" ($marker) [($i.index + 1)] ($i.item.label)"
}
print ""
let raw = (input " Select [1/2] or Enter to cancel: " | str trim)
if ($raw == "") { print " Cancelled."; exit 0 }
let idx = ($raw | into int | $in - 1)
if $idx < 0 or $idx >= ($profiles | length) {
error make { msg: $"Invalid selection: ($raw)" }
}
$profiles | get $idx | get name
} else {
"{{ profile }}"
}
let valid_names = ($profiles | get name)
if ($chosen not-in $valid_names) {
error make { msg: $"Unknown profile '($chosen)'. Valid: ($valid_names | str join ', ')" }
}
if $chosen == $current {
print $" Already on ($chosen) — nothing to do."
exit 0
}
open $ncl
| str replace --regex 'default_profile = "(leptos-hydration|htmx-ssr)"' $'default_profile = "($chosen)"'
| save -f $ncl
print $" ✅ ($current) → ($chosen)"
print ""
if $chosen == "htmx-ssr" {
print $" Dev: (ansi cyan)just dev::htmx(ansi reset) — cargo watch, no wasm32"
print $" Prod: (ansi cyan)just build-auto(ansi reset) — server-only binary"
} else {
print $" Dev: (ansi cyan)just start(ansi reset) — cargo leptos serve"
print $" Prod: (ansi cyan)just build-auto(ansi reset) — cargo leptos build --release"
}
# Build for production with automatic WASM-or-native dispatch driven by site/config/rendering.ncl (ADR-006)
build-auto:
@nu /Users/Akasha/Development/rustelo/scripts/check-wasm-needed.nu --verbose \
--routes-dir site/config \
--workspace-config site/config/rendering.ncl \
&& (echo "🔨 WASM required — cargo leptos build --release"; cargo leptos build --release) \
|| (echo "🪶 Pure htmx-ssr — skipping wasm32"; \
cargo build --release -p rustelo-leptos-server --no-default-features --features htmx-ssr)
# Serve production build
serve-prod:
@just build::serve-prod
# Serve with CMS features
serve-cms:
@just build::serve-cms
# Run all tests
test-run:
@just test::run
# Quality checks with mandatory rule validation (main interface)
quality:
@echo "🔥 Running quality checks with MANDATORY rule validation..."
@echo "📖 Reading critical rules from @rules/CLAUDE_CRITICAL_RULES.md..."
@scripts/rules/validate-rules.nu --strict
@just test::quality
# Build CSS files
css-build:
@just dev::css-build
# Watch CSS files
css-watch:
@just dev::css-watch
# Database setup
db-setup:
@just database::setup
# Database migrations
db-migrate:
@just database::migrate
# Generate documentation
docs-generate:
@just docs::generate
# Build documentation
docs-build:
@just docs::build
# Check code quality
check:
@just test::check
# Format code
fmt:
@just test::format
# Fast development modes
dev-fast:
@just dev::dev-fast
dev-coordinated:
@just dev::dev-coordinated
dev-minimal:
@just dev::ultra-minimal
build-artifacts:
@just dev::build-artifacts
build-status:
@just dev::build-status
clean-build-artifacts:
@just dev::clean-build-artifacts
# Complete project setup
setup:
@just utils::setup
# Project information
info:
@just utils::info
# Health check
health:
@just utils::health
# =============================================================================
# NAVIGATION TESTING SHORTCUTS (Common Routes)
# =============================================================================
# Test home route
nav-test-home:
@just dev::nav-test-route /
# Test services route
nav-test-services:
@just dev::nav-test-route /services
# Test contact route
nav-test-contact:
@just dev::nav-test-route /contact
# Test blog route
nav-test-blog:
@just dev::nav-test-route /blog
# Test Spanish home
nav-test-es:
@just dev::nav-test-route /es
# Test Spanish contact
nav-test-contactar:
@just dev::nav-test-route /es/contactar
# Clean build artifacts
clean:
@just build::clean
# =============================================================================
# CONVENIENCE COMMANDS (Direct Module Access)
# =============================================================================
# Browser testing
page-tester *ARGS:
@just test::page {{ARGS}}
pages-report *ARGS:
@just test::pages-report {{ARGS}}
page-new:
@just test::page-new
# Cross-platform build
cross-build:
@just build::cross
# Content operations
content-build:
@just dev::content-build
# Export NCL metadata to index.json (NCL pipeline)
content-ncl-to-json type="" lang="":
@just dev::content-ncl-to-json {{type}} {{lang}}
# Full NCL pipeline: _index.ncl → index.json + copy images
content-ncl-build type="" lang="":
@just dev::content-ncl-build {{type}} {{lang}}
# Copy page-bundle images to site/public for HTTP serving
content-copy-images type="" lang="":
@just dev::content-copy-images {{type}} {{lang}}
# Sync FTL files from source project index.html files (requires data-key attributes).
# Each project index.ncl must have source_html set.
sync-pages:
nu scripts/sync/sync-all-pages.nu
# Sync a single project FTL. Usage: just sync-page ontoref
sync-page project:
nu scripts/sync/sync-all-pages.nu --only {{project}}
# Preview FTL sync without writing. Usage: just sync-page-dry ontoref
sync-page-dry project:
nu scripts/sync/sync-all-pages.nu --only {{project}} --dry-run
# Sync site/public → target/site (mirrors what cargo-leptos does at build time).
# Use when the dev server is not running and static assets need to be available immediately.
sync-public:
@rsync -a --exclude='.DS_Store' site/public/ target/site/
@echo "synced site/public → target/site"
content-generate type title *args:
@just dev::content-generate {{type}} {{title}} {{args}}
content-generate-indices:
@just dev::content-generate-indices
# Generate *.ncl metadata files from existing markdown frontmatter
content-md-to-ncl type="" lang="":
@nu scripts/content/md-to-ncl.nu \
--content-dir "${SITE_CONTENT_PATH:-site/content}" \
{{ if type != "" { "--type " + type } else { "" } }} \
{{ if lang != "" { "--lang " + lang } else { "" } }}
# Fill translations field in all *.ncl files by cross-lang stem matching (implies --force)
content-md-to-ncl-translations type="" lang="":
@nu scripts/content/md-to-ncl.nu --fill-translations \
--content-dir "${SITE_CONTENT_PATH:-site/content}" \
{{ if type != "" { "--type " + type } else { "" } }} \
{{ if lang != "" { "--lang " + lang } else { "" } }}
# Validate all generated post *.ncl files pass their Nickel contracts
content-ncl-validate type="" lang="":
@nu scripts/content/md-to-ncl.nu --validate-contracts \
--content-dir "${SITE_CONTENT_PATH:-site/content}" \
{{ if type != "" { "--type " + type } else { "" } }} \
{{ if lang != "" { "--lang " + lang } else { "" } }}
# Generate _index.ncl files from per-post *.ncl metadata files
# Generate _index.ncl files. Pass validate=true to also check slug/id uniqueness.
# Usage: just content-ncl-index [type] [lang] [validate]
content-ncl-index type="" lang="" validate="":
@nu scripts/content/generate-ncl-index.nu \
--content-dir "${SITE_CONTENT_PATH:-site/content}" \
{{ if type != "" { "--type " + type } else { "" } }} \
{{ if lang != "" { "--lang " + lang } else { "" } }} \
{{ if validate != "" { "--validate" } else { "" } }}
# Generate + validate slug/id uniqueness in one step
content-ncl-index-validate type="" lang="":
@nu scripts/content/generate-ncl-index.nu --validate \
--content-dir "${SITE_CONTENT_PATH:-site/content}" \
{{ if type != "" { "--type " + type } else { "" } }} \
{{ if lang != "" { "--lang " + lang } else { "" } }}
# Same as content-ncl-index but shows what would be written without writing
content-ncl-index-dry type="" lang="":
@nu scripts/content/generate-ncl-index.nu --dry-run --verbose \
--content-dir "${SITE_CONTENT_PATH:-site/content}" \
{{ if type != "" { "--type " + type } else { "" } }} \
{{ if lang != "" { "--lang " + lang } else { "" } }}
# Standalone build-tools generation command
build-tools-info:
@just dev::build-tools-info
# Tools operations
build-tools-analyze format="markdown" output="site/info":
@just tools::build-tools-analyze {{format}} {{output}}
build-tools-manage mode="dashboard":
@just tools::tools-manage {{mode}}
build-tools-generate-page name template="basic":
@just tools::build-tools-generate-page {{name}} {{template}}
build-tools-status:
@just tools::build-tools-status
# Install development dependencies
npm-install:
@just utils::npm-install
cargo-check:
@just utils::cargo-check
# =============================================================================
# WORKFLOW COMMANDS
# =============================================================================
# Complete development workflow
workflow-dev:
@echo "🔄 Running development workflow..."
@just utils::setup-deps
@just css-build
@just check
@just test
@just dev
# Complete production workflow
workflow-prod:
@echo "🔄 Running production workflow..."
@just test::quality
@just build-prod
@just build::docker
@just deploy
# Pre-commit workflow
pre-commit:
@echo "🔄 Running pre-commit workflow..."
@just fmt
@just test::check-strict
@just test
@just css-build
# CI/CD workflow
ci:
@echo "🔄 Running CI/CD workflow..."
@just test::format-check
@just test::check-strict
@just test
@just test::audit
@just build-prod
# =============================================================================
# HELP COMMANDS (Delegated to help module)
# =============================================================================
# Show help for development commands
help-dev:
@just helptext::dev
# Show help for build commands
help-build:
@just helptext::build
# Show help for testing commands
help-test:
@just helptext::test
# Show help for database commands
help-db:
@just helptext::db
# Show help for documentation commands
help-docs:
@just helptext::docs
# Show help for utility commands
help-utils:
@just helptext::utils
# Show help for build-tools commands
help-build-tools:
@just helptext::build-tools
# Show help for modules
help-modules:
@just helptext::modules
# Show comprehensive help
help-all:
@just helptext::all
# Main help entry point
help:
@just helptext::main
# Help with topic argument
help-topic TOPIC:
@just helptext::topic {{TOPIC}}
# Alias h that redirects to help with arguments
h *TOPIC:
#!/usr/bin/env bash
TOPIC="{{TOPIC}}"
if [ -n "$TOPIC" ]; then
just help-topic "$TOPIC"
else
just help
fi
# Logo (delegated to help module)
logo:
@just helptext::logo
server-browser-logs:
@just dev::server-browser-logs
browser-tools-server:
@just dev::browser-tools-server
browser-logs page="/":
@just dev::browser-logs {{page}}
# =============================================================================
# LOCAL INSTALL
# Builds the leptos-hydration binary + WASM bundle, installs the binary to
# ~/.local/libexec, syncs rustelo nickel resources, copies the compiled site
# tree, and writes the site-root-aware wrapper to ~/.local/bin/rustelo-leptos-server.
# =============================================================================
local-install:
#!/usr/bin/env nu
let nickel_path = $"{{ rustelo_root }}/resources/nickel:{{ justfile_directory() }}/site/config"
let site_cfg = "{{ justfile_directory() }}/site/config/index.ncl"
print "→ building rustelo-leptos-server (release, leptos-hydration)"
with-env { NICKEL_IMPORT_PATH: $nickel_path, SITE_CONFIG_PATH: $site_cfg } {
^cargo leptos build --release --features content-static
}
let cargo_cfg = "{{ justfile_directory() }}/.cargo/config.toml"
let target_dir = (
$env.CARGO_TARGET_DIR? | default (
if ($cargo_cfg | path exists) {
(open $cargo_cfg).build?."target-dir"? | default "{{ justfile_directory() }}/target"
} else {
"{{ justfile_directory() }}/target"
}
)
)
let home = $env.HOME
let libexec = ($home | path join ".local/libexec")
let bin_dir = ($home | path join ".local/bin")
let share_dir = ($home | path join ".local/share/rustelo-leptos-server")
let nickel_dir = ($home | path join ".local/share/rustelo/nickel")
mkdir $libexec $bin_dir $share_dir $nickel_dir
print "→ installing binary → ~/.local/libexec/rustelo-leptos-server"
cp ($target_dir | path join "release/rustelo-leptos-server") ($libexec | path join "rustelo-leptos-server")
print "→ syncing rustelo nickel → ~/.local/share/rustelo/nickel"
^rsync -a --delete $"{{ rustelo_root }}/resources/nickel/" $"($nickel_dir)/"
print "→ installing compiled site tree (pkg/ + assets) → ~/.local/share/rustelo-leptos-server/site"
^rsync -a --delete ($target_dir | path join "site/") ($share_dir | path join "site/")
print "→ writing wrapper → ~/.local/bin/rustelo-leptos-server"
let wrapper_dst = ($bin_dir | path join "rustelo-leptos-server")
cp "{{ justfile_directory() }}/scripts/local-install/rustelo-leptos-server.sh" $wrapper_dst
^chmod 755 $wrapper_dst
print ""
print "✅ done — usage:"
print " rustelo-leptos-server # auto-detects site/ in $PWD"
print " rustelo-leptos-server --site <path> # explicit workspace root"
print $" # or place config at ($home)/.config/rustelo-leptos-server/site/"