@repokit/core 0.0.1

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 (79) hide show
  1. package/.vscode/settings.json +11 -0
  2. package/Cargo.lock +517 -0
  3. package/Cargo.toml +26 -0
  4. package/README.md +204 -0
  5. package/dist/cjs/CommandParser.js +68 -0
  6. package/dist/cjs/ConfigurationParser.js +46 -0
  7. package/dist/cjs/RepoKitCommand.js +16 -0
  8. package/dist/cjs/RepoKitConfig.js +10 -0
  9. package/dist/cjs/TaskPooler.js +42 -0
  10. package/dist/cjs/commands/parse_commands.js +15 -0
  11. package/dist/cjs/commands/parse_configuration.js +15 -0
  12. package/dist/cjs/index.js +19 -0
  13. package/dist/cjs/package.json +3 -0
  14. package/dist/cjs/types.js +2 -0
  15. package/dist/mjs/CommandParser.js +51 -0
  16. package/dist/mjs/ConfigurationParser.js +31 -0
  17. package/dist/mjs/RepoKitCommand.js +16 -0
  18. package/dist/mjs/RepoKitConfig.js +8 -0
  19. package/dist/mjs/TaskPooler.js +28 -0
  20. package/dist/mjs/commands/parse_commands.js +4 -0
  21. package/dist/mjs/commands/parse_configuration.js +4 -0
  22. package/dist/mjs/index.js +3 -0
  23. package/dist/mjs/package.json +4 -0
  24. package/dist/mjs/types.js +1 -0
  25. package/dist/types/CommandParser.d.ts +5 -0
  26. package/dist/types/ConfigurationParser.d.ts +4 -0
  27. package/dist/types/RepoKitCommand.d.ts +14 -0
  28. package/dist/types/RepoKitConfig.d.ts +6 -0
  29. package/dist/types/TaskPooler.d.ts +10 -0
  30. package/dist/types/commands/parse_commands.d.ts +1 -0
  31. package/dist/types/commands/parse_configuration.d.ts +1 -0
  32. package/dist/types/index.d.ts +3 -0
  33. package/dist/types/types.d.ts +18 -0
  34. package/install.sh +43 -0
  35. package/package.json +40 -0
  36. package/repokit.ts +36 -0
  37. package/src/CommandParser.ts +59 -0
  38. package/src/ConfigurationParser.ts +34 -0
  39. package/src/RepoKitCommand.ts +24 -0
  40. package/src/RepoKitConfig.ts +10 -0
  41. package/src/TaskPooler.ts +31 -0
  42. package/src/commands/parse_commands.ts +5 -0
  43. package/src/commands/parse_configuration.ts +5 -0
  44. package/src/index.ts +3 -0
  45. package/src/types.ts +22 -0
  46. package/tsconfig.json +24 -0
  47. package/workspaces/concurrency/mod.rs +1 -0
  48. package/workspaces/concurrency/thread_pool.rs +32 -0
  49. package/workspaces/configuration/configuration.rs +47 -0
  50. package/workspaces/configuration/configuration_template.ts +23 -0
  51. package/workspaces/configuration/mod.rs +1 -0
  52. package/workspaces/executables/external_executable.rs +4 -0
  53. package/workspaces/executables/intenal_executable.rs +9 -0
  54. package/workspaces/executables/internal_executable_definition.rs +8 -0
  55. package/workspaces/executables/mod.rs +3 -0
  56. package/workspaces/executor/executor.rs +62 -0
  57. package/workspaces/executor/mod.rs +1 -0
  58. package/workspaces/external_commands/external_commands.rs +125 -0
  59. package/workspaces/external_commands/mod.rs +1 -0
  60. package/workspaces/internal_commands/command_template.ts +24 -0
  61. package/workspaces/internal_commands/help.rs +146 -0
  62. package/workspaces/internal_commands/internal_registry.rs +59 -0
  63. package/workspaces/internal_commands/list_commands.rs +106 -0
  64. package/workspaces/internal_commands/list_owners.rs +74 -0
  65. package/workspaces/internal_commands/locate_command.rs +78 -0
  66. package/workspaces/internal_commands/mod.rs +10 -0
  67. package/workspaces/internal_commands/onboarder.rs +66 -0
  68. package/workspaces/internal_commands/register_command.rs +117 -0
  69. package/workspaces/internal_commands/search_commands.rs +189 -0
  70. package/workspaces/internal_commands/typescript_command.rs +63 -0
  71. package/workspaces/internal_commands/upgrade_repokit.rs +76 -0
  72. package/workspaces/logger/logger.rs +98 -0
  73. package/workspaces/logger/mod.rs +1 -0
  74. package/workspaces/main.rs +21 -0
  75. package/workspaces/repokit/interfaces.rs +41 -0
  76. package/workspaces/repokit/mod.rs +2 -0
  77. package/workspaces/repokit/repokit.rs +141 -0
  78. package/workspaces/validations/command_validations.rs +140 -0
  79. package/workspaces/validations/mod.rs +1 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export * from "./RepoKitConfig";
