--!nonstrict
--[[
	Author: Sceleratis (Davey_Bones)
	Name: Server-NoPlayerCollide
	Description: Adds :collisions, :nocollide, :resetcollisions and :setdefaultcollision commands;
				 enables/disables character-to-character collisions per player

	Place in a ModuleScript under Adonis_Loader > Config > Plugins, named "Server-PlayerCollisions"
--]]

local default_collidable_state: boolean = true --// Whether players can collide by default (on join)

return function(Vargs)
	local server = Vargs.Server
	local service = Vargs.Service

	local Settings = server.Settings
	local Commands = server.Commands
	local Functions = server.Functions
	local Remote = server.Remote
	local Admin = server.Admin

	local PhysicsService: PhysicsService = service.PhysicsService

	if PhysicsService:GetMaxCollisionGroups() < #PhysicsService:GetRegisteredCollisionGroups() + 2 then
		warn("PLAYERCOLLISIONS PLUGIN ABORTED; TOO MANY EXISTING COLLISION GROUPS ARE IN THE GAME")
		return
	end

	local COLLISION_GROUP_1_NAME = "Adonis_CollidablePlayers"
	local COLLISION_GROUP_2_NAME = "Adonis_NonCollidablePlayers"

	local playerCollidableStates: {[Player]: boolean} = {}

	local function applyCharPartCGroup(plr: Player, obj: Instance)
		if obj:IsA("BasePart") or obj:IsA('UnionOperation') then
			obj.CollisionGroup = if playerCollidableStates[plr] then COLLISION_GROUP_1_NAME
				else COLLISION_GROUP_2_NAME
		end
	end

	local function setPlayerCollidable(plr: Player, collidable: boolean)
		if playerCollidableStates[plr] == collidable then
			return
		end
		playerCollidableStates[plr] = collidable
		if plr.Character then
			for _, obj in plr.Character:GetDescendants() do
				applyCharPartCGroup(plr,obj)
			end
		end
		if Settings.CommandFeedback then
			Remote.MakeGui(plr, "Notification", {
				Title = "Collisions "..(collidable and "Enabled" or "Disabled");
				Message = string.format(
					"Your character will %s collide with other players' characters.",
					collidable and "now be able to" or "no longer"
				);
				Time = 5;
			})
		end
	end

	PhysicsService:RegisterCollisionGroup(COLLISION_GROUP_1_NAME)
	PhysicsService:RegisterCollisionGroup(COLLISION_GROUP_2_NAME)
	PhysicsService:CollisionGroupSetCollidable(COLLISION_GROUP_1_NAME, COLLISION_GROUP_1_NAME, true)
	PhysicsService:CollisionGroupSetCollidable(COLLISION_GROUP_1_NAME, COLLISION_GROUP_2_NAME, false)
	PhysicsService:CollisionGroupSetCollidable(COLLISION_GROUP_2_NAME, COLLISION_GROUP_2_NAME, false)

	service.Events.CharacterAdded:Connect(function(plr: Player, char: Model)
		if playerCollidableStates[plr] == nil then
			playerCollidableStates[plr] = default_collidable_state
		end

		char.DescendantAdded:Connect(function(obj)
			applyCharPartCGroup(plr,obj)
		end)
		for _, obj in char:GetDescendants() do
			applyCharPartCGroup(plr,obj)
		end
	end)

	service.Events.PlayerRemoving:Connect(function(plr: Player)
		playerCollidableStates[plr] = nil
	end)

	Commands.EnablePlayerCollision = {
		Prefix = Settings.Prefix;
		Commands = {"collisions", "collision", "cancollide", "collide"};
		Args = {"player (default: '"..Settings.SpecialPrefix.."all')"};
		Description = "Allows the target player's character to collide with other players' characters";
		AdminLevel = "Moderators";
		Function = function(plr: Player, args: {string})
			for _, v in if args[1] then service.GetPlayers(plr, args[1]) else service.GetPlayers() do
				setPlayerCollidable(v, true)
			end
		end
	}

	Commands.DisablePlayerCollision = {
		Prefix = Settings.Prefix;
		Commands = {"nocollide", "nocollision", "nocollisions", "cantcollide"};
		Args = {"player (default: '"..Settings.SpecialPrefix.."all')"};
		Description = "Opposite of "..Settings.Prefix.."collisions; allows players' characters to pass through each other";
		AdminLevel = "Moderators";
		Function = function(plr: Player, args: {string})
			for _, v in if args[1] then service.GetPlayers(plr, args[1]) else service.GetPlayers() do
				setPlayerCollidable(v, false)
			end
		end
	}

	Commands.ResetPlayerCollision = {
		Prefix = Settings.Prefix;
		Commands = {"resetcollisions", "resetcollision"};
		Args = {"player (default: '"..Settings.SpecialPrefix.."all')"};
		Description = "Reset the target player character's ability to collide";
		AdminLevel = "Moderators";
		Function = function(plr: Player, args: {string})
			for _, v in if args[1] then service.GetPlayers(plr, args[1]) else service.GetPlayers() do
				setPlayerCollidable(v, default_collidable_state)
			end
		end
	}

	Commands.SetDefaultPlayerCollision = {
		Prefix = Settings.Prefix;
		Commands = {
			--// bruh
			"setdefaultplayercollision",
			"setdefaultplayercollisions",
			"defaultplayercollision",
			"defaultplayercollisions",
			"setdefaultcollision",
			"setdefaultcollisions",
			"defaultcollision",
			"defaultcollisions"
		};
		Args = {"true/false"};
		Description = "Sets whether players' characters are collidable by default (assigned on join)";
		AdminLevel = "Moderators";
		Function = function(plr: Player, args: {string})
			local state = if args[1] and args[1]:lower() == "true" then true
				elseif args[1] and args[1]:lower() == "false" then false
				else nil
			assert(state ~= nil, "Argument #1 invalid or missing (must be 'true'/'false')")

			if default_collidable_state == state then
				Functions.Hint(
					string.format("Player collisions are already %s by default", state and "enabled" or "disabled"),
					{plr}
				)
			else
				default_collidable_state = state
				Functions.Hint(
					string.format("Player collisions are now %s by default", state and "enabled" or "disabled"),
					{plr}
				)
			end
		end
	}

	Admin.CacheCommands()
end


--!strict
--[[
	Author: Sceleratis/Davey_Bones and Expertcoderz
	Description: This plugin will restrict any player selectors
				 (all, others, me, etc.) to a specific admin level only.
				 This plugin can also protect admins with certain
				 levels from being selected using player selectors.

	Place this ModuleScript in Adonis_Loader > Config > Plugins and name it "Server-PlayerFinder Restrictor"
--]]

--// Permissions can be specified by rank number, rank name,
--// or even a list of rank number and/or rank names.
type Permission = string|number|{string|number}

--[[
	RESTRICTION vs. PROTECTION:
		Restriction:	prevent player selectors from being used by
						everyone without RESTRICTION_OVERRIDE_PERM
		Protection:		prevent player selectors from selecting those
						in PROTECTION_ELIGIBLE_PERM unless used by
						admins with PROTECTION_OVERRIDE_PERM

	If someone attempts to use a restricted player selector without
	RESTRICTION_OVERRIDE_PERM, they will get denied immediately from
	running the command. If they use an unrestricted player selector
	that happens to select a protected player, the command may still
	have effect on the other selected players that are non-protected.
]]

--// Players need this permission to use the player selectors:
--// (if nil, equivalent to nobody, i.e. restrict everyone)
--// (if 0, equivalent to everyone, i.e. disable restriction)
local RESTRICTION_OVERRIDE_PERM: Permission? = 300

--// Players with this permission can't be selected using player selectors:
--// (if nil, equivalent to nobody, i.e. disable protection)
--// (if 0, equivalent to everyone, i.e. make player selectors useless)
local PROTECTION_ELIGIBLE_PERM: Permission? = 900

--// Players with this permission may use player selectors on protected levels nonetheless:
--// (if nil, nobody will be allowed to override)
local PROTECTION_OVERRIDE_PERM: Permission? = 900

--// The player selectors to restrict:
local SELECTORS_TO_RESTRICT: {string} = {
	"all",
	"others",
	"nonadmins",
	"admins",
	"$group",
	"%team",
	"team-",
	"group-",
	"#number",
	"radius-",
}

return function(Vargs)
	local server = Vargs.Server
	local service = Vargs.Service

	local Admin = server.Admin
	local Functions = server.Functions

	for _, ind in SELECTORS_TO_RESTRICT do
		local finder = Functions.PlayerFinders[ind]
		if not finder then
			warn("Nonexistent player selector:", ind)
			continue
		end

		local oldFunc = finder.Function
		function finder.Function(msg, plr, par, players, ...)
			local plrLevel = Admin.GetLevel(plr)

			assert(
				RESTRICTION_OVERRIDE_PERM
					and Admin.CheckComLevel(plrLevel, RESTRICTION_OVERRIDE_PERM),
				`You are not allowed to use the player selector '{ind}'`
			)

			oldFunc(msg, plr, par, players, ...)

			if not PROTECTION_ELIGIBLE_PERM
				or (PROTECTION_OVERRIDE_PERM and
					Admin.CheckComLevel(plrLevel, PROTECTION_OVERRIDE_PERM))
			then
				return
			end

			local removedPlayerNames = {}
			for i, selectedPlayer in players do
				if Admin.CheckComLevel(
					Admin.GetLevel(selectedPlayer), PROTECTION_ELIGIBLE_PERM
					)
				then
					table.remove(players, i)
					table.insert(removedPlayerNames, service.FormatPlayer(selectedPlayer))
				end
			end

			local count = #removedPlayerNames
			if count > 0 then
				Functions.Hint(
					`{count} player(s) were deselected due to protection: {table.concat(
						removedPlayerNames, ", "
					)}`,
					{plr}
				)
			end
		end
	end
end


--!strict
--[[
	Original Author: TacoBellSaucePackets
	Original Source: https://devforum.roblox.com/t/adonis-g-api-usage/477194/4
	Description: This plugin will give a certain temporary permission level to private server owners

	Place this ModuleScript in Adonis_Loader > Config > Plugins and name it "Server-PrivateServerOwnerAdmin"
--]]

--// HeadAdmins can do :permadmin, so it's unsafe to to make them head admins unless you change the perm levels for those commands or disable SaveAdmins in the settings
local PRIVATE_SERVER_OWNER_ADMIN_LEVEL: number = 200 --// (admin levels are defined in loader Settings)

