1252 lines
53 KiB
Makefile
1252 lines
53 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/code"
|
||
|
|
secrets_base := justfile_directory() + "/../../workspaces/libre-daoshi"
|
||
|
|
rustelo_root := justfile_directory() + "/../../rustelo/code"
|
||
|
|
strops_root := justfile_directory() + "/../../stratumiops"
|
||
|
|
|
||
|
|
# reg.librecloud.online's push credential is SOPS-encrypted for libre-wuji's
|
||
|
|
# age identity — a different workspace/key than secrets_base (libre-daoshi)
|
||
|
|
# above. Override with LIAN_BUILD_WUJI_SECRETS_BASE if wuji lives somewhere
|
||
|
|
# else; defaults relative to this justfile like every other _root var here.
|
||
|
|
wuji_secrets_base := env_var_or_default("LIAN_BUILD_WUJI_SECRETS_BASE", justfile_directory() + "/../../workspaces/libre-wuji")
|
||
|
|
wuji_kage := wuji_secrets_base + "/infra/libre-wuji/.kage"
|
||
|
|
|
||
|
|
# 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)" }
|
||
|
|
|
||
|
|
# Render profile is derived, never declared (ADR-006): build_directives.ncl
|
||
|
|
# exports one directive PER profile, keyed by profile name. Mirror
|
||
|
|
# provisioning/build.nu's resolve-profile so the nickel export below (and
|
||
|
|
# the --directives handed to lian-build) see a flat BuildDirectives, not
|
||
|
|
# the profile-keyed dict.
|
||
|
|
let detector = "{{ rustelo_root }}/scripts/check-wasm-needed.nu"
|
||
|
|
let profile = if ($detector | path exists) {
|
||
|
|
let res = (do {
|
||
|
|
cd "{{ justfile_directory() }}"
|
||
|
|
^nu $detector --routes-dir site/config --workspace-config site/config/rendering.ncl
|
||
|
|
} | complete)
|
||
|
|
if $res.exit_code == 0 {
|
||
|
|
"leptos-hydration"
|
||
|
|
} else if $res.exit_code == 1 {
|
||
|
|
"htmx-ssr"
|
||
|
|
} else {
|
||
|
|
error make { msg: $"check-wasm-needed.nu failed: ($res.stderr | str trim)" }
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
let p = (open "{{ justfile_directory() }}/site/config/rendering.ncl" | parse --regex 'default_profile = "(?P<p>[^"]+)"' | get p?.0? | default "leptos-hydration")
|
||
|
|
if $p == "htmx-ssr" { "htmx-ssr" } else { "leptos-hydration" }
|
||
|
|
}
|
||
|
|
print $" render profile: ($profile)"
|
||
|
|
|
||
|
|
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/code/"
|
||
|
|
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)"'
|
||
|
|
| str replace 'SECRET_BASE_PLACEHOLDER' "{{ wuji_secrets_base }}:{{ secrets_base }}"
|
||
|
|
| str replace 'KEY_FILE_PLACEHOLDER' "{{ wuji_kage }}:{{ secrets_base }}/infra/libre-daoshi/.kage"
|
||
|
|
| save -f $proj_override
|
||
|
|
|
||
|
|
# Wrapper selects the single profile's directive — same trick as build.nu —
|
||
|
|
# so both the local nickel export and lian-build itself see a flat object.
|
||
|
|
let directives_src = "{{ justfile_directory() }}/lian-build/build_directives.ncl"
|
||
|
|
let directives_wrapper = $"{{ lian_build_root }}/jpl-directives-selected.ncl"
|
||
|
|
('(import "' + $directives_src + '")."' + $profile + '"') | save -f $directives_wrapper
|
||
|
|
|
||
|
|
let dir_raw = (do {
|
||
|
|
^nickel export --import-path "{{ lian_build_root }}" $directives_wrapper
|
||
|
|
} | complete)
|
||
|
|
if $dir_raw.exit_code != 0 { rm -f $proj_override $directives_wrapper; 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 $directives_wrapper --context $ctx --secrets-base "{{ secrets_base }}" o+e> $log_file
|
||
|
|
} else {
|
||
|
|
^lian-build build --directives $directives_wrapper --context $ctx --secrets-base "{{ secrets_base }}"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch { |e|
|
||
|
|
job kill $relay_job
|
||
|
|
rm -f $nkey_file $proj_override $directives_wrapper
|
||
|
|
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 $directives_wrapper
|
||
|
|
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/code/"
|
||
|
|
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)"'
|
||
|
|
| str replace 'SECRET_BASE_PLACEHOLDER' "{{ wuji_secrets_base }}:{{ secrets_base }}"
|
||
|
|
| str replace 'KEY_FILE_PLACEHOLDER' "{{ wuji_kage }}:{{ secrets_base }}/infra/libre-daoshi/.kage"
|
||
|
|
| 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"
|
||
|
|
mod content "justfiles/content.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
|
||
|
|
|
||
|
|
# Customization-seam verifier: brand / the site's own public origin / site identity
|
||
|
|
# must not be hardcoded in the ACTIVE htmx-ssr shared Rust (crates/server/src +
|
||
|
|
# crates/pages_htmx/src). Mirrors .ontoref/reflection/modes/validate-seam.ncl and is
|
||
|
|
# the transition condition of the consumer-neutrality FSM dimension. HARD GATE:
|
||
|
|
# exits 1 on any FAIL (currently green — so it only bites future regressions).
|
||
|
|
# Scope excludes crates/pages (Leptos hydration pages, optional dep absent from the
|
||
|
|
# htmx-ssr binary; their outbound project links are content, not baked identity).
|
||
|
|
validate-seam:
|
||
|
|
#!/usr/bin/env bash
|
||
|
|
set -uo pipefail
|
||
|
|
scope="crates/server/src crates/pages_htmx/src"
|
||
|
|
fails=0
|
||
|
|
echo "── check-no-brand-hex ──"
|
||
|
|
m=$(rg -n -i '#(e8a838|c0ccd8|0f1319|c88a20|f0c265)' $scope 2>/dev/null)
|
||
|
|
if [ -n "$m" ]; then echo "FAIL brand hex in shared code:"; echo "$m"; fails=$((fails+1)); else echo "PASS"; fi
|
||
|
|
echo "── check-no-hardcoded-origin ──"
|
||
|
|
m=$(rg -n 'https?://[a-z0-9.-]+\.(dev|com|pro|org)' $scope -g '*.rs' 2>/dev/null | rg -v '//!|///' | rg -vi 'example\.|w3\.org|schema\.org|localhost|nickel-lang|github\.com|yourusername')
|
||
|
|
if [ -n "$m" ]; then echo "FAIL hardcoded production origin in shared code:"; echo "$m"; fails=$((fails+1)); else echo "PASS"; fi
|
||
|
|
echo "── check-no-site-identity ──"
|
||
|
|
m=$(rg -n -i 'jesusperez|jpl-website' $scope -g '*.rs' 2>/dev/null | rg -v '//!|///')
|
||
|
|
if [ -n "$m" ]; then echo "FAIL site identity literal in shared code:"; echo "$m"; fails=$((fails+1)); else echo "PASS"; fi
|
||
|
|
if [ "$fails" -gt 0 ]; then echo "── seam: $fails check(s) FAILING — move the leak to its layer (brand→CSS, origin→env, identity→site/config) ──"; exit 1; fi
|
||
|
|
echo "── seam: all checks pass (active htmx-ssr surface respects the seam) ──"
|
||
|
|
|
||
|
|
# Route superset verifier (ADR-001, single-image deploy): the baked route table
|
||
|
|
# must declare a ContentIndex + PostViewer for every standard content kind, so a
|
||
|
|
# consumer's menu/About links resolve against the shared binary instead of 404-ing.
|
||
|
|
# Mirrors .ontoref/reflection/modes/validate-route-superset.ncl. Hard gate.
|
||
|
|
validate-route-superset:
|
||
|
|
#!/usr/bin/env bash
|
||
|
|
set -uo pipefail
|
||
|
|
routes=site/config/routes.ncl
|
||
|
|
fails=0
|
||
|
|
for kind in blog projects adr catalog; do
|
||
|
|
idx=$(rg -c "make_content_route \"ContentIndex\" \"$kind\"" "$routes" 2>/dev/null || true)
|
||
|
|
pv=$(rg -c "make_content_route \"PostViewer\" \"$kind\"" "$routes" 2>/dev/null || true)
|
||
|
|
if [ "${idx:-0}" -ge 1 ] && [ "${pv:-0}" -ge 1 ]; then
|
||
|
|
echo "PASS $kind (ContentIndex + PostViewer)"
|
||
|
|
else
|
||
|
|
miss=""; [ "${idx:-0}" -lt 1 ] && miss="$miss ContentIndex"; [ "${pv:-0}" -lt 1 ] && miss="$miss PostViewer"
|
||
|
|
echo "FAIL $kind — missing:$miss"; fails=$((fails+1))
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
if [ "$fails" -gt 0 ]; then echo "── route-superset: $fails kind(s) incomplete — add to site/config/{routes,content}.ncl + rebuild ──"; exit 1; fi
|
||
|
|
echo "── route-superset: all standard kinds present (baked superset complete) ──"
|
||
|
|
|
||
|
|
# Neutral-binary verifier (ADR-001): per-site identity must come from the running
|
||
|
|
# consumer's config at runtime, never baked/hardcoded into the shared binary. The
|
||
|
|
# logo is read from the consumer's site.ncl [logo]; the home hero must use that
|
||
|
|
# (the site_logo global), not a hardcoded path. Origin/name stay runtime env.
|
||
|
|
# Mirrors .ontoref/reflection/modes/validate-neutral-binary.ncl. Hard gate.
|
||
|
|
validate-neutral-binary:
|
||
|
|
#!/usr/bin/env bash
|
||
|
|
set -uo pipefail
|
||
|
|
fails=0
|
||
|
|
have() { if rg -q "$1" "$2" 2>/dev/null; then echo "PASS $3"; else echo "FAIL $3 — expected /$1/ in ${2##*/}"; fails=$((fails+1)); fi; }
|
||
|
|
absent() { m=$(rg -n "$1" "$2" 2>/dev/null); if [ -z "$m" ]; then echo "PASS $3"; else echo "FAIL $3 — hardcoded in ${2##*/}:"; echo "$m"; fails=$((fails+1)); fi; }
|
||
|
|
have 'logo_from_site_config|site\.ncl' crates/server/src/theme.rs "logo read from consumer site.ncl [logo] (config-driven)"
|
||
|
|
absent 'src="/images/' crates/pages_htmx/templates/pages/home.j2 "home template has no hardcoded image (logo via site_logo global, others via config/texts)"
|
||
|
|
have '"SITE_BASE_URL"' crates/server/src/shell/seo.rs "public origin from SITE_BASE_URL env"
|
||
|
|
have '"SITE_NAME"' crates/server/src/shell/seo.rs "site name from SITE_NAME env"
|
||
|
|
if [ "$fails" -gt 0 ]; then echo "── neutral-binary: $fails check(s) FAILING — move per-site identity to config (site.ncl) / the site_logo global ──"; exit 1; fi
|
||
|
|
echo "── neutral-binary: logo config-driven, no template hardcode, origin/name from env (binary site-neutral) ──"
|
||
|
|
|
||
|
|
# Config-drift verifier (ADR-001): a consumer's route paths must be a SUBSET of the
|
||
|
|
# baked route superset, or its menu/links 404 against the shared binary. Pass the
|
||
|
|
# consumer's site/config dir; defaults to this repo's own (self-check = zero drift).
|
||
|
|
# Mirrors .ontoref/reflection/modes/validate-config-drift.ncl. Hard gate.
|
||
|
|
validate-config-drift consumer_config="site/config":
|
||
|
|
#!/usr/bin/env bash
|
||
|
|
set -uo pipefail
|
||
|
|
NICKEL="{{ rustelo_root }}/resources/nickel"
|
||
|
|
paths() { nickel export --import-path "$NICKEL" --import-path "$1" "$1/routes.ncl" 2>/dev/null | jq -r '[.routes[].paths.en, .routes[].paths.es] | map(select(.)) | .[]' 2>/dev/null | sort -u; }
|
||
|
|
sup=$(paths site/config)
|
||
|
|
cons=$(paths "{{ consumer_config }}")
|
||
|
|
if [ -z "$sup" ]; then echo "FAIL could not export the superset (site/config/routes.ncl)"; exit 1; fi
|
||
|
|
if [ -z "$cons" ]; then echo "FAIL could not export consumer routes ({{ consumer_config }}/routes.ncl)"; exit 1; fi
|
||
|
|
drift=$(comm -23 <(echo "$cons") <(echo "$sup"))
|
||
|
|
if [ -n "$drift" ]; then echo "FAIL — consumer route paths NOT in the baked superset (these 404):"; echo "$drift" | sed 's/^/ /'; echo "── add them to site/config/routes.ncl + rebuild, or remove from the consumer ──"; exit 1; fi
|
||
|
|
echo "── config-drift: consumer routes ⊆ superset ($(echo "$cons" | grep -c . ) paths, 0 drift) ──"
|
||
|
|
|
||
|
|
# Framework↔consumer contract gate (ADR-001): all single-image verifiers at once.
|
||
|
|
# config-drift defaults to this repo's own config (self-check); pass a consumer dir
|
||
|
|
# to check a real consumer. Wire into CI to enforce the seam + superset + neutrality.
|
||
|
|
validate-contract consumer_config="site/config":
|
||
|
|
@just validate-seam
|
||
|
|
@just validate-route-superset
|
||
|
|
@just validate-neutral-binary
|
||
|
|
@just validate-config-drift "{{ consumer_config }}"
|
||
|
|
@echo "✓ framework↔consumer contract: seam · superset · neutral-binary · config-drift all green"
|
||
|
|
|
||
|
|
# 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:
|
||
|
|
#!/usr/bin/env nu
|
||
|
|
let nickel_path = $"{{ rustelo_root }}/resources/nickel:{{ justfile_directory() }}/site/config"
|
||
|
|
let site_cfg = "{{ justfile_directory() }}/site/config/index.ncl"
|
||
|
|
let check_script = "{{ rustelo_root }}/scripts/check-wasm-needed.nu"
|
||
|
|
let wasm_needed = (do { nu $check_script --verbose --routes-dir "site/config" --workspace-config "site/config/rendering.ncl" } | complete | get exit_code) == 0
|
||
|
|
if $wasm_needed {
|
||
|
|
print "🔨 WASM required — cargo leptos build --release"
|
||
|
|
with-env { NICKEL_IMPORT_PATH: $nickel_path, SITE_CONFIG_PATH: $site_cfg } {
|
||
|
|
^cargo leptos build --release
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
print "🪶 Pure htmx-ssr — skipping wasm32"
|
||
|
|
with-env { NICKEL_IMPORT_PATH: $nickel_path, SITE_CONFIG_PATH: $site_cfg } {
|
||
|
|
^cargo build --release -p rustelo-htmx-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 { "" } }}
|
||
|
|
|
||
|
|
# Full content publish pipeline for a type (default blog). Site-agnostic — drives
|
||
|
|
# $SITE_CONTENT_PATH / $RUSTELO_STATIC_DIR so it runs against ANY site's content
|
||
|
|
# (this outreach/site and others). Chain:
|
||
|
|
# 1. md-to-ncl --force → (re)generate .ncl metadata; the patch PRESERVES
|
||
|
|
# graph + thumbnail/thumbnail_dark from existing .ncl.
|
||
|
|
# 2. content-ncl-validate → gate: every .ncl passes its Nickel contract.
|
||
|
|
# 3. content-ncl-build → _index.ncl → index.json + filter-index + copy images
|
||
|
|
# (this is what the grid card reads).
|
||
|
|
# `schema_dir` is the metadata schema md-to-ncl writes the import against; leave
|
||
|
|
# empty for the built-in default, or point it at the site-local nickel schema
|
||
|
|
# (e.g. $HOME/.local/share/rustelo/nickel/content/metadata).
|
||
|
|
# Pre/post steps that are SITE-specific (brand-linkify, ES vocab gate, content
|
||
|
|
# -graph `just graph`, `just about-json`) stay in each site's justfile.
|
||
|
|
# Usage: SITE_CONTENT_PATH=site/content just content-publish blog
|
||
|
|
# just content-publish blog en $HOME/.local/share/rustelo/nickel/content/metadata
|
||
|
|
content-publish type="blog" lang="" schema_dir="":
|
||
|
|
@echo "📤 content-publish: type={{type}} lang={{lang}}"
|
||
|
|
@nu scripts/content/md-to-ncl.nu --force \
|
||
|
|
--content-dir "${SITE_CONTENT_PATH:-site/content}" \
|
||
|
|
{{ if schema_dir != "" { "--schema-dir " + schema_dir } else { "" } }} \
|
||
|
|
{{ if type != "" { "--type " + type } else { "" } }} \
|
||
|
|
{{ if lang != "" { "--lang " + lang } else { "" } }}
|
||
|
|
@nu scripts/content/md-to-ncl.nu --validate-contracts \
|
||
|
|
--content-dir "${SITE_CONTENT_PATH:-site/content}" \
|
||
|
|
{{ if schema_dir != "" { "--schema-dir " + schema_dir } else { "" } }} \
|
||
|
|
{{ if type != "" { "--type " + type } else { "" } }} \
|
||
|
|
{{ if lang != "" { "--lang " + lang } else { "" } }}
|
||
|
|
@just content-ncl-build "{{type}}" "{{lang}}"
|
||
|
|
@echo "✅ content-publish done — index.json + filter-index rebuilt"
|
||
|
|
|
||
|
|
# 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 htmx-ssr binary, installs it to ~/.local/libexec, syncs rustelo
|
||
|
|
# nickel resources, assembles htmx templates, and writes the site-root-aware
|
||
|
|
# wrapper to ~/.local/bin/rustelo-htmx-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-htmx-server (release, htmx-ssr)"
|
||
|
|
with-env { NICKEL_IMPORT_PATH: $nickel_path, SITE_CONFIG_PATH: $site_cfg } {
|
||
|
|
^cargo build --release -p rustelo-htmx-server --no-default-features --features htmx-ssr
|
||
|
|
}
|
||
|
|
|
||
|
|
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-htmx-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-htmx-server"
|
||
|
|
cp ($target_dir | path join "release/rustelo-htmx-server") ($libexec | path join "rustelo-htmx-server")
|
||
|
|
|
||
|
|
print "→ syncing rustelo nickel → ~/.local/share/rustelo/nickel"
|
||
|
|
^rsync -a --delete $"{{ rustelo_root }}/resources/nickel/" $"($nickel_dir)/"
|
||
|
|
|
||
|
|
print "→ assembling htmx templates"
|
||
|
|
let tmpl_out = ($target_dir | path join "site/htmx-templates")
|
||
|
|
nu "{{ justfile_directory() }}/scripts/build/assemble-htmx-templates.nu" --out $tmpl_out
|
||
|
|
|
||
|
|
print "→ installing templates → ~/.local/share/rustelo-htmx-server/htmx-templates"
|
||
|
|
^rsync -a --delete $"($tmpl_out)/" ($share_dir | path join "htmx-templates/")
|
||
|
|
|
||
|
|
print "→ installing htmx runtime → ~/.local/share/rustelo-htmx-server/htmx"
|
||
|
|
^rsync -a --delete $"{{ rustelo_root }}/templates/shared/htmx/" ($share_dir | path join "htmx/")
|
||
|
|
|
||
|
|
# gen-content-graph: build from rustelo workspace so workspace = true deps resolve.
|
||
|
|
# --manifest-path lets Cargo walk up to rustelo/code/Cargo.toml as workspace root.
|
||
|
|
print "→ building gen-content-graph (release, gen)"
|
||
|
|
let gen_manifest = $"{{ rustelo_root }}/crates/foundation/crates/rustelo_content_graph_ssr/Cargo.toml"
|
||
|
|
with-env { CARGO_TARGET_DIR: $target_dir, NICKEL_IMPORT_PATH: $nickel_path, SITE_CONFIG_PATH: $site_cfg } {
|
||
|
|
^cargo build --release -p rustelo_content_graph_ssr --features gen --manifest-path $gen_manifest
|
||
|
|
}
|
||
|
|
|
||
|
|
print "→ installing gen-content-graph → ~/.local/bin/gen-content-graph"
|
||
|
|
let gen_dst = ($bin_dir | path join "gen-content-graph")
|
||
|
|
cp ($target_dir | path join "release/gen-content-graph") $gen_dst
|
||
|
|
^chmod 755 $gen_dst
|
||
|
|
|
||
|
|
# content_processor: the REAL generator is the framework's rustelo_server bin
|
||
|
|
# (requires content-static); the project's same-named bin is a stub. Build it
|
||
|
|
# into rustelo's own target dir to avoid colliding with that stub in our target.
|
||
|
|
print "→ building content_processor (release, content-static)"
|
||
|
|
let rustelo_cargo_cfg = $"{{ rustelo_root }}/.cargo/config.toml"
|
||
|
|
let rustelo_target = (
|
||
|
|
if ($rustelo_cargo_cfg | path exists) {
|
||
|
|
(open $rustelo_cargo_cfg).build?."target-dir"? | default $"{{ rustelo_root }}/target"
|
||
|
|
} else {
|
||
|
|
$"{{ rustelo_root }}/target"
|
||
|
|
}
|
||
|
|
)
|
||
|
|
let cp_manifest = $"{{ rustelo_root }}/crates/foundation/crates/rustelo_server/Cargo.toml"
|
||
|
|
with-env { CARGO_TARGET_DIR: $rustelo_target, NICKEL_IMPORT_PATH: $nickel_path, SITE_CONFIG_PATH: $site_cfg } {
|
||
|
|
^cargo build --release -p rustelo_server --bin content_processor --features content-static --manifest-path $cp_manifest
|
||
|
|
}
|
||
|
|
|
||
|
|
print "→ installing content_processor → ~/.local/libexec/content_processor"
|
||
|
|
cp ($rustelo_target | path join "release/content_processor") ($libexec | path join "content_processor")
|
||
|
|
|
||
|
|
print "→ writing content_processor wrapper → ~/.local/bin/content_processor"
|
||
|
|
let cp_wrapper = ($bin_dir | path join "content_processor")
|
||
|
|
cp "{{ justfile_directory() }}/scripts/local-install/content_processor.sh" $cp_wrapper
|
||
|
|
^chmod 755 $cp_wrapper
|
||
|
|
|
||
|
|
print "→ writing wrapper → ~/.local/bin/rustelo-htmx-server"
|
||
|
|
let wrapper_dst = ($bin_dir | path join "rustelo-htmx-server")
|
||
|
|
cp "{{ justfile_directory() }}/scripts/local-install/rustelo-htmx-server.sh" $wrapper_dst
|
||
|
|
^chmod 755 $wrapper_dst
|
||
|
|
|
||
|
|
print ""
|
||
|
|
print "✅ done — usage:"
|
||
|
|
print " rustelo-htmx-server # auto-detects site/ in $PWD"
|
||
|
|
print " rustelo-htmx-server --site <path> # explicit workspace root"
|
||
|
|
print $" # or place site tree at ($home)/.config/rustelo-htmx-server/site/"
|
||
|
|
print " gen-content-graph # regenerate content_graph.json (needs ONTOREF_NICKEL_IMPORT_PATH)"
|
||
|
|
print " content_processor --content-type blog # regenerate site/r content indexes from site/content"
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# CONTENT TOOLKIT INSTALL
|
||
|
|
# Installs the Nushell content-authoring scripts to ~/.local/share/rustelo-content
|
||
|
|
# and the `rustelo-content` dispatcher to ~/.local/bin. Sites consume the toolkit
|
||
|
|
# via the content.just module (mod content) without a source checkout alongside.
|
||
|
|
# =============================================================================
|
||
|
|
content-install:
|
||
|
|
#!/usr/bin/env nu
|
||
|
|
let home = $env.HOME
|
||
|
|
let share = ($home | path join ".local/share/rustelo-content")
|
||
|
|
let bin = ($home | path join ".local/bin")
|
||
|
|
mkdir $share $bin
|
||
|
|
|
||
|
|
print "→ installing content scripts → ~/.local/share/rustelo-content"
|
||
|
|
(^rsync -a --delete
|
||
|
|
--exclude "old/" --exclude ".image-spend.jsonl" --exclude "pending/"
|
||
|
|
$"{{ justfile_directory() }}/scripts/content/" $"($share)/")
|
||
|
|
|
||
|
|
print "→ writing dispatcher → ~/.local/bin/rustelo-content"
|
||
|
|
let dst = ($bin | path join "rustelo-content")
|
||
|
|
cp "{{ justfile_directory() }}/scripts/local-install/rustelo-content.sh" $dst
|
||
|
|
^chmod 755 $dst
|
||
|
|
|
||
|
|
print ""
|
||
|
|
print "✅ done — usage from a site root (dir containing site/):"
|
||
|
|
print " rustelo-content images --dry-run --type blog"
|
||
|
|
print " rustelo-content sync validate-translations"
|
||
|
|
print " # via just module: mod content \".just/content.just\" → just content images"
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# AUTHORING SETUP
|
||
|
|
# Wire a consumer site to the authoring toolkit: ensure the rustelo-content CLI
|
||
|
|
# is installed, symlink this repo's content.just into <site>/.just/ (relative,
|
||
|
|
# portable), and add `mod authoring` to the site justfile. Idempotent.
|
||
|
|
# just authoring-setup /path/to/site (defaults to PWD)
|
||
|
|
# =============================================================================
|
||
|
|
authoring-setup site=".":
|
||
|
|
#!/usr/bin/env nu
|
||
|
|
let src_module = ("{{ justfile_directory() }}/justfiles/content.just" | path expand)
|
||
|
|
let site = ("{{ site }}" | path expand)
|
||
|
|
let site_just = ($site | path join "justfile")
|
||
|
|
if not ($site_just | path exists) {
|
||
|
|
error make { msg: $"No justfile at ($site) — pass a site root: just authoring-setup <path>" }
|
||
|
|
}
|
||
|
|
|
||
|
|
# 1. ensure the CLI is on PATH
|
||
|
|
if (which rustelo-content | is-empty) {
|
||
|
|
print "→ rustelo-content not found; running content-install"
|
||
|
|
just content-install
|
||
|
|
} else {
|
||
|
|
print "→ rustelo-content already on PATH"
|
||
|
|
}
|
||
|
|
|
||
|
|
# 2. symlink the module into <site>/.just/ with a relative target (survives
|
||
|
|
# relocating the whole tree; symlink target is relative to the link's dir)
|
||
|
|
let just_dir = ($site | path join ".just")
|
||
|
|
mkdir $just_dir
|
||
|
|
let link = ($just_dir | path join "content.just")
|
||
|
|
let from = ($just_dir | path split)
|
||
|
|
let to = ($src_module | path split)
|
||
|
|
let n = ([($from | length) ($to | length)] | math min)
|
||
|
|
mut i = 0
|
||
|
|
while $i < $n and ($from | get $i) == ($to | get $i) { $i = $i + 1 }
|
||
|
|
let ups = (0..<(($from | length) - $i) | each { ".." })
|
||
|
|
let rel = (($ups ++ ($to | skip $i)) | path join)
|
||
|
|
^ln -sf $rel $link
|
||
|
|
print $"→ linked ($link) → ($rel)"
|
||
|
|
|
||
|
|
# 3. ensure `mod authoring` in the site justfile (idempotent)
|
||
|
|
let txt = (open $site_just)
|
||
|
|
if ($txt | str contains "mod authoring") {
|
||
|
|
print "→ mod authoring already present"
|
||
|
|
} else {
|
||
|
|
let lines = ($txt | lines)
|
||
|
|
let assign_rows = ($lines | enumerate | where { |e|
|
||
|
|
let t = ($e.item | str trim)
|
||
|
|
(not ($t | str starts-with "#")) and ($t | str contains ":=")
|
||
|
|
})
|
||
|
|
let set_rows = ($lines | enumerate | where { |e| ($e.item | str trim | str starts-with "set ") })
|
||
|
|
let anchor = if (not ($assign_rows | is-empty)) {
|
||
|
|
($assign_rows | last | get index)
|
||
|
|
} else if (not ($set_rows | is-empty)) {
|
||
|
|
($set_rows | last | get index)
|
||
|
|
} else { -1 }
|
||
|
|
|
||
|
|
let modline = 'mod authoring ".just/content.just"'
|
||
|
|
let comment = '# Content-authoring toolkit (rustelo-content): images | sync | validate | new'
|
||
|
|
let new_lines = if $anchor < 0 {
|
||
|
|
([$comment $modline ""] ++ $lines)
|
||
|
|
} else {
|
||
|
|
($lines | enumerate | each { |e|
|
||
|
|
if $e.index == $anchor { [$e.item "" $comment $modline] } else { [$e.item] }
|
||
|
|
} | flatten)
|
||
|
|
}
|
||
|
|
$new_lines | str join "\n" | save --force $site_just
|
||
|
|
print $"→ added `mod authoring` to ($site_just)"
|
||
|
|
}
|
||
|
|
|
||
|
|
print ""
|
||
|
|
print "✅ site wired — run: just authoring images --dry-run --type blog"
|
||
|
|
|
||
|
|
# =============================================================================
|
||
|
|
# AUTHORING SETUP — BATCH
|
||
|
|
# Discover every justfile under <root> that targets this source repo (references
|
||
|
|
# "website-htmx-rustelo") and wire each site via authoring-setup. <root> defaults
|
||
|
|
# to the dir two levels above this repo (the shared Development tree). Idempotent.
|
||
|
|
# just authoring-setup-all [root]
|
||
|
|
# =============================================================================
|
||
|
|
authoring-setup-all root="":
|
||
|
|
#!/usr/bin/env nu
|
||
|
|
if (which rg | is-empty) {
|
||
|
|
error make { msg: "ripgrep (rg) is required for site discovery" }
|
||
|
|
}
|
||
|
|
let src_root = ("{{ justfile_directory() }}" | path expand)
|
||
|
|
let root = if ("{{ root }}" | is-empty) {
|
||
|
|
($src_root | path dirname | path dirname)
|
||
|
|
} else {
|
||
|
|
("{{ root }}" | path expand)
|
||
|
|
}
|
||
|
|
print $"→ scanning ($root) for sites targeting website-htmx-rustelo"
|
||
|
|
|
||
|
|
# rg exit 1 = no matches (benign); >1 = real error
|
||
|
|
let scan = (^rg -l --no-ignore -g "justfile" "website-htmx-rustelo" $root | complete)
|
||
|
|
if $scan.exit_code > 1 {
|
||
|
|
error make { msg: $"rg failed: ($scan.stderr)" }
|
||
|
|
}
|
||
|
|
|
||
|
|
let sites = (
|
||
|
|
$scan.stdout | lines
|
||
|
|
| where { |l| ($l | str trim) != "" }
|
||
|
|
| where { |l| not (($l | path expand) | str starts-with $src_root) }
|
||
|
|
| each { |j| $j | path expand | path dirname }
|
||
|
|
| uniq
|
||
|
|
)
|
||
|
|
if ($sites | is-empty) {
|
||
|
|
print " no consumer sites found."
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
print $" found ($sites | length) site\(s\):"
|
||
|
|
$sites | each { |s| print $" ($s)" }
|
||
|
|
print ""
|
||
|
|
for s in $sites {
|
||
|
|
print $"━━ wiring ($s)"
|
||
|
|
just -f $"{{ justfile_directory() }}/justfile" authoring-setup $s
|
||
|
|
}
|
||
|
|
print ""
|
||
|
|
print $"✅ wired ($sites | length) site\(s\)"
|