@repokit/core 3.0.1 → 3.0.3

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.
Files changed (44) hide show
  1. package/Cargo.lock +1056 -2
  2. package/Cargo.toml +4 -1
  3. package/dist/CommandParser.mjs +3 -3
  4. package/externals/CommandParser.ts +3 -3
  5. package/installation/install.sh +1 -1
  6. package/internals/configuration/configuration.rs +3 -2
  7. package/internals/configuration/mod.rs +2 -0
  8. package/internals/configuration/recovery.rs +42 -0
  9. package/internals/{internal_commands → configuration}/typescript_command.rs +20 -22
  10. package/internals/executables/internal_executable_definition.rs +1 -1
  11. package/internals/executables/mod.rs +1 -2
  12. package/internals/executor/executor.rs +13 -0
  13. package/internals/internal_commands/help.rs +5 -2
  14. package/internals/internal_commands/internal_registry.rs +6 -4
  15. package/internals/internal_commands/list_commands.rs +2 -2
  16. package/internals/internal_commands/list_owners.rs +2 -2
  17. package/internals/internal_commands/list_themes.rs +1 -1
  18. package/internals/internal_commands/list_version.rs +60 -0
  19. package/internals/internal_commands/locate_command.rs +5 -4
  20. package/internals/internal_commands/mod.rs +1 -1
  21. package/internals/internal_commands/onboarder.rs +1 -1
  22. package/internals/internal_commands/register_command.rs +3 -3
  23. package/internals/internal_commands/search_commands.rs +2 -2
  24. package/internals/internal_commands/upgrade_repokit.rs +37 -34
  25. package/internals/internal_filesystem/file_builder.rs +4 -0
  26. package/internals/internal_filesystem/internal_filesystem.rs +101 -9
  27. package/internals/logger/logger.rs +26 -14
  28. package/internals/main.rs +6 -3
  29. package/internals/post_processing/mod.rs +1 -0
  30. package/internals/post_processing/post_processor.rs +37 -0
  31. package/internals/repokit/command_definition.rs +11 -0
  32. package/internals/repokit/mod.rs +5 -1
  33. package/internals/repokit/repokit.rs +10 -14
  34. package/internals/repokit/repokit_command.rs +96 -0
  35. package/internals/repokit/repokit_config.rs +75 -0
  36. package/internals/repokit/repokit_construct_validator.rs +14 -0
  37. package/internals/repokit/runtime_compiler.rs +61 -0
  38. package/internals/themes/theme_inputs.rs +3 -2
  39. package/internals/themes/theme_registry.rs +23 -0
  40. package/internals/validations/command_validations.rs +4 -5
  41. package/package.json +1 -1
  42. package/internals/executables/external_executable.rs +0 -4
  43. package/internals/repokit/interfaces.rs +0 -48
  44. /package/internals/executables/{intenal_executable.rs → internal_executable.rs} +0 -0
@@ -3,9 +3,11 @@ use normalize_path::NormalizePath;
3
3
  use regex::Regex;
4
4
  use shellexpand::tilde;
5
5
  use std::{
6
+ collections::HashMap,
6
7
  fs::{self, File},
7
8
  io::{BufRead, BufReader, Lines},
8
9
  path::{Path, PathBuf},
10
+ sync::LazyLock,
9
11
  };
10
12
 
