analizer/node-sources.nu

#!/usr/bin/env nu
# Pin and assemble the source repositories used by the default Cyber node.

def checked [command: string, args: list<string>] {
    let result = (run-external $command ...$args | complete)
    if $result.exit_code != 0 {
        error make {msg: $"($command) failed: ($result.stderr | str trim)"}
    }
    $result.stdout | str trim
}

def metadata [root: string] {
    checked cargo [metadata --locked --format-version "1" --manifest-path ($root | path join Cargo.toml)] | from json
}

def packages [root: string] {
    let parent = ($root | path dirname)
    metadata $root | get packages | where source == null | each {|p|
        {name: $p.name, version: $p.version, manifest: ($p.manifest_path | path relative-to $parent)}
    } | sort-by manifest name
}

def clean [root: string] {
    let top = (checked git [-C $root rev-parse --show-toplevel] | path expand)
    if $top != ($root | path expand) {
        error make {msg: $"expected a repository at ($root)"}
    }
    if (checked git [-C $root status --porcelain --untracked-files=normal] | is-not-empty) {
        error make {msg: $"uncommitted source in ($root); use an isolated committed checkout"}
    }
}

def read-lock [root: string] {
    let lock = (open ($root | path join sources.lock.json))
    if $lock.schema != "cyber/node-sources/1" or $lock.profile != "default-and-tests" {
        error make {msg: "unsupported node source lock"}
    }
    let names = ($lock.repositories | get directory)
    if ($names | uniq | length) != ($names | length) {
        error make {msg: "duplicate source directory"}
    }
    for package in $lock.packages {
        if $package.manifest !~ '^[a-z][a-z0-9_-]*/([A-Za-z0-9_-]+/)*Cargo\.toml$' {
            error make {msg: "invalid local package manifest"}
        }
    }
    let expected = ($lock.packages | get manifest | each {|p| $p | path split | first }
        | where $it != "cyber" | uniq | sort)
    if ($names | sort) != $expected {
        error make {msg: "source repositories do not cover the local package set"}
    }
    for repo in $lock.repositories {
        if $repo.directory !~ '^[a-z][a-z0-9_-]*$' or $repo.directory == "cyber" {
            error make {msg: "invalid source directory"}
        }
        if $repo.revision !~ '^[0-9a-f]{40}$' or $repo.url !~ '^https://github\.com/cyberia-to/[A-Za-z0-9_.-]+\.git$' {
            error make {msg: $"invalid source pin for ($repo.directory)"}
        }
    }
    $lock
}

def verify [root: string] {
    let lock = (read-lock $root)
    clean $root
    if (open --raw ($root | path join Cargo.lock) | hash sha256) != $lock.cargo_lock_sha256 {
        error make {msg: "Cargo.lock differs from the source lock"}
    }
    let parent = ($root | path dirname)
    for repo in $lock.repositories {
        let dir = ($parent | path join $repo.directory)
        clean $dir
        let actual = (checked git [-C $dir rev-parse HEAD])
        if $actual != $repo.revision {
            error make {msg: $"source revision mismatch: ($repo.directory)"}
        }
    }
    if (packages $root) != $lock.packages {
        error make {msg: "resolved local packages differ from the source lock"}
    }
    print $"verified ($lock.repositories | length) dependency repositories and ($lock.packages | length) local packages"
}

# Capture records committed revisions. Checkout + check establishes that those
# revisions actually provide the resolved packages; capture alone is no gate.
def capture [root: string] {
    let local = (packages $root)
    let parent = ($root | path dirname)
    let names = ($local | get manifest | each {|p| $p | path split | first } | uniq | sort)
    let repositories = ($names | where $it != "cyber" | each {|name|
        let dir = ($parent | path join $name)
        {directory: $name,
         url: (checked git [-C $dir remote get-url origin]),
         revision: (checked git [-C $dir rev-parse HEAD])}
    })
    {schema: "cyber/node-sources/1", profile: "default-and-tests",
     cargo_lock_sha256: (open --raw ($root | path join Cargo.lock) | hash sha256),
     repositories: $repositories, packages: $local}
        | to json | save --force ($root | path join sources.lock.json)
    print "captured committed revisions; validate them with checkout and check"
}