return function(Vargs)
	local server = Vargs.Server
	local service = Vargs.Service

	local Admin = server.Admin

	--// If VIP Server is detected trigger code.
	if game.PrivateServerOwnerId ~= 0 then
		service.Events.PlayerAdded:Connect(function(plr: Player)
			if game.PrivateServerOwnerId == plr.UserId and PRIVATE_SERVER_OWNER_ADMIN_LEVEL > Admin.GetLevel(plr) then
				Admin.SetLevel(plr, PRIVATE_SERVER_OWNER_ADMIN_LEVEL)
			end
		end)
	end
end


--!nonstrict
--[[
	Author: EasternBloxxer
	Description: This plugin enables the setting of certain players' admin ranks via a BindableEvent

	Place in a ModuleScript under Adonis_Loader > Config > Plugins and named "Server-RemoteAdmin"

	Usage:
		AdminRemote:Fire(player: Player, key: string, adminLevel: string | number, isTemporary: boolean?)
	Example:
		game.ServerStorage.AdminRemote:Fire(game.Players.EasternBloxxer, "secretkey", "Moderators", true)
	(must be executed on the Server; the Developer Console can be used)

	The BindableEvent is located under ServerStorage.

	* REMEMBER TO CONFIGURE THE KEY BELOW TO HELP KEEP YOUR GAME SAFE. *
--]]

local KEY: any = "!CHANGE_THIS!"
local BINDABLEEVENT_NAME: string = "AdminRemote"

return function(Vargs)
	local server = Vargs.Server
	local service = Vargs.Service

	local Admin = server.Admin

	if KEY == "!CHANGE_THIS!" then
		warn("WARNING: The RemoteAdmin key is not set! The plugin will not run.")
		return
	end

	local bindableEvent: BindableEvent = service.ServerStorage:FindFirstChild(BINDABLEEVENT_NAME)
				or service.New("BindableEvent", {Parent = service.ServerStorage, Name = BINDABLEEVENT_NAME})

	bindableEvent.Event:Connect(function(player: Player, key: any, adminLevel: string | number, isTemporary: boolean?)
		if not (type(player) == "Instance" and player:IsA("Player")) then
			warn("AdminRemote Error: Argument #1 expects a Player object")
			return
		end

		if key == KEY then
			Admin.AddAdmin(player, adminLevel, isTemporary)
			warn("AdminRemote: Set admin level of " .. player.Name .. " to " .. tostring(adminLevel))
		else
			warn("The maze wasn't meant for you.")
		end
	end)
end


--!strict
--[[
	Author: pbstFusion
	Name: Server-Uninspiration
	Description: To provide people with text that is not vaguely inspiring, nor helpful.

	Place in a ModuleScript under Adonis_Loader > Config > Plugins, named "Server-Uninspiration"
--]]