2
+ export * from "./RepoKitCommand";
3
+ export * from "./types";
@@ -0,0 +1,18 @@
1
+ export interface IRepoKitConfig {
2
+ project: string;
3
+ commands?: Record<string, ICommand>;
4
+ }
5
+ export interface IRepoKitCommand {
6
+ name: string;
7
+ owner?: string;
8
+ description: string;
9
+ commands: Record<string, ICommand>;
10
+ }
11
+ export interface ICommand {
12
+ command: string;
13
+ description: string;
14
+ }
15
+ export interface ILocatedCommand extends IRepoKitCommand {
16
+ location: string;
17
+ }
18
+ export type AsyncTask<T> = () => Promise<T>;
package/install.sh ADDED
@@ -0,0 +1,43 @@
1
+ set -e
2
+
3
+ REPO_ROOT=$(git rev-parse --show-toplevel)
4
+
5
+ cd $REPO_ROOT
6
+
7
+ command_exists() {
8
+ command -v "$1" >/dev/null 2>&1
9
+ }
10
+
11
+ if command_exists rustc && command_exists cargo; then
12
+ echo "Rust is installed."
13
+ else
14
+ echo "Installing rust"
15
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
16
+ fi
17
+
18
+ if npm list --depth=0 tsx >/dev/null 2>&1; then
19
+ echo "Found tsx installation"
20
+ else
21
+ # Node Dependencies installation
22
+ if [ -f "${REPO_ROOT}/yarn.lock" ]; then
23
+ yarn add -D tsx
24
+ elif [ -f "${REPO_ROOT}/pnpm-lock.yaml" ]; then
25
+ pnpm add -D tsx
26
+ elif [ -f "${REPO_ROOT}/package-lock.json" ]; then
27
+ npm i -D tsx
28
+ else
29
+ echo "No node.js package manager detected"
30
+ echo "Run npm init to create your node.js project"
31
+ fi
32
+ fi
33
+
34
+
35
+ echo "Installing Repokit CLI"
36
+
37
+ SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
38
+ cd "$SCRIPT_DIR"
39
+
40
+ . "$HOME/.cargo/env"
41
+ RUSTFLAGS="-Awarnings" cargo build --release > /dev/null
42
+ cargo install --path . > /dev/null
43
+ repokit
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@repokit/core",
3
+ "version": "0.0.1",
4
+ "description": "A knowledgebase for your repository - wrapped in a CLI",
5
+ "license": "MIT",
6
+ "author": "Alex Figliolia",
7
+ "main": "dist/cjs/index.js",
8
+ "module": "dist/mjs/index.js",
9
+ "types": "dist/types/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/types/index.d.ts",
13
+ "import": "./dist/mjs/index.js",
14
+ "require": "./dist/cjs/index.js"
15
+ }
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "scripts": {
21
+ "build:all": "yarn build:ts && yarn lint:all && yarn install:repokit",
22
+ "build:rust": "cargo build --release",
23
+ "build:ts": "yarn ts-packager -e src",
24
+ "install:repokit": "yarn build:rust && cargo install --path .",
25
+ "lint:all": "repokit format:rust && repokit lint:ts",
26
+ "postinstall": "chmod +x ./install.sh && ./install.sh",
27
+ "repokit": "yarn build:ts && yarn install:repokit && repokit",
28
+ "run:dev": "cargo run --package repokit --bin repokit"
29
+ },
30
+ "devDependencies": {
31
+ "@figliolia/event-emitter": "^1.1.6",
32
+ "@figliolia/ts-packager": "^1.2.1",
33
+ "@types/node": "^25.0.10",
34
+ "oxfmt": "^0.27.0",
35
+ "oxlint": "^1.42.0",
36
+ "oxlint-tsgolint": "^0.11.3",
37
+ "tsx": "^4.21.0",
38
+ "typescript": "^5.9.3"
39
+ }
40
+ }
package/repokit.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { RepoKitConfig } from "@repokit/core";
2
+
3
+ export const RepoKit = new RepoKitConfig({
4
+ project: "Repokit",
5
+ commands: {
6
+ "lint:rust": {
7
+ command: "cargo clippy",
8
+ description: "Lints rust files",
9
+ },
10
+ "format:rust": {
11
+ command: "cargo clippy --fix --allow-dirty",
12
+ description: "Formats rust files",
13
+ },
14
+ "build:rust": {
15
+ command: "cargo build --release",
16
+ description: "Build CLI in production mode",
17
+ },
18
+ "run:rust": {
19
+ command: "cargo run --package repokit --bin repokit",
20
+ description: "Run CLI in development mode",
21
+ },
22
+ "install:rust": {
23
+ command: "repokit build:rust && cargo install --path .",
24
+ description: "Installs the production CLI and adds it to your path",
25
+ },
26
+ "lint:ts": {
27
+ command:
28
+ "yarn oxlint --type-aware --type-check --report-unused-disable-directives --fix && yarn oxfmt",
29
+ description: "Lints typescript files",
30
+ },
31
+ "build:ts": {
32
+ command: "yarn ts-packager -e src",
33
+ description: "Builds the typescript package",
34
+ },
35
+ },
36
+ });
@@ -0,0 +1,59 @@
1
+ import { parseArgs } from "node:util";
2
+ import { join } from "node:path";
3
+ import { stat } from "node:fs/promises";
4
+ import { existsSync } from "node:fs";
5
+
6
+ import type { ILocatedCommand } from "./types";
7
+ import { TaskPooler } from "./TaskPooler";
8
+ import { RepoKitCommand } from "./RepoKitCommand";
9
+
10
+ export class CommandParser {
11
+ public static async parse() {
12
+ const { paths, root } = this.parsePaths();
13
+ if (!root || !existsSync(root) || !(await stat(root)).isDirectory()) {
14
+ return console.log(JSON.stringify([]));
15
+ }
16
+ const pathList = paths.split(",").filter(Boolean);
17
+ const pool = new TaskPooler<ILocatedCommand[]>();
18
+ const results = await Promise.all(
19
+ pathList.map(path =>
20
+ pool.enqueue(() => this.parseCommand(join(root, path))),
21
+ ),
22
+ );
23
+ console.log(JSON.stringify(results.flat()));
24
+ }
25
+
26
+ private static async parseCommand(path: string) {
27
+ const commands: ILocatedCommand[] = [];
28
+ const declaredExports = await import(path);
29
+ for (const key in declaredExports) {
30
+ if (declaredExports[key] instanceof RepoKitCommand) {
31
+ commands.push({ ...declaredExports[key].toJSON(), location: path });
32
+ }
33
+ }
34
+ return commands;
35
+ }
36
+
37
+ private static parsePaths() {
38
+ try {
39
+ return parseArgs({
40
+ options: {
41
+ paths: {
42
+ default: "",
43
+ multiple: false,
44
+ short: "p",
45
+ type: "string",
46
+ },
47
+ root: {
48
+ default: "",
49
+ multiple: false,
50
+ short: "r",
51
+ type: "string",
52
+ },
53
+ },
54
+ }).values;
55
+ } catch {
56
+ return { paths: "", root: "" };
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,34 @@
1
+ import { parseArgs } from "node:util";
2
+ import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+
5
+ import { RepoKitConfig } from "./RepoKitConfig";
6
+
7
+ export class ConfigurationParser {
8
+ public static async parse() {
9
+ const root = this.parseRoot();
10
+ const path = join(root, "repokit.ts");
11
+ if (!existsSync(path)) {
12
+ return;
13
+ }
14
+ const config = await import(path);
15
+ for (const key in config) {
16
+ if (config[key] instanceof RepoKitConfig) {
17
+ return console.log(JSON.stringify(config[key]));
18
+ }
19
+ }
20
+ }
21
+
22
+ private static parseRoot() {
23
+ return parseArgs({
24
+ options: {
25
+ root: {
26
+ default: "",
27
+ multiple: false,
28
+ short: "r",
29
+ type: "string",
30
+ },
31
+ },
32
+ }).values.root;
33
+ }
34
+ }
@@ -0,0 +1,24 @@
1
+ import type { ICommand, IRepoKitCommand } from "./types";
2
+
3
+ export class RepoKitCommand {
4
+ name: string;
5
+ owner: string;
6
+ description: string;
7
+ commands: Record<string, ICommand>;
8
+ constructor({
9
+ name,
10
+ description,
11
+ owner = "",
12
+ commands = {},
13
+ }: IRepoKitCommand) {
14
+ this.name = name;
15
+ this.owner = owner;
16
+ this.commands = commands;
17
+ this.description = description;
18
+ }
19
+
20
+ public toJSON() {
21
+ const { name, owner, commands, description } = this;
22
+ return { name, owner, commands, description };
23
+ }
24
+ }
@@ -0,0 +1,10 @@
1
+ import type { ICommand, IRepoKitConfig } from "./types";
2
+
3
+ export class RepoKitConfig implements Required<IRepoKitConfig> {
4
+ project: string;
5
+ commands: Record<string, ICommand>;
6
+ constructor({ project, commands = {} }: IRepoKitConfig) {
7
+ this.project = project;
8
+ this.commands = commands;
9
+ }
10
+ }
@@ -0,0 +1,31 @@
1
+ import { AutoIncrementingID } from "@figliolia/event-emitter";
2
+
3
+ import type { AsyncTask } from "./types";
4
+
5
+ export class TaskPooler<T> {
6
+ private readonly IDs = new AutoIncrementingID();
7
+ private readonly runningTasks = new Map<string, Promise<T>>();
8
+ constructor(public maxSize = 10) {}
9
+
10
+ public enqueue(task: AsyncTask<T>) {
11
+ return new Promise<T>(resolve => {
12
+ if (this.runningTasks.size < 10) {
13
+ return resolve(this.indexRunningTask(task));
14
+ }
15
+ resolve(this.indexBehindNextOpening(task));
16
+ });
17
+ }
18
+
19
+ private indexRunningTask(task: AsyncTask<T>) {
20
+ const ID = this.IDs.get();
21
+ const promise = task();
22
+ this.runningTasks.set(ID, promise);
23
+ void promise.finally(() => this.runningTasks.delete(ID));
24
+ return promise;
25
+ }
26
+
27
+ private async indexBehindNextOpening(task: AsyncTask<T>) {
28
+ await Promise.race(this.runningTasks.values());
29
+ return this.indexRunningTask(task);
30
+ }
31
+ }
@@ -0,0 +1,5 @@
1
+ import { CommandParser } from "../CommandParser";
2
+
3
+ void (async () => {
4
+ await CommandParser.parse();
5
+ })();
@@ -0,0 +1,5 @@
1
+ import { ConfigurationParser } from "../ConfigurationParser";
2
+
3
+ void (async () => {
4
+ await ConfigurationParser.parse();
5
+ })();
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./RepoKitConfig";
2
+ export * from "./RepoKitCommand";
3
+ export * from "./types";
package/src/types.ts ADDED
@@ -0,0 +1,22 @@
1
+ export interface IRepoKitConfig {
2
+ project: string;
3
+ commands?: Record<string, ICommand>;
4
+ }
5
+
6
+ export interface IRepoKitCommand {
7
+ name: string;
8
+ owner?: string;
9
+ description: string;
10
+ commands: Record<string, ICommand>;
11
+ }
12
+
13
+ export interface ICommand {
14
+ command: string;
15
+ description: string;
16
+ }
17
+
18
+ export interface ILocatedCommand extends IRepoKitCommand {
19
+ location: string;
20
+ }
21
+
22
+ export type AsyncTask<T> = () => Promise<T>;
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "nodenext",
4
+ "target": "esnext",
5
+ "lib": ["ESNext"],
6
+ "moduleResolution": "nodenext",
7
+ "noUncheckedIndexedAccess": true,
8
+ "exactOptionalPropertyTypes": true,
9
+ "noImplicitReturns": true,
10
+ "noImplicitOverride": true,
11
+ "noUnusedLocals": true,
12
+ "noUnusedParameters": true,
13
+ "noFallthroughCasesInSwitch": true,
14
+ "noPropertyAccessFromIndexSignature": true,
15
+ "strict": true,
16
+ "isolatedModules": true,
17
+ "noUncheckedSideEffectImports": true,
18
+ "moduleDetection": "force",
19
+ "skipLibCheck": true,
20
+ "paths": {
21
+ "@repokit/core": ["./src/index.ts"]
22
+ }
23
+ }
24
+ }
@@ -0,0 +1 @@
1
+ pub mod thread_pool;
@@ -0,0 +1,32 @@
1
+ use tokio::{
2
+ runtime::{self, Runtime},
3
+ task::JoinHandle,
4
+ };
5
+
6
+ pub struct ThreadPool {
7
+ pub pool: Runtime,
8
+ }
9
+
10
+ impl ThreadPool {
11
+ pub fn new(threads_override: Option<usize>, pool_override: Option<Runtime>) -> ThreadPool {
12
+ let pool = pool_override.unwrap_or(ThreadPool::create_pool(threads_override));
13
+ ThreadPool { pool }
14
+ }
15
+
16
+ pub fn spawn<T: Send + 'static, F: (Fn() -> T) + 'static + Send>(
17
+ &mut self,
18
+ task: F,
19
+ ) -> JoinHandle<T> {
20
+ self.pool.spawn(async move { task() })
21
+ }
22
+
23
+ fn create_pool(threads: Option<usize>) -> Runtime {
24
+ let mut pool = runtime::Builder::new_multi_thread();
25
+ pool.enable_all();
26
+ match threads {
27
+ Some(size) => pool.worker_threads(size),
28
+ None => &pool,
29
+ };
30
+ pool.build().unwrap()
31
+ }
32
+ }
@@ -0,0 +1,47 @@
1
+ use std::{
2
+ fs::File,
3
+ io,
4
+ path::{Path, PathBuf},
5
+ process::exit,
6
+ };
7
+
8
+ use crate::logger::logger::Logger;
9
+
10
+ pub struct Configuration;
11
+
12
+ impl Configuration {
13
+ pub fn create(root: &str) {
14
+ let file_path = format!("{root}/repokit.ts");
15
+ let path_buf = Path::new(&file_path);
16
+ if path_buf.exists() {
17
+ return;
18
+ }
19
+ Configuration::welcome();
20
+ let mut source = File::open(Configuration::template_path()).expect("Template");
21
+ let mut target = File::create(path_buf).expect("creating");
22
+ io::copy(&mut source, &mut target).expect("writing");
23
+ target.sync_all().expect("Flushing");
24
+ Logger::info(
25
+ format!(
26
+ "Please fill out this file with your desired settings. Then run {}",
27
+ Logger::blue_bright("repokit onboard")
28
+ )
29
+ .as_str(),
30
+ );
31
+ Logger::log_file_path(file_path.as_str());
32
+ exit(0);
33
+ }
34
+
35
+ fn welcome() {
36
+ Logger::info("Welcome to Repokit! Let's get you setup");
37
+ Logger::info("Creating your configuration file:");
38
+ }
39
+
40
+ fn template_path() -> PathBuf {
41
+ let file_path = file!();
42
+ let dir = Path::new(file_path)
43
+ .parent()
44
+ .expect("Failed to get parent directory");
45
+ dir.join("configuration_template.ts")
46
+ }
47
+ }
@@ -0,0 +1,23 @@
1
+ import { RepoKitConfig } from "@repokit/core";
2
+
3
+ /**
4
+ * Please fill out this config file with your desired
5
+ * repokit settings
6
+ */
7
+ export const RepoKit = new RepoKitConfig({
8
+ project: "Your Project Name",
9
+ commands: {
10
+ "<your-first-command>": {
11
+ command: "<insert shell command here>",
12
+ description: "A description for using your command",
13
+ },
14
+ "<your-second-command>": {
15
+ command: "<insert shell command here>",
16
+ description: "A description for using your command",
17
+ },
18
+ "<your-third-command>": {
19
+ command: "<insert shell command here>",
20
+ description: "A description for using your command",
21
+ },
22
+ },
23
+ });
@@ -0,0 +1 @@
1
+ pub mod configuration;
@@ -0,0 +1,4 @@
1
+ trait ExternalExecutable {
2
+ fn run(command: String, args: Vec<String>);
3
+ fn help(&self);
4
+ }
@@ -0,0 +1,9 @@
1
+ use std::collections::HashMap;
2
+
3
+ use crate::executables::internal_executable_definition::InternalExecutableDefinition;
4
+
5
+ pub trait InternalExecutable {
6
+ fn run(&self, args: Vec<String>, internals: &HashMap<String, Box<dyn InternalExecutable>>);
7
+ fn help(&self);
8
+ fn get_definition(&self) -> &InternalExecutableDefinition;
9
+ }
@@ -0,0 +1,8 @@
1
+ use std::collections::HashMap;
2
+
3
+ #[derive(Clone)]
4
+ pub struct InternalExecutableDefinition {
5
+ pub name: &'static str,
6
+ pub description: &'static str,
7
+ pub args: HashMap<&'static str, &'static str>,
8
+ }
@@ -0,0 +1,3 @@
1
+ pub mod external_executable;
2
+ pub mod intenal_executable;
3
+ pub mod internal_executable_definition;
@@ -0,0 +1,62 @@
1
+ use std::ffi::OsStr;
2
+ use std::process::Command;
3
+ use std::str;
4
+
5
+ pub struct Executor {}
6
+
7
+ impl Executor {
8
+ pub fn exec<T: AsRef<OsStr>>(
9
+ command: T,
10
+ composer: impl Fn(&mut Command) -> &mut Command,
11
+ ) -> String {
12
+ let output = composer(&mut Executor::spawn(command))
13
+ .output()
14
+ .expect("command failed to execute");
15
+ if output.status.success() {
16
+ return Executor::unwrap(&output.stdout);
17
+ }
18
+ Executor::unwrap(&output.stderr)
19
+ }
20
+
21
+ pub fn with_stdio<T: AsRef<OsStr>>(
22
+ command: T,
23
+ composer: impl Fn(&mut Command) -> &mut Command,
24
+ ) {
25
+ let mut child = composer(&mut Executor::spawn(command))
26
+ .spawn()
27
+ .expect("Failed to execute");
28
+ child.wait().expect("failed to wait on child process");
29
+ }
30
+
31
+ pub fn spawn<T: AsRef<OsStr>>(program: T) -> Command {
32
+ let mut command = Executor::platform_command();
33
+ command.arg(program);
34
+ command
35
+ }
36
+
37
+ fn platform_command() -> Command {
38
+ if cfg!(target_os = "windows") {
39
+ return Executor::windows_command();
40
+ }
41
+ Executor::unix_command()
42
+ }
43
+
44
+ fn windows_command() -> Command {
45
+ let mut child_process = Command::new("cmd");
46
+ child_process.arg("/C");
47
+ child_process
48
+ }
49
+
50
+ fn unix_command() -> Command {
51
+ let mut child_process = Command::new("sh");
52
+ child_process.arg("-c");
53
+ child_process
54
+ }
55
+
56
+ fn unwrap(io: &[u8]) -> String {
57
+ str::from_utf8(io)
58
+ .expect("Invalid output")
59
+ .trim()
60
+ .to_string()
61
+ }
62
+ }
@@ -0,0 +1 @@
1
+ pub mod executor;