@lamentis/naome 1.3.8 → 1.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +2 -2
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/install_bridge.rs +56 -8
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/harness_health/integrity.rs +41 -23
- package/crates/naome-core/src/harness_health/manifest.rs +97 -0
- package/crates/naome-core/src/harness_health.rs +58 -106
- package/crates/naome-core/src/quality/cache.rs +122 -19
- package/crates/naome-core/src/quality/scanner/analysis.rs +4 -2
- package/crates/naome-core/src/quality/scanner/repo_paths.rs +27 -3
- package/crates/naome-core/src/quality/scanner.rs +5 -2
- package/crates/naome-core/src/workflow/integrity_support.rs +10 -3
- package/crates/naome-core/tests/harness_health.rs +149 -0
- package/crates/naome-core/tests/quality_performance.rs +63 -2
- package/installer/filesystem.js +38 -0
- package/installer/flows.js +6 -1
- package/installer/harness-file-ops.js +36 -8
- package/installer/manifest-state.js +2 -2
- package/installer/native.js +63 -18
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +1 -1
- package/templates/naome-root/.naome/bin/check-harness-health.js +25 -21
- package/templates/naome-root/.naome/bin/check-task-state.js +35 -42
- package/templates/naome-root/.naome/manifest.json +9 -9
package/Cargo.lock
CHANGED
|
@@ -76,7 +76,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|
|
76
76
|
|
|
77
77
|
[[package]]
|
|
78
78
|
name = "naome-cli"
|
|
79
|
-
version = "1.3.
|
|
79
|
+
version = "1.3.9"
|
|
80
80
|
dependencies = [
|
|
81
81
|
"naome-core",
|
|
82
82
|
"serde_json",
|
|
@@ -84,7 +84,7 @@ dependencies = [
|
|
|
84
84
|
|
|
85
85
|
[[package]]
|
|
86
86
|
name = "naome-core"
|
|
87
|
-
version = "1.3.
|
|
87
|
+
version = "1.3.9"
|
|
88
88
|
dependencies = [
|
|
89
89
|
"serde",
|
|
90
90
|
"serde_json",
|
|
@@ -10,8 +10,7 @@ pub fn run_install_bridge(
|
|
|
10
10
|
let package_root = option_value(args, "--package-root")
|
|
11
11
|
.map(PathBuf::from)
|
|
12
12
|
.or_else(|| std::env::var("NAOME_PACKAGE_ROOT").ok().map(PathBuf::from))
|
|
13
|
-
.or_else(resolve_package_root_from_exe)
|
|
14
|
-
.or_else(resolve_package_root_from_cwd);
|
|
13
|
+
.or_else(resolve_package_root_from_exe);
|
|
15
14
|
let installer_js = option_value(args, "--installer-js")
|
|
16
15
|
.map(PathBuf::from)
|
|
17
16
|
.or_else(|| std::env::var("NAOME_INSTALLER_JS").ok().map(PathBuf::from))
|
|
@@ -72,12 +71,61 @@ fn resolve_package_root_from_exe() -> Option<PathBuf> {
|
|
|
72
71
|
None
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
#[cfg(test)]
|
|
75
|
+
mod tests {
|
|
76
|
+
use std::fs;
|
|
77
|
+
use std::sync::{Mutex, OnceLock};
|
|
78
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
79
|
+
|
|
80
|
+
use super::run_install_bridge;
|
|
81
|
+
|
|
82
|
+
fn env_lock() -> &'static Mutex<()> {
|
|
83
|
+
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
|
|
84
|
+
LOCK.get_or_init(|| Mutex::new(()))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#[test]
|
|
88
|
+
fn install_bridge_does_not_discover_installer_from_cwd() {
|
|
89
|
+
let _guard = env_lock().lock().unwrap();
|
|
90
|
+
let original_dir = std::env::current_dir().unwrap();
|
|
91
|
+
let original_package_root = std::env::var_os("NAOME_PACKAGE_ROOT");
|
|
92
|
+
let original_installer_js = std::env::var_os("NAOME_INSTALLER_JS");
|
|
93
|
+
let temp_root = std::env::temp_dir().join(format!(
|
|
94
|
+
"naome-cwd-installer-test-{}",
|
|
95
|
+
SystemTime::now()
|
|
96
|
+
.duration_since(UNIX_EPOCH)
|
|
97
|
+
.unwrap()
|
|
98
|
+
.as_nanos()
|
|
99
|
+
));
|
|
100
|
+
let installer_path = temp_root
|
|
101
|
+
.join("packages")
|
|
102
|
+
.join("naome")
|
|
103
|
+
.join("bin")
|
|
104
|
+
.join("naome-node.js");
|
|
105
|
+
fs::create_dir_all(installer_path.parent().unwrap()).unwrap();
|
|
106
|
+
fs::write(&installer_path, "console.log('untrusted cwd installer');\n").unwrap();
|
|
107
|
+
|
|
108
|
+
std::env::remove_var("NAOME_PACKAGE_ROOT");
|
|
109
|
+
std::env::remove_var("NAOME_INSTALLER_JS");
|
|
110
|
+
std::env::set_current_dir(&temp_root).unwrap();
|
|
111
|
+
|
|
112
|
+
let args = vec!["install".to_string()];
|
|
113
|
+
let error = run_install_bridge("install", &args)
|
|
114
|
+
.expect_err("cwd-local installer must not be treated as trusted");
|
|
115
|
+
assert!(
|
|
116
|
+
error.to_string().contains("needs naome-node.js"),
|
|
117
|
+
"unexpected error: {error}"
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
std::env::set_current_dir(original_dir).unwrap();
|
|
121
|
+
match original_package_root {
|
|
122
|
+
Some(value) => std::env::set_var("NAOME_PACKAGE_ROOT", value),
|
|
123
|
+
None => std::env::remove_var("NAOME_PACKAGE_ROOT"),
|
|
80
124
|
}
|
|
125
|
+
match original_installer_js {
|
|
126
|
+
Some(value) => std::env::set_var("NAOME_INSTALLER_JS", value),
|
|
127
|
+
None => std::env::remove_var("NAOME_INSTALLER_JS"),
|
|
128
|
+
}
|
|
129
|
+
let _ = fs::remove_dir_all(temp_root);
|
|
81
130
|
}
|
|
82
|
-
None
|
|
83
131
|
}
|
|
@@ -29,12 +29,10 @@ pub(crate) fn is_integrity_hash(value: &str) -> bool {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
pub(crate) fn native_integrity_from_naome_command(content: &str) -> Option<String> {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
let value = &rest[..end];
|
|
37
|
-
is_integrity_hash(value).then(|| value.to_string())
|
|
32
|
+
content
|
|
33
|
+
.lines()
|
|
34
|
+
.find_map(native_integrity_assignment_value)
|
|
35
|
+
.map(ToString::to_string)
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
fn machine_sha256(relative_path: &str, content: &[u8]) -> String {
|
|
@@ -43,17 +41,22 @@ fn machine_sha256(relative_path: &str, content: &[u8]) -> String {
|
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
fn normalize_machine_owned_content(relative_path: &str, content: &[u8]) -> Vec<u8> {
|
|
44
|
+
let mut normalized = content.to_vec();
|
|
45
|
+
|
|
46
46
|
if relative_path == HEALTH_CHECKER_RELATIVE_PATH
|
|
47
47
|
|| relative_path == TASK_STATE_CHECKER_RELATIVE_PATH
|
|
48
48
|
{
|
|
49
|
-
|
|
49
|
+
normalized = replace_expected_integrity_block(&normalized);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
if relative_path ==
|
|
53
|
-
|
|
52
|
+
if relative_path == HEALTH_CHECKER_RELATIVE_PATH
|
|
53
|
+
|| relative_path == TASK_STATE_CHECKER_RELATIVE_PATH
|
|
54
|
+
|| relative_path == NAOME_COMMAND_RELATIVE_PATH
|
|
55
|
+
{
|
|
56
|
+
normalized = replace_native_integrity_line(&normalized);
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
normalized
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
fn replace_expected_integrity_block(content: &[u8]) -> Vec<u8> {
|
|
@@ -79,18 +82,33 @@ fn replace_expected_integrity_block(content: &[u8]) -> Vec<u8> {
|
|
|
79
82
|
|
|
80
83
|
fn replace_native_integrity_line(content: &[u8]) -> Vec<u8> {
|
|
81
84
|
let text = String::from_utf8_lossy(content);
|
|
82
|
-
let
|
|
83
|
-
let Some(start) = text.find(prefix) else {
|
|
84
|
-
return content.to_vec();
|
|
85
|
-
};
|
|
86
|
-
let Some(relative_end) = text[start..].find(";\n") else {
|
|
87
|
-
return content.to_vec();
|
|
88
|
-
};
|
|
89
|
-
let end = start + relative_end + ";\n".len();
|
|
90
|
-
|
|
85
|
+
let mut changed = false;
|
|
91
86
|
let mut next = String::with_capacity(text.len());
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
|
|
88
|
+
for segment in text.split_inclusive('\n') {
|
|
89
|
+
let (line, ending) = segment
|
|
90
|
+
.strip_suffix('\n')
|
|
91
|
+
.map(|line| (line, "\n"))
|
|
92
|
+
.unwrap_or((segment, ""));
|
|
93
|
+
if native_integrity_assignment_value(line).is_some() {
|
|
94
|
+
next.push_str("const expectedNativeBinaryIntegrity = \"sha256:generated\";");
|
|
95
|
+
next.push_str(ending);
|
|
96
|
+
changed = true;
|
|
97
|
+
} else {
|
|
98
|
+
next.push_str(segment);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if changed {
|
|
103
|
+
next.into_bytes()
|
|
104
|
+
} else {
|
|
105
|
+
content.to_vec()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fn native_integrity_assignment_value(line: &str) -> Option<&str> {
|
|
110
|
+
let value = line
|
|
111
|
+
.strip_prefix("const expectedNativeBinaryIntegrity = \"")?
|
|
112
|
+
.strip_suffix("\";")?;
|
|
113
|
+
is_integrity_hash(value).then_some(value)
|
|
96
114
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
use std::collections::HashSet;
|
|
2
|
+
|
|
3
|
+
use serde_json::Value;
|
|
4
|
+
|
|
5
|
+
use crate::install_plan::{MACHINE_OWNED_PATHS, PROJECT_OWNED_PATHS};
|
|
6
|
+
|
|
7
|
+
pub(super) fn validate_manifest_shape(manifest: &Value, errors: &mut Vec<String>) {
|
|
8
|
+
let Some(object) = manifest.as_object() else {
|
|
9
|
+
errors.push(".naome/manifest.json must be a JSON object.".to_string());
|
|
10
|
+
return;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
if object.get("name").and_then(Value::as_str) != Some("naome") {
|
|
14
|
+
errors.push(".naome/manifest.json name must be naome.".to_string());
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if !object
|
|
18
|
+
.get("harnessVersion")
|
|
19
|
+
.and_then(Value::as_str)
|
|
20
|
+
.is_some_and(is_version)
|
|
21
|
+
{
|
|
22
|
+
errors.push(".naome/manifest.json harnessVersion must be semver.".to_string());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if !string_array(object.get("machineOwned")).is_some() {
|
|
26
|
+
errors.push(".naome/manifest.json machineOwned must be a string array.".to_string());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if !string_array(object.get("projectOwned")).is_some() {
|
|
30
|
+
errors.push(".naome/manifest.json projectOwned must be a string array.".to_string());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if !object.get("integrity").is_some_and(Value::is_object) {
|
|
34
|
+
errors.push(".naome/manifest.json integrity must be an object.".to_string());
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub(super) fn validate_manifest_ownership(manifest: &Value, errors: &mut Vec<String>) {
|
|
39
|
+
let Some(object) = manifest.as_object() else {
|
|
40
|
+
return;
|
|
41
|
+
};
|
|
42
|
+
let Some(machine_owned) = string_array(object.get("machineOwned")) else {
|
|
43
|
+
return;
|
|
44
|
+
};
|
|
45
|
+
let Some(project_owned) = string_array(object.get("projectOwned")) else {
|
|
46
|
+
return;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
validate_contains_all(
|
|
50
|
+
&machine_owned,
|
|
51
|
+
MACHINE_OWNED_PATHS,
|
|
52
|
+
".naome/manifest.json machineOwned",
|
|
53
|
+
errors,
|
|
54
|
+
);
|
|
55
|
+
validate_contains_all(
|
|
56
|
+
&project_owned,
|
|
57
|
+
PROJECT_OWNED_PATHS,
|
|
58
|
+
".naome/manifest.json projectOwned",
|
|
59
|
+
errors,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pub(super) fn string_array(value: Option<&Value>) -> Option<Vec<String>> {
|
|
64
|
+
value.and_then(Value::as_array).and_then(|entries| {
|
|
65
|
+
entries
|
|
66
|
+
.iter()
|
|
67
|
+
.map(|entry| {
|
|
68
|
+
entry
|
|
69
|
+
.as_str()
|
|
70
|
+
.filter(|text| !text.trim().is_empty())
|
|
71
|
+
.map(ToString::to_string)
|
|
72
|
+
})
|
|
73
|
+
.collect()
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fn validate_contains_all(
|
|
78
|
+
actual_values: &[String],
|
|
79
|
+
expected_values: &[&str],
|
|
80
|
+
field_name: &str,
|
|
81
|
+
errors: &mut Vec<String>,
|
|
82
|
+
) {
|
|
83
|
+
let actual: HashSet<&str> = actual_values.iter().map(String::as_str).collect();
|
|
84
|
+
for expected in expected_values {
|
|
85
|
+
if !actual.contains(expected) {
|
|
86
|
+
errors.push(format!("{field_name} must include {expected}."));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fn is_version(value: &str) -> bool {
|
|
92
|
+
let parts: Vec<&str> = value.split('.').collect();
|
|
93
|
+
parts.len() == 3
|
|
94
|
+
&& parts
|
|
95
|
+
.iter()
|
|
96
|
+
.all(|part| !part.is_empty() && part.chars().all(|ch| ch.is_ascii_digit()))
|
|
97
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
mod integrity;
|
|
2
|
+
mod manifest;
|
|
2
3
|
|
|
3
|
-
use std::collections::
|
|
4
|
+
use std::collections::HashMap;
|
|
4
5
|
use std::fs;
|
|
5
6
|
use std::path::{Component, Path};
|
|
6
7
|
|
|
@@ -11,6 +12,7 @@ use self::integrity::{
|
|
|
11
12
|
is_integrity_hash, native_integrity_from_naome_command, sha256_bytes,
|
|
12
13
|
NAOME_COMMAND_RELATIVE_PATH, NATIVE_BINARY_RELATIVE_PATH,
|
|
13
14
|
};
|
|
15
|
+
use self::manifest::{string_array, validate_manifest_ownership, validate_manifest_shape};
|
|
14
16
|
use crate::install_plan::{MACHINE_OWNED_PATHS, PROJECT_OWNED_PATHS};
|
|
15
17
|
use crate::models::NaomeError;
|
|
16
18
|
|
|
@@ -56,62 +58,6 @@ pub fn validate_harness_health(
|
|
|
56
58
|
Ok(errors)
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
fn validate_manifest_shape(manifest: &Value, errors: &mut Vec<String>) {
|
|
60
|
-
let Some(object) = manifest.as_object() else {
|
|
61
|
-
errors.push(".naome/manifest.json must be a JSON object.".to_string());
|
|
62
|
-
return;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
if object.get("name").and_then(Value::as_str) != Some("naome") {
|
|
66
|
-
errors.push(".naome/manifest.json name must be naome.".to_string());
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if !object
|
|
70
|
-
.get("harnessVersion")
|
|
71
|
-
.and_then(Value::as_str)
|
|
72
|
-
.is_some_and(is_version)
|
|
73
|
-
{
|
|
74
|
-
errors.push(".naome/manifest.json harnessVersion must be semver.".to_string());
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if !string_array(object.get("machineOwned")).is_some() {
|
|
78
|
-
errors.push(".naome/manifest.json machineOwned must be a string array.".to_string());
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if !string_array(object.get("projectOwned")).is_some() {
|
|
82
|
-
errors.push(".naome/manifest.json projectOwned must be a string array.".to_string());
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if !object.get("integrity").is_some_and(Value::is_object) {
|
|
86
|
-
errors.push(".naome/manifest.json integrity must be an object.".to_string());
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
fn validate_manifest_ownership(manifest: &Value, errors: &mut Vec<String>) {
|
|
91
|
-
let Some(object) = manifest.as_object() else {
|
|
92
|
-
return;
|
|
93
|
-
};
|
|
94
|
-
let Some(machine_owned) = string_array(object.get("machineOwned")) else {
|
|
95
|
-
return;
|
|
96
|
-
};
|
|
97
|
-
let Some(project_owned) = string_array(object.get("projectOwned")) else {
|
|
98
|
-
return;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
validate_contains_all(
|
|
102
|
-
&machine_owned,
|
|
103
|
-
MACHINE_OWNED_PATHS,
|
|
104
|
-
".naome/manifest.json machineOwned",
|
|
105
|
-
errors,
|
|
106
|
-
);
|
|
107
|
-
validate_contains_all(
|
|
108
|
-
&project_owned,
|
|
109
|
-
PROJECT_OWNED_PATHS,
|
|
110
|
-
".naome/manifest.json projectOwned",
|
|
111
|
-
errors,
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
61
|
fn validate_manifest_integrity(
|
|
116
62
|
root: &Path,
|
|
117
63
|
manifest: &Value,
|
|
@@ -186,13 +132,41 @@ fn validate_native_decision_binary(
|
|
|
186
132
|
return Ok(());
|
|
187
133
|
};
|
|
188
134
|
|
|
189
|
-
|
|
135
|
+
let wrapper_paths = [
|
|
136
|
+
NAOME_COMMAND_RELATIVE_PATH,
|
|
137
|
+
".naome/bin/check-harness-health.js",
|
|
138
|
+
".naome/bin/check-task-state.js",
|
|
139
|
+
];
|
|
140
|
+
let wrapper_integrity = wrapper_paths
|
|
141
|
+
.iter()
|
|
142
|
+
.map(|relative_path| {
|
|
143
|
+
native_integrity_from_regular_file(root, relative_path)
|
|
144
|
+
.map(|expected| (*relative_path, expected))
|
|
145
|
+
})
|
|
146
|
+
.collect::<Result<Vec<_>, _>>()?;
|
|
147
|
+
let native_is_declared = machine_owned
|
|
148
|
+
.iter()
|
|
149
|
+
.any(|entry| entry == NATIVE_BINARY_RELATIVE_PATH);
|
|
150
|
+
let native_has_integrity = integrity.contains_key(NATIVE_BINARY_RELATIVE_PATH);
|
|
151
|
+
let native_path_present = fs::symlink_metadata(root.join(NATIVE_BINARY_RELATIVE_PATH)).is_ok();
|
|
152
|
+
let wrapper_requires_native = wrapper_integrity
|
|
190
153
|
.iter()
|
|
191
|
-
.any(|
|
|
154
|
+
.any(|(_, expected)| expected.is_some());
|
|
155
|
+
|
|
156
|
+
if !native_is_declared
|
|
157
|
+
&& !native_has_integrity
|
|
158
|
+
&& !wrapper_requires_native
|
|
159
|
+
&& !native_path_present
|
|
192
160
|
{
|
|
193
161
|
return Ok(());
|
|
194
162
|
}
|
|
195
163
|
|
|
164
|
+
if !native_is_declared {
|
|
165
|
+
errors.push(format!(
|
|
166
|
+
".naome/manifest.json machineOwned must include {NATIVE_BINARY_RELATIVE_PATH}."
|
|
167
|
+
));
|
|
168
|
+
}
|
|
169
|
+
|
|
196
170
|
validate_regular_file(root, NATIVE_BINARY_RELATIVE_PATH, errors)?;
|
|
197
171
|
|
|
198
172
|
if !root.join(NATIVE_BINARY_RELATIVE_PATH).exists()
|
|
@@ -222,22 +196,36 @@ fn validate_native_decision_binary(
|
|
|
222
196
|
));
|
|
223
197
|
}
|
|
224
198
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
let command_expected = native_integrity_from_naome_command(&command_content);
|
|
232
|
-
if command_expected.as_deref() != Some(manifest_expected) {
|
|
233
|
-
errors.push(format!(
|
|
234
|
-
"{NAOME_COMMAND_RELATIVE_PATH} native binary integrity does not match .naome/manifest.json."
|
|
235
|
-
));
|
|
199
|
+
for (relative_path, expected) in wrapper_integrity {
|
|
200
|
+
if expected.as_deref() != Some(manifest_expected) {
|
|
201
|
+
errors.push(format!(
|
|
202
|
+
"{relative_path} native binary integrity does not match .naome/manifest.json."
|
|
203
|
+
));
|
|
204
|
+
}
|
|
236
205
|
}
|
|
237
206
|
|
|
238
207
|
Ok(())
|
|
239
208
|
}
|
|
240
209
|
|
|
210
|
+
fn native_integrity_from_regular_file(
|
|
211
|
+
root: &Path,
|
|
212
|
+
relative_path: &str,
|
|
213
|
+
) -> Result<Option<String>, NaomeError> {
|
|
214
|
+
let file_path = root.join(relative_path);
|
|
215
|
+
if has_symlink_in_path(root, relative_path)? {
|
|
216
|
+
return Ok(None);
|
|
217
|
+
}
|
|
218
|
+
let Ok(metadata) = fs::symlink_metadata(&file_path) else {
|
|
219
|
+
return Ok(None);
|
|
220
|
+
};
|
|
221
|
+
if !metadata.is_file() {
|
|
222
|
+
return Ok(None);
|
|
223
|
+
}
|
|
224
|
+
Ok(native_integrity_from_naome_command(&fs::read_to_string(
|
|
225
|
+
file_path,
|
|
226
|
+
)?))
|
|
227
|
+
}
|
|
228
|
+
|
|
241
229
|
fn validate_naome_ignore(root: &Path, errors: &mut Vec<String>) -> Result<(), NaomeError> {
|
|
242
230
|
let relative_path = ".naomeignore";
|
|
243
231
|
if !root.join(relative_path).exists() || has_symlink_in_path(root, relative_path)? {
|
|
@@ -342,20 +330,6 @@ fn read_json(
|
|
|
342
330
|
}
|
|
343
331
|
}
|
|
344
332
|
|
|
345
|
-
fn validate_contains_all(
|
|
346
|
-
actual_values: &[String],
|
|
347
|
-
expected_values: &[&str],
|
|
348
|
-
field_name: &str,
|
|
349
|
-
errors: &mut Vec<String>,
|
|
350
|
-
) {
|
|
351
|
-
let actual: HashSet<&str> = actual_values.iter().map(String::as_str).collect();
|
|
352
|
-
for expected in expected_values {
|
|
353
|
-
if !actual.contains(expected) {
|
|
354
|
-
errors.push(format!("{field_name} must include {expected}."));
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
333
|
fn validate_regular_file(
|
|
360
334
|
root: &Path,
|
|
361
335
|
relative_path: &str,
|
|
@@ -423,25 +397,3 @@ fn has_symlink_in_path(root: &Path, relative_path: &str) -> Result<bool, NaomeEr
|
|
|
423
397
|
|
|
424
398
|
Ok(false)
|
|
425
399
|
}
|
|
426
|
-
|
|
427
|
-
fn string_array(value: Option<&Value>) -> Option<Vec<String>> {
|
|
428
|
-
value.and_then(Value::as_array).and_then(|entries| {
|
|
429
|
-
entries
|
|
430
|
-
.iter()
|
|
431
|
-
.map(|entry| {
|
|
432
|
-
entry
|
|
433
|
-
.as_str()
|
|
434
|
-
.filter(|text| !text.trim().is_empty())
|
|
435
|
-
.map(ToString::to_string)
|
|
436
|
-
})
|
|
437
|
-
.collect()
|
|
438
|
-
})
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
fn is_version(value: &str) -> bool {
|
|
442
|
-
let parts: Vec<&str> = value.split('.').collect();
|
|
443
|
-
parts.len() == 3
|
|
444
|
-
&& parts
|
|
445
|
-
.iter()
|
|
446
|
-
.all(|part| !part.is_empty() && part.chars().all(|ch| ch.is_ascii_digit()))
|
|
447
|
-
}
|