return function(Vargs)
	local server, service = Vargs.Server, Vargs.Service

	local Settings = server.Settings
	local Commands = server.Commands
	local Functions = server.Functions
	local Admin = server.Admin

	local BAD_QUOTES: {string} = {
		"It's never too late to go back to bed.",
		"I'm not superstitious, but I'm a little stitious.",
		"If you hate yourself, find an opinion you disagree with. You'll hate that much more.",
		"If you keep following your dreams, they'll file a restraining order.",
		"Don't trust random quotes you see online just because they've got a name. - Lenin",
		"what even am i doing",
		"The first step on the road to failure is trying.",
		"Today is pizza day, so head on down to the cafeteria to grab yourself a hot slice.",
		"Today's security code is: 5-33-41-18",
		"Insert other Sci-Fi genre quote here.",
		"Tiredness leads to lack of alertness, and lack of alertness leads to Adonis becoming bad.",
		"Ben. Yes";
		"Every day is another day.";
		"Activating security procedures.";
		"Bastening the turkey.";
		"It's illegal to eat toes on Saturn";
		"woooooooooosh";
	}

	Commands.Uninspired = {
		Prefix = Settings.Prefix;
		Commands = {"uninspire", "uninspiration", "badquote"};
		Description = "Exactly what it says: gives you ininspiration";
		Hidden = true;
		AdminLevel = "Players";
		Function = function(plr: Player)
			Functions.Message("Random Quote", BAD_QUOTES[math.random(1, #BAD_QUOTES)], {plr})
		end
	}

	Admin.CacheCommands()
end


local Workspace = script.Parent

return {
	setupFilesAfterEnv = { Workspace.testSetupFile },
	testMatch = { "**/*.(spec|test)?(.lua|.luau)", "**/__tests__/index" },
	snapshotSerializers = { Workspace.normalizeStackTraces },
}


local function removeRootFromStackTrace(line: string): string
	return line:gsub("LoadedCode.JestRoblox.", ""):gsub("ReplicatedStorage.Packages.", "")
end

local function serialize(val: string | { [any]: any }, config, indentation, depth, refs, printer): string
	if typeof(val) == "table" then
		val.message = removeRootFromStackTrace(val.message)
	elseif typeof(val) == "string" then
		val = removeRootFromStackTrace(val)
	end

	return printer(val, config, indentation, depth, refs, printer)
end

local function containsStackTrace(val: string): boolean
	return string.find(val, "%s*LoadedCode%.JestRoblox") ~= nil
		or string.find(val, "%s*ReplicatedStorage%.Packages") ~= nil
end

local function test(val: any): boolean
	return (typeof(val) == "string" and containsStackTrace(val))
		or (typeof(val) == "table" and typeof(val.message) == "string" and containsStackTrace(val.message))
end

return {
	serialize = serialize,
	test = test,
}


--[[*
 * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 ]]
-- Some of the `jest-runtime` tests are very slow and cause
-- timeouts on travis
-- jest.setTimeout(70000)
type Function = (...any) -> ...any

local JestGlobals = require(script.Parent.JestGlobals.JestGlobals)
local jestExpect = JestGlobals.expect

local ConvertAnsi = require(script.Parent.Parent.PrettyFormat).plugins.ConvertAnsi

jestExpect.addSnapshotSerializer(ConvertAnsi)


local run = require("./utils/run")

print("Running static analysis...")

local seleneCmd = { "selene", "src" }
local styluaCmd = { "stylua", "-c", "src" }
local analyzeCmd = { "robloxdev-cli", "analyze", "default.project.json", "--fastFlags.allOnLuau" }

run(seleneCmd)
run(styluaCmd)
run(analyzeCmd)


local fs = require("@std/fs")

local OPTIMIZE_DIRECTIVE = "--!optimize 1"

return function(path)
	local file = fs.open(path, "r+")
	if file == nil then
		error(`Failed to open file: {path}`)
	end

	local contents = string.split(fs.readFileToString(path), "\n")

	if contents[1] ~= OPTIMIZE_DIRECTIVE then
		table.insert(contents, 1, OPTIMIZE_DIRECTIVE)
		fs.write(file, table.concat(contents, "\n"))
	end

	fs.close(file)
end


local process = require("@lute/process")

return function(cmd)
	print(`> {table.concat(cmd, " ")}`)

	local ok, result = pcall(function()
		return process.run(cmd, { stdio = "inherit", env = { PATH = `{process.cwd()}:{process.env.PATH}` } })
	end)

	if not ok then
		error(result)
	end

	if result.ok then
		print(result.stdout)
		return result.stdout
	else
		error(`Error while running command: {result.stderr}`)
	end
end


--!strict
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

local Packages = script:FindFirstAncestor("JestBenchmark").Parent
local Object = require(Packages.LuauPolyfill).Object

local reporterModule = require(script.Parent.reporters.Reporter)
type Reporter<T = any> = reporterModule.Reporter<T>
type ReporterMap = { [string]: Reporter }

local exports = {}

local customReporters: ReporterMap = {}
exports.customReporters = customReporters

local function useCustomReporters(reporters: ReporterMap)
	Object.assign(exports.customReporters, reporters)
end
exports.useCustomReporters = useCustomReporters

local function useDefaultReporters()
	table.clear(exports.customReporters)
end
exports.useDefaultReporters = useDefaultReporters

return exports


--!strict
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

local Packages = script:FindFirstAncestor("JestBenchmark").Parent
local LuauPolyfill = require(Packages.LuauPolyfill)
local console = LuauPolyfill.console

export type metricLoggerFn = (metricName: string, value: any) -> ()

local function defaultMetricLogger(metricName: string, value: any)
	console.log(`{metricName}, {value}`)
end

local MetricLogger = {
	log = defaultMetricLogger,
}

MetricLogger.useCustomMetricLogger = function(customMetricLogger: metricLoggerFn)
	MetricLogger.log = customMetricLogger
end

MetricLogger.useDefaultMetricLogger = function()
	MetricLogger.log = defaultMetricLogger
end

return MetricLogger


local exports = {}

local function sum(nums: { number })
	local sum = 0
	for _, num in nums do
		sum += num
	end
	return sum
end
exports.sum = sum

local function average(nums: { number })
	if #nums > 0 then
		return sum(nums) / #nums
	end
	return 0
end
exports.average = average

return exports


--!strict
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

local Packages = script:FindFirstAncestor("JestBenchmark").Parent
local LuauPolyfill = require(Packages.LuauPolyfill)
local Object = LuauPolyfill.Object
local test = require(script.Parent.testModule)

local profilerModule = require(script.Parent.Profiler)
local initializeProfiler = profilerModule.initializeProfiler
type Profiler = profilerModule.Profiler

local reporterModule = require(script.Parent.reporters.Reporter)
type Reporter<T = any> = reporterModule.Reporter<T>
local sectionTimeReporterModule = require(script.Parent.reporters.SectionTimeReporter)
local initializeSectionTimeReporter = sectionTimeReporterModule.initializeSectionTimeReporter
local fpsReporterModule = require(script.Parent.reporters.FpsReporter)
local initializeFpsReporter = fpsReporterModule.initializeFpsReporter

local MetricLogger = require(script.Parent.MetricLogger)
type metricLoggerFn = MetricLogger.metricLoggerFn

local CustomReporters = require(script.Parent.CustomReporters)

local exports = {}

type ReporterMap = { [string]: Reporter }

local typesModule = require(Packages.JestTypes)
type Circus_TestName = typesModule.Circus_TestName
type BenchFn = (profiler: Profiler, reporters: ReporterMap) -> ()

export type setupBenchFn = () -> { [string]: Reporter }
export type outputFn = (metricName: string, value: any) -> ()

local function wrapBenchFnInProfiler(testName: Circus_TestName, benchFn: BenchFn)
	local sectionTimeReporter = initializeSectionTimeReporter()
	local fpsReporter = initializeFpsReporter()

	return function()
		local combinedReporters = Object.assign({
			SectionTime = sectionTimeReporter,
			FPS = fpsReporter,
		}, CustomReporters.customReporters) :: ReporterMap

		local reporterList = {}

		for _, value in combinedReporters do
			table.insert(reporterList, value)
		end

		local Profiler = initializeProfiler(reporterList, MetricLogger.log, testName)
		Profiler.start("total")
		benchFn(Profiler, combinedReporters)
		Profiler.stop()

		Profiler.finish()
	end
end

local benchmark = setmetatable({}, {
	__call = function(_self: any, testName: Circus_TestName, benchFn: BenchFn, timeout: number?)
		local testFn = wrapBenchFnInProfiler(testName, benchFn)
		return test(testName, testFn, timeout)
	end,
})

benchmark.only = function(testName: Circus_TestName, benchFn: BenchFn, timeout: number?)
	local testFn = wrapBenchFnInProfiler(testName, benchFn)
	return test.only(testName, testFn, timeout)
end

benchmark.skip = function(testName: Circus_TestName, benchFn: BenchFn, timeout: number?)
	local testFn = wrapBenchFnInProfiler(testName, benchFn)
	return test.skip(testName, testFn, timeout)
end

exports.benchmark = benchmark

return exports


--!strict
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

-- Re-export RunService.Heartbeat so that it can be easily mocked in tests
return game:GetService("RunService").Heartbeat


--!strict
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

local exports = {}

exports.benchmark = require(script.benchmark).benchmark
exports.Profiler = require(script.Profiler)
exports.Reporter = require(script.reporters.Reporter)
exports.HeartbeatReporter = require(script.reporters.HeartbeatReporter)
exports.MetricLogger = require(script.MetricLogger)
exports.CustomReporters = require(script.CustomReporters)

return exports


--!strict
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

local exports = {}

local initializeHeartbeatReporter = require(script.Parent.HeartbeatReporter).initializeHeartbeatReporter

local function averageFps(deltaTimeList: { number })
	if #deltaTimeList == 0 then
		return math.huge
	end

	local sum = 0
	for _, deltaTime in deltaTimeList do
		sum += deltaTime
	end
	local averageDeltaTime = sum / #deltaTimeList
	return 1 / averageDeltaTime
end

local function initializeFpsReporter()
	return initializeHeartbeatReporter("FPS", averageFps)
end
exports.initializeFpsReporter = initializeFpsReporter

return exports


--!strict
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

local exports = {}

local reporterModule = require(script.Parent.Reporter)
type Reporter<T> = reporterModule.Reporter<T>
type CollectorFn<T> = reporterModule.CollectorFn<T>
local initializeReporter = reporterModule.initializeReporter
local heartbeat = require(script.Parent.Parent.heartbeatModule)

local function initializeHeartbeatReporter(metricName: string, collectorFn: CollectorFn<number>): Reporter<number>
	local reporter = initializeReporter(metricName, collectorFn)
	local hasStarted = false
	local heartbeatConnection = nil
	local activeSections = 0

	local function start(sectionName: string)
		reporter.start(sectionName)
		activeSections += 1
		if not hasStarted then
			hasStarted = true
			heartbeatConnection = heartbeat:Connect(function(deltaTime)
				if activeSections > 0 then
					reporter.report(deltaTime)
				end
			end)
		end
	end

	local function stop()
		activeSections -= 1
		reporter.stop()
	end

	local function finish()
		if heartbeatConnection then
			heartbeatConnection:Disconnect()
		end
		return reporter.finish()
	end

	return {
		start = start,
		stop = stop,
		finish = finish,
		-- Reporting is handled internally
		report = function(_)
			error(`Attempted to call {metricName}Reporter.report. This reporter handles reporting internally.`)
		end,
	}
end
exports.initializeHeartbeatReporter = initializeHeartbeatReporter

return exports


--!strict
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

local exports = {}

local reporterModule = require(script.Parent.Reporter)
local initializeReporter = reporterModule.initializeReporter
type Reporter = reporterModule.Reporter<number>

local function sectionTime(profilerStartTimes: { number })
	if #profilerStartTimes == 0 then
		return 0
	end

	return profilerStartTimes[#profilerStartTimes] - profilerStartTimes[1]
end

local function initializeSectionTimeReporter(): Reporter
	local reporter = initializeReporter("SectionTime", sectionTime)

	local function start(sectionName: string)
		reporter.start(sectionName)
		reporter.report(os.clock())
	end

	local function stop()
		reporter.report(os.clock())
		reporter.stop()
	end

	return {
		start = start,
		stop = stop,
		finish = reporter.finish,
		report = function(_: any)
			error(`Attempted to call SectionTimeReporter.report. This reporter handles reporting internally.`)
		end,
	}
end
exports.initializeSectionTimeReporter = initializeSectionTimeReporter

return exports


--!strict
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

-- Re-export JestGlobals.test so that it can be easily mocked in tests
local Packages = script:FindFirstAncestor("JestBenchmark").Parent
local JestGlobals = require(Packages.JestGlobals)
local test = JestGlobals.test

return test


-- ROBLOX upstream: no upstream
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
local circusModule = require(script.circus)

local exports = circusModule

export type Event = circusModule.Event
export type State = circusModule.State

-- ROBLOX deviation: exporting runner alongside the main entry point
local runner = require(script.runner);
(exports :: any).runner = runner

return exports :: typeof(circusModule) & { runner: typeof(runner) }


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE upstream: Added some utilities to support keeping the upstream code consistent

local CurrentModule = script.Parent
local Packages = CurrentModule.Parent

local LuauPolyfill = require(Packages.LuauPolyfill)
local inspect = LuauPolyfill.util.inspect
type Array<T> = LuauPolyfill.Array<T>

local typesModule = require(CurrentModule.types)
type InspectOptions = typesModule.InspectOptions

function concatRestArgs(restArgs: Array<unknown>)
	local restStr = ""
	if #restArgs > 0 then
		restStr = " " .. table.concat(restArgs, " ")
	end
	return restStr
end

function getFormattedValue(value: unknown, inspectOptions: InspectOptions?)
	return if type(value) == "string" then value else inspect(value, inspectOptions)
end

function format(...)
	return formatter(nil, ...)
end

function formatWithOptions(inspectOptions: InspectOptions, fmt: unknown, ...)
	return formatter(inspectOptions, fmt, ...)
end

function formatter(inspectOptions: InspectOptions?, fmt: unknown, ...)
	local args = { ... }

	local fmtArgs = {}
	local extraArgs = {}

	local fmt_: string = ""

	if type(fmt) == "string" then
		local _, c = fmt:gsub("%%[sdj%%]", "")

		for key, value in pairs(args) do
			local formattedValue = getFormattedValue(value, inspectOptions)
			if key <= c then
				table.insert(fmtArgs, formattedValue)
			else
				table.insert(extraArgs, formattedValue)
			end
		end
		fmt_ = fmt
	else
		fmt_ = inspect(fmt, inspectOptions)
		for _, value in pairs(args) do
			table.insert(extraArgs, getFormattedValue(value, inspectOptions))
		end
	end

	return string.format(fmt_, table.unpack(fmtArgs)) .. concatRestArgs(extraArgs)
end

return {
	format = format,
	formatWithOptions = formatWithOptions,
	concatRestArgs = concatRestArgs,
}


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream
-- ROBLOX TODO: fix PrettyFormat types imports

type CompareKeys = ((a: string, b: string) -> number) | nil

type Plugin = any

export type Plugins = { [number]: Plugin }

type ThemeReceived = {
	comment: string?,
	content: string?,
	prop: string?,
	tag: string?,
	value: string?,
}

export type PrettyFormatOptions = {
	callToJSON: boolean?,
	compareKeys: CompareKeys,
	escapeRegex: boolean?,
	escapeString: boolean?,
	highlight: boolean?,
	indent: number?,
	maxDepth: number?,
	min: boolean?,
	plugins: Plugins?,
	printBasicPrototype: boolean?,
	printInstanceDefaults: boolean?,
	printInstanceTags: boolean?,
	printFunctionName: boolean?,
	redactStackTracesInStrings: boolean?,
	theme: ThemeReceived?,
	useStyledProperties: boolean?,
}

export type OptionsReceived = PrettyFormatOptions

return {}


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX upstream: no upstream

-- ROBLOX Comment: nil value placeholder
local NIL = newproxy(true)
local mt = getmetatable(NIL)
mt.__tostring = function()
	return "nil"
end

return NIL


-- /**
--  * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
--  *
--  * This source code is licensed under the MIT license found in the
--  * LICENSE file in the root directory of this source tree.
--  *
--  */

-- ROBLOX deviation START: add export for additional Expect types
local Packages = script.Parent
local ExpectModule = require(Packages.Expect)
export type MatcherState = ExpectModule.MatcherState
export type ExpectExtended<E, State = MatcherState> = ExpectModule.ExpectExtended<E, State>
-- ROBLOX deviation END

return require(script.index)


--!nonstrict
-- ROBLOX NOTE: no upstream
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]

local CurrentModule = script
local Packages = CurrentModule.Parent

local LuauPolyfill = require(Packages.LuauPolyfill)
type Object = LuauPolyfill.Object

local exports = {}

local GlobalMockerClass = {}

export type GlobalAutomockFn = {
	_isGlobalAutomockFn: true,
	_maybeMock: any,
	_maybeUnmocked: any,
}
export type GlobalAutomocks = { [string]: GlobalAutomockFn | GlobalAutomocks }
export type GlobalEnvLibrary = {
	_isMockGlobalLibrary: true,
	_automocksRef: GlobalAutomocks,
}
-- The GlobalEnv type should always look like the MOCKABLE_GLOBALS table;
-- users depend on GlobalEnv for autocomplete and type checking.
export type GlobalEnv = GlobalEnvLibrary & {
	print: typeof(print),
	warn: typeof(warn),
	math: GlobalEnvLibrary & {
		random: typeof(math.random),
	},
}
local MOCKABLE_GLOBALS = {
	print = print,
	warn = warn,
	math = {
		random = math.random,
	},
}

export type GlobalMocker = {
	isMockGlobalLibrary: (_self: GlobalMocker, object: any) -> boolean,
	automocks: GlobalAutomocks,
	envObject: GlobalEnv,
	currentlyMocked: boolean,
}

GlobalMockerClass.__index = GlobalMockerClass
function GlobalMockerClass.new(): GlobalMocker
	local self = setmetatable({}, GlobalMockerClass)

	self.automocks = self:_createGlobalAutomocks()
	self.envObject = self:_createGlobalEnv(self.automocks)
	self.currentlyMocked = false

	return (self :: any) :: GlobalMocker
end

function GlobalMockerClass:isMockGlobalLibrary(object: any): boolean
	return typeof(object) == "table" and object._isMockGlobalLibrary == true
end

function GlobalMockerClass:_createGlobalAutomocks(): GlobalAutomocks
	local function implement(mockableGlobals: Object, into: GlobalAutomocks)
		for name, mockableGlobal in mockableGlobals do
			if typeof(mockableGlobal) == "function" then
				into[name] = {
					_isGlobalAutomockFn = true,
					_maybeMock = nil,
					_maybeUnmocked = nil,
				}
			elseif typeof(mockableGlobal) == "table" then
				local subAutomocks = {}
				implement(mockableGlobal, subAutomocks)
				into[name] = subAutomocks
			else
				error("Unexpected mockable global type - this is an internal bug")
			end
		end
	end
	local automocks = {}
	implement(MOCKABLE_GLOBALS, automocks)
	return automocks
end

function GlobalMockerClass:_createGlobalEnv(automocks: GlobalAutomocks): GlobalEnv
	local function makeSentinelForLibrary(automocks: GlobalAutomocks, globalPath: { string })
		local library: GlobalEnvLibrary = {
			_isMockGlobalLibrary = true,
			_automocksRef = automocks,
		}

		-- Allow users to access nested libraries like `math`.
		for name, automock in automocks do
			if typeof(automock) == "table" and not automock._isGlobalAutomockFn then
				local libraryGlobalPath = table.clone(globalPath)
				table.insert(libraryGlobalPath, name)
				library[name] = makeSentinelForLibrary(automock, libraryGlobalPath)
			end
		end

		-- Users might want to mock functions that don't have an underlying
		-- implementation in Jest. Detect that and throw an error here to inform
		-- them Jest must explicitly support individual globals to be mocked.
		setmetatable(library, {
			__index = function(_, name: string)
				-- name is actually `unknown` type; the type declaration is a
				-- convenient lie so our code type checks without a fuss
				if typeof(name) ~= "string" then
					error(`Cannot index globalEnv with {name} (expected string)`)
				end

				-- Give $$ names like $$typeof a free pass, because they're used
				-- internally in some Jest/LuauPolyfill functions, and probably
				-- aren't a user accidentally misusing `globalEnv`.
				if string.sub(name, 1, 2) == "$$" then
					return nil
				end

				-- Unmocked functions aren't included in the actual object, so
				-- simulate them being included here (where we can dynamically
				-- fetch which function to actually return)
				local automock = automocks[name]
				if typeof(automock) == "table" and automock._isGlobalAutomockFn then
					return automock._maybeUnmocked or error("globalEnv has not been initialised by Jest here")
				end

				-- In theory, what we want to do is `table.concat` the global
				-- path, but including `name` at the end. Instead of doing table
				-- manipulation, just implement that with a plain loop.
				local qualifiedName = ""
				for _, parentName in globalPath do
					qualifiedName ..= parentName .. "."
				end
				qualifiedName ..= name
				error(`Jest does not yet support mocking the {qualifiedName} global.`)
			end,
		})

		return table.freeze(library)
	end

	-- This will match `GlobalEnv` in the end, but it's difficult to
	-- statically type check that, so for code cleanliness, just cast to it.
	return makeSentinelForLibrary(automocks, {}) :: any
end

exports.GlobalMocker = GlobalMockerClass
exports.MOCKABLE_GLOBALS = MOCKABLE_GLOBALS

return exports


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
--!strict
-- ROBLOX NOTE: no upstream

local CurrentModule = script.Parent
local InstanceProxy = require(CurrentModule.InstanceProxy)
type InstanceProxy<ClassType = Instance> = InstanceProxy.InstanceProxy<ClassType>

export type DataModelMocker = {
	mockInstance: <ClassType>(self: DataModelMocker, instance: ClassType & Instance) -> InstanceProxy<ClassType>,
	intoProxy: (self: DataModelMocker, object: unknown) -> InstanceProxy?,
}
type DataModelMocker_private = DataModelMocker & {
	_intoProxyMap: { [unknown]: InstanceProxy },
}

local DataModelMocker = {}
DataModelMocker.__index = DataModelMocker

function DataModelMocker.mockInstance<ClassType>(
	self: DataModelMocker_private,
	instance: ClassType & Instance
): InstanceProxy<ClassType>
	local proxy = self._intoProxyMap[instance]
	if proxy == nil then
		proxy = InstanceProxy.new(instance)
		-- This proxy is never removed from the table, and the strong reference
		-- is necessary so that the proxy is remembered even if the tested Luau
		-- code doesn't hold a reference for some time. This could cause a
		-- memory leak but this mocker is presumably destroyed at the end of the
		-- test suite, so this should be OK.
		self._intoProxyMap[instance] = proxy
		self._intoProxyMap[proxy] = proxy
		self._intoProxyMap[proxy.spy] = proxy
		self._intoProxyMap[proxy.controls] = proxy
		--[[
			ROBLOX TODO: mock ancestry/descendants to preserve instance proxies
			created in a hierarchy, not important for GetService mocking
		]]
	end
	return proxy :: any
end

function DataModelMocker.intoProxy(self: DataModelMocker_private, object: any): InstanceProxy?
	return self._intoProxyMap[object]
end

local exports = {}

function exports.new(): DataModelMocker
	local mocker: DataModelMocker_private = setmetatable({
		_intoProxyMap = {},
	}, DataModelMocker) :: any
	return mocker
end

return exports


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
--!strict
-- ROBLOX NOTE: no upstream

local CurrentModule = script
local InstanceProxy = require(CurrentModule.InstanceProxy)
local DataModelMocker = require(CurrentModule.DataModelMocker)

export type InstanceProxy<ClassType = Instance> = InstanceProxy.InstanceProxy<ClassType>
export type Spied<ClassType = Instance> = InstanceProxy.Spied<ClassType>
export type ProxyControls<ClassType = Instance> = InstanceProxy.ProxyControls<ClassType>

export type DataModelMocker = DataModelMocker.DataModelMocker

local exports = {}

exports.DataModelMocker = DataModelMocker

return exports


-- ROBLOX NOTE: no upstream
local exports = {}
exports.mocked = false
exports.actual = true

return exports


-- Captures the actual error messages from the native require for @rbx and @std
-- passthrough paths. These vary across roblox-cli versions, so we capture them
-- dynamically rather than hardcoding expected strings.
--
-- This module must be loaded with the real Roblox native require, NOT Jest's
-- sandboxed require.

local function captureNativeError(path): string?
	local ok, err = pcall(function()
		(require :: any)(path)
	end)
	if ok then
		return nil
	end
	local msg = tostring(err)
	-- Strip the "LoadedCode...:line: " location prefix so we get just the
	-- engine's error message, which is the part toThrow will match against.
	return msg:match(":%d+:%s*(.+)$") or msg
end

return {
	rbx = captureNativeError("@rbx/ThisWillNeverExist"),
	std = captureNativeError("@std/ThisWillNeverExist"),
}


-- ROBLOX NOTE: no upstream

local Packages = script.Parent.Parent.Parent
local JestGlobals = require(Packages.Dev.JestGlobals)
local jest = JestGlobals.jest

return {
	game = game,
	workspace = workspace,
	jest = jest,
}


--[[*
 * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 ]]
-- ROBLOX NOTE: Not paired with upstream. Just using for the "Jest Object" type

local CurrentModule = script.Parent
local Packages = CurrentModule.Parent

local JestFakeTimers = require(Packages.JestFakeTimers)
type FakeTimers = JestFakeTimers.FakeTimers

local moduleMockerModule = require(Packages.JestMock)
type ModuleMocker = moduleMockerModule.ModuleMocker

export type MockFactory = () -> ...unknown

export type Jest = {
	-- autoMockOn: () -> Jest,
	-- autoMockOff: () -> Jest,
	-- disableAutomock: () -> Jest,
	-- enableAutomock: () -> Jest,
	-- mock: (scriptInstance: ModuleScript, mockFactory: MockFactory, options: any) -> Jest,
	clearAllMocks: () -> Jest,
	resetAllMocks: () -> Jest,
	restoreAllMocks: () -> Jest,
	useFakeTimers: () -> Jest,
	useRealTimers: () -> Jest,
	resetModules: () -> Jest,
	isolateModules: (fn: () -> ()) -> Jest,
	fn: () -> ModuleMocker,
	-- genMockFromModule: (scriptInstance: ModuleScript) -> any,
	getRealSystemTime: () -> (),
	getTimerCount: () -> number,
	runAllTicks: () -> (),
	runAllTimers: () -> (),
	runOnlyPendingTimers: () -> (),
	-- setMock: (scriptInstance: ModuleScript, mock: unknown, options: { virtual: boolean? }?) -> Jest,
	setSystemTime: (now: (number | DateTime)?) -> (),
	setTimeout: any,
}

return {}


local PATH_SEPARATOR = "/"

local PASSTHROUGH_PREFIXES = { "@std", "@rbx" }

local function resolveInstancePath(relativeTo: Instance, instancePath: string): Instance?
	local parts = instancePath:split(PATH_SEPARATOR)
	local firstPart = parts[1]
	for _, prefix in PASSTHROUGH_PREFIXES do
		if firstPart == prefix then
			return nil
		end
	end

	local prevPathPart: string?
	local current = relativeTo

	for _, pathPart in parts do
		-- Splitting can produce empty strings (e.g. "./" → {".", ""} or
		-- "Foo//Bar" → {"Foo", "", "Bar"}). Skip them after the first part.
		if pathPart == "" and prevPathPart ~= nil then
			continue
		end

		if not prevPathPart and pathPart == "" then
			error("paths beginning with '/' are not supported in sandboxed string requires")
		end

		-- First part of the path needs special handling for aliases and
		-- ascending through parents
		if current == relativeTo then
			if pathPart == "@game" then
				current = game
				prevPathPart = pathPart
				continue
			elseif pathPart == "@self" then
				prevPathPart = pathPart
				continue
			elseif pathPart == "." then
				if not current.Parent then
					error(`could not resolve path (no parent for {current})`)
				end
				current = current.Parent
				prevPathPart = pathPart
				continue
			elseif pathPart == ".." then
				-- Paths beginning with `../` need to go up two parents, then
				-- any subsequent use of `..` will only move up one parent.
				if not (current.Parent and current.Parent.Parent) then
					error(`could not resolve path part "{pathPart}" relative to {current}`)
				end
				current = current.Parent.Parent
				prevPathPart = pathPart
				continue
			end
		end

		if pathPart == ".." then
			if prevPathPart and prevPathPart ~= pathPart then
				error("paths including '..' after the beginning are not supported in sandboxed string requires")
			end
			if not current.Parent then
				error(`could not resolve path part "{pathPart}" relative to {current}`)
			end
			current = current.Parent
		else
			local candidate = current:FindFirstChild(pathPart)
			if not candidate then
				error(`could not resolve an Instance named "{pathPart}" relative to {current}`)
			end
			current = candidate
		end

		prevPathPart = pathPart
	end

	return current
end

return resolveInstancePath


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

local CurrentModule = script.Parent
local Packages = CurrentModule.Parent
local LuauPolyfill = require(Packages.LuauPolyfill)
local Error = LuauPolyfill.Error

local RobloxShared = require(Packages.RobloxShared)
local getDataModelService = RobloxShared.getDataModelService

local function getFileSystemService()
	local success, result = pcall(function()
		return _G.__MOCK_FILE_SYSTEM__ or getDataModelService("FileSystemService")
	end)

	if not success then
		error(Error.new("Attempting to save snapshots in an environment where FileSystemService is inaccessible."))
	end

	return result
end

return getFileSystemService


local processServiceExists, ProcessService = pcall(function()
	return game:GetService("ProcessService")
end)

local args = {}

if processServiceExists then
	for _, arg in ProcessService:GetCommandLineArgs() do
		local a = arg:split("=")
		a[1] = a[1]:gsub("^%-%-", "")
		args[a[1]] = a[2] or true
	end
end

return args


local CurrentModule = script

local pathModule = require(CurrentModule.path)
local Path = pathModule.Path
export type Path = pathModule.Path

function makePathImpl()
	local path = Path.new()
	path:initialize("/", "/")
	return path
end

return {
	path = makePathImpl(),
	Path = Path,
}


--[[
    I copied this folder into here for testing purposes.

    Specifically, one of the unit tests tries to serialise this folder, and
    saves the result to a snapshot. If the snapshot changes, the test fails.

    So if you touch this folder, you'll break that test.

    Apart from that, nothing in here does anything meaningful.
]]

return nil


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

local CurrentModule = script.Parent.Parent
local Packages = CurrentModule.Parent

local JestGetType = require(Packages.JestGetType)
local getType = JestGetType.getType

local LuauPolyfill = require(Packages.LuauPolyfill)
local Object = LuauPolyfill.Object
local Array = LuauPolyfill.Array
local instanceof = LuauPolyfill.instanceof

local redactStackTrace = require(Packages.RobloxShared).redactStackTrace

local Types = require(CurrentModule.Types)
type Config = Types.Config
type Refs = Types.Refs
type Printer = Types.Printer

local RedactStackTraces = {}

function RedactStackTraces.serialize(
	val: any,
	config: Config,
	indentation: string,
	depth: number,
	refs: Refs,
	printer: Printer
): string
	depth = depth + 1
	local ty = getType(val)
	if ty == "string" then
		local interiorConfig = table.clone(config)
		interiorConfig.plugins = table.clone(interiorConfig.plugins)
		table.remove(interiorConfig.plugins, table.find(interiorConfig.plugins, RedactStackTraces))
		local pretty = printer(val, interiorConfig, indentation, depth, refs)
		if config.redactStackTracesInStrings then
			pretty = redactStackTrace(pretty) :: string
		end
		return pretty
	elseif ty == "error" then
		local interiorConfig = table.clone(config)
		interiorConfig.plugins = table.clone(interiorConfig.plugins)
		table.remove(interiorConfig.plugins, table.find(interiorConfig.plugins, RedactStackTraces))
		local pretty = printer(val, interiorConfig, indentation, depth, refs)
		return redactStackTrace(pretty) :: string
	else
		error("not supported")
	end
end

function RedactStackTraces.test(val: any): boolean
	local ty = getType(val)
	return ty == "error" or ty == "string"
end

return RedactStackTraces


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream
-- this plugin serializes Roblox Instance objects
-- https://developer.roblox.com/en-us/api-reference/class/Instance

local CurrentModule = script.Parent.Parent
local Packages = CurrentModule.Parent

local JestGetType = require(Packages.JestGetType)
local getType = JestGetType.getType

local LuauPolyfill = require(Packages.LuauPolyfill)
local Object = LuauPolyfill.Object
local Array = LuauPolyfill.Array
local instanceof = LuauPolyfill.instanceof

local RobloxInstance = require(Packages.RobloxShared).RobloxInstance
local InstanceSubset = RobloxInstance.InstanceSubset

local printTableEntries = require(CurrentModule.Collections).printTableEntries

local Types = require(CurrentModule.Types)
type Config = Types.Config
type Refs = Types.Refs
type Printer = Types.Printer

local function printInstance(
	val: any,
	config: Config,
	indentation: string,
	depth: number,
	refs: Refs,
	printer: Printer
): string
	local result = ""

	local printChildrenList = val:GetChildren()
	table.sort(printChildrenList, function(a, b)
		return a.Name < b.Name
	end)

	local propertiesMap = RobloxInstance.listProps(val, {
		useStyledProperties = config.useStyledProperties,
	})
	local printPropsList = Object.keys(propertiesMap)
	if not config.printInstanceDefaults then
		local defaultsMap = RobloxInstance.listDefaultProps(val.ClassName, config.useStyledProperties)
		printPropsList = Array.filter(printPropsList, function(name)
			return propertiesMap[name] ~= defaultsMap[name]
		end)
	end
	table.sort(printPropsList)

	-- Get tags if config option is enabled
	local tags = if config.printInstanceTags then RobloxInstance.getTags(val) else {}
	table.sort(tags)

	local willPrintProps = #printPropsList > 0
	local willPrintChildren = #printChildrenList > 0
	local willPrintTags = #tags > 0

	if willPrintProps or willPrintChildren or willPrintTags then
		result = result .. config.spacingOuter

		local indentationNext = indentation .. config.indent

		-- print tags of Instance first
		if willPrintTags then
			local printTagsValue = printer(tags, config, indentationNext, depth, refs)
			result = string.format("%s%s%s: %s", result, indentationNext, '"Tags"', printTagsValue)

			if willPrintProps or willPrintChildren then
				result = result .. "," .. config.spacingInner
			elseif not config.min then
				result = result .. ","
			end
		end

		-- print properties of Instance
		for propOrder, propName in ipairs(printPropsList) do
			local propValue = propertiesMap[propName]
			if propValue == Object.None then
				propValue = nil
			end

			-- collapses output for Instance values to avoid loops
			local valueDepth = if getType(propValue) == "Instance" then math.huge else depth
			local printName = printer(propName, config, indentationNext, depth, refs)
			local printValue = printer(propValue, config, indentationNext, valueDepth, refs)

			result = string.format("%s%s%s: %s", result, indentationNext, printName, printValue)

			if propOrder ~= #printPropsList or willPrintChildren then
				result = result .. "," .. config.spacingInner
			elseif not config.min then
				result = result .. ","
			end
		end

		-- recursively print children of Instance
		for childOrder, child in ipairs(printChildrenList) do
			local printName = printer(child.Name, config, indentationNext, depth, refs)
			local printValue = printer(child, config, indentationNext, depth, refs)

			result = string.format("%s%s%s: %s", result, indentationNext, printName, printValue)

			if childOrder ~= #printChildrenList then
				result = result .. "," .. config.spacingInner
			elseif not config.min then
				result = result .. ","
			end
		end

		result = result .. config.spacingOuter .. indentation
	end

	return result
end

local function serialize(
	val: any,
	config: Config,
	indentation: string,
	depth: number,
	refs: Refs,
	printer: Printer
): string
	depth = depth + 1

	if depth >= config.maxDepth then
		return string.format('"%s" [%s]', val.Name, val.ClassName)
	end

	if instanceof(val, InstanceSubset) then
		return val.ClassName .. " {" .. printTableEntries(val.subset, config, indentation, depth, refs, printer) .. "}"
	end

	return val.ClassName .. " {" .. printInstance(val, config, indentation, depth, refs, printer) .. "}"
end

local function test(val: any): boolean
	return getType(val) == "Instance" or instanceof(val, InstanceSubset)
end

return {
	serialize = serialize,
	test = test,
}


--!nonstrict
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
local CurrentModule = script.Parent
local Packages = CurrentModule.Parent

local RobloxApi = require(CurrentModule.RobloxApiDump)

local getType = require(Packages.JestGetType).getType

local LuauPolyfill = require(Packages.LuauPolyfill)
local Array = LuauPolyfill.Array
local Object = LuauPolyfill.Object

local CurrentModuleExpect = require(CurrentModule.expect)
local equals = CurrentModuleExpect.equals
local isObjectWithKeys = CurrentModuleExpect.isObjectWithKeys
local hasPropertyInObject = CurrentModuleExpect.hasPropertyInObject
local isAsymmetric = CurrentModuleExpect.isAsymmetric

local exports = {}

-- Unsafe because no checks are performed that this property is readable.
local function readPropUnsafe(instance: Instance, propertyName: string): unknown
	return instance[propertyName]
end

-- Unsafe because no checks are performed that this property is readable via GetStyled.
local function readStyledPropUnsafe(instance: Instance, propertyName: string): unknown
	return (instance :: any):GetStyled(propertyName)
end

-- Unsafe because no checks are performed that this property is writable.
local function writePropUnsafe(instance: Instance, propertyName: string, value: unknown): ()
	instance[propertyName] = value
end

function exports.readProp(instance: Instance, propertyName: string)
	return pcall(readPropUnsafe, instance, propertyName)
end

function exports.readStyledProp(instance: Instance, propertyName: string)
	return pcall(readStyledPropUnsafe, instance, propertyName)
end

function exports.writeProp(instance: Instance, propertyName: string, value: unknown)
	return pcall(writePropUnsafe, instance, propertyName, value)
end

-- Unsafe because no checks are performed that these properties are readable.
local function listPropsUnsafe(className: string): { [string]: true }
	local unsafeProps = {}
	local inheritFrom = RobloxApi[className]
	while inheritFrom ~= nil do
		for _, unsafeProp in ipairs(inheritFrom.Properties) do
			unsafeProps[unsafeProp] = true
		end
		inheritFrom = RobloxApi[inheritFrom.Superclass]
	end
	return unsafeProps
end

export type ListPropsOptions = {
	warmRead: boolean?,
	useStyledProperties: boolean?,
}

function exports.listProps(instance: Instance, options: (boolean | ListPropsOptions)?): { [string]: unknown }
	-- Support legacy boolean argument for backwards compatibility
	local warmRead: boolean? = nil
	local useStyledProperties: boolean? = nil
	if typeof(options) == "boolean" then
		warmRead = options
	elseif typeof(options) == "table" then
		warmRead = options.warmRead
		useStyledProperties = options.useStyledProperties
	end

	local props = listPropsUnsafe(instance.ClassName)

	-- cold read - values may not be stable, but at least we can weed out
	-- property reads that will result in errors
	for unsafeProp in props do
		local ok, propValue
		if useStyledProperties then
			-- Try GetStyled first, fall back to regular read if not available
			ok, propValue = exports.readStyledProp(instance, unsafeProp)
			if not ok then
				ok, propValue = exports.readProp(instance, unsafeProp)
			end
		else
			ok, propValue = exports.readProp(instance, unsafeProp)
		end

		if ok then
			props[unsafeProp] = if propValue == nil then Object.None else propValue
		else
			props[unsafeProp] = nil
		end
	end
	if warmRead then
		-- warm read - quantum UI bugs will no longer affect values here
		for safeProp in props do
			local ok, propValue
			if useStyledProperties then
				ok, propValue = pcall(readStyledPropUnsafe, instance, safeProp)
				if not ok then
					propValue = readPropUnsafe(instance, safeProp)
				end
			else
				propValue = readPropUnsafe(instance, safeProp)
			end
			props[safeProp] = if propValue == nil then Object.None else propValue
		end
	end
	return props
end

function exports.getTags(instance: Instance): { string }
	local ok, tags = pcall(function()
		return (instance :: any):GetTags()
	end)
	if ok then
		return tags
	end
	return {}
end

do
	-- Hidden from outside code.
	local cachedDefaults = {}
	local cachedStyledDefaults = {}
	function exports.listDefaultProps(className: string, useStyledProperties: boolean?): { [string]: unknown }
		local cache = if useStyledProperties then cachedStyledDefaults else cachedDefaults
		local cached = cache[className]
		if cached ~= nil then
			return cached
		end

		local ok, instance = pcall(Instance.new, className)
		if not ok then
			error("Class type is abstract or not creatable - cannot list defaults")
		end
		local defaults = exports.listProps(instance, {
			useStyledProperties = useStyledProperties,
		})
		instance:Destroy()

		cache[className] = defaults
		return defaults
	end
end

-- given an Instance and a property-value table subset
-- returns true if all property-values in the subset table exist in the Instance
-- and returns false otherwise
-- returns nil for undefined behavior
function exports.instanceSubsetEquality(instance: any, subset: any): boolean | nil
	local function subsetEqualityWithContext(seenReferences)
		return function(localInstance, localSubset)
			seenReferences = seenReferences or {}

			if getType(localInstance) ~= "Instance" or not isObjectWithKeys(localSubset) then
				return nil
			end

			return Array.every(Object.keys(localSubset), function(prop)
				local subsetVal = localSubset[prop]
				if isObjectWithKeys(subsetVal) then
					-- return false for circular references
					if seenReferences[subsetVal] then
						return false
					end
					seenReferences[subsetVal] = true
				end
				local ok, value = exports.readProp(localInstance, prop)
				local result = ok and equals(value, subsetVal, { subsetEqualityWithContext(seenReferences) })

				seenReferences[subsetVal] = nil
				return result
			end)
		end
	end

	local inner = subsetEqualityWithContext()

	if inner then
		return inner(instance, subset)
	end
	return inner
end

-- InstanceSubset object behaves like an Instance when serialized by pretty-format

local InstanceSubset = {}
exports.InstanceSubset = InstanceSubset

InstanceSubset.__index = InstanceSubset

function InstanceSubset.new(className, subset)
	table.sort(subset)
	local self = {
		ClassName = className,
		subset = subset,
	}

	setmetatable(self, InstanceSubset)
	return self
end

-- given an Instance and an expected property-value table subset, returns
-- an InstanceSubset object representing the found subset of Instance with values in the subset table
-- and a InstanceSubset object representing the expected subset table
function exports.getInstanceSubset(instance: any, subset: any, seenReferences_: any?): (any, any)
	local seenReferences = seenReferences_ or {}

	local foundSubset: any = {}
	seenReferences[instance] = foundSubset

	-- return non-table primitives
	if equals(instance, subset) then
		return subset, subset
	elseif typeof(subset) ~= "table" or isAsymmetric(subset) then
		return instance, subset
	end

	-- collect non-table primitive values
	local expectedSubset = {}
	for k, v in pairs(subset) do
		if typeof(v) ~= "table" then
			expectedSubset[k] = v
		end
	end

	for name, subsetPropOrChild in pairs(subset) do
		local ok, realPropOrChild = exports.readProp(instance, name)
		if not ok then
			continue
		elseif seenReferences[realPropOrChild] ~= nil then
			error("Circular reference passed into .toMatchInstance(subset)")
		else
			expectedSubset[name] = {}
			foundSubset[name], expectedSubset[name] =
				exports.getInstanceSubset(realPropOrChild, subsetPropOrChild, seenReferences)
		end
	end

	-- serialize with a different ClassName if the subset has ClassName defined,
	-- otherwise treat it as the same class as the original Instance
	local subsetClassName = instance.ClassName
	if typeof(subset) == "table" and rawget(subset, "ClassName") then
		subsetClassName = rawget(subset, "ClassName")
	end

	local found = InstanceSubset.new(instance.ClassName, foundSubset)
	local expected = InstanceSubset.new(subsetClassName, expectedSubset)
	return found, expected
end

return exports


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE:  no upstream: replaces node js writeable stream API

export type Writeable = {
	isTTY: boolean?,
	write: (self: Writeable, data: string) -> (),
}

local Writeable = {}
Writeable.__index = Writeable

function Writeable.new(options: { write: (data: string) -> () }?): Writeable
	local self = setmetatable({}, Writeable)
	self._writeFn = if options ~= nil and typeof(options.write) == "function" then options.write else print
	self.isTTY = false
	return (self :: any) :: Writeable
end

function Writeable:write(data: string)
	self._writeFn(data)
end

return {
	Writeable = Writeable,
}


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

local CurrentModule = script.Parent.Parent
local Packages = CurrentModule.Parent

local JestGlobals = require(Packages.Dev.JestGlobals)
local expect = JestGlobals.expect
local describe = JestGlobals.describe
local it = JestGlobals.it

local getParent = require(CurrentModule.getParent)

describe("getParent", function()
	it("works on Unix paths", function()
		expect(getParent("/Users/rng/jest-roblox/src/Submodules/expect/src/__tests__/__snapshots__/snapshot.snap.lua")).toEqual(
			"/Users/rng/jest-roblox/src/Submodules/expect/src/__tests__/__snapshots__/snapshot.snap.lua"
		)
	end)

	it("works on Windows paths", function()
		expect(
			getParent(
				"C:\\Users\\Raymond\\jest-roblox\\src\\Submodules\\expect\\src\\__tests__\\__snapshots__\\snapshot.snap.lua"
			)
		).toEqual(
			"C:\\Users\\Raymond\\jest-roblox\\src\\Submodules\\expect\\src\\__tests__\\__snapshots__\\snapshot.snap.lua"
		)
	end)

	it("gets parent directory", function()
		expect(
			getParent("/Users/rng/jest-roblox/src/Submodules/expect/src/__tests__/__snapshots__/snapshot.snap.lua", 1)
		).toEqual("/Users/rng/jest-roblox/src/Submodules/expect/src/__tests__/__snapshots__")
	end)

	it("gets grandparent directory", function()
		expect(
			getParent("/Users/rng/jest-roblox/src/Submodules/expect/src/__tests__/__snapshots__/snapshot.snap.lua", 2)
		).toEqual("/Users/rng/jest-roblox/src/Submodules/expect/src/__tests__")
	end)
end)


return function(line: string): string
	local spacing, filePath, lineNumber, extra = line:match('(%s*)%[string "(.-)"%]:(%d+)(.*)')
	if filePath then
		local match = filePath
		if spacing then
			match = spacing .. match
		end
		if lineNumber then
			match = match .. ":" .. lineNumber
		end
		if extra then
			match = match .. extra
		end
		return match
	end
	return line
end


-- moved from jest-snapshot/utils.lua

local Packages = script.Parent.Parent
local LuauPolyfill = require(Packages.LuauPolyfill)
local Error = LuauPolyfill.Error

local getParent = require(script.Parent.getParent)
local getDataModelService = require(script.Parent.getDataModelService)

local FileSystemService = getDataModelService("FileSystemService")

local function ensureDirectoryExists(filePath: string)
	-- ROBLOX deviation: gets path of parent directory, GetScriptFilePath can only be called on ModuleScripts
	local path = getParent(filePath, 1)
	local ok, err = pcall(function()
		if FileSystemService and not FileSystemService:Exists(path) then
			FileSystemService:CreateDirectories(path)
		end
	end)

	if not ok and err:find("Error%(13%): Access Denied%. Path is outside of sandbox%.") then
		error(
			Error.new(
				"Provided path is invalid: you likely need to provide a different argument to --fs.readwrite.\n"
					.. "You may need to pass in `--fs.readwrite=$PWD`"
			)
		)
	end
end

return ensureDirectoryExists


local function escapePatternCharacters(str: string): string
	return string.gsub(str, "([%(%)%.%%%+%-%*%?%[%^%$])", "%%%1")
end

return {
	escapePatternCharacters = escapePatternCharacters,
}


-- checks that the service exists and is accessible before returning it, otherwise returns nil
return function(service: string)
	local success, result = pcall(function()
		local service = game:GetService(service)
		local _ = service.Name
		return service
	end)

	return success and result or nil
end


-- ROBLOX deviation: added to handle file paths in snapshot/State
local function getParent(path: string, level_: number?): string
	local level = if level_ then level_ else 0

	local isUnixPath = string.sub(path, 1, 1) == "/"
	local t = {}

	for p in string.gmatch(path, "[^\\/][^\\/]*") do
		table.insert(t, p)
	end
	if level > 0 then
		t = { table.unpack(t, 1, #t - level) }
	end

	if isUnixPath then
		return "/" .. table.concat(t, "/")
	end

	return table.concat(t, "\\")
end

return getParent


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

local function getRelativePath(script_: Instance, rootDir: Instance?): string
	local path = script_.Name

	local curr: Instance = script_
	while curr.Parent and curr ~= rootDir do
		curr = curr.Parent
		path = curr.Name .. "/" .. path
	end

	return if rootDir ~= nil then path else "/" .. path
end

return getRelativePath


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
local CurrentModule = script

local nodeUtilsModule = require(CurrentModule.nodeUtils)
export type NodeJS_WriteStream = nodeUtilsModule.NodeJS_WriteStream
local exports = {
	cleanLoadStringStack = require(CurrentModule.cleanLoadStringStack),
	dedent = require(CurrentModule.dedent).dedent,
	escapePatternCharacters = require(CurrentModule.escapePatternCharacters).escapePatternCharacters,
	ensureDirectoryExists = require(CurrentModule.ensureDirectoryExists),
	getDataModelService = require(CurrentModule.getDataModelService),
	getParent = require(CurrentModule.getParent),
	expect = require(CurrentModule.expect),
	getRelativePath = require(CurrentModule.getRelativePath),
	RobloxInstance = require(CurrentModule.RobloxInstance),
	nodeUtils = nodeUtilsModule,
	normalizePromiseError = require(CurrentModule.normalizePromiseError),
	pruneDeps = require(CurrentModule.pruneDeps),
	redactStackTrace = require(CurrentModule.redactStackTrace),
	stripAnsi = require(CurrentModule.stripAnsi),
}

local WriteableModule = require(CurrentModule.Writeable)
exports.Writeable = WriteableModule.Writeable
export type Writeable = WriteableModule.Writeable

return exports


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream

local WriteableModule = require(script.Parent.Writeable)
local Writeable = WriteableModule.Writeable
type Writeable = WriteableModule.Writeable
local process = {
	stdout = Writeable.new(),
	stderr = Writeable.new(),
}
local function exit(code: number)
	error(("Exited with code: %d"):format(code))
end
export type NodeJS_WriteStream = Writeable

local HttpService = game:GetService("HttpService")
local JSON = {
	stringify = function(obj: any, ...): string
		if select("#", ...) > 0 then
			warn(
				"JSON.stringify doesn't currently support more than 1 argument. All additional arguments will be ignored."
			)
		end
		return HttpService:JSONEncode(obj)
	end,
	parse = function(str: string, ...): any
		if select("#", ...) > 0 then
			warn("JSON.parse doesn't currently support more than 1 argument. All additional arguments will be ignored.")
		end
		return HttpService:JSONDecode(str)
	end,
}

return {
	process = process,
	exit = exit,
	JSON = JSON,
}


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX NOTE: no upstream
local srcWorkspace = script.Parent
local rootWorkspace = srcWorkspace.Parent
local Packages = rootWorkspace

local LuauPolyfill = require(Packages.LuauPolyfill)
local Boolean = LuauPolyfill.Boolean
type Object = LuauPolyfill.Object

local function getPromiseErrorStack(error_: any)
	local currentError = error_
	while not Boolean.toJSBoolean(currentError.trace) and currentError.parent do
		currentError = currentError.parent
	end
	return currentError.trace or ""
end

local function getPromiseErrorMessage(error_: any)
	local currentError = error_
	local message = error_.error
	while not Boolean.toJSBoolean(currentError.trace) and currentError.parent do
		currentError = currentError.parent
		message = currentError.error or message
	end
	return currentError.message or message
end

return function(err: Object)
	err.message = getPromiseErrorMessage(err)
	err.stack = getPromiseErrorStack(err)
	return err
end


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]

local loadModuleEnabled = pcall((debug :: any).loadmodule, Instance.new("ModuleScript"))

local cleanLoadStringStack = require(script.Parent.cleanLoadStringStack)

local function pruneDeps(str: string?): string?
	if str == nil then
		return nil
	end

	local newLines = {}
	for _, line in (str :: string):split("\n") do
		if line:find("_Index.") then
			continue
		end
		if not loadModuleEnabled then
			line = cleanLoadStringStack(line)
		end
		table.insert(newLines, line)
	end
	return table.concat(newLines, "\n")
end

return pruneDeps


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]

local TRACE_LINE = `[%w_%-]+%.[%w_%-%.]+%:%d+[%w \t_]*`
local REDACT_TRACE_WITH = ("\nRedacted.Stack.Trace:1337 function epicDuck"):rep(4):sub(2)
local LUA_ERROR_LINE = `[%w_%-]+%.[%w_%-%.]+%:%d+%:[%w \t_]*`
local REDACT_LUA_ERROR_WITH = "Redacted.Stack.Trace:1337: The epic duck is coming!"

local function redactStackTrace(str: string?): string?
	if str == nil then
		return nil
	else
		local newLines = {}
		local lastLineRedacted = false
		for _, line in (str :: string):split("\n") do
			local lineWithRedactions = line:gsub(TRACE_LINE, if lastLineRedacted then "" else REDACT_TRACE_WITH)
				:gsub(LUA_ERROR_LINE, if lastLineRedacted then "" else REDACT_LUA_ERROR_WITH)
			lastLineRedacted = line ~= lineWithRedactions
			if not lastLineRedacted or lineWithRedactions:match("%S") then
				table.insert(newLines, lineWithRedactions)
			end
		end
		return table.concat(newLines, "\n")
	end
end

return redactStackTrace


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- ROBLOX upstream: no upstream
local function stripAnsi(text: string)
	local res = string.gsub(text, "[\27\155][][()#;?%d]*[A-PRZcf-ntqry=><~]", "")
	return res
end
return stripAnsi


--[[
	badger
	badge service wrapper
]]

local BadgeService = game:GetService("BadgeService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

export type Badge = {
	index: number,
	id: number,
}

type PlayerInfo = {
	threads_waiting_for_id: { [number]: { thread } },
	threads_waiting_for_all: { thread }?,
	owned: { [number]: true? },
	has_been_destroyed: true?,
	ids_to_check: { number },
	amount_checked: number,
	check_success: boolean,
	running: boolean,
	thread: thread,
	start: number,
}

local THREADS_WAITING_FOR_PLAYER_INFO = {} :: { [Player]: { thread }? }
local CHECK_BADGES = BadgeService.CheckUserBadgesAsync
local PLAYER_INFOS = {} :: { [Player]: PlayerInfo }
local ID_TO_BADGE = {} :: { [number]: Badge? }
local AWARD = BadgeService.AwardBadge
local IDS = {} :: { number }
local PAIR_INCREMENT = 9
local NULL = nil :: any
--[[
	BadgeService:CheckUserBadgesAsync() allows 5 requests per player each minute
	so every 12 seconds we should be trying to fetch
--]]
local FETCH_DELAY = 12
local TOTAL_IDS = 0

local function GET_INFO(player: Player): PlayerInfo?
	local info = PLAYER_INFOS[player]

	if not info then
		local threads = THREADS_WAITING_FOR_PLAYER_INFO[player]
		local thread = coroutine.running()

		if threads then
			table.insert(threads, thread)
		else
			THREADS_WAITING_FOR_PLAYER_INFO[player] = { thread }
		end
		info = coroutine.yield()
	end

	return info
end

local function GET_IDS_OWNED(player: Player): { [number]: true? }?
	local info = GET_INFO(player)

	if info then
		if info.amount_checked == TOTAL_IDS then
			return info.owned
		else
			local threads = info.threads_waiting_for_all

			if threads then
				table.insert(threads, coroutine.running())
			else
				info.threads_waiting_for_all = { coroutine.running() }
			end
			return coroutine.yield()
		end
	else
		return nil
	end
end

local badger_mt = {}

--[[
	Gets or creates a badge symbol for the given Id.

	WARNING: Badge symbols will not be gc'd, but why would you need them to gc anyways...
]]
function badger_mt.__call<S>(self: S, id: number): Badge
	local badge_for_id = ID_TO_BADGE[id]

	if badge_for_id then
		return badge_for_id
	else
		TOTAL_IDS += 1

		local badge = table.freeze({
			index = TOTAL_IDS,
			id = id
		})

		ID_TO_BADGE[id] = badge
		table.insert(IDS, id)
		return badge
	end
end

local badger = setmetatable({}, badger_mt)

--[[
	Returns a Set of all the ids for each badge a player currently has, otherwise returns nil if unsuccessful.
]]
function badger.get_ids(player: Player): { [number]: true? }?
	local ids = GET_IDS_OWNED(player)

	return if ids then
			table.clone(ids)
		else
			nil
end

--[[
	Awards the provided badge to the player.

	Note: This method can fail, so be sure to implement retrying if nessacary.
]]
function badger.award(player: Player, badge: Badge): boolean
	local info = GET_INFO(player)

	if info then
		local success = AWARD(BadgeService, player.UserId, badge.id)
		info = PLAYER_INFOS[player]

		if success and info then
			info.owned[badge.id] = true
		end
		return success
	else
		return false
	end
end

--[[
	Checks if a player has the provided badge.

	Note: This doesn't account for the case where a player removes a badge from their inventory,
	but thats highly unlikely. And its pretty safe to assume its because they don't want it.
]]
function badger.has(player: Player, badge: Badge): boolean
	local info = GET_INFO(player)

	if info then
		local id = badge.id

		if info.amount_checked >= badge.index then
			return info.owned[id] :: any
		else
			local waitlists = info.threads_waiting_for_id
			local threads = waitlists[id]

			if threads then
				table.insert(threads, coroutine.running())
			else
				waitlists[id] = { coroutine.running() }
			end
			return coroutine.yield()
		end
	else
		return false
	end
end

--[[
	Returns a Set of all the badges a player currently has, otherwise returns nil if unsuccessful.
]]
function badger.get_badges(player: Player): { [Badge]: true? }?
	local ids_owned = GET_IDS_OWNED(player)

	if ids_owned then
		-- using table.clone to pre allocate the table hash
		local badges: any = table.clone(ids_owned)

		for id in ids_owned do
			badges[ID_TO_BADGE[id]] = true
		end
		return badges
	else
		return nil
	end
end

do

	local function bulk_defer_threads(threads: { thread }, value: unknown)
		for _, thread in threads do
			task.defer(thread, value)
		end
	end

	local function fetch_thread_runner(player: Player, info: PlayerInfo)
		local threads_waiting_for_id = info.threads_waiting_for_id
		local ids_to_check = info.ids_to_check
		local userid = player.UserId
		local owned = info.owned

		while not info.has_been_destroyed do
			info.start = os.clock()
			local success, awarded: { number } = pcall(
				CHECK_BADGES, BadgeService, userid, info.ids_to_check
			)

			if success then
				info.amount_checked += #ids_to_check

				for _, id in ids_to_check do
					local threads_waiting = threads_waiting_for_id[id]
					local is_awarded = table.find(awarded, id) ~= nil

					if threads_waiting then
						bulk_defer_threads(threads_waiting, is_awarded)
						threads_waiting_for_id[id] = nil
					end

					if is_awarded then
						owned[id] = true
					end
				end
			end

			info.check_success = success
			info.running = false
			coroutine.yield()
		end
	end

	Players.PlayerRemoving:Connect(function(player)
		local info = PLAYER_INFOS[player]
		PLAYER_INFOS[player] = nil

		while info.running do
			task.wait()
		end

		local threads_waiting_for_all = info.threads_waiting_for_all

		if threads_waiting_for_all then
			bulk_defer_threads(threads_waiting_for_all, nil)
		end

		for _, threads in info.threads_waiting_for_id do
			bulk_defer_threads(threads, false)
		end

		task.cancel(info.thread)
	end)

	Players.PlayerAdded:Connect(function(player)
		local threads_waiting = THREADS_WAITING_FOR_PLAYER_INFO[player]
		local info: PlayerInfo = {
			ids_to_check = table.move(IDS, 1, 1 + PAIR_INCREMENT, 1, {}),
			threads_waiting_for_id = {},
			check_success = false,
			amount_checked = 0,
			start = os.clock(),
			running = true,
			thread = NULL,
			owned = {},
		}
		PLAYER_INFOS[player] = info
		info.thread = task.defer(fetch_thread_runner, player, info)

		if threads_waiting then
			THREADS_WAITING_FOR_PLAYER_INFO[player] = nil
			bulk_defer_threads(threads_waiting, info)
		end
	end)

	RunService.PostSimulation:Connect(function()
		for player, info in PLAYER_INFOS do
			local amount_checked = info.amount_checked
			local check_start = amount_checked + 1

			--[[
				1: the thread isn't doing work to fetch info
				2: the thread hasn't successfully fetched if the player owns every badge
				3: it must be 12 seconds since the previous fetch started
			--]]
			if
				not info.running and
				amount_checked ~= TOTAL_IDS and
				os.clock() - info.start >= FETCH_DELAY
			then
				local check_end = check_start + PAIR_INCREMENT
				local ids_to_check = info.ids_to_check

				table.clear(ids_to_check)
				info.ids_to_check = table.move(
					IDS, check_start, check_end, 1, ids_to_check
				)

				task.defer(info.thread)
			end
		end
	end)

	table.freeze(badger_mt)
	table.freeze(badger)

end

return badger


--[[
	character
	simple utility module for getting accurate character types,
	and with a special added function that also runs the callback
	if the player already has a character
]]

local Players = game:GetService("Players")

export type Character = Model & {
    Humanoid: Humanoid & {
        HumanoidDescription: HumanoidDescription & {
			[string]: (AccessoryDescription | BodyPartDescription)?,
		},
        Animator: Animator,
    },
    HumanoidRootPart: BasePart,
    BodyColors: BodyColors,
	PrimaryPart: BasePart,
    Animate: LocalScript & {
		ScaleDampeningPercent: NumberValue,
		PlayEmote: BindableEvent,
		cheer: StringValue,
		climb: StringValue,
		dance: StringValue,
		dance2: StringValue,
		dance3: StringValue,
		fall: StringValue,
		idle: StringValue,
		jump: StringValue,
		laugh: StringValue,
		mood: StringValue,
		point: StringValue,
		pose: StringValue,
		run: StringValue,
		sit: StringValue,
		swim: StringValue,
		swimidle: StringValue,
		toollunge: StringValue,
		toolnone: StringValue,
		toolslash: StringValue,
		walk: StringValue,
		wave: StringValue,
	},
    Head: BasePart,
}

-- maps players w their characters appearance loaded to their character
local CHARACTER_APPEARANCE_LOADED = {} :: { [Player]: Character? }

local function GET_APPEARANCE_LOADED(player: Player): Character?
	return CHARACTER_APPEARANCE_LOADED[player]
end

-- this function exists so u don't have to do
-- local character = (player.Character :: any) :: character.Character?
local function GET(player: Player): Character?
	return player.Character :: any
end

local character = {
	GET_APPEARANCE_LOADED = GET_APPEARANCE_LOADED,
	get = GET,
}

function character.removing_once(player: Player, f: (character: Character) -> ()): () -> ()
	local removing_conn = player.CharacterRemoving:Once(f :: any)

	return function()
		if removing_conn.Connected then
			removing_conn:Disconnect()
		end
	end
end

function character.removing(player: Player, f: (character: Character) -> ()): () -> ()
	local removing_conn = player.CharacterRemoving:Connect(f :: any)

	return function()
		removing_conn:Disconnect()
	end
end

function character.added(
	player: Player,
	f: (character: Character) -> (),
	dont_run_for_existing_character: boolean?
): () -> ()
	local character = GET(player)
	local removing_conn: RBXScriptConnection?
	local thread: thread

	if character and not dont_run_for_existing_character then
		thread = task.defer(f, character)

		removing_conn = player.CharacterRemoving:Once(function()
			coroutine.close(thread)
		end)
	end

	local added_conn = player.CharacterAdded:Connect(f :: any)

	return function()
		if removing_conn and removing_conn.Connected then
			removing_conn:Disconnect()
		end

		if added_conn then
			added_conn:Disconnect()
		end

		coroutine.close(thread)
	end
end

function character.added_once(
	player: Player,
	f: (character: Character) -> (),
	dont_run_for_existing_character: boolean?
): () -> ()
	local character = GET(player)

	if character and not dont_run_for_existing_character then
		local thread = task.defer(f, character)

		local conn = player.CharacterRemoving:Once(function()
			coroutine.close(thread)
		end)

		return function()
			if conn.Connected then
				conn:Disconnect()
			end
			coroutine.close(thread)
		end
	else
		local conn = player.CharacterAdded:Once(f :: any)

		return function()
			if conn.Connected then
				conn:Disconnect()
			end
		end
	end
end

function character.appearance_loaded(
	player: Player,
	f: (character: Character) -> (),
	dont_run_for_existing_character: boolean?
): () -> ()
	local character = GET_APPEARANCE_LOADED(player)
	local removing_conn: RBXScriptConnection?
	local thread: thread

	if character and not dont_run_for_existing_character then
		thread = task.defer(f, character)

		removing_conn = player.CharacterRemoving:Once(function()
			coroutine.close(thread)
		end)
	end

	local added_conn = player.CharacterAppearanceLoaded:Connect(f :: any)

	return function()
		if removing_conn and removing_conn.Connected then
			removing_conn:Disconnect()
		end

		if added_conn then
			added_conn:Disconnect()
		end

		coroutine.close(thread)
	end
end

function character.appearance_loaded_once(
	player: Player,
	f: (character: Character) -> (),
	dont_run_for_existing_character: boolean?
): () -> ()
	local character = GET_APPEARANCE_LOADED(player)

	if character and not dont_run_for_existing_character then
		local thread = task.defer(f, character)

		local conn = player.CharacterRemoving:Once(function()
			coroutine.close(thread)
		end)

		return function()
			if conn.Connected then
				conn:Disconnect()
			end
			coroutine.close(thread)
		end
	else
		local conn = player.CharacterAppearanceLoaded:Once(f :: any)

		return function()
			if conn.Connected then
				conn:Disconnect()
			end
		end
	end
end

character.get_appearance_loaded = GET_APPEARANCE_LOADED
character.get = GET

do

	local function on_player_added(player: Player)
		player.CharacterAppearanceLoaded:Connect(function(character: any)
			CHARACTER_APPEARANCE_LOADED[player] = character
		end)
		player.CharacterRemoving:Connect(function()
			CHARACTER_APPEARANCE_LOADED[player] = nil
		end)
	end

	Players.PlayerRemoving:Connect(function(player)
		CHARACTER_APPEARANCE_LOADED[player] = nil
	end)

	for _, player in Players:GetPlayers() do
		task.spawn(on_player_added, player)
	end
	Players.PlayerAdded:Connect(on_player_added)

end

return table.freeze(character)


--!nocheck
--!nolint

--[[
	cross
	basic cross runtime utility, covering the bear nessisities
]]

local cross_task = require("@self/task")
local cross_warn = require("@self/warn")

type Runtime = "Roblox" | "Lune" | "Unknown" | "Lute"

local RUNTIME: Runtime = if game and workspace then
		"Roblox"
	elseif string.find(_VERSION, "Lute") then
		"Lute"
	elseif string.find(_VERSION, "Lune") then
		"Lune"
	else
		"Unknown"

local STD_TASK_EXISTS, STD_TASK = pcall(require, "@std/task")
local WARN: typeof(cross_warn) = warn or cross_warn
local TASK: typeof(cross_task) = if task then
		task
	elseif STD_TASK_EXISTS then
		STD_TASK
	elseif RUNTIME == "Lune" then
		(require)("@lune/task")
	else
		cross_task

do
	local can_set_G = true

	local function SET_G(key: string, value: unknown)
		if can_set_G and not _G[key] then
			_G[key] = value
		end
	end

	if not _RUNTIME then
		local success = pcall(SET_G, "_RUNTIME", RUNTIME)

		if not success then
			can_set_G = false
		end
		_RUNTIME = RUNTIME
	end

	if not warn then
		SET_G("warn", WARN)
		warn = WARN
	end

	if not task then
		SET_G("task", TASK)
		task = TASK
	end

end

return table.freeze({
	cancel = TASK.cancel,
	spawn = TASK.spawn,
	defer = TASK.defer,
	delay = TASK.delay,
	runtime = RUNTIME,
	wait = TASK.wait,
	warn = WARN,
})


--[[
	task
	a good enough pure luau version of roblox's task lib
	that doesn't require wrapping ur whole script in a
	task.spawn or anything
]]

local function SPAWN<A..., R...>(
	thread_or_func: thread | (A...) -> R...,
	...: A...
): thread
	if type(thread_or_func) == "thread" then
		coroutine.resume(thread_or_func, ...)
		return thread_or_func
	else
		local thread = coroutine.create(thread_or_func)
		coroutine.resume(thread, ...)
		return thread
	end
end

local function WAIT(seconds: number?): number
	local start_time = os.clock()
	local end_time = start_time + (seconds or 1)
	local clock_time: number

	repeat
		clock_time = os.clock()
	until clock_time >= end_time
	return clock_time - start_time
end

local task = {
	defer = SPAWN,
	spawn = SPAWN,
	wait = WAIT,
}

function task.cancel(thread: thread)
	local success = coroutine.close(thread)

	if not success then
		error(debug.traceback(thread, "[TASK]: could not cancel thread"))
	end
end

function task.delay<A..., R...>(
	seconds: number,
	thread_or_func: thread | (A...) -> R...,
	...: A...
): thread
	return SPAWN(function(
		seconds: number,
		thread_or_func: thread | (A...) -> R...,
		...: A...
	)
		WAIT(seconds)

		if type(thread_or_func) == "thread" then
			coroutine.resume(thread_or_func, ...)
		else
			thread_or_func(...)
		end
	end, seconds, thread_or_func, ...)
end

return table.freeze(task)