# Inventory only verified inputs. Vendored third-party packages remain explicit.
def inventory [root: string] {
    verify $root
    let lock = (read-lock $root)
    let revision = (checked git [-C $root rev-parse HEAD])
    let product = ($lock.packages | where name == true-cyber | first)
    let components = ($lock.repositories | each {|repo|
        let local = ($lock.packages | where {|p| ($p.manifest | path split | first) == $repo.directory })
        $repo | insert packages ($local | where {|p| not ($p.manifest | str contains "/vendor/") })
            | insert vendored ($local | where {|p| $p.manifest | str contains "/vendor/" })
    })
    let data = {
        product: ($product | insert revision $revision | insert url "https://github.com/cyberia-to/cyber.git"),
        profile: $lock.profile,
        source_lock_sha256: (open --raw ($root | path join sources.lock.json) | hash sha256),
        cargo_lock_sha256: $lock.cargo_lock_sha256,
        components: $components
    }
    let rows = ($components | each {|c|
        let url = ($c.url | str replace --regex '\.git$' '')
        let packages = ($c.packages | each {|p| $"`($p.name)@($p.version)`" } | str join ", ")
        $"| [($c.directory)]\(($url)\) | ($packages) | [($c.revision | str substring 0..11)]\(($url)/commit/($c.revision)\) |"
    })
    let vendors = ($components | each {|c| $c.vendored | each {|p|
        $"- `($p.name)@($p.version)` in `($p.manifest)`, carried by `($c.directory)@($c.revision)`."
    }} | flatten)
    let notes = ([
        "## soft3 dependency inventory"
        ""
        $"Cyber `($product.version)`, source [($revision)]\(https://github.com/cyberia-to/cyber/commit/($revision)\)."
        ""
        $"Profile: `($lock.profile)` โ€” the source closure used to build and test this node."
        "Component presence records build inputs; runtime capability scope is described in the node product specification."
        ""
        "| component | packages | exact source |"
        "|---|---|---|"
    ] | append $rows | append [
        ""
        "### vendored third-party packages"
        ""
    ] | append $vendors | append [
        ""
        "Full commit hashes, manifest paths and lock checksums are included in `soft3-dependencies.json`."
        ""
    ] | str join (char nl))
    let dist = ($root | path join dist)
    mkdir $dist
    $data | to json | save --force ($dist | path join soft3-dependencies.json)
    $notes | save --force ($dist | path join soft3-dependencies.md)
    print $"inventoried ($components | length) soft3 repositories"
}

def checkout [root: string, destination: string, mirror: string] {
    let lock = (read-lock $root)
    clean $root
    let destination = ($destination | path expand)
    if ($destination | path exists) {
        error make {msg: "destination must be new; an incomplete checkout is retained for inspection"}
    }
    let product = {directory: "cyber", url: (checked git [-C $root remote get-url origin]),
                   revision: (checked git [-C $root rev-parse HEAD])}
    mkdir $destination
    for repo in ($lock.repositories | append $product) {
        let dir = ($destination | path join $repo.directory)
        checked git [init --quiet $dir] | ignore
        checked git [-C $dir remote add origin $repo.url] | ignore
        let fetch_source = if ($mirror | is-empty) { $repo.url } else { $mirror | path expand | path join $repo.directory }
        checked git [-C $dir fetch --quiet --depth "1" $fetch_source $repo.revision] | ignore
        checked git [-C $dir checkout --quiet --detach FETCH_HEAD] | ignore
        print $"checked out ($repo.directory) ($repo.revision | str substring 0..7)"
    }
    verify ($destination | path join cyber)
}

def main [
    graph_path: string
    action: string = "check"
    --destination: string = ""
    --mirror-root: string = ""
] {
    let root = ($graph_path | path expand)
    match $action {
        "capture" => { capture $root }
        "inventory" => { inventory $root }
        "check" => { verify $root }
        "checkout" => {
            if ($destination | is-empty) { error make {msg: "checkout requires --destination"} }
            checkout $root $destination $mirror_root
        }
        _ => { error make {msg: "expected capture, check, checkout or inventory"} }
    }
}

Graph