Merge pull request #49 from DeterminateSystems/flake-lock-lib

Create a separate crate for the flake.lock parsing logic
This commit is contained in:
Luc Perkins 2023-07-10 08:37:21 -07:00 committed by GitHub
commit e915cfa5c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 376 additions and 268 deletions

View File

@ -11,3 +11,6 @@ insert_final_newline = true
[*.rs]
indent_size = 4
[*.hbs]
insert_final_newline = false

42
Cargo.lock generated
View File

@ -210,6 +210,7 @@ dependencies = [
"clap",
"handlebars",
"is_ci",
"parse-flake-lock",
"reqwest",
"serde",
"serde_json",
@ -574,6 +575,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "parse-flake-lock"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "percent-encoding"
version = "2.2.0"
@ -638,18 +648,18 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.59"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.28"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
dependencies = [
"proc-macro2",
]
@ -815,18 +825,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.163"
version = "1.0.170"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2"
checksum = "a56657f512baabca8f840542f9ca8152aecf182c473c26e46e58d6aab4f6e439"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.163"
version = "1.0.170"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
checksum = "77d477848e6b23adba0db397777d5aad864555bc17fd9c89abb3b8009788b7b8"
dependencies = [
"proc-macro2",
"quote",
@ -835,9 +845,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.96"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
dependencies = [
"itoa",
"ryu",
@ -894,9 +904,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "syn"
version = "2.0.18"
version = "2.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2"
dependencies = [
"proc-macro2",
"quote",
@ -915,18 +925,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.40"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.40"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
dependencies = [
"proc-macro2",
"quote",

View File

@ -3,11 +3,18 @@ name = "flake-checker"
version = "0.1.11"
edition = "2021"
[workspace]
members = [
".",
"parse-flake-lock"
]
[dependencies]
chrono = { version = "0.4.25", default-features = false, features = [ "clock" ] }
clap = { version = "4.3.0", default-features = false, features = [ "derive", "env", "std", "wrap_help" ] }
handlebars = { version = "4.3.7", default-features = false }
is_ci = "1.1.1"
parse-flake-lock = { path = "./parse-flake-lock" }
reqwest = { version = "0.11.18", default-features = false, features = [ "blocking", "rustls-tls-native-roots" ]}
serde = { version = "1.0.163", features = [ "derive" ] }
serde_json = { version = "1.0.96", default-features = false }

View File

@ -64,6 +64,38 @@ To disable diagnostic reporting, set the diagnostics URL to an empty string by p
You can read the full privacy policy for [Determinate Systems][detsys], the creators of this tool and the [Determinate Nix Installer][installer], [here][privacy].
## Rust library
The Nix Flake Checker is written in [Rust].
This repo exposes a [`parse-flake-lock`](./parse-flake-lock) crate that you can use to parse [`flake.lock` files][lockfile] in your own Rust projects.
To add that dependency:
```toml
[dependencies]
parse-flake-lock = { git = "https://github.com/DeterminateSystems/flake-checker", branch = "main" }
```
Here's an example usage:
```rust
use std::path::Path;
use parse_flake_lock::{FlakeLock, FlakeLockParseError};
fn main() -> Result<(), FlakeLockParseError> {
let flake_lock = FlakeLock::new(Path::new("flake.lock"))?;
println!("flake.lock info:");
println!("version: {version}", version=flake_lock.version);
println!("root node: {root:?}", root=flake_lock.root);
println!("all nodes: {nodes:?}", nodes=flake_lock.nodes);
Ok(())
}
```
The `parse-flake-lock` crate doesn't yet exhaustively parse all input node types, instead using a "fallthrough" mechanism that parses input types that don't yet have explicit struct definitions to a [`serde_json::value::Value`][val].
If you'd like to help make the parser more exhaustive, [pull requests][prs] are quite welcome.
[action]: https://github.com/DeterminateSystems/flake-checker-action
[detsys]: https://determinate.systems
[flakes]: https://zero-to-nix.com/concepts/flakes
@ -74,4 +106,7 @@ You can read the full privacy policy for [Determinate Systems][detsys], the crea
[nixos-org]: https://github.com/NixOS
[nixpkgs]: https://github.com/NixOS/nixpkgs
[privacy]: https://determinate.systems/privacy
[prs]: /pulls
[rust]: https://rust-lang.org
[telemetry]: https://github.com/DeterminateSystems/nix-flake-checker/blob/main/src/telemetry.rs#L29-L43
[val]: https://docs.rs/serde_json/latest/serde_json/value/enum.Value.html

View File

@ -0,0 +1,9 @@
[package]
name = "parse-flake-lock"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0.170", features = ["derive"] }
serde_json = "1.0.100"
thiserror = "1.0.43"

254
parse-flake-lock/src/lib.rs Normal file
View File

@ -0,0 +1,254 @@
#![allow(dead_code)]
use std::collections::{HashMap, VecDeque};
use std::fmt;
use std::fs::read_to_string;
use std::path::Path;
use serde::de::{self, MapAccess, Visitor};
use serde::{Deserialize, Deserializer};
/// A custom error type for the `parse-flake-lock` crate.
#[derive(Debug, thiserror::Error)]
pub enum FlakeLockParseError {
/// The `flake.lock` can be parsed as JSON but is nonetheless invalid.
#[error("invalid flake.lock file: {0}")]
Invalid(String),
/// The `flake.lock` file couldn't be found.
#[error("couldn't find the flake.lock file: {0}")]
NotFound(#[from] std::io::Error),
/// The specified `flake.lock` file couldn't be parsed as JSON.
#[error("couldn't parse the flake.lock file as json: {0}")]
Json(#[from] serde_json::Error),
}
/// A Rust representation of a Nix [`flake.lock`
/// file](https://zero-to-nix.com/concepts/flakes#lockfile).
#[derive(Clone, Debug)]
pub struct FlakeLock {
/// The `nodes` field of the `flake.lock`, representing all input [Node]s for the flake.
pub nodes: HashMap<String, Node>,
/// The `root` of the `flake.lock` with all input references resolved into the corresponding
/// [Node]s represented by the `nodes` field.
pub root: HashMap<String, Node>,
/// The version of the `flake.lock` (incremented whenever the `flake.nix` dependencies are
/// updated).
pub version: usize,
}
/// A custom [Deserializer] for `flake.lock` files, which are standard JSON but require some special
/// logic to create a meaningful Rust representation.
impl<'de> Deserialize<'de> for FlakeLock {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
enum Field {
Nodes,
Root,
Version,
}
struct FlakeLockVisitor;
impl<'de> Visitor<'de> for FlakeLockVisitor {
type Value = FlakeLock;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct FlakeLock")
}
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
where
V: MapAccess<'de>,
{
let mut nodes = None;
let mut root = None;
let mut version = None;
while let Some(key) = map.next_key()? {
match key {
Field::Nodes => {
if nodes.is_some() {
return Err(de::Error::duplicate_field("nodes"));
}
nodes = Some(map.next_value()?);
}
Field::Root => {
if root.is_some() {
return Err(de::Error::duplicate_field("root"));
}
root = Some(map.next_value()?);
}
Field::Version => {
if version.is_some() {
return Err(de::Error::duplicate_field("version"));
}
version = Some(map.next_value()?);
}
}
}
let nodes: HashMap<String, Node> =
nodes.ok_or_else(|| de::Error::missing_field("nodes"))?;
let root: String = root.ok_or_else(|| de::Error::missing_field("root"))?;
let version: usize = version.ok_or_else(|| de::Error::missing_field("version"))?;
let mut root_nodes = HashMap::new();
let root_node = &nodes[&root];
let Node::Root(root_node) = root_node else {
return Err(de::Error::custom(format!("root node was not a Root node, but was a {} node", root_node.variant())));
};
for (root_name, root_input) in root_node.inputs.iter() {
let inputs: VecDeque<String> = match root_input.clone() {
Input::String(s) => [s].into(),
Input::List(keys) => keys.into(),
};
let real_node = chase_input_node(&nodes, inputs).map_err(|e| {
de::Error::custom(format!("failed to chase input {}: {:?}", root_name, e))
})?;
root_nodes.insert(root_name.clone(), real_node.clone());
}
Ok(FlakeLock {
nodes,
root: root_nodes,
version,
})
}
}
deserializer.deserialize_any(FlakeLockVisitor)
}
}
fn chase_input_node(
nodes: &HashMap<String, Node>,
mut inputs: VecDeque<String>,
) -> Result<&Node, FlakeLockParseError> {
let Some(next_input) = inputs.pop_front() else {
unreachable!("there should always be at least one input");
};
let mut node = &nodes[&next_input];
for input in inputs {
let maybe_node_inputs = match node {
Node::Repo(node) => node.inputs.to_owned(),
Node::Fallthrough(node) => match node.get("inputs") {
Some(node_inputs) => serde_json::from_value(node_inputs.clone())
.map_err(FlakeLockParseError::Json)?,
None => None,
},
Node::Root(_) => None,
};
let node_inputs = match maybe_node_inputs {
Some(node_inputs) => node_inputs,
None => {
return Err(FlakeLockParseError::Invalid(format!(
"lock node should have had some inputs but had none:\n{:?}",
node
)));
}
};
let next_inputs = &node_inputs[&input];
node = match next_inputs {
Input::String(s) => &nodes[s],
Input::List(inputs) => chase_input_node(nodes, inputs.to_owned().into())?,
};
}
Ok(node)
}
impl FlakeLock {
/// Instantiate a new [FlakeLock] from the provided [Path].
pub fn new(path: &Path) -> Result<Self, FlakeLockParseError> {
let flake_lock_file = read_to_string(path)?;
let flake_lock: FlakeLock = serde_json::from_str(&flake_lock_file)?;
Ok(flake_lock)
}
}
/// A flake input [node]. This enum represents two concrete node types, [RepoNode] and [RootNode],
/// and uses the `Fallthrough` variant to capture node types that don't have explicitly defined
/// structs in this library, representing them as raw [Value][serde_json::value::Value]s.
///
/// [node]: https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-flake.html#lock-files
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub enum Node {
/// A [RepoNode] flake input for a [Git](https://git-scm.com) repository (or another version
/// control system).
Repo(Box<RepoNode>),
/// A [RootNode] specifying an [Input] map.
Root(RootNode),
/// A "catch-all" variant for node types that don't (yet) have explicit struct definitions in
/// this crate.
Fallthrough(serde_json::value::Value), // Covers all other node types
}
impl Node {
fn variant(&self) -> &'static str {
match self {
Node::Root(_) => "Root",
Node::Repo(_) => "Repo",
Node::Fallthrough(_) => "Fallthrough", // Covers all other node types
}
}
}
/// An enum type representing node input references.
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub enum Input {
String(String),
List(Vec<String>),
}
/// A flake [Node] representing a raw mapping of strings to [Input]s.
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RootNode {
/// A mapping of the flake's input [Node]s.
pub inputs: HashMap<String, Input>,
}
/// A [Node] representing a [Git](https://git-scm.com) repository (or another version control
/// system).
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RepoNode {
/// The node's inputs.
pub inputs: Option<HashMap<String, Input>>,
/// The "locked" attributes of the input (set by Nix).
pub locked: RepoLocked,
/// The "original" (user-supplied) attributes of the input.
pub original: RepoOriginal,
}
#[derive(Clone, Debug, Deserialize)]
pub struct RepoLocked {
#[serde(alias = "lastModified")]
pub last_modified: i64,
#[serde(alias = "narHash")]
pub nar_hash: String,
pub owner: String,
pub repo: String,
pub rev: String,
#[serde(alias = "type")]
pub node_type: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct RepoOriginal {
pub owner: String,
pub repo: String,
#[serde(alias = "type")]
pub node_type: String,
#[serde(alias = "ref")]
pub git_ref: Option<String>,
}

View File

@ -2,6 +2,8 @@
pub enum FlakeCheckerError {
#[error("env var error: {0}")]
EnvVar(#[from] std::env::VarError),
#[error("couldn't parse flake.lock: {0}")]
FlakeLock(#[from] parse_flake_lock::FlakeLockParseError),
#[error("couldn't access flake.lock: {0}")]
Io(#[from] std::io::Error),
#[error("couldn't parse flake.lock: {0}")]

View File

@ -1,15 +1,12 @@
#![allow(dead_code)]
use std::collections::{HashMap, VecDeque};
use std::fmt;
use std::fs::read_to_string;
use std::path::Path;
use std::collections::HashMap;
use crate::issue::{Disallowed, Issue, IssueKind, NonUpstream, Outdated};
use crate::FlakeCheckerError;
use chrono::{Duration, Utc};
use serde::de::{self, Deserializer, MapAccess, Visitor};
use serde::Deserialize;
use parse_flake_lock::{FlakeLock, Node};
// Update this when necessary by running the get-allowed-refs.sh script to fetch
// the current values from monitoring.nixos.org
@ -46,13 +43,44 @@ impl Default for FlakeCheckConfig {
}
}
fn nixpkgs_deps(
flake_lock: &FlakeLock,
keys: Vec<String>,
) -> Result<HashMap<String, Node>, FlakeCheckerError> {
let mut deps: HashMap<String, Node> = HashMap::new();
for (key, node) in flake_lock.root.clone() {
if let Node::Repo(_) = node {
if keys.contains(&key) {
deps.insert(key, node);
}
}
}
let missing: Vec<String> = keys
.iter()
.filter(|k| !deps.contains_key(*k))
.map(String::from)
.collect();
if !missing.is_empty() {
let error_msg = format!(
"no nixpkgs dependency found for specified {}: {}",
if missing.len() > 1 { "keys" } else { "key" },
missing.join(", ")
);
return Err(FlakeCheckerError::Invalid(error_msg));
}
Ok(deps)
}
pub(crate) fn check_flake_lock(
flake_lock: &FlakeLock,
config: &FlakeCheckConfig,
) -> Result<Vec<Issue>, FlakeCheckerError> {
let mut issues = vec![];
let deps = flake_lock.nixpkgs_deps(config.nixpkgs_keys.clone())?;
let deps = nixpkgs_deps(flake_lock, config.nixpkgs_keys.clone())?;
for (name, dep) in deps {
if let Node::Repo(repo) = dep {
@ -99,247 +127,6 @@ pub(crate) fn check_flake_lock(
Ok(issues)
}
#[derive(Clone, Debug)]
pub(crate) struct FlakeLock {
nodes: HashMap<String, Node>,
root: HashMap<String, Node>,
version: usize,
}
impl<'de> Deserialize<'de> for FlakeLock {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
enum Field {
Nodes,
Root,
Version,
}
struct FlakeLockVisitor;
impl<'de> Visitor<'de> for FlakeLockVisitor {
type Value = FlakeLock;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct FlakeLock")
}
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
where
V: MapAccess<'de>,
{
let mut nodes = None;
let mut root = None;
let mut version = None;
while let Some(key) = map.next_key()? {
match key {
Field::Nodes => {
if nodes.is_some() {
return Err(de::Error::duplicate_field("nodes"));
}
nodes = Some(map.next_value()?);
}
Field::Root => {
if root.is_some() {
return Err(de::Error::duplicate_field("root"));
}
root = Some(map.next_value()?);
}
Field::Version => {
if version.is_some() {
return Err(de::Error::duplicate_field("version"));
}
version = Some(map.next_value()?);
}
}
}
let nodes: HashMap<String, Node> =
nodes.ok_or_else(|| de::Error::missing_field("nodes"))?;
let root: String = root.ok_or_else(|| de::Error::missing_field("root"))?;
let version: usize = version.ok_or_else(|| de::Error::missing_field("version"))?;
let mut root_nodes = HashMap::new();
let root_node = &nodes[&root];
let Node::Root(root_node) = root_node else {
return Err(de::Error::custom(format!("root node was not a Root node, but was a {} node", root_node.variant())));
};
for (root_name, root_input) in root_node.inputs.iter() {
let inputs: VecDeque<String> = match root_input.clone() {
Input::String(s) => [s].into(),
Input::List(keys) => keys.into(),
};
let real_node = chase_input_node(&nodes, inputs).map_err(|e| {
de::Error::custom(format!("failed to chase input {}: {:?}", root_name, e))
})?;
root_nodes.insert(root_name.clone(), real_node.clone());
}
Ok(FlakeLock {
nodes,
root: root_nodes,
version,
})
}
}
deserializer.deserialize_any(FlakeLockVisitor)
}
}
fn chase_input_node(
nodes: &HashMap<String, Node>,
mut inputs: VecDeque<String>,
) -> Result<&Node, FlakeCheckerError> {
let Some(next_input) = inputs.pop_front() else {
unreachable!("there should always be at least one input");
};
let mut node = &nodes[&next_input];
for input in inputs {
let maybe_node_inputs = match node {
Node::Repo(node) => node.inputs.to_owned(),
Node::Fallthrough(node) => match node.get("inputs") {
Some(node_inputs) => {
serde_json::from_value(node_inputs.clone()).map_err(FlakeCheckerError::Json)?
}
None => None,
},
Node::Root(_) => None,
};
let node_inputs = match maybe_node_inputs {
Some(node_inputs) => node_inputs,
None => {
return Err(FlakeCheckerError::Invalid(format!(
"lock node should have had some inputs but had none:\n{:?}",
node
)));
}
};
let next_inputs = &node_inputs[&input];
node = match next_inputs {
Input::String(s) => &nodes[s],
Input::List(inputs) => chase_input_node(nodes, inputs.to_owned().into())?,
};
}
Ok(node)
}
impl FlakeLock {
pub(crate) fn new(path: &Path) -> Result<Self, FlakeCheckerError> {
let flake_lock_file = read_to_string(path)?;
let flake_lock: FlakeLock = serde_json::from_str(&flake_lock_file)?;
Ok(flake_lock)
}
fn nixpkgs_deps(&self, keys: Vec<String>) -> Result<HashMap<String, Node>, FlakeCheckerError> {
let mut deps: HashMap<String, Node> = HashMap::new();
for (key, node) in self.root.clone() {
if let Node::Repo(_) = node {
if keys.contains(&key) {
deps.insert(key, node);
}
}
}
let missing: Vec<String> = keys
.iter()
.filter(|k| !deps.contains_key(*k))
.map(String::from)
.collect();
if !missing.is_empty() {
let error_msg = format!(
"no nixpkgs dependency found for specified {}: {}",
if missing.len() > 1 { "keys" } else { "key" },
missing.join(", ")
);
return Err(FlakeCheckerError::Invalid(error_msg));
}
Ok(deps)
}
}
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub(crate) enum Node {
Repo(Box<RepoNode>),
Root(RootNode),
Fallthrough(serde_json::value::Value), // Covers all other node types
}
impl Node {
fn variant(&self) -> &'static str {
match self {
Node::Root(_) => "Root",
Node::Repo(_) => "Repo",
Node::Fallthrough(_) => "Fallthrough", // Covers all other node types
}
}
fn is_nixpkgs(&self) -> bool {
match self {
Self::Repo(repo) => {
repo.locked.node_type == "github" && repo.original.repo == "nixpkgs"
}
_ => false,
}
}
}
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub(crate) enum Input {
String(String),
List(Vec<String>),
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct RootNode {
pub(crate) inputs: HashMap<String, Input>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct RepoNode {
pub(crate) inputs: Option<HashMap<String, Input>>,
pub(crate) locked: RepoLocked,
pub(crate) original: RepoOriginal,
}
#[derive(Clone, Debug, Deserialize)]
pub(crate) struct RepoLocked {
#[serde(alias = "lastModified")]
pub(crate) last_modified: i64,
#[serde(alias = "narHash")]
pub(crate) nar_hash: String,
pub(crate) owner: String,
pub(crate) repo: String,
pub(crate) rev: String,
#[serde(alias = "type")]
pub(crate) node_type: String,
}
#[derive(Clone, Debug, Deserialize)]
pub(crate) struct RepoOriginal {
pub(crate) owner: String,
pub(crate) repo: String,
#[serde(alias = "type")]
pub(crate) node_type: String,
#[serde(alias = "ref")]
pub(crate) git_ref: Option<String>,
}
#[cfg(test)]
mod test {
use std::path::PathBuf;

View File

@ -5,13 +5,14 @@ mod summary;
mod telemetry;
use error::FlakeCheckerError;
use flake::{check_flake_lock, FlakeCheckConfig, FlakeLock};
use flake::{check_flake_lock, FlakeCheckConfig};
use summary::Summary;
use std::path::PathBuf;
use std::process::ExitCode;
use clap::Parser;
use parse_flake_lock::FlakeLock;
/// A flake.lock checker for Nix projects.
#[derive(Parser)]
@ -19,7 +20,7 @@ use clap::Parser;
struct Cli {
/// Don't send aggregate sums of each issue type.
///
/// See: https://github.com/determinateSystems/flake-checker.
/// See <https://github.com/determinateSystems/flake-checker>.
#[arg(long, env = "NIX_FLAKE_CHECKER_NO_TELEMETRY", default_value_t = false)]
no_telemetry: bool,

View File

@ -110,4 +110,4 @@ While <a href="https://github.com/NixOS/nixpkgs">upstream Nixpkgs</a> isn't bull
{{/if}}
{{/if}}
<p>Feedback? Let us know at <a href="https://github.com/DeterminateSystems/flake-checker">DeterminateSystems/flake-checker</a>.</p>
<p>Feedback? Let us know at <a href="https://github.com/DeterminateSystems/flake-checker">DeterminateSystems/flake-checker</a>.</p>

View File

@ -83,4 +83,4 @@ software is!) it has a wide range of security measures in place, most notably
continuous integration testing with Hydra, that mitigate a great deal of supply
chain risk.
{{/if}}
{{/if}}
{{/if}}