From b1892a8098c3fce482c2761b5c5b7447c6aa0cf1 Mon Sep 17 00:00:00 2001 From: Erica Marigold Date: Thu, 21 Nov 2024 10:37:38 +0000 Subject: [PATCH] init: initial binlib logic and demo lune package --- .gitignore | 3 + .luaurc | 3 + .vscode/settings.json | 7 + core/README.md | 0 core/pesde.toml | 19 ++ core/src/compression.luau | 97 +++++++ core/src/github.luau | 91 ++++++ core/src/init.luau | 176 ++++++++++++ core/src/platform/arch.luau | 38 +++ core/src/platform/descriptor.luau | 59 ++++ core/src/platform/detection/executable.luau | 92 ++++++ core/src/platform/detection/init.luau | 7 + core/src/platform/detection/pattern.luau | 34 +++ core/src/platform/os.luau | 34 +++ core/src/platform/result.luau | 7 + core/src/platform/toolchain.luau | 19 ++ core/src/utils/copy.luau | 15 + core/src/utils/eq.luau | 29 ++ core/src/utils/exec.luau | 193 +++++++++++++ core/src/utils/result_option_conv.luau | 28 ++ core/src/utils/rev_table.luau | 6 + core/src/utils/semver.luau | 296 ++++++++++++++++++++ core/src/utils/string.luau | 51 ++++ lune/init.luau | 10 + lune/pesde.lock | 170 +++++++++++ lune/pesde.toml | 19 ++ pesde.lock | 9 + pesde.toml | 13 + 28 files changed, 1525 insertions(+) create mode 100644 .gitignore create mode 100644 .luaurc create mode 100644 .vscode/settings.json create mode 100644 core/README.md create mode 100644 core/pesde.toml create mode 100644 core/src/compression.luau create mode 100644 core/src/github.luau create mode 100644 core/src/init.luau create mode 100644 core/src/platform/arch.luau create mode 100644 core/src/platform/descriptor.luau create mode 100644 core/src/platform/detection/executable.luau create mode 100644 core/src/platform/detection/init.luau create mode 100644 core/src/platform/detection/pattern.luau create mode 100644 core/src/platform/os.luau create mode 100644 core/src/platform/result.luau create mode 100644 core/src/platform/toolchain.luau create mode 100644 core/src/utils/copy.luau create mode 100644 core/src/utils/eq.luau create mode 100644 core/src/utils/exec.luau create mode 100644 core/src/utils/result_option_conv.luau create mode 100644 core/src/utils/rev_table.luau create mode 100644 core/src/utils/semver.luau create mode 100644 core/src/utils/string.luau create mode 100644 lune/init.luau create mode 100644 lune/pesde.lock create mode 100644 lune/pesde.toml create mode 100644 pesde.lock create mode 100644 pesde.toml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67b9151 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +lune_packages/ +luau_packages/ +core/pesde.lock diff --git a/.luaurc b/.luaurc new file mode 100644 index 0000000..086d9f1 --- /dev/null +++ b/.luaurc @@ -0,0 +1,3 @@ +{ + "languageMode": "strict" +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..afb4651 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "luau-lsp.require.mode": "relativeToFile", + "luau-lsp.require.directoryAliases": { + "@lune/": "~/.lune/.typedefs/0.8.9/" + }, + "stylua.targetReleaseVersion": "latest" +} \ No newline at end of file diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..e69de29 diff --git a/core/pesde.toml b/core/pesde.toml new file mode 100644 index 0000000..6b23e8e --- /dev/null +++ b/core/pesde.toml @@ -0,0 +1,19 @@ +name = "compeydev/binlib" +version = "0.1.0" +includes = ["src/", "pesde.toml", "LICENSE", "README.md"] + +[target] +environment = "lune" +lib = "src/init.luau" + +[dependencies] +pathfs = { name = "jiwonz/pathfs", version = "^0.1.0" } +dirs = { name = "jiwonz/dirs", version = "^0.1.1" } +semver = { name = "0x5eal/semver", version = "^0.1.0", target = "luau" } + +[peer_dependencies] +result = { name = "lukadev_0/result", version = "^1.2.0" } +option = { name = "lukadev_0/option", version = "^1.2.0" } + +[indices] +default = "https://github.com/daimond113/pesde-index" diff --git a/core/src/compression.luau b/core/src/compression.luau new file mode 100644 index 0000000..f6bbad1 --- /dev/null +++ b/core/src/compression.luau @@ -0,0 +1,97 @@ +local serde = require("@lune/serde") +local process = require("@lune/process") + +local dirs = require("../lune_packages/dirs") +local pathfs = require("../lune_packages/pathfs") + +local revTable = require("./utils/rev_table") +local CommandBuilder = require("./utils/exec") +local types = require("./utils/result_option_conv") +local Option = types.Option +type Option = types.Option +local Result = types.Result +type Result = types.Result + +export type CompressionFormat = "TarGz" | "TarXz" | "Zip" + +local function detectFormat(fileName: string): Option + local fileNameParts = string.split(string.lower(fileName), ".") + revTable(fileNameParts) + + if fileNameParts[1] == "zip" then + return Option.Some("Zip" :: CompressionFormat) :: Option + end + + if fileNameParts[2] == "tar" then + if fileNameParts[1] == "gz" then + return Option.Some("TarGz" :: CompressionFormat) :: Option + end + + if fileNameParts[1] == "xz" then + return Option.Some("TarXz" :: CompressionFormat) :: Option + end + end + + return Option.None :: Option +end + +-- TODO: Use a type function to make all CompressionFormat lowercase +local decompress: { [CompressionFormat]: (compressed: buffer) -> Result } = { + Zip = function(compressed: buffer) + -- FIXME: remove any usage + return (Option.from(dirs.cacheDir()):map(function(cacheDir) + local progCacheDir = cacheDir:join("pesde-bin") + if not pathfs.isDir(progCacheDir) then + pathfs.writeDir(progCacheDir) + end + + return progCacheDir :: pathfs.AsPath + end) :: Option):match({ + Some = function(dir) + -- Generate a unique file name and write the contents to the temporary file + local tmpFile = dir:join(`{serde.hash("blake3", compressed)}.zip`) + local tmpFilePath = tmpFile:toString() + pathfs.writeFile(tmpFile, compressed) + + -- Create the directory to decompress into + local decompressedDir = pathfs.Path.from(tmpFile):withExtension("") + pathfs.writeDir(decompressedDir) + + -- Run unzip to decompress the file + local child = CommandBuilder + .new("unzip") + :withArgs({ tmpFilePath, "-d", decompressedDir:toString() }) + -- FIXME: remove unknown usage + :withStdioStrategy({ + stdout = Option.Some("pipe" :: CommandBuilder.StdioStrategy) :: Option, + stderr = Option.Some(if process.env.PESDE_LOG == "debug" then "forward" else "pipe" :: CommandBuilder.StdioStrategy) :: Option, + } :: CommandBuilder.IoStrategyMapping) + :intoChildProcess() + + child:start() + local status = child:waitForChild() + + -- Cleanup temporary file and handle errors + pathfs.removeFile(tmpFile) + if not status.ok then + return Result.Err( + `DecompressError::CommandFailed(exitCode={status.code})` + ) :: Result + end + + return Result.Ok(decompressedDir) :: Result + end, + + None = function() + return Result.Err("DecompressError::NoCacheDir") :: Result + end, + }) + end, + + -- TODO: Other formats +} + +-- local path = "pesde-0.5.0-rc.7-windows-x86_64.zip" +-- print(decompress[detectFormat(path):unwrap()](buffer.fromstring(pathfs.readFile(path)))) + +return { decompress = decompress, detectFormat = detectFormat } diff --git a/core/src/github.luau b/core/src/github.luau new file mode 100644 index 0000000..6623493 --- /dev/null +++ b/core/src/github.luau @@ -0,0 +1,91 @@ +local net = require("@lune/net") + +local copy = require("./utils/copy") +local types = require("./utils/result_option_conv") +local Option = types.Option +local Result = types.Result +type Option = types.Option +type Result = types.Result + +local Github = {} +export type Github = typeof(setmetatable(Github :: GithubFields, { __index = Github })) +type GithubFields = { + req: net.FetchParams, + retries: number, +} + +export type Config = { + authToken: Option, + retries: Option, +} +export type GithubOperation = "FetchReleases" | "GetMetadata" | "GetActionArtifacts" + +local API_BASE_URL = "https://api.github.com" +local DEFAULT_MAX_RETRIES = 5 +local DEFAULT_CONFIG: Config = { + authToken = Option.None :: Option, + retries = Option.Some(DEFAULT_MAX_RETRIES) :: Option, +} + +function Github.new(repo: string, config: Option) + local configOrDefault = config:unwrapOr(DEFAULT_CONFIG) + return setmetatable( + { + req = { + url = API_BASE_URL .. "/repos/" .. repo, + headers = { + ["Authorization"] = configOrDefault.authToken:mapOr("", function(token) + return `Bearer {token}` + end), + }, + } :: net.FetchParams, + config = config, + retries = configOrDefault.retries:unwrapOr(DEFAULT_MAX_RETRIES), + } :: GithubFields, + { + __index = Github, + } + ) +end + +-- FIXME: Remove unknown usage here +function Github.queueTransactions(self: Github, operations: { GithubOperation }): { Result } + local queue: { (retries: number) -> Result } = table.create(#operations) + + for _, operation: GithubOperation in operations do + local req: net.FetchParams = copy(self.req) + + if operation == "FetchReleases" then + req.url ..= "/releases" + req.method = "GET" + end + + -- TODO: Other methods + + table.insert(queue, function(retries: number) + local lastCode: number + for _ = 1, retries do + local resp = net.request(req) + lastCode = resp.statusCode + + if not resp.ok then + continue + end + + return Result.Ok(net.jsonDecode(resp.body)) + end + + return Result.Err(`Github::RespErr(statusCode={lastCode})`) + end) + end + + local results = {} + for _, req in queue do + local ok, respRes: Result = pcall(req, self.retries) + table.insert(results, if ok then respRes else Result.Err("Github::IoError")) + end + + return results +end + +return Github diff --git a/core/src/init.luau b/core/src/init.luau new file mode 100644 index 0000000..38a4d11 --- /dev/null +++ b/core/src/init.luau @@ -0,0 +1,176 @@ +--> ENTRYPOINT; The main logic is as follows: +--> * First, we fetch the artifacts +--> * We try going through the list and using pattern matching to figure out +--> which is the artifact to download +--> * If we get no matches, we try downloading all the artifacts +--> * The artifacts are all extracted and we try detecting the platform from +--> binary data +--> * We then install the artifact with the matching platform descriptor + +local net = require("@lune/net") +local process = require("@lune/process") + +local pathfs = require("../lune_packages/pathfs") +local dirs = require("../lune_packages/dirs") +local semver = require("../luau_packages/semver") + +local types = require("./utils/result_option_conv") +local Option = types.Option +type Option = types.Option + +local Github = require("./github") +local PlatformDescriptor = require("./platform/descriptor") +local compression = require("./compression") +local eq = require("./utils/eq") + +export type ToolId = { + alias: Option, + repo: string, + version: Option, +} + +export type GithubReleases = { + { + tag_name: string, + prerelease: boolean, + draft: boolean, + assets: { + { + name: string, + browser_download_url: string, + size: number, + content_type: string, + } + }, + } +} + +local function downloadAndDecompress(asset: { + name: string, + browser_download_url: string, + size: number, + content_type: string, +}): pathfs.Path + local contentsResp = net.request(asset.browser_download_url) + if not contentsResp.ok then + return error(`Failed to download asset {asset.name}: HTTP Code {contentsResp.statusCode}`) + end + + local decompressedPath = compression.decompress + [compression.detectFormat(asset.name):unwrap()](buffer.fromstring(contentsResp.body)) + :unwrap() :: pathfs.Path + + return decompressedPath +end + +local function runTool(path: pathfs.Path) + -- TODO: Fixup our wrapper and use that instead in the future + process.spawn(path:toString(), process.args, { + stdio = "forward", + shell = true, + }) +end + +local TOOL_INSTALL_DIR = (dirs.homeDir() or error("Couldn't get home dir :(")):join(".pesde"):join("bin") + +function installTool(tool: ToolId) + local toolAlias = tool.alias:unwrapOr(string.split(tool.repo, "/")[2]) + local toolInstallPath = TOOL_INSTALL_DIR:join(toolAlias) + + -- TODO: This is a bit too much overhead on startup and takes about 2s + -- We can improve this by following what rokit does, where we symlink + -- the tool's path to this script, and check the file that we are being + -- invoked as, in order to figure out what tool to execute + + -- We can create "linker" scripts for each tool at ~/.pesde/bins with + -- contents like so: + --[[ + #!/bin/env -S lune run + + local pathInfo = debug.info(1, "s") + local path = string.sub(pathInfo, 10, #pathInfo - 2)) + + -- Now we can use `path` to figure out the real tool to execute + -- ... + ]] + if pathfs.isFile(toolInstallPath) then + runTool(toolInstallPath) + return + end + + local client = Github.new( + tool.repo, + Option.Some({ + -- TODO: Maybe use the `gh auth token` command to get the token + authToken = Option.from(process.env.GITHUB_TOKEN) :: Option, + retries = Option.None :: Option, + }) :: Option + ) + + local releases = client:queueTransactions({ "FetchReleases" })[1]:unwrap() :: GithubReleases + local assets = tool.version:match({ + Some = function(version: semver.SemverImpl) + for _, release in releases do + if semver.parse(release.tag_name):unwrap() :: semver.SemverImpl == version then + return release.assets + end + end + + return error(`No release found for version {version}`) + end, + + None = function() + return releases[0].assets + end, + }) + + -- TODO: Use index type fn in solver v2 + local matchingAsset: { + name: string, + browser_download_url: string, + size: number, + content_type: string, + } + + local currentDesc = PlatformDescriptor.currentSystem() + local aliasPath = pathfs.Path.from(toolAlias):withExtension(if currentDesc.os == "windows" then "exe" else "") + + for _, asset in assets do + local desc = PlatformDescriptor.fromString(asset.name):unwrap() + if eq(currentDesc, desc) then + matchingAsset = asset + end + end + + local binaryPath: pathfs.Path + if matchingAsset == nil then + warn("No matching asset found, downloading all assets") + for _, asset in assets do + local decompressedPath = downloadAndDecompress(asset) + + for _, file in pathfs.readDir(decompressedPath) do + local filePath = decompressedPath:join(file) + local nativeDesc = PlatformDescriptor.fromExecutable(filePath:toString()):unwrap() + + if eq(currentDesc, nativeDesc) then + binaryPath = filePath + break + end + end + end + else + local decompressedPath = downloadAndDecompress(matchingAsset) + binaryPath = decompressedPath:join(aliasPath) + if not pathfs.isFile(binaryPath) then + error(`No matching binary found in {decompressedPath}`) + end + end + + pathfs.move(binaryPath, toolInstallPath) + runTool(toolInstallPath) +end + +return { + installTool = installTool, + runTool = runTool +} \ No newline at end of file diff --git a/core/src/platform/arch.luau b/core/src/platform/arch.luau new file mode 100644 index 0000000..6bb77c9 --- /dev/null +++ b/core/src/platform/arch.luau @@ -0,0 +1,38 @@ +--> Mostly a recreation of rokit's detection logic in Luau +--> See https://github.com/rojo-rbx/rokit/blob/a6b84c/lib/descriptor/arch.rs + +local process = require("@lune/process") +local detection = require("./detection") + +local types = require("../utils/result_option_conv") +local Option = types.Option +type Option = types.Option + +export type Arch = process.Arch | "arm" | "x86" + +local ARCH_SUBSTRINGS: { [Arch]: { string } } = { + aarch64 = { "aarch64", "arm64", "armv9" }, + x86_64 = { "x86-64", "x86_64", "amd64", "win64", "win-x64" }, + arm = { "arm32", "armv7" }, + x86 = { "i686", "i386", "win32", "win-x86" }, +} + +local ARCH_FULL_WORDS: { [Arch]: { string } } = { + aarch64 = {}, + x86_64 = { "x64", "win" }, + arm = { "arm" }, + x86 = { "x86" }, +} + +return { + detect = function(str: string): Option + return detection.detect(str, ARCH_SUBSTRINGS, ARCH_FULL_WORDS) + end, + + detectFromExecutable = function(binaryContents: buffer) + return Option.from(detection.detectFromExecutable(binaryContents)) + :map(function(inner: detection.ExecutableDetectionResult) + return inner.arch + end) + end, +} diff --git a/core/src/platform/descriptor.luau b/core/src/platform/descriptor.luau new file mode 100644 index 0000000..4c39de7 --- /dev/null +++ b/core/src/platform/descriptor.luau @@ -0,0 +1,59 @@ +local process = require("@lune/process") +local fs = require("@lune/fs") + +local os = require("./os") +local arch = require("./arch") +local toolchain = require("./toolchain") +local result = require("./result") +local detectFromExecutable = require("./detection/executable") + +local types = require("../utils/result_option_conv") +local Option = types.Option +local Result = types.Result +type Option = types.Option +type Result = types.Result + +local PlatformDescriptor = {} +type PlatformDescriptor = { + os: process.OS, + arch: Option, + toolchain: Option, +} + +function PlatformDescriptor.currentSystem(): PlatformDescriptor + return { + os = process.os, + arch = Option.Some(process.arch) :: Option, + toolchain = Option.None :: Option, + } +end + +function PlatformDescriptor.fromString(str: string) + local detectedOs = os.detect(str) + if detectedOs:isNone() then + return Result.Err("NoPatternDetected" :: result.PlatformError) + end + + return Result.Ok({ + os = detectedOs:unwrap() :: process.OS, + arch = arch.detect(str), + toolchain = toolchain.detect(str), + } :: PlatformDescriptor) +end + +function PlatformDescriptor.fromExecutable(path: string): result.PlatformResult + local binaryContents = fs.readFile(path) + local detected = + Option.from(detectFromExecutable(buffer.fromstring(binaryContents))) :: Option + local platformDesc = detected:map(function(inner: detectFromExecutable.ExecutableDetectionResult) + local innerClone: PlatformDescriptor = table.clone(inner) :: any + innerClone.toolchain = Option.None :: Option + return innerClone + end) :: Option + + return platformDesc:okOr( + "NoExecutableDetected" :: result.PlatformError + ) :: result.PlatformResult +end + +return PlatformDescriptor diff --git a/core/src/platform/detection/executable.luau b/core/src/platform/detection/executable.luau new file mode 100644 index 0000000..7baf63b --- /dev/null +++ b/core/src/platform/detection/executable.luau @@ -0,0 +1,92 @@ +local process = require("@lune/process") + +type Arch = process.Arch | "arm" | "x86" +export type ExecutableDetectionResult = { + os: process.OS?, + arch: Arch?, +} + +return function(binaryContents: buffer): ExecutableDetectionResult? + -- Windows PE + do + local DOS_HEADER = "MZ" + local PE_HEADER_OFFSET = 0x3c + local PE_MAGIC_SIGNATURE = "PE\x00\x00" + local PE_MACHINE_TYPES: { [number]: Arch } = { + [0x8664] = "x86_64", + [0x01c0] = "arm", + [0xaa64] = "aarch64", + [0x014c] = "x86", + } + + if buffer.readstring(binaryContents, 0, 2) == DOS_HEADER then + -- File was a DOS executable, jump to PE header to get the magic offset + local signatureOffset = buffer.readu8(binaryContents, PE_HEADER_OFFSET) + + -- Check if the value at the magic offset was the PE magic signature + if buffer.readstring(binaryContents, signatureOffset, 4) == PE_MAGIC_SIGNATURE then + -- After the first 4 magic bytes, the next 2 bytes are architecture information + local machineType = buffer.readu16(binaryContents, signatureOffset + 4) + return { + os = "windows", + arch = PE_MACHINE_TYPES[machineType], + } + end + end + end + + -- Linux ELF + do + local ELF_MAGIC_START = 0x7f + local ELF_MAGIC_SIGNATURE = "ELF" + local ELF_MACHINE_TYPES: { [number]: Arch } = { + [0x3e] = "x86_64", + [0x28] = "arm", + [0xb7] = "aarch64", + [0x03] = "x86", + } + + -- ELF files have a magic signature of [0x7f, 'E', 'L', 'F'] + if + buffer.readu8(binaryContents, 0) == ELF_MAGIC_START + and buffer.readstring(binaryContents, 1, 3) == ELF_MAGIC_SIGNATURE + then + -- Machine type is located after 16 bytes of ident and 2 bytes of type + local machineType = buffer.readu16(binaryContents, 18) + return { + os = "linux", + arch = ELF_MACHINE_TYPES[machineType], + } + end + end + + -- macOS Mach-O + do + local MACHO_MAGIC_32 = 0xFEEDFACE + local MACHO_MAGIC_64 = 0xFEEDFACF + local MACHO_CPU_TYPES = { + x86 = 0x7, + arm = 0xc, + } + + -- First 4 bytes are the magic depending on 32 or 64 bit + -- Next 2 bytes are the CPU type + local headerStart = buffer.readu32(binaryContents, 0) + local cpuType = buffer.readu16(binaryContents, 4) + + local is64bit = headerStart == MACHO_MAGIC_64 + local is32bit = headerStart == MACHO_MAGIC_32 + + return { + os = "macos", + --stylua: ignore + arch = ( + if is64bit and cpuType == MACHO_CPU_TYPES.x86 then "x86_64" + elseif is64bit and cpuType == MACHO_CPU_TYPES.arm then "aarch64" + elseif is32bit and cpuType == MACHO_CPU_TYPES.x86 then "x86" + elseif is32bit and cpuType == MACHO_CPU_TYPES.arm then "arm" + else nil + ), + } + end +end diff --git a/core/src/platform/detection/init.luau b/core/src/platform/detection/init.luau new file mode 100644 index 0000000..c0b42e2 --- /dev/null +++ b/core/src/platform/detection/init.luau @@ -0,0 +1,7 @@ +local executable = require("./executable") +export type ExecutableDetectionResult = executable.ExecutableDetectionResult + +return { + detect = require("./pattern"), + detectFromExecutable = executable, +} diff --git a/core/src/platform/detection/pattern.luau b/core/src/platform/detection/pattern.luau new file mode 100644 index 0000000..28efc52 --- /dev/null +++ b/core/src/platform/detection/pattern.luau @@ -0,0 +1,34 @@ +local String = require("../../utils/string") + +local types = require("../../utils/result_option_conv") +local Option = types.Option +type Option = types.Option + +local function charWordSep(char: string) + return char == " " or char == "-" or char == "_" +end + +return function(str: string, substrings: { [T]: { string } }, fullWords: { [T]: { string } }): Option + local lowercased = string.lower(str) + + -- Look for substring matches + for item: T, keywords in substrings do + for _, keyword in keywords do + if string.find(lowercased, keyword) then + return Option.Some(item) :: Option + end + end + end + + -- If no substring matches found, look for a full word as a component + local components = String.splitAtChar(lowercased, charWordSep) + for _, component in components do + for item, keywords in fullWords do + if table.find(keywords, component) then + return Option.Some(item) :: Option + end + end + end + + return Option.None :: Option +end diff --git a/core/src/platform/os.luau b/core/src/platform/os.luau new file mode 100644 index 0000000..9538a5e --- /dev/null +++ b/core/src/platform/os.luau @@ -0,0 +1,34 @@ +--> Mostly a recreation of rokit's detection logic in Luau +--> See https://github.com/rojo-rbx/rokit/blob/a6b84c/lib/descriptor/os.rs + +local process = require("@lune/process") + +local detection = require("./detection") + +local Option = require("../../lune_packages/option") +type Option = Option.Option + +local OS_SUBSTRINGS: { [process.OS]: { string } } = { + windows = { "windows" }, + macos = { "macos", "darwin", "apple" }, + linux = { "linux", "ubuntu", "debian", "fedora" }, +} + +local OS_FULL_WORDS: { [process.OS]: { string } } = { + windows = { "win", "win32", "win64" }, + macos = { "mac", "osx" }, + linux = {}, +} + +return { + detect = function(str: string): Option + return detection.detect(str, OS_SUBSTRINGS, OS_FULL_WORDS) + end, + + detectFromExecutable = function(binaryContents: buffer): Option + return Option.from(detection.detectFromExecutable(binaryContents)) + :map(function(inner: detection.ExecutableDetectionResult) + return inner.os + end) + end, +} diff --git a/core/src/platform/result.luau b/core/src/platform/result.luau new file mode 100644 index 0000000..da14582 --- /dev/null +++ b/core/src/platform/result.luau @@ -0,0 +1,7 @@ +local types = require("../utils/result_option_conv") +type Result = types.Result + +export type PlatformError = "NoPatternDetected" | "NoExecutableDetected" | "UnknownExecutableField" +export type PlatformResult = Result + +return "" diff --git a/core/src/platform/toolchain.luau b/core/src/platform/toolchain.luau new file mode 100644 index 0000000..89171a4 --- /dev/null +++ b/core/src/platform/toolchain.luau @@ -0,0 +1,19 @@ +local types = require("../utils/result_option_conv") +local Option = types.Option +type Option = types.Option + +local TOOLCHAINS: { Toolchain } = { "msvc", "gnu", "musl" } +export type Toolchain = "msvc" | "gnu" | "musl" + +return { + detect = function(str: string): Option + for _, toolchain: Toolchain in TOOLCHAINS do + if string.find(str, toolchain) then + -- FIXME: remove any usage + return Option.Some(toolchain :: any) :: Option + end + end + + return Option.None :: Option + end, +} diff --git a/core/src/utils/copy.luau b/core/src/utils/copy.luau new file mode 100644 index 0000000..3b91da8 --- /dev/null +++ b/core/src/utils/copy.luau @@ -0,0 +1,15 @@ +local function copy(tab: T): T + if typeof(tab) == "table" then + local copied = {} + + for k, v in tab do + copied[k] = copy(v) + end + + return copied :: any + end + + return tab +end + +return copy diff --git a/core/src/utils/eq.luau b/core/src/utils/eq.luau new file mode 100644 index 0000000..3e18c15 --- /dev/null +++ b/core/src/utils/eq.luau @@ -0,0 +1,29 @@ +-- !! FIXME: THIS IS BROKEN !! +local function eq(this: any, that: any): boolean + if type(this) ~= type(that) then + return false + end + + if type(this) == "table" then + local visited = {} + + for key, value in pairs(this) do + if not eq(value, that[key]) then + return false + end + visited[key] = true + end + + for key, _ in pairs(that) do + if not visited[key] then + return false + end + end + + return true + end + + return this == that +end + +return eq diff --git a/core/src/utils/exec.luau b/core/src/utils/exec.luau new file mode 100644 index 0000000..d4a641d --- /dev/null +++ b/core/src/utils/exec.luau @@ -0,0 +1,193 @@ +--> Builder pattern class to spawn, manage and kill child processes + +local process = require("@lune/process") +local task = require("@lune/task") + +local types = require("../utils/result_option_conv") +local Option = types.Option +type Option = types.Option + +local CommandBuilder = {} +type CommandBuilderFields = { + program: string, + args: { string }, + retries: Option, + ignoreErrors: Option, + stdioStrategy: Option, +} +export type CommandBuilder = typeof(setmetatable({} :: CommandBuilderFields, { __index = CommandBuilder })) +export type StdioStrategy = "pipe" | "forward" | "none" +export type IoStrategyMapping = { + stdout: Option, + stderr: Option, +} +export type ChildProcess = { + _thread: thread, + _pid: string, + _status: ChildStatus, + start: (self: ChildProcess) -> (), + waitForChild: (self: ChildProcess) -> ChildStatus, + kill: (self: ChildProcess) -> (), +} +export type ChildStatus = { ok: boolean, code: number, io: { + stdout: string, + stderr: string, +} } + +-- FIXME: remove unknown usage +local DEFAULT_STDIO_STRATEGY: IoStrategyMapping = { + stdout = Option.Some("pipe" :: StdioStrategy) :: Option, + stderr = Option.Some("pipe" :: StdioStrategy) :: Option, +} +local DEFAULT_RETRIES = 0 +local DEFAULT_IGNORE_ERRORS = false + +function CommandBuilder.new(program: string) + return setmetatable( + { + program = program, + args = {}, + retries = Option.None, + ignoreErrors = Option.None, + stdioStrategy = Option.None :: Option, + } :: CommandBuilderFields, + { + __index = CommandBuilder, + } + ) +end + +function CommandBuilder.withArg(self: CommandBuilder, arg: string): CommandBuilder + table.insert(self.args, arg) + return self +end + +function CommandBuilder.withArgs(self: CommandBuilder, args: { string }): CommandBuilder + for _, arg in args do + self:withArg(arg) + end + + return self +end + +function CommandBuilder.withMaxRetries(self: CommandBuilder, retries: number): CommandBuilder + self.retries = Option.Some(retries) :: Option + return self +end + +function CommandBuilder.withIgnoreErrors(self: CommandBuilder, yes: boolean): CommandBuilder + self.ignoreErrors = Option.Some(yes) :: Option + return self +end + +function CommandBuilder.withStdioStrategy( + self: CommandBuilder, + strategy: StdioStrategy | IoStrategyMapping +): CommandBuilder + -- FIXME: remove unknown usage + self.stdioStrategy = Option.Some(if typeof(strategy) == "string" + then { + stdout = Option.Some(strategy) :: Option, + stderr = Option.Some(strategy) :: Option, + } + else strategy) :: Option + return self +end + +local function intoSpawnOptionsStdioKind(strategy: StdioStrategy): process.SpawnOptionsStdioKind + if strategy == "pipe" then + return "default" + end + + if strategy == "forward" then + return "forward" + end + + if strategy == "none" then + return "none" + end + + error(`Non-strategy provided: {strategy}`) +end + +function CommandBuilder.intoChildProcess(self: CommandBuilder): ChildProcess + local child = { + _thread = coroutine.create(function(this: ChildProcess) + local retries = self.retries:unwrapOr(DEFAULT_RETRIES) + local ignoreErrors = self.ignoreErrors:unwrapOr(DEFAULT_IGNORE_ERRORS) + local argsList = table.concat(self.args, " ") + + for _ = 0, retries do + local spawned = process.spawn( + if process.os == "windows" + then `(Start-Process {self.program} -Passthru -NoNewWindow -ArgumentList \"{argsList}\").Id` + else `{self.program} {argsList} & echo $!`, + {}, + { + stdio = self + .stdioStrategy + -- FIXME: remove unknown usage + :orOpt( + Option.Some(DEFAULT_STDIO_STRATEGY) :: Option + ) + :map(function(mappings: IoStrategyMapping) + local translatedMappings: process.SpawnOptionsStdio = {} + for field, value in mappings do + translatedMappings[field] = + intoSpawnOptionsStdioKind((value :: Option):unwrap()) + end + + return translatedMappings + end) + :unwrap(), + shell = true, + } + ) + + if spawned.ok then + local lines = spawned.stdout:split("\n") + + -- TODO: Abstract upvalues here into a channels primitive + this._pid = assert(table.remove(lines, 1), "Failed to get PID") + this._status = { + code = spawned.code, + ok = spawned.code == 0 and not ignoreErrors, + io = { + stdout = table.concat(lines, "\n"), + stderr = spawned.stderr, + }, + } + break + end + end + end), + + start = function(self: ChildProcess) + coroutine.resume(self._thread, self) + end, + + waitForChild = function(self: ChildProcess): ChildStatus + while coroutine.status(self._thread) ~= "dead" or self._status == nil do + task.wait(0.1) + end + + return self._status + end, + + kill = function(self: ChildProcess) + coroutine.close(self._thread) + local killResult = process.spawn( + if process.os == "windows" then `Stop-Process -Id {self._pid} -Force` else `kill {self._pid}`, + { + shell = true, + } + ) + + assert(killResult.ok, `Failed to kill process with PID {self._pid}`) + end, + } :: ChildProcess + + return child +end + +return CommandBuilder diff --git a/core/src/utils/result_option_conv.luau b/core/src/utils/result_option_conv.luau new file mode 100644 index 0000000..96500a7 --- /dev/null +++ b/core/src/utils/result_option_conv.luau @@ -0,0 +1,28 @@ +--> Non-exhaustive set of extensions for Option<->Result conversion + +local OptionImpl = require("../../lune_packages/option") +local ResultImpl = require("../../lune_packages/result") + +local Option = {} +local Result = {} + +export type Option = OptionImpl.Option & typeof(Option) +export type Result = ResultImpl.Result + +function Option.okOr(self: Option, err: E): Result + return self:mapOrElse(function() + return ResultImpl.Err(err) + end, function(val) + return ResultImpl.Ok(val) + end) +end + +return { + Option = setmetatable(OptionImpl, { + __index = Option, + }), + + Result = setmetatable(ResultImpl, { + __index = Result, + }), +} diff --git a/core/src/utils/rev_table.luau b/core/src/utils/rev_table.luau new file mode 100644 index 0000000..92803e4 --- /dev/null +++ b/core/src/utils/rev_table.luau @@ -0,0 +1,6 @@ +return function(tab) + local len = #tab + for pos = 1, len // 2 do + tab[pos], tab[len - pos + 1] = tab[len - pos + 1], tab[pos] + end +end diff --git a/core/src/utils/semver.luau b/core/src/utils/semver.luau new file mode 100644 index 0000000..b47a325 --- /dev/null +++ b/core/src/utils/semver.luau @@ -0,0 +1,296 @@ +local types = require("../utils/result_option_conv") +local Option = types.Option +type Option = types.Option +local Result = types.Result + +local function stringStartsWith(str: string, sub: string) + return string.sub(str, 1, #sub) == sub +end + +local function hasLeadingZeros(num: string) + return string.match(num, "^(0+)") ~= nil +end + +local function doLeadingZeroesCheck(type: "major" | "minor" | "patch", component: string) + -- We only do this check if each component has more than 1 digit as an optimization + if #component > 1 and hasLeadingZeros(component) then + return Result.Err({ + msg = Option.Some(`Found disallowed leading zeros in {string.upper(type)} component`), + kind = { + id = "LeadingZerosPresent", + component = type, + } :: ParseError, + } :: SemverError) + end + + return Result.Ok(nil) +end + +type InvalidTypes = "char" | "symbol" +local function getInvalidTypeName(invalidComponent: string): InvalidTypes + local typePatterns: { [InvalidTypes]: string } = { + char = "%a", + symbol = "%p", + } + + local presentType: InvalidTypes + + for type: InvalidTypes, pattern in typePatterns do + if string.match(invalidComponent, pattern) then + presentType = type + break + end + end + + return presentType +end + +local Semver = {} + +export type SemverImpl = typeof(setmetatable({} :: Version, Semver)) + +export type PreleaseType = "alpha" | "beta" | "rc" + +export type PrereleaseVersion = { + type: PreleaseType, + ordinal: Option, +} + +export type Version = { + major: number, + minor: number, + patch: number, + buildMetadata: Option, + prerelease: Option, +} + +export type SemverResult = types.Result +export type SemverError = { + msg: Option, + kind: SemverErrorKind, +} +export type SemverErrorKind = ParseError | {} +export type ParseError = + { id: "MandatoryComponentMissing", components: { string } } + | { + id: "InvalidComponentType", + component: "major" | "minor" | "patch", + expected: "number", + got: "char" | "symbol" | "unknown", + } + | { id: "LeadingZerosPresent", component: "major" | "minor" | "patch" } + | { id: "InvalidIdentifierCharacter", component: "prerelease" | "metadata" } + | { id: "InvalidPrereleaseType", component: "prerelease", type: string } + | { id: "InvalidPrereleaseOrdinalType", expected: "number", got: "char" | "symbol" } + +local PRERELEASE_LEX_ORDER: { [PreleaseType]: number } = { + alpha = 1, + beta = 2, + rc = 3, +} + +function Semver.parse(ver: string): SemverResult + local components = string.split(ver, ".") + if #components < 3 then + return Result.Err({ + msg = Option.Some(`Expected MAJOR.MINOR.PATCH format, missing {#components} / 3 components`), + kind = { + id = "MandatoryComponentMissing", + components = components, + } :: ParseError, + } :: SemverError) + end + + local patchStr, ext = string.match(components[3], "(%d)([-+]?.*)") + if patchStr == nil then + return Result.Err({ + msg = Option.Some(`Expected patch to be only a number`), + kind = { + id = "InvalidComponentType", + expected = "number", + got = "unknown", + component = "patch", + } :: ParseError, + } :: SemverError) + end + + local major, minor, patch = tonumber(components[1]), tonumber(components[2]), tonumber(patchStr) + if major == nil or minor == nil or patch == nil then + local invalidComponentType: "major" | "minor" | "patch", invalidComponent: string + + if major == nil then + invalidComponentType, invalidComponent = "major", components[1] + elseif minor == nil then + invalidComponentType, invalidComponent = "minor", components[2] + elseif patch == nil then + invalidComponentType, invalidComponent = "patch", patchStr :: string + end + + local presentType: InvalidTypes = getInvalidTypeName(invalidComponent) + return Result.Err({ + msg = Option.Some( + `Expected {string.upper(invalidComponentType)} to be only a number, but got {presentType}` + ), + kind = { + id = "InvalidComponentType", + expected = "number", + got = presentType, + component = invalidComponentType, + } :: ParseError, + } :: SemverError) + end + + -- All components were valid numbers, but we check to see if they had any leading zeros + -- Leading zeros are invalid in semver + local majorLeadingZeros = doLeadingZeroesCheck("major", components[1]) + if majorLeadingZeros:isErr() then + return majorLeadingZeros + end + + local minorLeadingZeros = doLeadingZeroesCheck("minor", components[2]) + if minorLeadingZeros:isErr() then + return minorLeadingZeros + end + + local patchLeadingZeros = doLeadingZeroesCheck("patch", patchStr) + if patchLeadingZeros:isErr() then + return patchLeadingZeros + end + + local parsed: Version = { + major = major :: number, + minor = minor :: number, + patch = patch :: number, + buildMetadata = Option.None :: Option, + prerelease = Option.None :: Option, + } + + if ext ~= nil then + if stringStartsWith(ext, "-") then + -- Prerelease information + local prereleaseType = string.sub(ext, 2) + if prereleaseType ~= "alpha" and prereleaseType ~= "beta" and prereleaseType ~= "rc" then + return Result.Err({ + msg = Option.Some(`Expected prerelease type to be alpha | beta | rc, but got {prereleaseType}`), + kind = { + id = "InvalidPrereleaseType", + component = "prerelease", + type = prereleaseType, + } :: ParseError, + } :: SemverError) + end + + local function badPrereleaseType(prerelease: string): SemverResult + local invalidType: InvalidTypes = getInvalidTypeName(prerelease) + return Result.Err({ + msg = Option.Some(`Expected PRERELEASE to only be a number, but got {invalidType}`), + kind = { + id = "InvalidPrereleaseOrdinalType", + expected = "number", + got = invalidType, + } :: ParseError, + } :: SemverError) + end + + local prereleaseOrdinal = Option.None :: Option + + -- TODO: Cleanup this recovery logic of recomputing a bit more, maybe remove it entirely + if components[4] ~= nil then + local ordinalNum = tonumber(components[4]) + if ordinalNum == nil then + -- If it wasn't a valid number for the ordinal, that must mean one of two things: + -- a) The PRERELEASE component was bad + -- b) The component has build metadata after prerelease info + -- Here, we handle both those cases + + local potentialOrdinalNumber, potentialBuildMetadata = string.match(components[4], "(%d)+(.*)") + if potentialOrdinalNumber == nil then + return badPrereleaseType(components[4]) + end + + if potentialBuildMetadata ~= nil then + parsed.buildMetadata = Option.Some(potentialBuildMetadata :: string) :: Option + end + + if potentialOrdinalNumber ~= nil then + ordinalNum = tonumber(potentialOrdinalNumber) + if ordinalNum == nil then + return badPrereleaseType(components[4]) + end + end + end + + prereleaseOrdinal = Option.Some(ordinalNum :: number) :: Option + end + + parsed.prerelease = Option.Some({ + type = prereleaseType, + ordinal = prereleaseOrdinal, + } :: PrereleaseVersion) :: Option + end + + if stringStartsWith(ext, "+") then + -- Build metadata information; we remove the leading `+` symbol + parsed.buildMetadata = Option.Some(string.sub(ext, 2)) :: Option + end + end + + return Result.Ok(setmetatable(parsed :: Version, Semver)) +end + +local function prereleaseEq(leftPrerelease: PrereleaseVersion?, rightPrerelease: PrereleaseVersion?): boolean + if leftPrerelease == nil or rightPrerelease == nil then + return true + end + + return leftPrerelease.type == rightPrerelease.type and leftPrerelease.ordinal == rightPrerelease.ordinal +end + +--- Returns whether a prerelease is lesser than the other +local function prereleaseLt(leftPrerelease: PrereleaseVersion?, rightPrerelase: PrereleaseVersion?): boolean + if not prereleaseEq(leftPrerelease, rightPrerelase) then + if leftPrerelease ~= nil and rightPrerelase ~= nil then + if leftPrerelease.type == rightPrerelase.type then + return leftPrerelease.ordinal:unwrapOr(0) < rightPrerelase.ordinal:unwrapOr(0) + end + + return PRERELEASE_LEX_ORDER[leftPrerelease.type] < PRERELEASE_LEX_ORDER[rightPrerelase.type] + end + + return not (leftPrerelease == nil and rightPrerelase ~= nil) + and (leftPrerelease ~= nil and rightPrerelase == nil) + end + + return true +end + +local function prereleaseLe(leftPrerelease: PrereleaseVersion?, rightPrerelase: PrereleaseVersion?): boolean + return prereleaseLt(leftPrerelease, rightPrerelase) or prereleaseEq(leftPrerelease, rightPrerelase) +end + +function Semver.__eq(left: SemverImpl, right: SemverImpl): boolean + return left.major == right.major + and left.minor == right.minor + and left.patch == right.patch + and prereleaseEq(left.prerelease:unwrapOrNil(), right.prerelease:unwrapOrNil()) +end + +function Semver.__lt(left: SemverImpl, right: SemverImpl): boolean + return if left.major ~= right.major + then left.major < right.major + elseif left.minor ~= right.minor then left.minor < right.minor + elseif left.patch ~= right.patch then left.patch < right.patch + else prereleaseLt(left.prerelease:unwrapOrNil(), right.prerelease:unwrapOrNil()) +end + +function Semver.__le(left: SemverImpl, right: SemverImpl): boolean + return if left.major ~= right.major + then left.major <= right.major + elseif left.minor ~= right.minor then left.minor <= right.minor + elseif left.patch ~= right.patch then left.patch <= right.patch + else prereleaseLe(left.prerelease:unwrapOrNil(), right.prerelease:unwrapOrNil()) +end + +return Semver + +-- TODO: Testing! diff --git a/core/src/utils/string.luau b/core/src/utils/string.luau new file mode 100644 index 0000000..d382a2e --- /dev/null +++ b/core/src/utils/string.luau @@ -0,0 +1,51 @@ +local String = {} + +function String.splitAt(str: string, pos: number): { string }? + local left, right = string.sub(str, 1, pos - 1), string.sub(str, pos + 1) + + if left == nil or right == nil then + return nil + end + + return { left, right } +end + +function String.splitAtChar(str: string, char: string | number | (c: string) -> boolean): { string } + local strChars = string.split(str, "") + local splitsPositions: { number } = {} + + for pos, strChar in strChars do + local innerChar = if typeof(char) == "number" + then string.char(char) + elseif typeof(char) == "function" then char(strChar) + elseif typeof(char) == "string" and #char == 1 then char + else error(`Expected char, got {typeof(char)}`) + + if innerChar == true or strChar == innerChar then + table.insert(splitsPositions, pos) + end + end + + local splitted: { string } = {} + for idx, splitPos in splitsPositions do + local previous = splitted[#splitted] + local relativeSplit = if previous + then String.splitAt(previous, splitPos - splitsPositions[idx - 1]) + else String.splitAt(str, splitPos) + + for _, split in assert(relativeSplit) do + table.insert(splitted, split) + end + end + + local result = {} + for idx, split in splitted do + if idx % 2 ~= 0 or idx == #splitted or idx == #splitted - 1 then + table.insert(result, split) + end + end + + return result +end + +return String diff --git a/lune/init.luau b/lune/init.luau new file mode 100644 index 0000000..8d9de94 --- /dev/null +++ b/lune/init.luau @@ -0,0 +1,10 @@ +local core = require("./lune_packages/core") +local semver = require("./luau_packages/semver") +local Option = require("./lune_packages/option") +type Option = Option.Option + +core.installTool({ + alias = Option.Some("lune"), + repo = "lune-org/lune", + version = Option.Some(semver.parse("0.8.9"):unwrap()) :: Option, +} :: core.ToolId) diff --git a/lune/pesde.lock b/lune/pesde.lock new file mode 100644 index 0000000..212e474 --- /dev/null +++ b/lune/pesde.lock @@ -0,0 +1,170 @@ +name = "compeydev/lune" +version = "0.1.0" +target = "lune" + +[graph."0x5eal/semver"."0.1.0 luau"] +direct = ["semver", { name = "0x5eal/semver", version = "^0.1.0", target = "luau" }] +ty = "standard" + +[graph."0x5eal/semver"."0.1.0 luau".target] +environment = "luau" +lib = "lib/init.luau" + +[graph."0x5eal/semver"."0.1.0 luau".dependencies] +"lukadev_0/option" = ["1.2.0 luau", "option"] +"lukadev_0/result" = ["1.2.0 luau", "result"] + +[graph."0x5eal/semver"."0.1.0 luau".pkg_ref] +ref_ty = "pesde" +name = "0x5eal/semver" +version = "0.1.0" +index_url = "https://github.com/daimond113/pesde-index" + +[graph."0x5eal/semver"."0.1.0 luau".pkg_ref.dependencies] +frktest = [{ name = "itsfrank/frktest", version = "^0.0.2", index = "https://github.com/daimond113/pesde-index", target = "lune" }, "dev"] +option = [{ name = "lukadev_0/option", version = "^1.2.0", index = "https://github.com/daimond113/pesde-index" }, "peer"] +result = [{ name = "lukadev_0/result", version = "^1.2.0", index = "https://github.com/daimond113/pesde-index" }, "peer"] + +[graph."0x5eal/semver"."0.1.0 luau".pkg_ref.target] +environment = "luau" +lib = "lib/init.luau" + +[graph."compeydev/binlib"."0.1.0 lune"] +direct = ["core", { workspace = "compeydev/binlib", version = "^" }] +ty = "standard" + +[graph."compeydev/binlib"."0.1.0 lune".target] +environment = "lune" +lib = "src/init.luau" + +[graph."compeydev/binlib"."0.1.0 lune".dependencies] +"0x5eal/semver" = ["0.1.0 luau", "semver"] +"jiwonz/dirs" = ["0.1.2 lune", "dirs"] +"jiwonz/pathfs" = ["0.1.0 lune", "pathfs"] +"lukadev_0/option" = ["1.2.0 lune", "option"] +"lukadev_0/result" = ["1.2.0 lune", "result"] + +[graph."compeydev/binlib"."0.1.0 lune".pkg_ref] +ref_ty = "workspace" +path = "core" + +[graph."compeydev/binlib"."0.1.0 lune".pkg_ref.dependencies] +dirs = [{ name = "jiwonz/dirs", version = "^0.1.1", index = "https://github.com/daimond113/pesde-index" }, "standard"] +option = [{ name = "lukadev_0/option", version = "^1.2.0", index = "https://github.com/daimond113/pesde-index" }, "peer"] +pathfs = [{ name = "jiwonz/pathfs", version = "^0.1.0", index = "https://github.com/daimond113/pesde-index" }, "standard"] +result = [{ name = "lukadev_0/result", version = "^1.2.0", index = "https://github.com/daimond113/pesde-index" }, "peer"] +semver = [{ name = "0x5eal/semver", version = "^0.1.0", index = "https://github.com/daimond113/pesde-index", target = "luau" }, "standard"] + +[graph."compeydev/binlib"."0.1.0 lune".pkg_ref.target] +environment = "lune" +lib = "src/init.luau" + +[graph."jiwonz/dirs"."0.1.2 lune"] +ty = "standard" + +[graph."jiwonz/dirs"."0.1.2 lune".target] +environment = "lune" +lib = "src/init.luau" + +[graph."jiwonz/dirs"."0.1.2 lune".dependencies] +"jiwonz/pathfs" = ["0.1.0 lune", "pathfs"] + +[graph."jiwonz/dirs"."0.1.2 lune".pkg_ref] +ref_ty = "pesde" +name = "jiwonz/dirs" +version = "0.1.2" +index_url = "https://github.com/daimond113/pesde-index" + +[graph."jiwonz/dirs"."0.1.2 lune".pkg_ref.dependencies] +pathfs = [{ name = "jiwonz/pathfs", version = "^0.1.0", index = "https://github.com/daimond113/pesde-index" }, "standard"] + +[graph."jiwonz/dirs"."0.1.2 lune".pkg_ref.target] +environment = "lune" +lib = "src/init.luau" + +[graph."jiwonz/pathfs"."0.1.0 lune"] +ty = "standard" + +[graph."jiwonz/pathfs"."0.1.0 lune".target] +environment = "lune" +lib = "init.luau" + +[graph."jiwonz/pathfs"."0.1.0 lune".pkg_ref] +ref_ty = "pesde" +name = "jiwonz/pathfs" +version = "0.1.0" +index_url = "https://github.com/daimond113/pesde-index" + +[graph."jiwonz/pathfs"."0.1.0 lune".pkg_ref.target] +environment = "lune" +lib = "init.luau" + +[graph."lukadev_0/option"."1.2.0 lune"] +direct = ["option", { name = "lukadev_0/option", version = "^1.2.0" }] +ty = "standard" + +[graph."lukadev_0/option"."1.2.0 lune".target] +environment = "lune" +lib = "lib/init.luau" + +[graph."lukadev_0/option"."1.2.0 lune".pkg_ref] +ref_ty = "pesde" +name = "lukadev_0/option" +version = "1.2.0" +index_url = "https://github.com/daimond113/pesde-index" + +[graph."lukadev_0/option"."1.2.0 lune".pkg_ref.target] +environment = "lune" +lib = "lib/init.luau" + +[graph."lukadev_0/option"."1.2.0 luau"] +ty = "peer" + +[graph."lukadev_0/option"."1.2.0 luau".target] +environment = "luau" +lib = "lib/init.luau" + +[graph."lukadev_0/option"."1.2.0 luau".pkg_ref] +ref_ty = "pesde" +name = "lukadev_0/option" +version = "1.2.0" +index_url = "https://github.com/daimond113/pesde-index" + +[graph."lukadev_0/option"."1.2.0 luau".pkg_ref.target] +environment = "luau" +lib = "lib/init.luau" + +[graph."lukadev_0/result"."1.2.0 lune"] +direct = ["result", { name = "lukadev_0/result", version = "^1.2.0" }] +ty = "standard" + +[graph."lukadev_0/result"."1.2.0 lune".target] +environment = "lune" +lib = "lib/init.luau" + +[graph."lukadev_0/result"."1.2.0 lune".pkg_ref] +ref_ty = "pesde" +name = "lukadev_0/result" +version = "1.2.0" +index_url = "https://github.com/daimond113/pesde-index" + +[graph."lukadev_0/result"."1.2.0 lune".pkg_ref.target] +environment = "lune" +lib = "lib/init.luau" + +[graph."lukadev_0/result"."1.2.0 luau"] +ty = "peer" + +[graph."lukadev_0/result"."1.2.0 luau".target] +environment = "luau" +lib = "lib/init.luau" + +[graph."lukadev_0/result"."1.2.0 luau".pkg_ref] +ref_ty = "pesde" +name = "lukadev_0/result" +version = "1.2.0" +index_url = "https://github.com/daimond113/pesde-index" + +[graph."lukadev_0/result"."1.2.0 luau".pkg_ref.target] +environment = "luau" +lib = "lib/init.luau" diff --git a/lune/pesde.toml b/lune/pesde.toml new file mode 100644 index 0000000..223659b --- /dev/null +++ b/lune/pesde.toml @@ -0,0 +1,19 @@ +name = "compeydev/lune" +version = "0.1.0" +description = "A standalone Luau runtime" +authors = ["CompeyDev ", "Filip Tibell "] +repository = "https://github.com/CompeyDev/pesde-tooling/blob/main/lune" +license = "MIT" + +[target] +environment = "lune" +bin = "init.luau" + +[dependencies] +result = { name = "lukadev_0/result", version = "^1.2.0" } +option = { name = "lukadev_0/option", version = "^1.2.0" } +semver = { name = "0x5eal/semver", version = "^0.1.0", target = "luau" } +core = { workspace = "compeydev/binlib", version = "^" } + +[indices] +default = "https://github.com/daimond113/pesde-index" diff --git a/pesde.lock b/pesde.lock new file mode 100644 index 0000000..4ac8b7a --- /dev/null +++ b/pesde.lock @@ -0,0 +1,9 @@ +name = "compeydev/pesde_bins" +version = "0.1.0" +target = "lune" + +[workspace."compeydev/binlib"] +lune = "core" + +[workspace."compeydev/lune"] +lune = "lune" diff --git a/pesde.toml b/pesde.toml new file mode 100644 index 0000000..6e17a6a --- /dev/null +++ b/pesde.toml @@ -0,0 +1,13 @@ +name = "compeydev/pesde_bins" +version = "0.1.0" +private = true + +workspace_members = ["lune", "core"] + +[target] +environment = "lune" + +[indices] +default = "https://github.com/daimond113/pesde-index" + +