11
13
  use crate::{
@@ -13,6 +15,9 @@ use crate::{
13
15
  logger::logger::Logger,
14
16
  };
15
17
 
18
+ pub static VERSION_REGEX: LazyLock<Regex> =
19
+ LazyLock::new(|| Regex::new(r#"\d*\.\d*.\d*"#).unwrap());
20
+
16
21
  pub struct InternalFileSystem {
17
22
  root: String,
18
23
  }
@@ -114,6 +119,77 @@ impl InternalFileSystem {
114
119
  None
115
120
  }
116
121
 
122
+ pub fn get_package_manager(root: &str) -> &str {
123
+ let manager_map = HashMap::from([
124
+ ("npm", "package-lock.json"),
125
+ ("yarn", "yarn.lock"),
126
+ ("pnpm", "pnpm-lock.yaml"),
127
+ ("bun", "bun.lockb"),
128
+ ]);
129
+ for (manager, lock_file) in manager_map {
130
+ let path = Path::new(&root).join(lock_file).normalize();
131
+ if path.exists() && path.is_file() {
132
+ return manager;
133
+ }
134
+ }
135
+ "npm"
136
+ }
137
+
138
+ pub fn get_install_command(root: &str) -> &str {
139
+ let npm_install = "npm i -D";
140
+ let package_manager = InternalFileSystem::get_package_manager(root);
141
+ let manager_map = HashMap::from([
142
+ ("npm", npm_install),
143
+ ("yarn", "yarn add -D"),
144
+ ("pnpm", "pnpm i -D"),
145
+ ("bun", "bun add -D"),
146
+ ]);
147
+ manager_map.get(package_manager).unwrap_or(&npm_install)
148
+ }
149
+
150
+ pub fn get_node_executor(root: &str) -> &str {
151
+ let npx = "npx";
152
+ let package_manager = InternalFileSystem::get_package_manager(root);
153
+ let manager_map = HashMap::from([
154
+ ("npm", "npx"),
155
+ ("yarn", "yarn run -T"),
156
+ ("pnpm", "pnpm run"),
157
+ ("bun", "bunx"),
158
+ ]);
159
+ manager_map.get(package_manager).unwrap_or(&npx)
160
+ }
161
+
162
+ pub fn get_typescript_version(node_executor: &str) -> u32 {
163
+ let stdout = Executor::exec(format!("{} tsc --version", node_executor), |cmd| cmd);
164
+ let lines: Vec<&str> = stdout
165
+ .split("\n")
166
+ .filter_map(|s| {
167
+ let trimmed = s.trim();
168
+ if trimmed.is_empty() {
169
+ return None;
170
+ }
171
+ Some(trimmed)
172
+ })
173
+ .collect();
174
+ let fallback_version = "5.0.0";
175
+ let version = lines.last().unwrap_or(&fallback_version);
176
+ let captures: Vec<String> = VERSION_REGEX
177
+ .captures_iter(version)
178
+ .filter_map(|item| {
179
+ item.get(0)
180
+ .map(|match_text| match_text.as_str().to_string())
181
+ })
182
+ .collect();
183
+ let fallback_version_str = fallback_version.to_string();
184
+ let semver = captures.first().unwrap_or(&fallback_version_str);
185
+ semver
186
+ .chars()
187
+ .next()
188
+ .unwrap_or('5')
189
+ .to_digit(10)
190
+ .unwrap_or(5)
191
+ }
192
+
117
193
  fn commands_directory(&self) -> PathBuf {
118
194
  self.absolute(format!("{}/dist/commands", self.package_directory()).as_str())
119
195
  }
@@ -122,7 +198,7 @@ impl InternalFileSystem {
122
198
  self.absolute(format!("{}/externals/templates", self.package_directory()).as_str())
123
199
  }
124
200
 
125
- fn package_directory(&self) -> String {
201
+ pub fn package_directory(&self) -> String {
126
202
  format!("./node_modules/{}", self.package_name())
127
203
  }
128
204
 
@@ -142,7 +218,7 @@ impl InternalFileSystem {
142
218
  Some(home) => {
143
219
  let dot_file_path = home.join(".repokit");
144
220
  if !&dot_file_path.exists() {
145
- let found_version = self.current_version();
221
+ let found_version = self.installed_repokit_version();
146
222
  found_version.as_ref()?;
147
223
  let version_string = found_version.unwrap();
148
224
  let result = fs::write(&dot_file_path, format!("{version_string}\n"));
@@ -171,7 +247,7 @@ impl InternalFileSystem {
171
247
  }
172
248
  }
173
249
 
174
- pub fn current_version(&self) -> Option<String> {
250
+ pub fn installed_repokit_version(&self) -> Option<String> {
175
251
  let package_path = Path::new(&self.root)
176
252
  .join(InternalFileSystem::package_directory(self))
177
253
  .normalize();
@@ -182,7 +258,7 @@ impl InternalFileSystem {
182
258
  }
183
259
  let lines = BufReader::new(file.unwrap()).lines();
184
260
  let version_matcher = Regex::new(r#""([^"]*)""#).unwrap();
185
- for line in lines.flatten() {
261
+ for line in lines.map_while(Result::ok) {
186
262
  if line.contains("\"version\": ") {
187
263
  let captures: Vec<String> = version_matcher
188
264
  .captures_iter(&line)
@@ -191,12 +267,28 @@ impl InternalFileSystem {
191
267
  .map(|match_text| match_text.as_str().to_string())
192
268
  })
193
269
  .collect();
194
- let version = captures.get(1);
195
- if version.is_some() {
196
- return Some(version.unwrap().to_string());
197
- } else {
198
- return None;
270
+ if let Some(version) = captures.get(1)
271
+ && VERSION_REGEX.is_match(version)
272
+ {
273
+ return Some(version.to_string());
199
274
  }
275
+ return None;
276
+ }
277
+ }
278
+ None
279
+ }
280
+
281
+ pub fn runtime_repokit_version() -> Option<String> {
282
+ if let Some(home) = InternalFileSystem::home() {
283
+ let version = Executor::exec(
284
+ format!(
285
+ "head -n 1 {}",
286
+ home.join(".repokit").normalize().to_str().unwrap()
287
+ ),
288
+ |cmd| cmd,
289
+ );
290
+ if VERSION_REGEX.is_match(&version) {
291
+ return Some(version);
200
292
  }
201
293
  }
202
294
  None
@@ -1,10 +1,10 @@
1
- use std::process::exit;
2
1
  use std::sync::LazyLock;
3
2
  use std::sync::Mutex;
4
3
  use std::sync::MutexGuard;
5
4
 
6
5
  use colored::{ColoredString, Colorize};
7
6
 
7
+ use crate::post_processing::post_processor::PostProcessor;
8
8
  use crate::themes::theme::Theme;
9
9
  use crate::themes::theme_registry::ThemeRegistry;
10
10
 
@@ -13,7 +13,7 @@ static REGISTERED_NAME: LazyLock<Mutex<String>> =
13
13
 
14
14
  static THEMES: LazyLock<Mutex<ThemeRegistry>> = LazyLock::new(|| Mutex::new(ThemeRegistry::new()));
15
15
 
16
- pub struct Logger {}
16
+ pub struct Logger;
17
17
 
18
18
  impl Logger {
19
19
  pub fn set_name(value: &str) {
@@ -30,20 +30,18 @@ impl Logger {
30
30
 
31
31
  pub fn exit_with_info(message: &str) {
32
32
  Logger::info(message);
33
- exit(0);
33
+ PostProcessor::get().flush();
34
34
  }
35
35
 
36
36
  pub fn exit_with_error(message: &str) {
37
37
  Logger::error(message);
38
- exit(0);
38
+ PostProcessor::get().flush();
39
39
  }
40
40
 
41
41
  pub fn list(items: &[&str], indentation: Option<i32>) {
42
42
  Logger::with_surrounding_space(|| {
43
- let mut pointer = 0;
44
- for item in items {
45
- println!("{}{}. {}", Logger::indent(indentation), pointer + 1, item);
46
- pointer += 1
43
+ for (index, item) in items.iter().enumerate() {
44
+ println!("{}{}. {}", Logger::indent(indentation), index + 1, item);
47
45
  }
48
46
  })
49
47
  }
@@ -60,11 +58,25 @@ impl Logger {
60
58
  }
61
59
 
62
60
  pub fn log_file_path(path: &str) {
63
- println!(
64
- "\n{}{}\n",
65
- Logger::indent(None),
66
- Logger::with_theme(|theme| theme.highlight(path))
67
- );
61
+ Logger::with_surrounding_space(|| {
62
+ println!(
63
+ "{}{}",
64
+ Logger::indent(None),
65
+ Logger::with_theme(|theme| theme.highlight(path))
66
+ );
67
+ })
68
+ }
69
+
70
+ pub fn list_file_paths(paths: &Vec<String>) {
71
+ Logger::with_surrounding_space(|| {
72
+ for path in paths {
73
+ println!(
74
+ "{}{}",
75
+ Logger::indent(None),
76
+ Logger::with_theme(|theme| theme.highlight(path))
77
+ );
78
+ }
79
+ })
68
80
  }
69
81
 
70
82
  pub fn indent(times: Option<i32>) -> String {
@@ -109,7 +121,7 @@ impl Logger {
109
121
  Logger::info(format!("I was unable to {operation} in your repository").as_str());
110
122
  Logger::error("Please verify the permissions on your working directory or file a bug here");
111
123
  Logger::log_issue_link();
112
- exit(0);
124
+ PostProcessor::get().flush();
113
125
  }
114
126
 
115
127
  fn info_prefix() -> String {
package/internals/main.rs CHANGED
@@ -1,6 +1,7 @@
1
1
  use crate::{
2
- internal_commands::typescript_command::TypescriptCommand,
3
- internal_filesystem::internal_filesystem::InternalFileSystem, repokit::repokit::RepoKit,
2
+ configuration::typescript_command::TypescriptCommand,
3
+ internal_filesystem::internal_filesystem::InternalFileSystem,
4
+ repokit::{repokit::RepoKit, runtime_compiler::RuntimeCompiler},
4
5
  };
5
6
 
6
7
  mod argv;
@@ -11,13 +12,15 @@ mod file_walker;
11
12
  mod internal_commands;
12
13
  mod internal_filesystem;
13
14
  mod logger;
15
+ mod post_processing;
14
16
  mod repokit;
15
17
  mod themes;
16
18
  mod validations;
17
19
 
18
20
  fn main() {
19
21
  let root = InternalFileSystem::find_root();
22
+ RuntimeCompiler::hop_to_runtime_version(&root);
20
23
  let config = TypescriptCommand::new(&root).parse_configuration();
21
- let kit = RepoKit::new(root, config);
24
+ let kit = RepoKit::new(&root, config);
22
25
  kit.invoke();
23
26
  }
@@ -0,0 +1 @@
1
+ pub mod post_processor;
@@ -0,0 +1,37 @@
1
+ use std::{
2
+ process::exit,
3
+ sync::{LazyLock, Mutex, MutexGuard},
4
+ };
5
+
6
+ pub struct PostProcessor {
7
+ tasks: Vec<Box<dyn Fn() + Send + 'static>>,
8
+ }
9
+
10
+ impl PostProcessor {
11
+ pub fn new() -> Self {
12
+ PostProcessor { tasks: Vec::new() }
13
+ }
14
+
15
+ pub fn get() -> MutexGuard<'static, PostProcessor> {
16
+ REPOKIT_POST_PROCESSOR.lock().unwrap()
17
+ }
18
+
19
+ pub fn register_task<F>(&mut self, task: F)
20
+ where
21
+ F: Fn() + Send + 'static,
22
+ {
23
+ self.tasks.push(Box::new(task));
24
+ }
25
+
26
+ pub fn flush(&mut self) {
27
+ for task in self.tasks.iter() {
28
+ task();
29
+ }
30
+ self.tasks.clear();
31
+ self.tasks.shrink_to_fit();
32
+ exit(0);
33
+ }
34
+ }
35
+
36
+ static REPOKIT_POST_PROCESSOR: LazyLock<Mutex<PostProcessor>> =
37
+ LazyLock::new(|| Mutex::new(PostProcessor::new()));
@@ -0,0 +1,11 @@
1
+ use std::collections::HashMap;
2
+
3
+ use schemars::JsonSchema;
4
+ use serde::Deserialize;
5
+
6
+ #[derive(Debug, Deserialize, Clone, JsonSchema)]
7
+ pub struct CommandDefinition {
8
+ pub command: String,
9
+ pub description: String,
10
+ pub args: Option<HashMap<String, String>>,
11
+ }
@@ -1,2 +1,6 @@
1
- pub mod interfaces;
1
+ pub mod command_definition;
2
2
  pub mod repokit;
3
+ pub mod repokit_command;
4
+ pub mod repokit_config;
5
+ pub mod repokit_construct_validator;
6
+ pub mod runtime_compiler;
@@ -1,19 +1,15 @@
1
- use std::{
2
- collections::HashMap,
3
- env::args,
4
- path::Path,
5
- process::{self},
6
- };
1
+ use std::{collections::HashMap, env::args, path::Path};
7
2
 
8
3
  use crate::{
9
4
  executables::{
10
- intenal_executable::InternalExecutable, internal_executable_definition::RepoKitScope,
5
+ internal_executable::InternalExecutable, internal_executable_definition::RepoKitScope,
11
6
  },
12
7
  executor::executor::Executor,
13
8
  internal_commands::help::Help,
14
9
  internal_filesystem::internal_filesystem::InternalFileSystem,
15
10
  logger::logger::Logger,
16
- repokit::interfaces::{RepoKitCommand, RepoKitConfig},
11
+ post_processing::post_processor::PostProcessor,
12
+ repokit::{repokit_command::RepoKitCommand, repokit_config::RepoKitConfig},
17
13
  validations::command_validations::CommandValidations,
18
14
  };
19
15
 
@@ -22,17 +18,17 @@ pub struct RepoKit {
22
18
  }
23
19
 
24
20
  impl RepoKit {
25
- pub fn new(root: String, configuration: RepoKitConfig) -> RepoKit {
21
+ pub fn new(root: &str, configuration: RepoKitConfig) -> RepoKit {
26
22
  Logger::set_name(&configuration.project);
27
23
  for theme in &configuration.themes {
28
24
  Logger::with_registry(|mut registry| registry.register_user_theme(theme))
29
25
  }
30
- let theme = InternalFileSystem::new(&root).read_theme_preference();
31
- Logger::with_registry(|mut registry| registry.set_theme(&root, &theme));
26
+ let theme = InternalFileSystem::new(root).read_theme_preference();
27
+ Logger::with_registry(|mut registry| registry.set_theme(root, &theme));
32
28
  RepoKit {
33
29
  scope: RepoKitScope {
34
- root,
35
30
  configuration,
31
+ root: root.to_string(),
36
32
  },
37
33
  }
38
34
  }
@@ -42,7 +38,7 @@ impl RepoKit {
42
38
  let validator = CommandValidations::from(self);
43
39
  let internals = validator.collect_and_validate_internals();
44
40
  if internals.contains_key(&command) {
45
- let interface = internals.get(&command).expect("Unknown command");
41
+ let interface = internals.get(&command).expect("known command");
46
42
  return interface.run(args, &internals);
47
43
  }
48
44
  if self.scope.configuration.commands.contains_key(&command) {
@@ -87,7 +83,7 @@ impl RepoKit {
87
83
  if argv.len() < 2 {
88
84
  let (internals, externals) = self.collect_and_validate();
89
85
  Help::list_all(&self.scope.configuration.commands, &internals, &externals);
90
- process::exit(0);
86
+ PostProcessor::get().flush();
91
87
  }
92
88
  let command = &argv[1];
93
89
  let args = &(&argv)[2..];
@@ -0,0 +1,96 @@
1
+ use std::{
2
+ collections::HashMap,
3
+ sync::LazyLock,
4
+ };
5
+
6
+ use jsonschema::Validator;
7
+ use schemars::JsonSchema;
8
+ use serde::Deserialize;
9
+ use serde_json::{Value, from_value, to_value};
10
+
11
+ use crate::{
12
+ configuration::recovery::Recovery,
13
+ logger::logger::Logger,
14
+ post_processing::post_processor::PostProcessor,
15
+ repokit::{
16
+ command_definition::CommandDefinition,
17
+ repokit_construct_validator::RepoKitConstructValidator,
18
+ },
19
+ };
20
+
21
+ #[derive(Debug, Deserialize, Clone, JsonSchema)]
22
+ pub struct RepoKitCommand {
23
+ pub name: String,
24
+ pub owner: String,
25
+ pub location: String,
26
+ pub description: String,
27
+ pub commands: HashMap<String, CommandDefinition>,
28
+ }
29
+
30
+ static REPOKIT_COMMAND_VALIDATOR: LazyLock<Validator> = LazyLock::new(|| {
31
+ Validator::new(&to_value(schemars::schema_for!(RepoKitCommand)).unwrap()).unwrap()
32
+ });
33
+
34
+ impl RepoKitCommand {
35
+ fn register_encountered_errors(root: &str, failed_paths: Vec<String>) {
36
+ let root_clone = root.to_string();
37
+ PostProcessor::get().register_task(move || {
38
+ println!();
39
+ if !failed_paths.is_empty() {
40
+ let appendage = if failed_paths.len() != 1 { "s" } else { "" };
41
+ Logger::error(
42
+ format!(
43
+ "I encountered an error in the following command{}",
44
+ appendage
45
+ )
46
+ .as_str(),
47
+ );
48
+ Logger::list_file_paths(&failed_paths);
49
+ } else {
50
+ Logger::info("There was an error parsing one or more of your commands");
51
+ }
52
+ Logger::info("You can validate a command file's syntactical correctness by running");
53
+ Logger::log_file_path(
54
+ &Recovery::new(&root_clone).get_typecheck_command("<optional-path-to-file>"),
55
+ );
56
+ });
57
+ }
58
+ }
59
+
60
+ impl RepoKitConstructValidator<Vec<Value>, Vec<RepoKitCommand>> for RepoKitCommand {
61
+ fn from_input(root: &str, input: Vec<Value>) -> Vec<RepoKitCommand> {
62
+ let mut result: Vec<RepoKitCommand> = Vec::new();
63
+ let mut failures = 0;
64
+ let mut failed_paths: Vec<String> = Vec::new();
65
+ for command in input {
66
+ let repokit_command: Result<RepoKitCommand, serde_json::Error> =
67
+ from_value(command.clone());
68
+ if !RepoKitCommand::is_valid(&REPOKIT_COMMAND_VALIDATOR, &command)
69
+ || repokit_command.is_err()
70
+ {
71
+ failures += 1;
72
+ if let Some(path) = RepoKitCommand::on_parsing_error(root, command) {
73
+ failed_paths.push(path);
74
+ }
75
+ } else {
76
+ let mut valid_command = repokit_command.expect("assertion success");
77
+ valid_command.location = format!("{}/{}", root, valid_command.location);
78
+ result.push(valid_command);
79
+ }
80
+ }
81
+ if failures != 0 {
82
+ RepoKitCommand::register_encountered_errors(root, failed_paths);
83
+ }
84
+ result
85
+ }
86
+
87
+ fn on_parsing_error(root: &str, command: Value) -> Option<String> {
88
+ let location = command.get("location");
89
+ println!();
90
+ if location.is_some_and(|v| v.is_string()) {
91
+ let path = format!("{}/{}", &root, location.unwrap().as_str().unwrap());
92
+ return Some(path);
93
+ }
94
+ None
95
+ }
96
+ }
@@ -0,0 +1,75 @@
1
+ use std::{
2
+ collections::HashMap,
3
+ path::Path,
4
+ process::exit,
5
+ sync::LazyLock,
6
+ };
7
+
8
+ use jsonschema::Validator;
9
+ use schemars::JsonSchema;
10
+ use serde::Deserialize;
11
+ use serde_json::{Value, from_value, to_value};
12
+
13
+ use crate::{
14
+ configuration::recovery::Recovery,
15
+ logger::logger::Logger,
16
+ post_processing::post_processor::PostProcessor,
17
+ repokit::{
18
+ command_definition::CommandDefinition, repokit_command::RepoKitCommand,
19
+ repokit_construct_validator::RepoKitConstructValidator,
20
+ },
21
+ themes::theme_inputs::RepoKitTheme,
22
+ };
23
+
24
+ #[derive(Debug, Deserialize, Clone, JsonSchema)]
25
+ pub struct RootCommand {
26
+ pub name: String,
27
+ pub command: String,
28
+ pub description: String,
29
+ pub args: Option<HashMap<String, String>>,
30
+ }
31
+
32
+ impl RootCommand {
33
+ pub fn from(name: &str, command: &CommandDefinition) -> RootCommand {
34
+ RootCommand {
35
+ name: name.to_string(),
36
+ args: command.args.clone(),
37
+ command: command.command.to_string(),
38
+ description: command.description.to_string(),
39
+ }
40
+ }
41
+ }
42
+
43
+ #[derive(Debug, Deserialize, Clone, JsonSchema)]
44
+ pub struct RepoKitConfig {
45
+ pub project: String,
46
+ pub thirdParty: Vec<RepoKitCommand>,
47
+ pub commands: HashMap<String, CommandDefinition>,
48
+ pub themes: Vec<RepoKitTheme>,
49
+ }
50
+
51
+ static REPOKIT_CONFIG_VALIDATOR: LazyLock<Validator> = LazyLock::new(|| {
52
+ Validator::new(&to_value(schemars::schema_for!(RepoKitConfig)).unwrap()).unwrap()
53
+ });
54
+
55
+ impl RepoKitConstructValidator<Value, RepoKitConfig> for RepoKitConfig {
56
+ fn from_input(root: &str, input: Value) -> RepoKitConfig {
57
+ let repokit_config: Result<RepoKitConfig, serde_json::Error> = from_value(input.clone());
58
+ if !RepoKitConfig::is_valid(&REPOKIT_CONFIG_VALIDATOR, &input) || repokit_config.is_err() {
59
+ RepoKitConfig::on_parsing_error(root, Value::Null);
60
+ }
61
+ repokit_config.expect("assertions succeeded")
62
+ }
63
+
64
+ fn on_parsing_error(root: &str, _: Value) -> Option<String> {
65
+ let path_buf = Path::new(&root).join("repokit.ts");
66
+ let path = path_buf.to_str().expect("exists");
67
+ let mut recovery = Recovery::new(root);
68
+ recovery.run(path);
69
+ println!();
70
+ Logger::info("There was an error parsing your configuration");
71
+ recovery.prompt_to_fix_errors(path);
72
+ PostProcessor::get().flush();
73
+ exit(0);
74
+ }
75
+ }
@@ -0,0 +1,14 @@
1
+ use jsonschema::Validator;
2
+ use serde_json::Value;
3
+
4
+ pub trait RepoKitConstructValidator<T, V> {
5
+ fn from_input(root: &str, input: T) -> V;
6
+ fn on_parsing_error(root: &str, value: Value) -> Option<String>;
7
+
8
+ fn is_valid(validator: &Validator, input: &Value) -> bool {
9
+ if validator.validate(input).is_err() {
10
+ return false;
11
+ }
12
+ true
13
+ }
14
+ }
@@ -0,0 +1,61 @@
1
+ use std::{env::args, path::PathBuf, process::exit};
2
+
3
+ use normalize_path::NormalizePath;
4
+ use terminal_spinners::{BOUNCING_BALL, SpinnerBuilder};
5
+
6
+ use crate::{
7
+ executor::executor::Executor, internal_filesystem::internal_filesystem::InternalFileSystem,
8
+ logger::logger::Logger,
9
+ };
10
+
11
+ pub struct RuntimeCompiler;
12
+
13
+ impl RuntimeCompiler {
14
+ pub fn hop_to_runtime_version(root: &str) {
15
+ RuntimeCompiler::with_version_mismatch(root, |installed_version, fs| {
16
+ let package_path = fs.absolute(&fs.package_directory());
17
+ let install_path = package_path.join("installation/install.sh").normalize();
18
+ if install_path.is_absolute() && install_path.exists() {
19
+ Logger::info(
20
+ format!(
21
+ "Switching to version {}",
22
+ Logger::with_theme(|theme| theme.highlight(installed_version))
23
+ )
24
+ .as_str(),
25
+ );
26
+ if let Some(errors) = RuntimeCompiler::run_post_install(&package_path) {
27
+ println!("{errors}");
28
+ } else {
29
+ RuntimeCompiler::re_run_command();
30
+ }
31
+ }
32
+ });
33
+ }
34
+
35
+ fn with_version_mismatch(root: &str, func: impl Fn(&str, InternalFileSystem)) {
36
+ let scoped_fs = InternalFileSystem::new(root);
37
+ if let Some(installed_version) = scoped_fs.installed_repokit_version()
38
+ && let Some(runtime_version) = InternalFileSystem::runtime_repokit_version()
39
+ && runtime_version != installed_version
40
+ {
41
+ func(&installed_version, scoped_fs);
42
+ }
43
+ }
44
+
45
+ fn run_post_install(cwd: &PathBuf) -> Option<String> {
46
+ let handle = SpinnerBuilder::new()
47
+ .spinner(&BOUNCING_BALL)
48
+ .text(" Installing")
49
+ .start();
50
+ let result =
51
+ Executor::exec_with_errors("./installation/install.sh", |cmd| cmd.current_dir(cwd));
52
+ handle.done();
53
+ result
54
+ }
55
+
56
+ fn re_run_command() {
57
+ let args: Vec<String> = args().collect();
58
+ Executor::with_stdio(args.join(" "), |cmd| cmd);
59
+ exit(0);
60
+ }
61
+ }
@@ -1,4 +1,5 @@
1
1
  use colored::Color;
2
+ use schemars::JsonSchema;
2
3
  use serde::Deserialize;
3
4
 
4
5
  pub struct ThemeInputColors {
@@ -16,7 +17,7 @@ pub struct ThemeInput {
16
17
  pub colors: ThemeInputColors,
17
18
  }
18
19
 
19
- #[derive(Debug, Deserialize, Clone)]
20
+ #[derive(Debug, Deserialize, Clone, JsonSchema)]
20
21
  pub struct RepoKitThemeColors {
21
22
  pub prefixColor: Option<String>,
22
23
  pub commandColor: Option<String>,
@@ -27,7 +28,7 @@ pub struct RepoKitThemeColors {
27
28
  pub highlightColor: Option<String>,
28
29
  }
29
30
 
30
- #[derive(Debug, Deserialize, Clone)]
31
+ #[derive(Debug, Deserialize, Clone, JsonSchema)]
31
32
  pub struct RepoKitTheme {
32
33
  pub name: String,
33
34
  pub colors: RepoKitThemeColors,