diff --git a/.apify/test.mock.json b/.apify/test.mock.json
new file mode 100644
index 0000000..35390cd
--- /dev/null
+++ b/.apify/test.mock.json
@@ -0,0 +1,14 @@
+{
+ "Name": "Test Mock",
+ "Endpoint": "/test",
+ "Method": "GET",
+ "Responses": [
+ {
+ "Condition": "default",
+ "StatusCode": 200,
+ "ResponseTemplate": {
+ "message": "I'm running out of ideas."
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Apify.csproj b/Apify.csproj
index bc80be4..0602121 100644
--- a/Apify.csproj
+++ b/Apify.csproj
@@ -26,13 +26,14 @@
-
+
+
@@ -49,5 +50,4 @@
-
diff --git a/Commands/AboutCommand.cs b/Commands/AboutCommand.cs
index 5f15d0b..b0f6417 100644
--- a/Commands/AboutCommand.cs
+++ b/Commands/AboutCommand.cs
@@ -2,6 +2,7 @@
using System.Reflection;
using Apify.Services;
using Apify.Utils;
+using Jint;
namespace Apify.Commands
@@ -31,6 +32,25 @@ public AboutCommand() : base("about", "Getting application information")
private Task ExecuteAsync()
{
+
+
+
+ // Find your resource name here
+ var expresso = new DynamicScriptingManager();
+
+// Add a fake window object to the JS global scope
+
+
+
+ var result = expresso.Compile("faker.number.int({ min: 1, max: 100 })");
+
+ Console.WriteLine(result.ToString());
+
+
+
+ //Console.WriteLine(engine.Evaluate("Config.MockServer").ToString());
+
+ return Task.CompletedTask;
ConsoleHelper.WriteHeader("About Apify");
Console.WriteLine("A robust and powerful CLI tool for testing APIs and a mock server.");
Console.WriteLine();
diff --git a/Commands/CallCommand.cs b/Commands/CallCommand.cs
index 68a1aa4..f17d86f 100644
--- a/Commands/CallCommand.cs
+++ b/Commands/CallCommand.cs
@@ -140,7 +140,7 @@ private async Task ExecuteRunCommand(CallCommandOptions options)
var assertionExecutor = new AssertionExecutor(response, requestSchema);
- var testResults = await assertionExecutor.RunAsync(requestSchema.Tests ?? new List());
+ var testResults = await assertionExecutor.RunAsync(requestSchema.Tests ?? new List(), configService.LoadEnvironment(envName));
apiExecutor.DisplayTestStats(testResults);
apiExecutor.DisplayTestResults(testResults);
diff --git a/Commands/CreateMockCommand.cs b/Commands/CreateMockCommand.cs
index 2f3970f..3be2f51 100644
--- a/Commands/CreateMockCommand.cs
+++ b/Commands/CreateMockCommand.cs
@@ -192,8 +192,7 @@ private Task GatherMockApiInformation(CreateMockCommandOpt
// Basic mock API information
name = ConsoleHelper.PromptInput("Mock API name (e.g., Get User)");
endpoint = ConsoleHelper.PromptInput("Endpoint path (e.g., /api/users/1 or /users):");
- method = ConsoleHelper.PromptChoice("HTTP method:", ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"
- ]);
+ method = ConsoleHelper.PromptChoice("HTTP method:", ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
}
@@ -259,10 +258,10 @@ private Task GatherMockApiInformation(CreateMockCommandOpt
while (true)
{
- string headerName = ConsoleHelper.PromptInput("Header name (e.g., Cache-Control):", required:false);
+ string headerName = ConsoleHelper.PromptInput("Header name (e.g., Cache-Control)", required:false);
if (string.IsNullOrWhiteSpace(headerName)) break;
- string headerValue = ConsoleHelper.PromptInput($"Value for {headerName}:");
+ string headerValue = ConsoleHelper.PromptInput($"Value for {headerName}");
headers[headerName] = headerValue;
}
}
@@ -274,7 +273,7 @@ private Task GatherMockApiInformation(CreateMockCommandOpt
{
while (true)
{
- string delayStr = ConsoleHelper.PromptInput("Delay in milliseconds (e.g., 500):");
+ string delayStr = ConsoleHelper.PromptInput("Delay in milliseconds (e.g., 500)");
if (int.TryParse(delayStr, out var delay) && delay >= 0)
{
@@ -321,7 +320,7 @@ private int PromptForStatusCode()
{
while (true)
{
- int customCode = ConsoleHelper.PromptInput("Enter custom status code (100-599):");
+ int customCode = ConsoleHelper.PromptInput("Enter custom status code (100-599)");
if (customCode is >= 100 and <= 599)
{
return customCode;
diff --git a/Commands/CreateRequestCommand.cs b/Commands/CreateRequestCommand.cs
index f2b7844..5662719 100644
--- a/Commands/CreateRequestCommand.cs
+++ b/Commands/CreateRequestCommand.cs
@@ -34,7 +34,7 @@ public class CreateRequestCommand: Command
var urlOption = new Option(
"--url",
() => "",
- "URL for the request (e.g., {{baseUrl}}/users/{{userId}} or https://api.example.com/users)"
+ "URL for the request (e.g., {{env.baseUrl}}/users/{{vars.userId}} or https://api.example.com/users)"
);
var forceOption = new Option(
@@ -102,7 +102,7 @@ private async Task ExecuteAsync(string filePath, string name, string method, str
name = ConsoleHelper.PromptInput("API request name (e.g., Get User)");
method = ConsoleHelper.PromptChoice("Choose HTTP Method?", ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"
]);
- url = ConsoleHelper.PromptInput("URL (e.g., {{baseUrl}}/users/{{userId}} or https://api.example.com/users)", required: true);
+ url = ConsoleHelper.PromptInput("URL (e.g., {{env.baseUrl}}/users/{{vars.userId}} or https://api.example.com/users)", required: true);
}
diff --git a/Commands/InitCommand.cs b/Commands/InitCommand.cs
index dafebda..0cad44f 100644
--- a/Commands/InitCommand.cs
+++ b/Commands/InitCommand.cs
@@ -220,7 +220,7 @@ private async Task ExecuteAsync(string? name, bool mock, bool force, bool debug)
Tests = [
new AssertionEntity {
Title = "Status code is successful",
- Case = "Assert.Response.StatusCodeIs(200)",
+ Case = "$.assert.equals($.assert.response.getStatusCode(), 200)",
}
]
};
@@ -246,20 +246,20 @@ private async Task ExecuteAsync(string? name, bool mock, bool force, bool debug)
{
Json = new JObject
{
- { "name", "{{expr|> Faker.Name.FirstName()}}" },
- { "job", "{{expr|> Faker.Name.JobTitle()}}" }
+ { "name", "{# $.faker.person.fullName() #}" },
+ { "job", "{# $.faker.person.jobTitle() #}" }
}
},
PayloadType = PayloadContentType.Json,
Tests = [
new AssertionEntity {
Title = "Status code is Created",
- Case = "Assert.Response.StatusCodeIs(201)",
+ Case = "$.assert.equals($.response.getStatusCode(), 201)",
},
new AssertionEntity {
Title = "The value of response's name matches the request body",
- Case = "Assert.Equals(Request.Body.Json.name, Response.Json.name)",
+ Case = "$.assert.equals($.request.getBody().json.name, $.response.getJson().name)",
}
]
};
@@ -276,14 +276,14 @@ private async Task ExecuteAsync(string? name, bool mock, bool force, bool debug)
if (mock)
{
- // Create a sample Get User by ID mock definition
+ // Create a sample Get User by ID mock definition
string getUserByIdMockJson = @"{
""Name"": ""Mock User by ID"",
""Method"": ""GET"",
""Endpoint"": ""/api/users/{id}"",
""Responses"": [
{
- ""Condition"": ""path[\""id\""] == \""2\"""",
+ ""Condition"": ""$.path.id == 2"",
""StatusCode"": 200,
""Headers"": {
""X-Apify-Version"": ""{{env.apiKey}}""
@@ -299,8 +299,8 @@ private async Task ExecuteAsync(string? name, bool mock, bool force, bool debug)
""StatusCode"": 200,
""ResponseTemplate"": {
""id"": ""{{path.id}}"",
- ""name"": ""{{expr|> Faker.Name.FirstName()}} {{expr|> Faker.Name.LastName()}}"",
- ""email"": ""{{expr|> Faker.Internet.Email()}}""
+ ""name"": ""{# $.faker.person.fullName() #}"",
+ ""email"": ""{# $.faker.internet.email() #}""
}
}
]
diff --git a/Commands/MockServerCommand.cs b/Commands/MockServerCommand.cs
index 3108dfe..9cbdec2 100644
--- a/Commands/MockServerCommand.cs
+++ b/Commands/MockServerCommand.cs
@@ -12,31 +12,37 @@ public MockServerCommand(): base("server:mock", "Start a mock API server based o
description: "The port on which to run the mock server (on Windows, ports above 1024 may not require admin rights)",
getDefaultValue: () => 0);
- var directoryOption = new Option(
- name: "--directory",
- description: "The directory containing mock definition files",
- getDefaultValue: () => ".apify");
+ var projectDirectoryOption = new Option(
+ name: "--project",
+ description: "The project directory containing mock definition files",
+ getDefaultValue: () => "");
var verboseOption = new Option(
name: "--verbose",
description: "Show detailed output",
getDefaultValue: () => false);
-
+
+ var watchOption = new Option(
+ name: "--watch",
+ description: "Watch for file changes and reload the server automatically");
+ watchOption.AddAlias("-w");
+
AddOption(portOption);
- AddOption(directoryOption);
+ AddOption(projectDirectoryOption);
AddOption(verboseOption);
-
- this.SetHandler(async (port, directory, verbose, debug) =>
+ AddOption(watchOption);
+
+ this.SetHandler(async (port, projectDirectory, verbose, watch, debug) =>
{
- await RunMockServerAsync(port, directory, verbose, debug);
- }, portOption, directoryOption, verboseOption, RootOption.DebugOption);
-
+ await RunMockServerAsync(port, projectDirectory, verbose, watch, debug);
+ }, portOption, projectDirectoryOption, verboseOption, watchOption, RootOption.DebugOption);
+
}
-
- private async Task RunMockServerAsync(int port, string directory, bool verbose, bool debug)
+
+ private async Task RunMockServerAsync(int port, string projectDirectory, bool verbose, bool watch, bool debug)
{
- var mockServer = new MockServerService(directory, debug);
- await mockServer.StartAsync(port, verbose);
+ var mockServer = new MockServerService(projectDirectory, debug);
+ await mockServer.StartAsync(port, verbose, watch);
}
}
}
\ No newline at end of file
diff --git a/Commands/TestsCommand.cs b/Commands/TestsCommand.cs
index 69d167a..b14f816 100644
--- a/Commands/TestsCommand.cs
+++ b/Commands/TestsCommand.cs
@@ -136,7 +136,7 @@ private async Task RunAllTestsAsync(bool verbose, string directory, string? envN
var assertionExecutor = new AssertionExecutor(response, requestSchema);
- var testResults = await assertionExecutor.RunAsync(requestSchema.Tests ?? new List());
+ var testResults = await assertionExecutor.RunAsync(requestSchema.Tests ?? new List(), configService.LoadEnvironment(envName));
totalTestResults.AddTestResults(requestSchema.Url, testResults);
Console.SetCursorPosition(0, cursorPosition);
diff --git a/Models/ResponseDefinitionSchema.cs b/Models/ResponseDefinitionSchema.cs
index 5e48fc0..582fbab 100644
--- a/Models/ResponseDefinitionSchema.cs
+++ b/Models/ResponseDefinitionSchema.cs
@@ -1,3 +1,4 @@
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Apify.Models;
@@ -5,14 +6,32 @@ namespace Apify.Models;
public class ResponseDefinitionSchema
{
+ [JsonProperty("isSuccessful")]
public bool IsSuccessful { get; set; }
+
+ [JsonProperty("statusCode")]
public int StatusCode { get; set; }
+
+ [JsonProperty("headers")]
public Dictionary Headers { get; set; } = new Dictionary();
+
+ [JsonProperty("contentHeaders")]
public Dictionary ContentHeaders { get; set; } = new Dictionary();
+
+ [JsonProperty("contentType")]
public string? ContentType { get; set; }
+
+ [JsonProperty("body")]
public string Body { get; set; } = string.Empty;
+
+
+ [JsonProperty("json")]
public JToken? Json { get; set; }
+
+ [JsonProperty("responseTimeMs")]
public long ResponseTimeMs { get; set; }
+
+ [JsonProperty("errorMessage")]
public string? ErrorMessage { get; set; }
public string GetHeader(string name)
diff --git a/README.md b/README.md
index be22f7f..c19911c 100644
--- a/README.md
+++ b/README.md
@@ -150,7 +150,7 @@ For other platforms, replace `linux-x64` with your target platform:
- macOS: `osx-x64`
- ARM64: `linux-arm64` or `osx-arm64`
-> For documentation, please visit [Apify Documentation](https://apify.dev/docs).
+> For documentation, please visit [Apify Documentation](docs/Apify-Documentation.md).
## Development
To contribute to Apify or build it from source, follow these steps:
diff --git a/Services/ApiExecutor.cs b/Services/ApiExecutor.cs
index 18ac917..003da81 100644
--- a/Services/ApiExecutor.cs
+++ b/Services/ApiExecutor.cs
@@ -149,7 +149,7 @@ public async Task ExecuteRequestAsync(RequestDefinitio
// Include the error message in the response body for test assertion context
response.Body = ex.ToString();
- //response.Body = $"{{\"error\": \"{ex.Message.Replace("\"", "\\\"")}\", \"exception_type\": \"{ex.GetType().Name}\"}}";
+ //response.Body = $"{{\"error\": \"{ex.Message.Evaluate("\"", "\\\"")}\", \"exception_type\": \"{ex.GetType().Name}\"}}";
}
return response;
@@ -295,7 +295,7 @@ public RequestDefinitionSchema ApplyEnvToApiDefinition(RequestDefinitionSchema r
}
}
- apiDefContent = StubManager.Replace(apiDefContent, stubReplacor);
+ apiDefContent = TagInterpolationManager.Evaluate(apiDefContent, stubReplacor);
if (string.IsNullOrEmpty(apiDefContent))
{
@@ -325,6 +325,11 @@ public void DisplayTestResults(TestResults testResults)
if (_options?.Tests == false)
{
+ if (_options.Debug)
+ {
+ ConsoleHelper.WriteWarning("Tests are not enabled, skipping test results display.");
+ }
+
return; // No need to display test results if tests are not enabled
}
diff --git a/Services/AssertionExecutor.cs b/Services/AssertionExecutor.cs
index 3f39ff3..139c70d 100644
--- a/Services/AssertionExecutor.cs
+++ b/Services/AssertionExecutor.cs
@@ -1,5 +1,5 @@
using Apify.Models;
-using DynamicExpresso;
+using Apify.Utils;
namespace Apify.Services;
@@ -7,6 +7,7 @@ public class AssertionExecutor
{
private readonly ResponseDefinitionSchema _responseDefinitionSchema;
private readonly RequestDefinitionSchema _requestDefinitionSchema;
+ private EnvironmentSchema? _environment;
@@ -16,20 +17,40 @@ public AssertionExecutor(ResponseDefinitionSchema responseDefinitionSchema, Requ
_requestDefinitionSchema = requestDefinitionSchema;
}
-
- public TestResults Run(List assertions)
+
+ private TestResults Run(List assertions)
{
- var interpreter = new Interpreter();
+ var scriptMan = new DynamicScriptingManager();
var assertionTracker = new AssertionTracker();
var testResults = new TestResults();
- interpreter.SetVariable("Assert", new Assert(_responseDefinitionSchema, assertionTracker));
- interpreter.SetVariable("Response", _responseDefinitionSchema);
- interpreter.SetVariable("Request", _requestDefinitionSchema);
+ scriptMan.SetVariable("assertionTracker", assertionTracker);
+
+ if (_environment != null)
+ {
+ var envString = JsonHelper.SerializeWithEscapeSpecialChars(_environment.Variables); //JsonConvert.SerializeObject(_environment.Variables);
+ var envExpr = $"JSON.parse('{envString}')";
+ scriptMan.SetPropertyToAppObject("env", envExpr);
+ }
+
+ var jsonReq = JsonHelper.SerializeWithEscapeSpecialChars(_requestDefinitionSchema);
+ var jsonResp = JsonHelper.SerializeWithEscapeSpecialChars(_responseDefinitionSchema);
+
+ scriptMan.ExecuteScriptFromAssembly("Apify.includes.request.js");
+ scriptMan.ExecuteScriptFromAssembly("Apify.includes.response.js");
+ scriptMan.ExecuteScriptFromAssembly("Apify.includes.assert.js");
+ var reqObjExpr = @"new Request('" + jsonReq + @"')";
+ var respObjExpr = @"new Response('" + jsonResp + @"')";
+
+ scriptMan.SetPropertyToAppObject("request", reqObjExpr);
+ scriptMan.SetPropertyToAppObject("response", respObjExpr);
+ scriptMan.SetPropertyToAppObject("assert", "new Assert()");
+
foreach (var assertion in assertions)
{
bool testStatus;
+
if (string.IsNullOrEmpty(assertion.Case))
{
throw new ArgumentException("Assertion expression cannot be null or empty.", nameof(assertion.Case));
@@ -37,7 +58,7 @@ public TestResults Run(List assertions)
try
{
- testStatus = interpreter.Eval(assertion.Case);
+ testStatus = scriptMan.Compile(assertion.Case);
}
catch (Exception ex)
{
@@ -52,8 +73,10 @@ public TestResults Run(List assertions)
return testResults;
}
- public Task RunAsync(List assertions)
+ public Task RunAsync(List assertions, EnvironmentSchema? environment = null)
{
+ _environment = environment;
+
return Task.FromResult(Run(assertions));
}
diff --git a/Services/ConditionEvaluator.cs b/Services/ConditionEvaluator.cs
index 35b4010..7f9e244 100644
--- a/Services/ConditionEvaluator.cs
+++ b/Services/ConditionEvaluator.cs
@@ -1,4 +1,5 @@
using Apify.Utils;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Apify.Services
@@ -8,11 +9,11 @@ namespace Apify.Services
///
public class ConditionEvaluator
{
- private readonly DynamicExpressionManager _dynamicExpression;
+ private readonly DynamicScriptingManager _scriptMan;
public ConditionEvaluator()
{
- _dynamicExpression = new DynamicExpressionManager();
+ _scriptMan = new DynamicScriptingManager();
}
@@ -35,23 +36,29 @@ public bool EvaluateCondition(
try
{
- // Set up parameters for the expression
- _dynamicExpression.GetInterpreter().SetVariable("headers", headers);
- _dynamicExpression.GetInterpreter().SetVariable("body", body);
- _dynamicExpression.GetInterpreter().SetVariable("query", queryParams);
- _dynamicExpression.GetInterpreter().SetVariable("path", pathParams);
+ var headersString = JsonHelper.SerializeWithEscapeSpecialChars(headers);
+ _scriptMan.SetPropertyToAppObject("headers", $"tryParseJson('{headersString}')");
+
+ var bodyString = JsonHelper.SerializeWithEscapeSpecialChars(body);
+ _scriptMan.SetPropertyToAppObject("body", $"tryParseJson('{bodyString}')");
+
+ var queryParamsString = JsonHelper.SerializeWithEscapeSpecialChars(queryParams);
+ _scriptMan.SetPropertyToAppObject("query", $"tryParseJson('{queryParamsString}')");
+
+ var pathParamsString = JsonHelper.SerializeWithEscapeSpecialChars(pathParams);
+ _scriptMan.SetPropertyToAppObject("path", $"tryParseJson('{pathParamsString}')");
// // Add special accessor objects for query parameters and headers
// // For accessing query parameters in a more natural way (q.parameter)
- // _dynamicExpression.GetInterpreter().SetVariable("q", MiscHelper.DictionaryToExpandoObject(queryParams));
+ // _scriptMan.GetInterpreter().SetValue("q", MiscHelper.DictionaryToExpandoObject(queryParams));
//
// // For accessing headers in a more natural way (h.header)
- // _dynamicExpression.GetInterpreter().SetVariable("h", MiscHelper.DictionaryToExpandoObject(headers));
- // _dynamicExpression.GetInterpreter().SetVariable("p", MiscHelper.DictionaryToExpandoObject(pathParams));
+ // _scriptMan.GetInterpreter().SetValue("h", MiscHelper.DictionaryToExpandoObject(headers));
+ // _scriptMan.GetInterpreter().SetValue("p", MiscHelper.DictionaryToExpandoObject(pathParams));
// Evaluate the expression
- var result = _dynamicExpression.Compile(condition);
+ var result = _scriptMan.Compile(condition);
return result;
}
catch (Exception e)
diff --git a/Services/ConfigService.cs b/Services/ConfigService.cs
index 57674fd..093f823 100644
--- a/Services/ConfigService.cs
+++ b/Services/ConfigService.cs
@@ -37,7 +37,7 @@ private string GetConfigFilePath()
// If the config file path is already set, return it
if (_configFilePath != null)
{
- return _configFilePath;
+ return _configFilePath = Path.Combine(_configFilePath, ConfigFileName);
}
// Only use the current working directory (where the command is executed from)
diff --git a/Services/DynamicExpressionManager.cs b/Services/DynamicExpressionManager.cs
deleted file mode 100644
index 87f6113..0000000
--- a/Services/DynamicExpressionManager.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-using DynamicExpresso;
-using System.Text.RegularExpressions;
-
-namespace Apify.Services;
-
-public class DynamicExpressionManager
-{
- private Interpreter? _interpreter;
- private string _exprPattern = @"^ *expr *\|>";
-
- public DynamicExpressionManager()
- {
- _interpreter = new Interpreter(InterpreterOptions.Default);
-
- // Register helper methods to be used in expressions
- _interpreter.SetFunction("int", new Func(s => int.TryParse(s, out int result) ? result : 0));
- _interpreter.SetFunction("Parse", new Func(s => int.TryParse(s, out int result) ? result : 0));
- _interpreter.SetFunction("ToLower", new Func(s => s.ToLower()));
- _interpreter.SetFunction("ToUpper", new Func(s => s.ToUpper()));
- _interpreter.SetFunction("Contains", new Func((source, value) =>
- source.Contains(value, StringComparison.OrdinalIgnoreCase)));
- }
-
- public void SetVariables(Dictionary vars)
- {
- if (_interpreter == null)
- {
- throw new InvalidOperationException("Interpreter is not initialized.");
- }
-
- foreach (var v in vars)
- {
- _interpreter.SetVariable(v.Key, v.Value);
- }
-
- }
-
- public Interpreter GetInterpreter()
- {
- return _interpreter ?? throw new InvalidOperationException("Interpreter is not initialized.");
- }
-
- public void SetFunctions(Dictionary funcs)
- {
- if (_interpreter == null)
- {
- throw new InvalidOperationException("Interpreter is not initialized.");
- }
-
- foreach (var func in funcs)
- {
- _interpreter.SetFunction(func.Key, func.Value);
- }
- }
-
- public DynamicExpressionManager SetFunction(string name, Delegate func)
- {
- if (_interpreter == null)
- {
- throw new InvalidOperationException("Interpreter is not initialized.");
- }
-
- _interpreter.SetFunction(name, func);
-
- return this;
- }
-
- public string Compile(string expr)
- {
- return Compile(expr);
-
- }
-
- public T Compile(string expr)
- {
- expr = expr.Replace("\\", "");
- try
- {
- var result = _interpreter!.Eval(expr);
- return result;
- } catch (Exception)
- {
- return default(T)!; // Return default value for type T
- }
- }
-
- public bool IsEvalExpression(string expr)
- {
- if (string.IsNullOrWhiteSpace(expr)) return false;
-
- var funcPattern = @"^[A-Za-z_][A-Za-z0-9_]*\s*\((.*)?\)$";
-
- if (expr.StartsWith("Faker.") || Regex.IsMatch(expr.Trim(), funcPattern) || Regex.IsMatch(expr.Trim(), _exprPattern))
- {
- return true;
- }
-
- return false;
- }
-
- public string GetExpression(string expr)
- {
- if (_interpreter == null)
- {
- throw new InvalidOperationException("Interpreter is not initialized.");
- }
-
- if (string.IsNullOrWhiteSpace(expr))
- {
- return string.Empty;
- }
-
- if (Regex.IsMatch(expr.Trim(), _exprPattern))
- {
- var pattern = @"^ *expr *\|>(.*)";
- var match = Regex.Match(expr, pattern);
-
- if (!match.Success)
- {
- return string.Empty;
- }
-
- return match.Groups[1].Value.Trim();
- }
-
- return expr.Trim();
- }
-}
\ No newline at end of file
diff --git a/Services/DynamicScriptingManager.cs b/Services/DynamicScriptingManager.cs
new file mode 100644
index 0000000..124f2da
--- /dev/null
+++ b/Services/DynamicScriptingManager.cs
@@ -0,0 +1,149 @@
+using Jint;
+using System.Reflection;
+
+namespace Apify.Services;
+
+public class DynamicScriptingManager
+{
+ private Engine? _interpreter;
+ private Assembly _assembly = Assembly.GetExecutingAssembly();
+
+ public DynamicScriptingManager()
+ {
+ _interpreter = new Engine();
+
+ _interpreter.Execute("window = this;");
+ ExecuteScriptFromAssembly("Apify.includes.app.js");
+ }
+
+ public void SetVariables(Dictionary vars)
+ {
+ if (_interpreter == null)
+ {
+ throw new InvalidOperationException("Interpreter is not initialized.");
+ }
+
+ foreach (var v in vars)
+ {
+ _interpreter.SetValue(v.Key, v.Value);
+ }
+
+ }
+
+ public void SetVariable(string name, object value)
+ {
+ if (_interpreter == null)
+ {
+ throw new InvalidOperationException("Interpreter is not initialized.");
+ }
+
+ _interpreter.SetValue(name, value);
+ }
+
+ public void SetPropertyToAppObject(string name, string expression)
+ {
+ if (_interpreter == null)
+ {
+ throw new InvalidOperationException("Interpreter is not initialized.");
+ }
+
+ _interpreter.Execute($"apify.{name} = {expression};");
+ }
+
+ public Engine GetInterpreter()
+ {
+ return _interpreter ?? throw new InvalidOperationException("Interpreter is not initialized.");
+ }
+
+ public void SetFunctions(Dictionary funcs)
+ {
+ if (_interpreter == null)
+ {
+ throw new InvalidOperationException("Interpreter is not initialized.");
+ }
+
+ foreach (var func in funcs)
+ {
+ _interpreter.SetValue(func.Key, func.Value);
+ }
+ }
+
+ public DynamicScriptingManager SetFunction(string name, Delegate func)
+ {
+ if (_interpreter == null)
+ {
+ throw new InvalidOperationException("Interpreter is not initialized.");
+ }
+
+ _interpreter.SetValue(name, func);
+
+ return this;
+ }
+
+ public void Execute(string expr)
+ {
+ if (_interpreter == null)
+ {
+ throw new InvalidOperationException("Interpreter is not initialized.");
+ }
+
+ if (string.IsNullOrWhiteSpace(expr))
+ {
+ return;
+ }
+
+
+ if (string.IsNullOrWhiteSpace(expr))
+ {
+ return;
+ }
+
+ _interpreter!.Execute(expr);
+ }
+
+ public string Compile(string expr)
+ {
+ return _interpreter?.Evaluate(expr).AsObject().ToString() ?? string.Empty;
+ }
+
+ public T Compile(string expr)
+ {
+ expr = expr.Replace("\\", "");
+ try
+ {
+ var result = _interpreter!.Evaluate(expr);
+ if (result.IsNull() || result.IsUndefined())
+ {
+ return default(T)!; // Return default value for type T
+ }
+
+ return (T)Convert.ChangeType(result.ToObject(), typeof(T))!;
+
+ } catch (Exception)
+ {
+ return default!; // Return default value for type T
+ }
+ }
+
+ public void ExecuteScriptFromAssembly(string resourceName)
+ {
+ if (_interpreter == null)
+ {
+ throw new InvalidOperationException("Interpreter is not initialized.");
+ }
+
+ using (Stream? stream = _assembly.GetManifestResourceStream(resourceName))
+ {
+ if (stream == null)
+ {
+ throw new ArgumentException($"Resource '{resourceName}' not found in assembly.");
+ }
+
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ string js = reader.ReadToEnd();
+ _interpreter.Execute(js);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Services/MockServerService.cs b/Services/MockServerService.cs
index 6f7ff5a..1825c79 100644
--- a/Services/MockServerService.cs
+++ b/Services/MockServerService.cs
@@ -10,7 +10,7 @@ namespace Apify.Services
{
public class MockServerService
{
- private readonly string _mockDirectory;
+ private readonly string _projectDirectory;
private readonly List _mockSchemaDefinitions = [];
private readonly ConfigService _configService;
private readonly ConditionEvaluator _conditionEvaluator = new ConditionEvaluator();
@@ -18,27 +18,69 @@ public class MockServerService
private bool _debug;
private HttpListener? _listener;
private bool _isRunning;
-
- public MockServerService(string mockDirectory, bool debug = false)
+ private FileSystemWatcher? _watcher;
+ private Timer? _reloadDebounceTimer;
+ private readonly object _reloadLock = new object();
+ private int _port;
+ private readonly TaskCompletionSource