@isentinel/jest-roblox 0.1.5 → 0.2.0

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.
@@ -14,11 +14,11 @@ import * as crypto from "node:crypto";
14
14
  import { randomUUID } from "node:crypto";
15
15
  import buffer from "node:buffer";
16
16
  import { defuFn } from "defu";
17
- import { execFileSync } from "node:child_process";
18
- import { TraceMap, originalPositionFor, sourceContentFor } from "@jridgewell/trace-mapping";
19
17
  import { getTsconfig } from "get-tsconfig";
18
+ import { TraceMap, originalPositionFor, sourceContentFor } from "@jridgewell/trace-mapping";
20
19
  import hljs from "highlight.js/lib/core";
21
20
  import typescript from "highlight.js/lib/languages/typescript";
21
+ import { execFileSync } from "node:child_process";
22
22
  import { parseJSONC } from "confbox";
23
23
  import { Visitor, parseSync } from "oxc-parser";
24
24
  //#region src/reporter/parser.ts
@@ -412,7 +412,7 @@ const defineConfig = createDefineConfig();
412
412
  const defineProject = createDefineConfig();
413
413
  //#endregion
414
414
  //#region src/test-runner.bundled.luau
415
- var test_runner_bundled_default = "type PatchState__DARKLUA_TYPE_a = {\n robloxSharedExports: any,\n originalGetDataModelService: any,\n Runtime: any,\n originalRequireInternalModule: any,\n}\n\ntype Config__DARKLUA_TYPE_b = {\n jestPath: string?,\n projects: { string }?,\n setupFiles: { string }?,\n setupFilesAfterEnv: { string }?,\n _coverage: boolean?,\n _timing: boolean?,\n}\n\ntype CapturedMessage__DARKLUA_TYPE_c = { message: string, messageType: number, timestamp: number }\nlocal __JEST_MODULES __JEST_MODULES={cache={}, load=function(m)if not __JEST_MODULES.cache[m]then __JEST_MODULES.cache[m]={c=__JEST_MODULES[m]()}end return __JEST_MODULES.cache[m].c end}do function __JEST_MODULES.a()--!strict\n\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\n\nlocal module = {}\n\nfunction module.findInstance(path: string): Instance\n local parts = string.split(path, \"/\")\n\n local success, current = pcall(function()\n return game:FindService(parts[1])\n end)\n assert(success, `Failed to find service {parts[1]}: {current}`)\n\n for i = 2, #parts do\n assert(current, `Failed to find '{parts[i - 1]}' in path {path}`)\n current = current:FindFirstChild(parts[i])\n end\n\n assert(current, `Failed to find instance at path {path}`)\n\n return current\nend\n\nfunction module.getJest(config: { jestPath: string? }): ModuleScript\n local jestPath = config.jestPath\n if jestPath then\n local instance = module.findInstance(jestPath)\n assert(instance, `Failed to find Jest instance at path {jestPath}`)\n assert(instance:IsA(\"ModuleScript\"), `Instance at path {jestPath} is not a ModuleScript`)\n return instance :: ModuleScript\n end\n\n local jestInstance = ReplicatedStorage:FindFirstChild(\"Jest\", true)\n assert(jestInstance, \"Failed to find Jest instance in ReplicatedStorage\")\n assert(jestInstance:IsA(\"ModuleScript\"), \"Jest instance in ReplicatedStorage is not a ModuleScript\")\n return jestInstance\nend\n\nfunction module.findRobloxShared(jestModule: ModuleScript): Instance?\n local parent = jestModule.Parent\n if parent then\n local found = parent:FindFirstChild(\"RobloxShared\") or parent:FindFirstChild(\"jest-roblox-shared\")\n if found then\n return found\n end\n\n if parent.Parent then\n found = parent.Parent:FindFirstChild(\"RobloxShared\") or parent.Parent:FindFirstChild(\"jest-roblox-shared\")\n if found then\n return found\n end\n end\n end\n\n return game:FindFirstChild(\"RobloxShared\", true)\nend\n\nfunction module.findSiblingPackage(jestModule: ModuleScript, ...: string): Instance?\n local parent = jestModule.Parent\n if parent then\n for _, name in { ... } do\n local found = parent:FindFirstChild(name)\n if found then\n return found\n end\n end\n\n if parent.Parent then\n for _, name in { ... } do\n local found = parent.Parent:FindFirstChild(name)\n if found then\n return found\n end\n end\n end\n end\n\n for _, name in { ... } do\n local found = game:FindFirstChild(name, true)\n if found then\n return found\n end\n end\n\n return nil\nend\n\nreturn module\nend function __JEST_MODULES.b()--!strict\n\nlocal function getInstancePath(instance: Instance): string\n local parts = {} :: { string }\n local current: Instance? = instance\n while current and current ~= game do\n table.insert(parts, 1, current.Name)\n current = current.Parent\n end\n\n return table.concat(parts, \"/\")\nend\n\nlocal CoreScriptSyncService = {}\n\nfunction CoreScriptSyncService:GetScriptFilePath(instance: Instance): string\n return getInstancePath(instance)\nend\n\nreturn CoreScriptSyncService\nend function __JEST_MODULES.c()--!strict\n\nlocal function normalizeSnapPath(path: string): string\n return (string.gsub(path, \"%.snap%.lua$\", \".snap.luau\"))\nend\n\nlocal function create(snapshotWrites: { [string]: string })\n local FileSystemService = {}\n\n function FileSystemService:WriteFile(path: string, contents: string)\n snapshotWrites[normalizeSnapPath(path)] = contents\n end\n\n function FileSystemService:CreateDirectories(_path: string) end\n\n function FileSystemService:Exists(path: string): boolean\n return snapshotWrites[normalizeSnapPath(path)] ~= nil\n end\n\n function FileSystemService:Remove(path: string)\n snapshotWrites[normalizeSnapPath(path)] = nil\n end\n\n function FileSystemService:IsRegularFile(path: string): boolean\n return snapshotWrites[normalizeSnapPath(path)] ~= nil\n end\n\n return FileSystemService\nend\n\nreturn create\nend function __JEST_MODULES.d()--!strict\n\nlocal CoreScriptSyncService = __JEST_MODULES.load('b')\nlocal InstanceResolver = __JEST_MODULES.load('a')\n\nlocal module = {}\n\nfunction module.createMockGetDataModelService(snapshotWrites: { [string]: string })\n local FileSystemService = __JEST_MODULES.load('c')(snapshotWrites)\n\n return function(service: string): any\n if service == \"FileSystemService\" then\n return FileSystemService\n elseif service == \"CoreScriptSyncService\" then\n return CoreScriptSyncService\n end\n\n local success, result = pcall(function()\n local service_ = game:GetService(service)\n local _ = service_.Name\n return service_\n end)\n\n return if success then result else nil\n end\nend\n\n\n\n\n\n\n\n\nfunction module.patch(jestModule: ModuleScript, snapshotWrites: { [string]: string }): PatchState__DARKLUA_TYPE_a?\n local mockGetDataModelService = module.createMockGetDataModelService(snapshotWrites)\n\n local robloxSharedInstance = InstanceResolver.findRobloxShared(jestModule)\n if not robloxSharedInstance then\n warn(\"Could not find RobloxShared; snapshot support unavailable\")\n return nil\n end\n\n local robloxSharedExports = (require :: any)(robloxSharedInstance)\n local originalGetDataModelService = robloxSharedExports.getDataModelService\n robloxSharedExports.getDataModelService = mockGetDataModelService\n\n local getDataModelServiceChild = robloxSharedInstance:FindFirstChild(\"getDataModelService\")\n\n local jestRuntimeModule = InstanceResolver.findSiblingPackage(jestModule, \"JestRuntime\", \"jest-runtime\")\n if not jestRuntimeModule then\n warn(\"Could not find JestRuntime; snapshot interception unavailable\")\n return {\n robloxSharedExports = robloxSharedExports,\n originalGetDataModelService = originalGetDataModelService,\n Runtime = nil,\n originalRequireInternalModule = nil,\n }\n end\n\n local Runtime = (require :: any)(jestRuntimeModule)\n local originalRequireInternalModule = Runtime.requireInternalModule\n\n Runtime.requireInternalModule = function(self: any, from: any, to: any, ...): any\n local target = if to ~= nil then to else from\n if getDataModelServiceChild and typeof(target) == \"Instance\" and target == getDataModelServiceChild then\n return mockGetDataModelService\n end\n\n return originalRequireInternalModule(self, from, to, ...)\n end\n\n return {\n robloxSharedExports = robloxSharedExports,\n originalGetDataModelService = originalGetDataModelService,\n Runtime = Runtime,\n originalRequireInternalModule = originalRequireInternalModule,\n }\nend\n\nfunction module.unpatch(state: PatchState__DARKLUA_TYPE_a?)\n if not state then\n return\n end\n\n if state.robloxSharedExports and state.originalGetDataModelService then\n state.robloxSharedExports.getDataModelService = state.originalGetDataModelService\n end\n\n if state.Runtime and state.originalRequireInternalModule then\n state.Runtime.requireInternalModule = state.originalRequireInternalModule\n end\nend\n\nreturn module\nend function __JEST_MODULES.e()--!strict\n\nlocal HttpService = game:GetService(\"HttpService\")\nlocal LogService = game:GetService(\"LogService\")\n\nlocal InstanceResolver = __JEST_MODULES.load('a')\nlocal SnapshotPatch = __JEST_MODULES.load('d')\n\n\n\n\n\n\n\n\n\n\nlocal function fail(err: string)\n return {\n success = false,\n err = err,\n }\nend\n\n\n\nlocal function interceptWriteable(writeable: any, buffer: { CapturedMessage__DARKLUA_TYPE_c }, messageType: number)\n local original = writeable._writeFn\n if typeof(original) ~= \"function\" then\n return function() end\n end\n\n writeable._writeFn = function(data: string)\n table.insert(buffer, {\n message = data,\n messageType = messageType,\n timestamp = os.clock(),\n })\n original(data)\n end\n\n return function()\n writeable._writeFn = original\n end\nend\n\nlocal module = {}\n\nfunction module.run(callingScript: LuaSourceContainer, config: Config__DARKLUA_TYPE_b): (string, string)\n local t0 = os.clock()\n local timingEnabled = config._timing\n local coverageEnabled = config._coverage\n\n local t_findJest0 = os.clock()\n local findSuccess, findValue = pcall(InstanceResolver.getJest, config)\n local t_findJest = os.clock()\n\n if not findSuccess then\n local logSuccess, logHistory = pcall(function()\n return HttpService:JSONEncode(LogService:GetLogHistory())\n end)\n\n return HttpService:JSONEncode(fail(findValue :: any)), if logSuccess then logHistory else \"[]\"\n end\n\n LogService:ClearOutput()\n\n local snapshotWrites: { [string]: string } = {}\n\n local t_patchSnapshot0 = os.clock()\n local patchState = SnapshotPatch.patch(findValue, snapshotWrites)\n local t_patchSnapshot = os.clock()\n\n local t_requireJest0 = os.clock()\n local Jest = (require :: any)(findValue)\n local t_requireJest = os.clock()\n\n -- Intercept Jest's stdout/stderr to capture output synchronously.\n -- Jest writes via process.stdout/stderr (Writeable objects whose _writeFn\n -- defaults to print). Wrapping _writeFn captures messages like\n -- \"No tests found\" that are printed just before exit(1) throws.\n local capturedMessages: { CapturedMessage__DARKLUA_TYPE_c } = {}\n local restoreStdout: (() -> ())?\n local restoreStderr: (() -> ())?\n\n local interceptOk = pcall(function()\n local nodeModules = findValue.Parent.Parent.Parent :: any\n local RobloxShared = (require :: any)(nodeModules[\"@rbxts-js\"].RobloxShared)\n local process = RobloxShared.nodeUtils.process\n\n restoreStdout = interceptWriteable(process.stdout, capturedMessages, 0)\n restoreStderr = interceptWriteable(process.stderr, capturedMessages, 1)\n end)\n\n if not interceptOk then\n restoreStdout = nil\n restoreStderr = nil\n end\n\n local function runTests()\n local t_resolveProjects0 = os.clock()\n local projects = {}\n\n assert(\n config.projects and #config.projects > 0,\n \"No projects configured. Set 'projects' in jest.config.ts or pass --projects.\"\n )\n\n for _, projectPath in config.projects do\n table.insert(projects, InstanceResolver.findInstance(projectPath))\n end\n\n config.projects = {}\n local t_resolveProjects = os.clock()\n\n local t_resolveSetupFiles0 = os.clock()\n if config.setupFiles and #config.setupFiles > 0 then\n local resolved = {}\n\n for _, setupPath in config.setupFiles do\n table.insert(resolved, InstanceResolver.findInstance(setupPath))\n end\n\n config.setupFiles = resolved :: any\n end\n if config.setupFilesAfterEnv and #config.setupFilesAfterEnv > 0 then\n local resolved = {}\n\n for _, setupPath in config.setupFilesAfterEnv do\n table.insert(resolved, InstanceResolver.findInstance(setupPath))\n end\n\n config.setupFilesAfterEnv = resolved :: any\n end\n local t_resolveSetupFiles = os.clock()\n\n -- Strip private keys before Jest.runCLI (safe: single-task execution per VM)\n config._timing = nil :: any\n config._coverage = nil :: any\n\n if coverageEnabled then\n _G.__jest_roblox_cov = {}\n end\n\n local t_jestRunCLI0 = os.clock()\n local jestResult = Jest.runCLI(callingScript, config, projects):expect()\n local t_jestRunCLI = os.clock()\n\n local result: { [string]: any } = {\n success = true,\n value = jestResult,\n }\n\n if timingEnabled then\n result._timing = {\n findJest = t_findJest - t_findJest0,\n patchSnapshot = t_patchSnapshot - t_patchSnapshot0,\n requireJest = t_requireJest - t_requireJest0,\n resolveProjects = t_resolveProjects - t_resolveProjects0,\n resolveSetupFiles = t_resolveSetupFiles - t_resolveSetupFiles0,\n jestRunCLI = t_jestRunCLI - t_jestRunCLI0,\n total = os.clock() - t0,\n }\n end\n\n if next(snapshotWrites) then\n result._snapshotWrites = snapshotWrites\n end\n\n if coverageEnabled then\n result._coverage = _G.__jest_roblox_cov\n end\n\n return result\n end\n\n local jestDone = false\n local runSuccess = false\n local runValue: any = nil\n\n task.spawn(function()\n local ok, val = pcall(runTests)\n jestDone = true\n runSuccess = ok\n runValue = val\n end)\n\n local infiniteYieldMessage: string? = nil\n local watchdogConnection = LogService.MessageOut:Connect(function(message: string, messageType: Enum.MessageType)\n if\n messageType == Enum.MessageType.MessageWarning\n and string.find(message, \"Infinite yield possible\")\n and not infiniteYieldMessage\n then\n infiniteYieldMessage = message\n end\n end)\n\n while not jestDone and not infiniteYieldMessage do\n task.wait(0.1)\n end\n\n watchdogConnection:Disconnect()\n\n if restoreStdout then\n restoreStdout()\n end\n\n if restoreStderr then\n restoreStderr()\n end\n\n if not jestDone and infiniteYieldMessage then\n runSuccess = false\n runValue = \"Infinite yield detected, aborting tests: \" .. infiniteYieldMessage\n end\n\n SnapshotPatch.unpatch(patchState)\n\n local jestResult\n if not runSuccess then\n jestResult = HttpService:JSONEncode(fail(runValue :: any))\n else\n jestResult = HttpService:JSONEncode(runValue)\n end\n\n local logSuccess, logHistory = pcall(function()\n return HttpService:JSONEncode(capturedMessages)\n end)\n\n return jestResult, if logSuccess then logHistory else \"[]\"\nend\n\nreturn module\nend end--!strict\n\nlocal HttpService = game:GetService(\"HttpService\")\n\nlocal Runner = __JEST_MODULES.load('e')\n\nlocal config = HttpService:JSONDecode([=[__CONFIG_JSON__]=])\n\nreturn Runner.run(script, config)\n";
415
+ var test_runner_bundled_default = "type PatchState__DARKLUA_TYPE_a = {\n robloxSharedExports: any,\n originalGetDataModelService: any,\n Runtime: any,\n originalRequireInternalModule: any,\n}\n\ntype Config__DARKLUA_TYPE_b = {\n jestPath: string?,\n projects: { string }?,\n setupFiles: { string }?,\n setupFilesAfterEnv: { string }?,\n _coverage: boolean?,\n _timing: boolean?,\n}\n\ntype CapturedMessage__DARKLUA_TYPE_c = { message: string, messageType: number, timestamp: number }\nlocal __JEST_MODULES={cache={}::any}do do local function __modImpl()--!strict\n\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\n\nlocal module = {}\n\nfunction module.findInstance(path: string): Instance\n local parts = string.split(path, \"/\")\n\n local success, current = pcall(function()\n return game:FindService(parts[1])\n end)\n assert(success, `Failed to find service {parts[1]}: {current}`)\n\n for i = 2, #parts do\n assert(current, `Failed to find '{parts[i - 1]}' in path {path}`)\n current = current:FindFirstChild(parts[i])\n end\n\n assert(current, `Failed to find instance at path {path}`)\n\n return current\nend\n\nfunction module.getJest(config: { jestPath: string? }): ModuleScript\n local jestPath = config.jestPath\n if jestPath then\n local instance = module.findInstance(jestPath)\n assert(instance, `Failed to find Jest instance at path {jestPath}`)\n assert(instance:IsA(\"ModuleScript\"), `Instance at path {jestPath} is not a ModuleScript`)\n return instance :: ModuleScript\n end\n\n local jestInstance = ReplicatedStorage:FindFirstChild(\"Jest\", true)\n assert(jestInstance, \"Failed to find Jest instance in ReplicatedStorage\")\n assert(jestInstance:IsA(\"ModuleScript\"), \"Jest instance in ReplicatedStorage is not a ModuleScript\")\n return jestInstance\nend\n\nfunction module.findRobloxShared(jestModule: ModuleScript): Instance?\n local parent = jestModule.Parent\n if parent then\n local found = parent:FindFirstChild(\"RobloxShared\") or parent:FindFirstChild(\"jest-roblox-shared\")\n if found then\n return found\n end\n\n if parent.Parent then\n found = parent.Parent:FindFirstChild(\"RobloxShared\") or parent.Parent:FindFirstChild(\"jest-roblox-shared\")\n if found then\n return found\n end\n end\n end\n\n return game:FindFirstChild(\"RobloxShared\", true)\nend\n\nfunction module.findSiblingPackage(jestModule: ModuleScript, ...: string): Instance?\n local parent = jestModule.Parent\n if parent then\n for _, name in { ... } do\n local found = parent:FindFirstChild(name)\n if found then\n return found\n end\n end\n\n if parent.Parent then\n for _, name in { ... } do\n local found = parent.Parent:FindFirstChild(name)\n if found then\n return found\n end\n end\n end\n end\n\n for _, name in { ... } do\n local found = game:FindFirstChild(name, true)\n if found then\n return found\n end\n end\n\n return nil\nend\n\nreturn module\nend function __JEST_MODULES.a():typeof(__modImpl())local v=__JEST_MODULES.cache.a if not v then v={c=__modImpl()}__JEST_MODULES.cache.a=v end return v.c end end do local function __modImpl()--!strict\n\nlocal function getInstancePath(instance: Instance): string\n local parts = {} :: { string }\n local current: Instance? = instance\n while current and current ~= game do\n table.insert(parts, 1, current.Name)\n current = current.Parent\n end\n\n return table.concat(parts, \"/\")\nend\n\nlocal CoreScriptSyncService = {}\n\nfunction CoreScriptSyncService:GetScriptFilePath(instance: Instance): string\n return getInstancePath(instance)\nend\n\nreturn CoreScriptSyncService\nend function __JEST_MODULES.b():typeof(__modImpl())local v=__JEST_MODULES.cache.b if not v then v={c=__modImpl()}__JEST_MODULES.cache.b=v end return v.c end end do local function __modImpl()--!strict\n\nlocal function normalizeSnapPath(path: string): string\n return (string.gsub(path, \"%.snap%.lua$\", \".snap.luau\"))\nend\n\nlocal function create(snapshotWrites: { [string]: string })\n local FileSystemService = {}\n\n function FileSystemService:WriteFile(path: string, contents: string)\n snapshotWrites[normalizeSnapPath(path)] = contents\n end\n\n function FileSystemService:CreateDirectories(_path: string) end\n\n function FileSystemService:Exists(path: string): boolean\n return snapshotWrites[normalizeSnapPath(path)] ~= nil\n end\n\n function FileSystemService:Remove(path: string)\n snapshotWrites[normalizeSnapPath(path)] = nil\n end\n\n function FileSystemService:IsRegularFile(path: string): boolean\n return snapshotWrites[normalizeSnapPath(path)] ~= nil\n end\n\n return FileSystemService\nend\n\nreturn create\nend function __JEST_MODULES.c():typeof(__modImpl())local v=__JEST_MODULES.cache.c if not v then v={c=__modImpl()}__JEST_MODULES.cache.c=v end return v.c end end do local function __modImpl()--!strict\n\nlocal CoreScriptSyncService = __JEST_MODULES.b()\nlocal InstanceResolver = __JEST_MODULES.a()\n\nlocal module = {}\n\nfunction module.createMockGetDataModelService(snapshotWrites: { [string]: string })\n local FileSystemService = __JEST_MODULES.c()(snapshotWrites)\n\n return function(service: string): any\n if service == \"FileSystemService\" then\n return FileSystemService\n elseif service == \"CoreScriptSyncService\" then\n return CoreScriptSyncService\n end\n\n local success, result = pcall(function()\n local service_ = game:GetService(service)\n local _ = service_.Name\n return service_\n end)\n\n return if success then result else nil\n end\nend\n\n\n\n\n\n\n\n\nfunction module.patch(jestModule: ModuleScript, snapshotWrites: { [string]: string }): PatchState__DARKLUA_TYPE_a?\n local mockGetDataModelService = module.createMockGetDataModelService(snapshotWrites)\n\n local robloxSharedInstance = InstanceResolver.findRobloxShared(jestModule)\n if not robloxSharedInstance then\n warn(\"Could not find RobloxShared; snapshot support unavailable\")\n return nil\n end\n\n local robloxSharedExports = (require :: any)(robloxSharedInstance)\n local originalGetDataModelService = robloxSharedExports.getDataModelService\n robloxSharedExports.getDataModelService = mockGetDataModelService\n\n local getDataModelServiceChild = robloxSharedInstance:FindFirstChild(\"getDataModelService\")\n\n local jestRuntimeModule = InstanceResolver.findSiblingPackage(jestModule, \"JestRuntime\", \"jest-runtime\")\n if not jestRuntimeModule then\n warn(\"Could not find JestRuntime; snapshot interception unavailable\")\n return {\n robloxSharedExports = robloxSharedExports,\n originalGetDataModelService = originalGetDataModelService,\n Runtime = nil,\n originalRequireInternalModule = nil,\n }\n end\n\n local Runtime = (require :: any)(jestRuntimeModule)\n local originalRequireInternalModule = Runtime.requireInternalModule\n\n Runtime.requireInternalModule = function(self: any, from: any, to: any, ...): any\n local target = if to ~= nil then to else from\n if getDataModelServiceChild and typeof(target) == \"Instance\" and target == getDataModelServiceChild then\n return mockGetDataModelService\n end\n\n return originalRequireInternalModule(self, from, to, ...)\n end\n\n return {\n robloxSharedExports = robloxSharedExports,\n originalGetDataModelService = originalGetDataModelService,\n Runtime = Runtime,\n originalRequireInternalModule = originalRequireInternalModule,\n }\nend\n\nfunction module.unpatch(state: PatchState__DARKLUA_TYPE_a?)\n if not state then\n return\n end\n\n if state.robloxSharedExports and state.originalGetDataModelService then\n state.robloxSharedExports.getDataModelService = state.originalGetDataModelService\n end\n\n if state.Runtime and state.originalRequireInternalModule then\n state.Runtime.requireInternalModule = state.originalRequireInternalModule\n end\nend\n\nreturn module\nend function __JEST_MODULES.d():typeof(__modImpl())local v=__JEST_MODULES.cache.d if not v then v={c=__modImpl()}__JEST_MODULES.cache.d=v end return v.c end end do local function __modImpl()--!strict\n\nlocal HttpService = game:GetService(\"HttpService\")\nlocal LogService = game:GetService(\"LogService\")\n\nlocal InstanceResolver = __JEST_MODULES.a()\nlocal SnapshotPatch = __JEST_MODULES.d()\n\n\n\n\n\n\n\n\n\n\nlocal function fail(err: string)\n return {\n success = false,\n err = err,\n }\nend\n\n\n\nlocal function interceptWriteable(writeable: any, buffer: { CapturedMessage__DARKLUA_TYPE_c }, messageType: number)\n local original = writeable._writeFn\n if typeof(original) ~= \"function\" then\n return function() end\n end\n\n writeable._writeFn = function(data: string)\n table.insert(buffer, {\n message = data,\n messageType = messageType,\n timestamp = os.clock(),\n })\n original(data)\n end\n\n return function()\n writeable._writeFn = original\n end\nend\n\nlocal module = {}\n\nfunction module.run(callingScript: LuaSourceContainer, config: Config__DARKLUA_TYPE_b): (string, string)\n local t0 = os.clock()\n local timingEnabled = config._timing\n local coverageEnabled = config._coverage\n\n local t_findJest0 = os.clock()\n local findSuccess, findValue = pcall(InstanceResolver.getJest, config)\n local t_findJest = os.clock()\n\n if not findSuccess then\n local logSuccess, logHistory = pcall(function()\n return HttpService:JSONEncode(LogService:GetLogHistory())\n end)\n\n return HttpService:JSONEncode(fail(findValue :: any)), if logSuccess then logHistory else \"[]\"\n end\n\n LogService:ClearOutput()\n\n local snapshotWrites: { [string]: string } = {}\n\n local t_patchSnapshot0 = os.clock()\n local patchState = SnapshotPatch.patch(findValue, snapshotWrites)\n local t_patchSnapshot = os.clock()\n\n local t_requireJest0 = os.clock()\n local Jest = (require :: any)(findValue)\n local t_requireJest = os.clock()\n\n -- Intercept Jest's stdout/stderr to capture output synchronously.\n -- Jest writes via process.stdout/stderr (Writeable objects whose _writeFn\n -- defaults to print). Wrapping _writeFn captures messages like\n -- \"No tests found\" that are printed just before exit(1) throws.\n local capturedMessages: { CapturedMessage__DARKLUA_TYPE_c } = {}\n local restoreStdout: (() -> ())?\n local restoreStderr: (() -> ())?\n\n local interceptOk = pcall(function()\n local nodeModules = findValue.Parent.Parent.Parent :: any\n local RobloxShared = (require :: any)(nodeModules[\"@rbxts-js\"].RobloxShared)\n local process = RobloxShared.nodeUtils.process\n\n restoreStdout = interceptWriteable(process.stdout, capturedMessages, 0)\n restoreStderr = interceptWriteable(process.stderr, capturedMessages, 1)\n end)\n\n if not interceptOk then\n restoreStdout = nil\n restoreStderr = nil\n end\n\n local function runTests()\n local t_resolveProjects0 = os.clock()\n local projects = {}\n\n assert(\n config.projects and #config.projects > 0,\n \"No projects configured. Set 'projects' in jest.config.ts or pass --projects.\"\n )\n\n for _, projectPath in config.projects do\n table.insert(projects, InstanceResolver.findInstance(projectPath))\n end\n\n config.projects = {}\n local t_resolveProjects = os.clock()\n\n local t_resolveSetupFiles0 = os.clock()\n if config.setupFiles and #config.setupFiles > 0 then\n local resolved = {}\n\n for _, setupPath in config.setupFiles do\n table.insert(resolved, InstanceResolver.findInstance(setupPath))\n end\n\n config.setupFiles = resolved :: any\n end\n if config.setupFilesAfterEnv and #config.setupFilesAfterEnv > 0 then\n local resolved = {}\n\n for _, setupPath in config.setupFilesAfterEnv do\n table.insert(resolved, InstanceResolver.findInstance(setupPath))\n end\n\n config.setupFilesAfterEnv = resolved :: any\n end\n local t_resolveSetupFiles = os.clock()\n\n -- Strip private keys before Jest.runCLI (safe: single-task execution per VM)\n config._timing = nil :: any\n config._coverage = nil :: any\n\n if coverageEnabled then\n _G.__jest_roblox_cov = {}\n end\n\n local t_jestRunCLI0 = os.clock()\n local jestResult = Jest.runCLI(callingScript, config, projects):expect()\n local t_jestRunCLI = os.clock()\n\n local result: { [string]: any } = {\n success = true,\n value = jestResult,\n }\n\n if timingEnabled then\n result._timing = {\n findJest = t_findJest - t_findJest0,\n patchSnapshot = t_patchSnapshot - t_patchSnapshot0,\n requireJest = t_requireJest - t_requireJest0,\n resolveProjects = t_resolveProjects - t_resolveProjects0,\n resolveSetupFiles = t_resolveSetupFiles - t_resolveSetupFiles0,\n jestRunCLI = t_jestRunCLI - t_jestRunCLI0,\n total = os.clock() - t0,\n }\n end\n\n if next(snapshotWrites) then\n result._snapshotWrites = snapshotWrites\n end\n\n if coverageEnabled then\n result._coverage = _G.__jest_roblox_cov\n end\n\n return result\n end\n\n local jestDone = false\n local runSuccess = false\n local runValue: any = nil\n\n task.spawn(function()\n local ok, val = pcall(runTests)\n jestDone = true\n runSuccess = ok\n runValue = val\n end)\n\n local infiniteYieldMessage: string? = nil\n local watchdogConnection = LogService.MessageOut:Connect(function(message: string, messageType: Enum.MessageType)\n if\n messageType == Enum.MessageType.MessageWarning\n and string.find(message, \"Infinite yield possible\")\n and not infiniteYieldMessage\n then\n infiniteYieldMessage = message\n end\n end)\n\n while not jestDone and not infiniteYieldMessage do\n task.wait(0.1)\n end\n\n watchdogConnection:Disconnect()\n\n if restoreStdout then\n restoreStdout()\n end\n\n if restoreStderr then\n restoreStderr()\n end\n\n if not jestDone and infiniteYieldMessage then\n runSuccess = false\n runValue = \"Infinite yield detected, aborting tests: \" .. infiniteYieldMessage\n end\n\n SnapshotPatch.unpatch(patchState)\n\n local jestResult\n if not runSuccess then\n jestResult = HttpService:JSONEncode(fail(runValue :: any))\n else\n jestResult = HttpService:JSONEncode(runValue)\n end\n\n local logSuccess, logHistory = pcall(function()\n return HttpService:JSONEncode(capturedMessages)\n end)\n\n return jestResult, if logSuccess then logHistory else \"[]\"\nend\n\nreturn module\nend function __JEST_MODULES.e():typeof(__modImpl())local v=__JEST_MODULES.cache.e if not v then v={c=__modImpl()}__JEST_MODULES.cache.e=v end return v.c end end end--!strict\n\nlocal HttpService = game:GetService(\"HttpService\")\n\nlocal Runner = __JEST_MODULES.e()\n\nlocal config = HttpService:JSONDecode([=[__CONFIG_JSON__]=])\n\nreturn Runner.run(script, config)\n";
416
416
  //#endregion
