diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..78deaad --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,71 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +defaults: + run: + shell: bash + +jobs: + fmt: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install tooling + uses: CompeyDev/setup-rokit@v0.1.2 + with: + cache: true + + - name: Check formatting + run: lune run fmt -- --check + + test: + needs: ["fmt"] + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install tooling + uses: CompeyDev/setup-rokit@v0.1.2 + with: + cache: true + + - name: Install pesde + uses: ./.github/workflows/pesde.yml + with: + pesde-token: ${{ secrets.PESDE_TOKEN }} + + - name: Install dependencies + run: pesde install + + - name: Run tests + run: lune run tests + + typecheck: + needs: ["test"] + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install tooling + uses: CompeyDev/setup-rokit@v0.1.2 + with: + cache: true + + - name: Install pesde + uses: ./.github/workflows/pesde.yml + with: + pesde-token: ${{ secrets.PESDE_TOKEN }} + + - name: Install dependencies + run: pesde install + + - name: Typecheck + run: lune run typecheck diff --git a/.github/workflows/pesde.yml b/.github/workflows/pesde.yml new file mode 100644 index 0000000..b77ee89 --- /dev/null +++ b/.github/workflows/pesde.yml @@ -0,0 +1,29 @@ +name: Install pesde +description: Installs pesde CLI and authenticates with the registry + +inputs: + pesde-token: + description: "Token for publishing to the pesde registry" + required: false + +runs: + using: composite + steps: + - name: Download pesde + shell: bash + run: | + latest_release=$(curl -s https://api.github.com/repos/daimond113/pesde/releases | jq '[.[] | select(.prerelease == true or .prerelease == false)][0]') + download_url=$(echo "$latest_release" | jq -r '.assets[] | select(.name | endswith("linux-x86_64.tar.gz")) | .browser_download_url') + + curl -L -o /tmp/pesde.tar.gz "$download_url" + tar -xzvf /tmp/pesde.tar.gz + chmod +x pesde + + ./pesde self-install + rm ./pesde + echo "$HOME/.pesde/bin" >> $GITHUB_PATH + + - name: Authenticate into pesde registry + if: inputs.pesde-token != '' + shell: bash + run: pesde auth login --token ${{ inputs.pesde-token }} diff --git a/.lune/exec.luau b/.lune/exec.luau new file mode 100644 index 0000000..87864d5 --- /dev/null +++ b/.lune/exec.luau @@ -0,0 +1,110 @@ +--> lib: Builder pattern class to spawn child processes + +local stdio = require("@lune/stdio") +local process = require("@lune/process") + +local Option = require("../luau_packages/option") +type Option = Option.Option + +local CommandBuilder = {} + +export type CommandBuilder = typeof(setmetatable({} :: CommandBuilderFields, { __index = CommandBuilder })) +type CommandBuilderFields = { + program: string, + args: { string }, + stdioStrategy: Option, +} +export type StdioStrategy = "pipe" | "forward" | "none" +export type IoStrategyMapping = { + stdout: Option, + stderr: Option, +} + +-- FIXME: remove unknown usage +local DEFAULT_STDIO_STRATEGY: IoStrategyMapping = { + stdout = Option.Some("pipe" :: StdioStrategy) :: Option, + stderr = Option.Some("pipe" :: StdioStrategy) :: Option, +} +function CommandBuilder.new(program: string) + return setmetatable( + { + program = program, + args = {}, + 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.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.exec(self: CommandBuilder): process.SpawnResult + print("$", self.program, table.concat(self.args, " ")) + local child = process.spawn(self.program, self.args, { + shell = true, + 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(), + }) + + if not child.ok then + print(`\n{stdio.color("red")}[luau-lsp]{stdio.color("reset")} Exited with code`, child.code) + end + + return child +end + +return CommandBuilder diff --git a/.lune/fmt.luau b/.lune/fmt.luau new file mode 100644 index 0000000..76bbd7f --- /dev/null +++ b/.lune/fmt.luau @@ -0,0 +1,7 @@ +--> Run stylua to check for formatting errors + +local process = require("@lune/process") + +local CommandBuilder = require("./exec") + +process.exit(CommandBuilder.new("stylua"):withArg("."):withArgs(process.args):withStdioStrategy("forward"):exec().code) diff --git a/.lune/typecheck.luau b/.lune/typecheck.luau new file mode 100644 index 0000000..80dc122 --- /dev/null +++ b/.lune/typecheck.luau @@ -0,0 +1,15 @@ +--> Run luau-lsp analysis to check for type errors + +local process = require("@lune/process") + +local CommandBuilder = require("./exec") + +process.exit( + CommandBuilder.new("luau-lsp") + :withArg("analyze") + :withArgs({ "--settings", ".vscode/settings.json" }) + :withArgs({ "--ignore", "'./**/.pesde/**'" }) + :withArg(".") + :withStdioStrategy("forward") + :exec().code +) diff --git a/rokit.toml b/rokit.toml new file mode 100644 index 0000000..488405a --- /dev/null +++ b/rokit.toml @@ -0,0 +1,9 @@ +# This file lists tools managed by Rokit, a toolchain manager for Roblox projects. +# For more information, see https://github.com/rojo-rbx/rokit + +# New tools can be added by running `rokit add ` in a terminal. + +[tools] +lune = "lune-org/lune@0.8.9" +stylua = "JohnnyMorganz/StyLua@2.0.1" +luau-lsp = "JohnnyMorganz/luau-lsp@1.35.0"