349 lines
10 KiB
Plaintext
349 lines
10 KiB
Plaintext
|
|
use store.nu [daemon-export]
|
||
|
|
|
||
|
|
# Manifest operations — operational modes and publication services.
|
||
|
|
#
|
||
|
|
# Reads $ONTOREF_PROJECT_ROOT/.ontology/manifest.ncl to determine:
|
||
|
|
# - layers, operational_modes, consumption_modes, publication_services
|
||
|
|
#
|
||
|
|
# Commands:
|
||
|
|
# manifest load export and parse the project manifest
|
||
|
|
# manifest mode <id> switch to an operational mode (run pre/post activate)
|
||
|
|
# manifest mode list list available operational modes
|
||
|
|
# manifest publish <service-id> run a publication workflow
|
||
|
|
# manifest publish list list available publication services
|
||
|
|
# manifest layers show layers with committed status
|
||
|
|
# manifest consumers show consumption modes
|
||
|
|
|
||
|
|
def manifest-path []: nothing -> string {
|
||
|
|
let root = if ($env.ONTOREF_PROJECT_ROOT? | is-not-empty) {
|
||
|
|
$env.ONTOREF_PROJECT_ROOT
|
||
|
|
} else {
|
||
|
|
$env.PWD
|
||
|
|
}
|
||
|
|
$"($root)/.ontology/manifest.ncl"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Export and parse the project manifest NCL
|
||
|
|
export def "manifest load" []: nothing -> record {
|
||
|
|
let path = (manifest-path)
|
||
|
|
if not ($path | path exists) {
|
||
|
|
error make { msg: $"No manifest at ($path) — create .ontology/manifest.ncl first" }
|
||
|
|
}
|
||
|
|
|
||
|
|
# Build import path: project .ontology/ + ontoref ontology/ + existing NICKEL_IMPORT_PATH
|
||
|
|
let project_ontology = ($path | path dirname)
|
||
|
|
let ontoref_ontology = if ($env.ONTOREF_ROOT? | is-not-empty) {
|
||
|
|
$"($env.ONTOREF_ROOT)/ontology"
|
||
|
|
} else {
|
||
|
|
""
|
||
|
|
}
|
||
|
|
|
||
|
|
let extra_paths = ([$project_ontology, $ontoref_ontology] | where { $in | is-not-empty })
|
||
|
|
let existing = ($env.NICKEL_IMPORT_PATH? | default "")
|
||
|
|
let import_path = if ($existing | is-not-empty) {
|
||
|
|
($extra_paths | append ($existing | split row ":") | uniq | str join ":")
|
||
|
|
} else {
|
||
|
|
($extra_paths | str join ":")
|
||
|
|
}
|
||
|
|
|
||
|
|
daemon-export $path --import-path $import_path
|
||
|
|
}
|
||
|
|
|
||
|
|
# Find an operational mode by id
|
||
|
|
def find-mode [manifest: record, id: string]: nothing -> record {
|
||
|
|
let modes = ($manifest.operational_modes? | default [])
|
||
|
|
let found = ($modes | where { ($in.id? | default "") == $id })
|
||
|
|
if ($found | is-empty) {
|
||
|
|
let available = ($modes | each { $in.id? | default "?" } | str join ", ")
|
||
|
|
error make { msg: $"Mode '($id)' not found. Available: ($available)" }
|
||
|
|
}
|
||
|
|
$found | first
|
||
|
|
}
|
||
|
|
|
||
|
|
# Find a publication service by id
|
||
|
|
def find-service [manifest: record, id: string]: nothing -> record {
|
||
|
|
let services = ($manifest.publication_services? | default [])
|
||
|
|
let found = ($services | where { ($in.id? | default "") == $id })
|
||
|
|
if ($found | is-empty) {
|
||
|
|
let available = ($services | each { $in.id? | default "?" } | str join ", ")
|
||
|
|
error make { msg: $"Service '($id)' not found. Available: ($available)" }
|
||
|
|
}
|
||
|
|
$found | first
|
||
|
|
}
|
||
|
|
|
||
|
|
# Run a list of shell commands sequentially, abort on failure
|
||
|
|
def run-commands [commands: list<string>, label: string]: nothing -> bool {
|
||
|
|
for cmd in $commands {
|
||
|
|
print $" [$label] ($cmd)"
|
||
|
|
let result = (do { nu -c $cmd } | complete)
|
||
|
|
if $result.exit_code != 0 {
|
||
|
|
print $" [$label] FAILED: ($cmd)"
|
||
|
|
if ($result.stderr | is-not-empty) {
|
||
|
|
print $" ($result.stderr | lines | first 5 | str join '\n ')"
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
print $" [$label] OK"
|
||
|
|
}
|
||
|
|
true
|
||
|
|
}
|
||
|
|
|
||
|
|
# Switch to an operational mode — runs pre_activate, shows status, runs post_activate
|
||
|
|
export def "manifest mode" [
|
||
|
|
id: string # Mode id: dev, publish, investigate, ci
|
||
|
|
--dry-run (-n) # Show what would happen without executing
|
||
|
|
]: nothing -> record {
|
||
|
|
let m = (manifest load)
|
||
|
|
let mode = (find-mode $m $id)
|
||
|
|
|
||
|
|
let layers = ($m.layers? | default [])
|
||
|
|
let visible = ($mode.visible_layers? | default [])
|
||
|
|
let hidden = ($layers | where { not ($in.id in $visible) } | each { $in.id })
|
||
|
|
|
||
|
|
print ""
|
||
|
|
print $"── Mode: ($mode.id) ──"
|
||
|
|
if ($mode.description? | is-not-empty) {
|
||
|
|
print $" ($mode.description)"
|
||
|
|
}
|
||
|
|
print ""
|
||
|
|
print $" Audit level: ($mode.audit_level)"
|
||
|
|
print $" Visible layers: ($visible | str join ', ')"
|
||
|
|
if ($hidden | is-not-empty) {
|
||
|
|
print $" Hidden layers: ($hidden | str join ', ')"
|
||
|
|
}
|
||
|
|
|
||
|
|
let pre = ($mode.pre_activate? | default [])
|
||
|
|
let post = ($mode.post_activate? | default [])
|
||
|
|
|
||
|
|
if $dry_run {
|
||
|
|
if ($pre | is-not-empty) {
|
||
|
|
print ""
|
||
|
|
print " Would run pre_activate:"
|
||
|
|
for cmd in $pre { print $" ($cmd)" }
|
||
|
|
}
|
||
|
|
if ($post | is-not-empty) {
|
||
|
|
print ""
|
||
|
|
print " Would run post_activate:"
|
||
|
|
for cmd in $post { print $" ($cmd)" }
|
||
|
|
}
|
||
|
|
print ""
|
||
|
|
return { mode: $id, status: "dry-run", pre_ok: true, post_ok: true }
|
||
|
|
}
|
||
|
|
|
||
|
|
mut pre_ok = true
|
||
|
|
if ($pre | is-not-empty) {
|
||
|
|
print ""
|
||
|
|
print " pre_activate:"
|
||
|
|
$pre_ok = (run-commands $pre "pre")
|
||
|
|
if not $pre_ok {
|
||
|
|
print ""
|
||
|
|
print $" Mode activation ABORTED — pre_activate failed."
|
||
|
|
print $" Fix the issues and retry: ontoref mode ($id)"
|
||
|
|
return { mode: $id, status: "failed", pre_ok: false, post_ok: false }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
mut post_ok = true
|
||
|
|
if ($post | is-not-empty) {
|
||
|
|
print ""
|
||
|
|
print " post_activate:"
|
||
|
|
$post_ok = (run-commands $post "post")
|
||
|
|
if not $post_ok {
|
||
|
|
print ""
|
||
|
|
print $" WARNING: post_activate failed. Mode is active but not fully verified."
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let status = if $pre_ok and $post_ok { "active" } else { "partial" }
|
||
|
|
print ""
|
||
|
|
print $" Mode '($id)' → ($status)"
|
||
|
|
{ mode: $id, status: $status, pre_ok: $pre_ok, post_ok: $post_ok }
|
||
|
|
}
|
||
|
|
|
||
|
|
# List available operational modes
|
||
|
|
export def "manifest mode list" [
|
||
|
|
--fmt: string = "table" # Output format: table | json
|
||
|
|
]: nothing -> table {
|
||
|
|
let m = (manifest load)
|
||
|
|
let modes = ($m.operational_modes? | default [])
|
||
|
|
let default_mode = ($m.default_mode? | default "dev")
|
||
|
|
|
||
|
|
let rows = ($modes | each {|mode|
|
||
|
|
{
|
||
|
|
id: $mode.id,
|
||
|
|
description: ($mode.description? | default ""),
|
||
|
|
audit: ($mode.audit_level? | default "Standard"),
|
||
|
|
layers: ($mode.visible_layers? | default [] | length),
|
||
|
|
pre: ($mode.pre_activate? | default [] | length),
|
||
|
|
post: ($mode.post_activate? | default [] | length),
|
||
|
|
default: ($mode.id == $default_mode),
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
match $fmt {
|
||
|
|
"json" => { $rows | to json }
|
||
|
|
_ => { $rows }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Run a publication workflow — pre_publish, confirmation, post_publish
|
||
|
|
export def "manifest publish" [
|
||
|
|
id: string # Service id: crates-io-public, gitea-private, etc.
|
||
|
|
--dry-run (-n) # Show what would happen without executing
|
||
|
|
--yes (-y) # Skip confirmation prompt
|
||
|
|
]: nothing -> record {
|
||
|
|
let m = (manifest load)
|
||
|
|
let svc = (find-service $m $id)
|
||
|
|
|
||
|
|
print ""
|
||
|
|
print $"── Publish: ($svc.id) ──"
|
||
|
|
print $" Artifact: ($svc.artifact)"
|
||
|
|
print $" Scope: ($svc.scope)"
|
||
|
|
if ($svc.registry_url? | is-not-empty) {
|
||
|
|
print $" Registry: ($svc.registry_url)"
|
||
|
|
}
|
||
|
|
print $" Auth: ($svc.auth_method)"
|
||
|
|
print $" Trigger: ($svc.trigger)"
|
||
|
|
if ($svc.condition? | is-not-empty) {
|
||
|
|
print $" Condition: ($svc.condition)"
|
||
|
|
}
|
||
|
|
|
||
|
|
let pre = ($svc.pre_publish? | default [])
|
||
|
|
let post = ($svc.post_publish? | default [])
|
||
|
|
|
||
|
|
if $dry_run {
|
||
|
|
if ($pre | is-not-empty) {
|
||
|
|
print ""
|
||
|
|
print " Would run pre_publish:"
|
||
|
|
for cmd in $pre { print $" ($cmd)" }
|
||
|
|
}
|
||
|
|
print ""
|
||
|
|
print " Would execute publish action"
|
||
|
|
if ($post | is-not-empty) {
|
||
|
|
print ""
|
||
|
|
print " Would run post_publish:"
|
||
|
|
for cmd in $post { print $" ($cmd)" }
|
||
|
|
}
|
||
|
|
print ""
|
||
|
|
return { service: $id, status: "dry-run" }
|
||
|
|
}
|
||
|
|
|
||
|
|
# Pre-publish checks
|
||
|
|
mut pre_ok = true
|
||
|
|
if ($pre | is-not-empty) {
|
||
|
|
print ""
|
||
|
|
print " pre_publish:"
|
||
|
|
$pre_ok = (run-commands $pre "pre")
|
||
|
|
if not $pre_ok {
|
||
|
|
print ""
|
||
|
|
print $" Publish ABORTED — pre_publish failed."
|
||
|
|
return { service: $id, status: "failed", stage: "pre_publish" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Confirmation gate
|
||
|
|
if not $yes {
|
||
|
|
print ""
|
||
|
|
let answer = (input $" Proceed with publish to ($svc.id)? [y/n] ")
|
||
|
|
if not ($answer | str starts-with "y") {
|
||
|
|
print " Publish cancelled."
|
||
|
|
return { service: $id, status: "cancelled" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Post-publish actions
|
||
|
|
mut post_ok = true
|
||
|
|
if ($post | is-not-empty) {
|
||
|
|
print ""
|
||
|
|
print " post_publish:"
|
||
|
|
$post_ok = (run-commands $post "post")
|
||
|
|
if not $post_ok {
|
||
|
|
print ""
|
||
|
|
print " WARNING: post_publish failed. Publish may have succeeded but follow-up actions incomplete."
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let status = if $pre_ok and $post_ok { "published" } else { "partial" }
|
||
|
|
print ""
|
||
|
|
print $" Service '($id)' → ($status)"
|
||
|
|
{ service: $id, status: $status }
|
||
|
|
}
|
||
|
|
|
||
|
|
# List available publication services
|
||
|
|
export def "manifest publish list" [
|
||
|
|
--fmt: string = "table"
|
||
|
|
]: nothing -> table {
|
||
|
|
let m = (manifest load)
|
||
|
|
let services = ($m.publication_services? | default [])
|
||
|
|
|
||
|
|
let rows = ($services | each {|svc|
|
||
|
|
{
|
||
|
|
id: $svc.id,
|
||
|
|
artifact: ($svc.artifact? | default ""),
|
||
|
|
scope: ($svc.scope? | default ""),
|
||
|
|
auth: ($svc.auth_method? | default "None"),
|
||
|
|
trigger: ($svc.trigger? | default ""),
|
||
|
|
pre: ($svc.pre_publish? | default [] | length),
|
||
|
|
post: ($svc.post_publish? | default [] | length),
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
match $fmt {
|
||
|
|
"json" => { $rows | to json }
|
||
|
|
_ => { $rows }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Show all layers with their visibility status
|
||
|
|
export def "manifest layers" [
|
||
|
|
--mode (-m): string = "" # Show visibility for a specific mode
|
||
|
|
]: nothing -> table {
|
||
|
|
let m = (manifest load)
|
||
|
|
let layers = ($m.layers? | default [])
|
||
|
|
|
||
|
|
if ($mode | is-not-empty) {
|
||
|
|
let op_mode = (find-mode $m $mode)
|
||
|
|
let visible = ($op_mode.visible_layers? | default [])
|
||
|
|
$layers | each {|l|
|
||
|
|
{
|
||
|
|
id: $l.id,
|
||
|
|
committed: $l.committed,
|
||
|
|
visible: ($l.id in $visible),
|
||
|
|
paths: ($l.paths | str join ", "),
|
||
|
|
description: ($l.description? | default ""),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
$layers | each {|l|
|
||
|
|
{
|
||
|
|
id: $l.id,
|
||
|
|
committed: $l.committed,
|
||
|
|
paths: ($l.paths | str join ", "),
|
||
|
|
description: ($l.description? | default ""),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Show consumption modes — who consumes and what they need
|
||
|
|
export def "manifest consumers" [
|
||
|
|
--fmt: string = "table"
|
||
|
|
]: nothing -> table {
|
||
|
|
let m = (manifest load)
|
||
|
|
let consumers = ($m.consumption_modes? | default [])
|
||
|
|
|
||
|
|
let rows = ($consumers | each {|c|
|
||
|
|
{
|
||
|
|
consumer: ($c.consumer? | default ""),
|
||
|
|
needs: ($c.needs? | default [] | str join ", "),
|
||
|
|
audit: ($c.audit_level? | default "Standard"),
|
||
|
|
description: ($c.description? | default ""),
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
match $fmt {
|
||
|
|
"json" => { $rows | to json }
|
||
|
|
_ => { $rows }
|
||
|
|
}
|
||
|
|
}
|