417
417
  //#region src/test-script.ts
418
418
  function buildJestArgv(options) {
@@ -824,59 +824,6 @@ function resolveFunctionValues(config) {
824
824
  return resolved;
825
825
  }
826
826
  //#endregion
827
- //#region src/utils/rojo-tree.ts
828
- function resolveNestedProjects(tree, rootDirectory) {
829
- return resolveTree(tree, rootDirectory, rootDirectory, /* @__PURE__ */ new Set());
830
- }
831
- function collectPaths(node, result) {
832
- for (const [key, value] of Object.entries(node)) if (key === "$path" && typeof value === "string") result.push(value.replaceAll("\\", "/"));
833
- else if (typeof value === "object" && !Array.isArray(value) && !key.startsWith("$")) collectPaths(value, result);
834
- }
835
- function inlineNestedProject(projectPath, currentDirectory, originalRoot, visited) {
836
- const chain = new Set(visited);
837
- chain.add(projectPath);
838
- let content;
839
- try {
840
- content = readFileSync(projectPath, "utf-8");
841
- } catch (err) {
842
- const relativePath = relative(currentDirectory, projectPath);
843
- throw new Error(`Could not read nested Rojo project: ${relativePath}`, { cause: err });
844
- }
845
- return resolveTree(JSON.parse(content).tree, dirname(projectPath), originalRoot, chain);
846
- }
847
- function resolveRootRelativePath(currentDirectory, value, originalRoot) {
848
- return relative(originalRoot, join(currentDirectory, value)).replaceAll("\\", "/");
849
- }
850
- function resolveTree(node, currentDirectory, originalRoot, visited) {
851
- const resolved = {};
852
- for (const [key, value] of Object.entries(node)) {
853
- if (key === "$path" && typeof value === "string" && value.endsWith(".project.json")) {
854
- const projectPath = join(currentDirectory, value);
855
- if (visited.has(projectPath)) throw new Error(`Circular project reference: ${value}`);
856
- const innerTree = inlineNestedProject(projectPath, currentDirectory, originalRoot, visited);
857
- for (const [innerKey, innerValue] of Object.entries(innerTree)) resolved[innerKey] = innerValue;
858
- continue;
859
- }
860
- if (key === "$path" && typeof value === "string") {
861
- resolved[key] = resolveRootRelativePath(currentDirectory, value, originalRoot);
862
- continue;
863
- }
864
- if (key.startsWith("$") || typeof value !== "object" || Array.isArray(value)) {
865
- resolved[key] = value;
866
- continue;
867
- }
868
- resolved[key] = resolveTree(value, currentDirectory, originalRoot, visited);
869
- }
870
- return resolved;
871
- }
872
- //#endregion
873
- //#region src/types/rojo.ts
874
- const rojoProjectSchema = type({
875
- "name": "string",
876
- "servePort?": "number.integer",
877
- "tree": "object"
878
- }).as();
879
- //#endregion
880
827
  //#region src/utils/normalize-windows-path.ts
881
828
  const DRIVE_LETTER_START_REGEX = /^[A-Za-z]:\//;
882
829
  function normalizeWindowsPath(input = "") {
@@ -2405,6 +2352,59 @@ function buildMappings(tree, prefix) {
2405
2352
  return mappings;
2406
2353
  }
2407
2354
  //#endregion
2355
+ //#region src/types/rojo.ts
2356
+ const rojoProjectSchema = type({
2357
+ "name": "string",
2358
+ "servePort?": "number.integer",
2359
+ "tree": "object"
2360
+ }).as();
2361
+ //#endregion
2362
+ //#region src/utils/rojo-tree.ts
2363
+ function resolveNestedProjects(tree, rootDirectory) {
2364
+ return resolveTree(tree, rootDirectory, rootDirectory, /* @__PURE__ */ new Set());
2365
+ }
2366
+ function collectPaths(node, result) {
2367
+ for (const [key, value] of Object.entries(node)) if (key === "$path" && typeof value === "string") result.push(value.replaceAll("\\", "/"));
2368
+ else if (typeof value === "object" && !Array.isArray(value) && !key.startsWith("$")) collectPaths(value, result);
2369
+ }
2370
+ function inlineNestedProject(projectPath, currentDirectory, originalRoot, visited) {
2371
+ const chain = new Set(visited);
2372
+ chain.add(projectPath);
2373
+ let content;
2374
+ try {
2375
+ content = readFileSync(projectPath, "utf-8");
2376
+ } catch (err) {
2377
+ const relativePath = relative(currentDirectory, projectPath);
2378
+ throw new Error(`Could not read nested Rojo project: ${relativePath}`, { cause: err });
2379
+ }
2380
+ return resolveTree(JSON.parse(content).tree, dirname(projectPath), originalRoot, chain);
2381
+ }
2382
+ function resolveRootRelativePath(currentDirectory, value, originalRoot) {
2383
+ return relative(originalRoot, join(currentDirectory, value)).replaceAll("\\", "/");
2384
+ }
2385
+ function resolveTree(node, currentDirectory, originalRoot, visited) {
2386
+ const resolved = {};
2387
+ for (const [key, value] of Object.entries(node)) {
2388
+ if (key === "$path" && typeof value === "string" && value.endsWith(".project.json")) {
2389
+ const projectPath = join(currentDirectory, value);
2390
+ if (visited.has(projectPath)) throw new Error(`Circular project reference: ${value}`);
2391
+ const innerTree = inlineNestedProject(projectPath, currentDirectory, originalRoot, visited);
2392
+ for (const [innerKey, innerValue] of Object.entries(innerTree)) resolved[innerKey] = innerValue;
2393
+ continue;
2394
+ }
2395
+ if (key === "$path" && typeof value === "string") {
2396
+ resolved[key] = resolveRootRelativePath(currentDirectory, value, originalRoot);
2397
+ continue;
2398
+ }
2399
+ if (key.startsWith("$") || typeof value !== "object" || Array.isArray(value)) {
2400
+ resolved[key] = value;
2401
+ continue;
2402
+ }
2403
+ resolved[key] = resolveTree(value, currentDirectory, originalRoot, visited);
2404
+ }
2405
+ return resolved;
2406
+ }
2407
+ //#endregion
2408
2408
  //#region src/executor.ts
2409
2409
  function isLuauProject(testFiles, tsconfigMappings) {
2410
2410
  if (tsconfigMappings.length > 0) return false;
@@ -2436,6 +2436,21 @@ function resolveAllTsconfigMappings(projectRoot) {
2436
2436
  mappings.sort((a, b) => b.outDir.length - a.outDir.length);
2437
2437
  return mappings;
2438
2438
  }
2439
+ function resolveTsconfigDirectories(projectRoot) {
2440
+ const tsconfig = getTsconfig(projectRoot, "tsconfig.lib.json") ?? getTsconfig(projectRoot);
2441
+ const tsconfigDirectory = tsconfig !== null ? path$1.dirname(path$1.resolve(tsconfig.path)) : void 0;
2442
+ const resolvedRoot = path$1.resolve(projectRoot);
2443
+ if (!(tsconfigDirectory?.startsWith(resolvedRoot) === true) || tsconfig?.config.compilerOptions === void 0) return {
2444
+ outDir: void 0,
2445
+ rootDir: void 0
2446
+ };
2447
+ const outDirectory = tsconfig.config.compilerOptions.outDir ?? "out";
2448
+ const rootDirectory = tsconfig.config.compilerOptions.rootDir ?? "src";
2449
+ return {
2450
+ outDir: normalizeDirectoryPath(outDirectory),
2451
+ rootDir: normalizeDirectoryPath(rootDirectory)
2452
+ };
2453
+ }
2439
2454
  function formatExecuteOutput(options) {
2440
2455
  const { config, result, sourceMapper, timing, version } = options;
2441
2456
  if (config.silent) return "";
@@ -3082,4 +3097,4 @@ function writeGameOutput(filePath, entries) {
3082
3097
  fs$1.writeFileSync(absolutePath, JSON.stringify(entries, null, 2));
3083
3098
  }
3084
3099
  //#endregion
3085
- export { hashBuffer as A, extractJsonFromOutput as B, resolveNestedProjects as C, createStudioBackend as D, StudioBackend as E, VALID_BACKENDS as F, defineConfig as I, defineProject as L, generateTestScript as M, DEFAULT_CONFIG as N, OpenCloudBackend as O, ROOT_ONLY_KEYS as P, isValidBackend as R, collectPaths as S, resolveConfig as T, parseJestOutput as V, formatResult as _, formatAnnotations as a, formatBanner as b, execute as c, findFormatterOptions as d, formatJson as f, formatMultiProjectResult as g, formatFailure as h, runTypecheck as i, buildJestArgv as j, createOpenCloudBackend as k, formatExecuteOutput as l, formatAgentMultiProject as m, parseGameOutput as n, formatJobSummary as o, writeJsonFile as p, writeGameOutput as r, resolveGitHubActionsOptions as s, formatGameOutputNotice as t, loadCoverageManifest as u, formatTestSummary as v, loadConfig$1 as w, rojoProjectSchema as x, formatTypecheckSummary as y, LuauScriptError as z };
3100
+ export { createOpenCloudBackend as A, LuauScriptError as B, formatTypecheckSummary as C, StudioBackend as D, resolveConfig as E, ROOT_ONLY_KEYS as F, parseJestOutput as H, VALID_BACKENDS as I, defineConfig as L, buildJestArgv as M, generateTestScript as N, createStudioBackend as O, DEFAULT_CONFIG as P, defineProject as R, formatTestSummary as S, loadConfig$1 as T, extractJsonFromOutput as V, writeJsonFile as _, formatAnnotations as a, formatMultiProjectResult as b, execute as c, resolveTsconfigDirectories as d, collectPaths as f, formatJson as g, findFormatterOptions as h, runTypecheck as i, hashBuffer as j, OpenCloudBackend as k, formatExecuteOutput as l, rojoProjectSchema as m, parseGameOutput as n, formatJobSummary as o, resolveNestedProjects as p, writeGameOutput as r, resolveGitHubActionsOptions as s, formatGameOutputNotice as t, loadCoverageManifest as u, formatAgentMultiProject as v, formatBanner as w, formatResult as x, formatFailure as y, isValidBackend as z };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { B as extractJsonFromOutput, D as createStudioBackend, E as StudioBackend, I as defineConfig, L as defineProject, M as generateTestScript, N as DEFAULT_CONFIG, O as OpenCloudBackend, P as ROOT_ONLY_KEYS, T as resolveConfig, V as parseJestOutput, _ as formatResult, a as formatAnnotations, c as execute, f as formatJson, h as formatFailure, i as runTypecheck, j as buildJestArgv, k as createOpenCloudBackend, l as formatExecuteOutput, n as parseGameOutput, o as formatJobSummary, p as writeJsonFile, r as writeGameOutput, t as formatGameOutputNotice, v as formatTestSummary, w as loadConfig } from "./game-output-71ciORUU.mjs";
1
+ import { A as createOpenCloudBackend, D as StudioBackend, E as resolveConfig, F as ROOT_ONLY_KEYS, H as parseJestOutput, L as defineConfig, M as buildJestArgv, N as generateTestScript, O as createStudioBackend, P as DEFAULT_CONFIG, R as defineProject, S as formatTestSummary, T as loadConfig, V as extractJsonFromOutput, _ as writeJsonFile, a as formatAnnotations, c as execute, g as formatJson, i as runTypecheck, k as OpenCloudBackend, l as formatExecuteOutput, n as parseGameOutput, o as formatJobSummary, r as writeGameOutput, t as formatGameOutputNotice, x as formatResult, y as formatFailure } from "./game-output-C0KykXIi.mjs";
2
2
  export { DEFAULT_CONFIG, OpenCloudBackend, ROOT_ONLY_KEYS, StudioBackend, buildJestArgv, createOpenCloudBackend, createStudioBackend, defineConfig, defineProject, execute, extractJsonFromOutput, formatAnnotations, formatExecuteOutput, formatFailure, formatGameOutputNotice, formatJobSummary, formatJson, formatResult, formatTestSummary, generateTestScript, loadConfig, parseGameOutput, parseJestOutput, resolveConfig, runTypecheck, writeGameOutput, writeJsonFile };
Binary file