diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bb97166..9b603c8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -114,7 +114,7 @@ jobs:
- name: Analyze StateQ with luau-lsp
run: |
- ./scripts/sourcemap.sh default.project.json stateQSourcemap.json
+ ./scripts/sourcemap.sh rbxm.project.json stateQSourcemap.json
./scripts/analyze.sh stateQSourcemap.json src/StateQ
- name: Analyze tests with luau-lsp
diff --git a/.vscode/settings.json.example b/.vscode/settings.json.example
index 70a2fb9..1589bdb 100644
--- a/.vscode/settings.json.example
+++ b/.vscode/settings.json.example
@@ -1,13 +1,9 @@
{
- "luau-lsp.require.mode": "relativeToFile",
- "luau-lsp.require.directoryAliases": {
- "@lune/": "~/.lune/.typedefs/0.8.8/"
- },
- "luau-lsp.sourcemap.rojoProjectFile": "test.project.json",
- "luau-lsp.ignoreGlobs": [
- "**/_Index/**",
- "**/*.d.luau",
- "*Packages/**"
- ],
- "cSpell.words": ["rokit", "rbxl", "rojo"]
+ "luau-lsp.require.mode": "relativeToFile",
+ "luau-lsp.require.directoryAliases": {
+ "@lune/": "~/.lune/.typedefs/0.8.8/"
+ },
+ "luau-lsp.sourcemap.rojoProjectFile": "test.project.json",
+ "luau-lsp.ignoreGlobs": ["**/*.d.luau", "*Packages/**"],
+ "cSpell.words": ["rokit", "rbxl", "rojo"]
}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 424cd7a..19377f5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -23,7 +23,7 @@ Various tools used by the project are installed with [Rokit](https://github.com/
1. Run the following command in the repository directory:
```bash
-rokit install
+> rokit install
```
This installs tools from `rokit.toml` and adds them to your system environment path variable so you can use the tools in the command line.
@@ -34,6 +34,7 @@ Additionally, if you're using VS Code it's recommended to install the following
- [johnnymorganz.stylua](https://marketplace.visualstudio.com/items?itemName=JohnnyMorganz.stylua)
- [kampfkarren.selene-vscode](https://marketplace.visualstudio.com/items?itemName=Kampfkarren.selene-vscode)
- [streetsidesoftware.code-spell-checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker)
+
> [!TIP]
> You should be automatically prompted to install these plugins when opening the project in VS Code because they're listed in [extensions.json](.vscode/extensions.json)
@@ -58,7 +59,7 @@ The following tools are installed by Rokit:
This project depends on packages to run and for testing. These packages are installed by Wally by running the following command:
```bash
-wally install
+> wally install
```
This will create `Packages` and `DevPackages` folders in the top level of the directory that are referenced by the `*.project.json` files.
@@ -83,8 +84,9 @@ The following packages are installed as dev dependencies:
Lune provides type definitions and documentation, but has to be configured for your editor. To do so, do the following steps (source: [Lune Editor Setup](https://lune-org.github.io/docs/getting-started/4-editor-setup))
Run the command:
+
```bash
-lune setup
+> lune setup
```
Then, modify your editor settings. For VS Code, open [`settings.json`](./.vscode/settings.json) and verify it contains the following:
@@ -102,13 +104,22 @@ An example [`settings.json`](./.vscode/settings.json) file is provided (see [`se
Rojo builds from json files that map files on your file system to locations in the roblox data model. This project includes two project.json files:
-| File | Purpose |
-| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| [`default.project.json`](./default.project.json) | The distributable consumer build in the form of a single ModuleScript and its children. This defines the structure when a consumer links this project into their `project.json` file. |
-| [`test.project.json`](./test.project.json) | Useful for developing this project, because it defines an entire place file that can be opened and synced rather than just the ModuleScript. This is the one you should build and serve with Rojo during development of this project. |
+| File | Purpose |
+| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [`test.project.json`](./test.project.json) | Useful for developing this project, because it defines an entire place file that can be opened, synced, and tested. This is the one you should build and serve with Rojo during development of this project. |
+| [`rbxm.project.json`](./rbxm.project.json) | The distributable rbxm build in the form of a ModuleScript and its children inside a Packages folder containing dependencies. This is built and made available on each Release as a downloadable artifact. |
+| [`default.project.json`](./default.project.json) | The standalone module without dependencies included. This defines the structure when a consumer links this project into their `project.json` file as a submodule, requiring the consumer to install dependencies and place this into the Packages folder. |
If you plan to [run tests](#run-tests) from CLI (recommended), the test runner script automatically builds before running tests. You don't need to build it yourself.
+There's also an available Lune command to build artifacts for each build target, like the CD script does.
+
+```bash
+> lune run build
+```
+
+This creates a `build` folder containing a subfolder for each build target (rbxm, zip, wally tar), which contains the artifact for that build target.
+
If you're running tests in studio yourself (not recommended) instead of using the CLI, you can do an initial project build by expanding this section and doing either of the following:
@@ -118,6 +129,7 @@ If you're running tests in studio yourself (not recommended) instead of using th
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| CLI | `rojo build test.project.json -o StateQ-Test.rbxl` |
| VSC Extension | Click Rojo on the status bar at the bottom, mouse over `test.project.json` in the pop-up menu, and click the Build icon on the right of the list item. |
+
## Sync file changes to studio
@@ -166,8 +178,8 @@ However, running tests in Roblox Studio requires flipping the `FFlagEnableLoadMo
This instructions to flip this flag differ on Mac vs Windows.
-* **For Mac users:** see [this issue](https://github.com/jsdotlua/jest-lua/issues/6).
-* **For Windows users:** see [this devforum post](https://devforum.roblox.com/t/how-to-return-altenter-to-its-prior-functionality/997206)
+- **For Mac users:** see [this issue](https://github.com/jsdotlua/jest-lua/issues/6).
+- **For Windows users:** see [this devforum post](https://devforum.roblox.com/t/how-to-return-altenter-to-its-prior-functionality/997206)
You need to set the `FFlagEnableLoadModule` value to `true`. Be sure to restart Roblox Studio after flipping the flag.
@@ -179,7 +191,7 @@ You need to set the `FFlagEnableLoadModule` value to `true`. Be sure to restart
| CLI (recommended) | `lune run test` |
| Roblox Studio | Open the test place file `StateQ-Test.rbxl` [built in the above step](#build-the-project) in Roblox Studio and run the place (server only). The output widget will show the test results. |
-### Continuous Integration (CI)
+## Continuous integration (CI)
CI checks are set up to run on pull requests. These checks must pass before merging, including:
@@ -188,35 +200,47 @@ CI checks are set up to run on pull requests. These checks must pass before merg
1. `analyze` with luau-lsp
1. `test` with jest running in Lune
-#### Running CI Locally
+### Running CI locally
To run the same CI checks locally that would run on GitHub, a number of Lune scripts are provided.
From the project directory, you can run the following:
+```bash
> lune run ci
+```
This will run all the same checks that would run on GitHub.
Alternatively, you can run individual steps yourself:
+```bash
> lune run lint
-
> lune run formatCheck
-
> lune run analyze
-
> lune run test
+```
There is an additional script available to fix formatting with StyLua, that does not run on GitHub:
+```bash
> lune run formatFix
+```
-## Releasing
+## Creating a release
1. To make a release, use the GitHub web interface to [create a new release](https://github.com/BusyCityGuy/finite-state-machine-luau/releases/new).
1. Choose a tag with a version number like `v0.0.0`, beginning with `v` and using semantic versioning. No suffix like `-pre` is supported, only numbers as shown.
1. Create a release title, preferably something like "Version 0.0.0" to be consistent with the tag.
1. Describe the changes in the release
1. Publish release
-1. A GitHub Actions workflow will automatically run and upload .zip and .rbxm artifacts to the release. It will also automatically publish the release to Wally, and update hardcoded numbers throughout the codebase like versions and copyright years.
\ No newline at end of file
+
+## Continuous deployment (CD)
+
+When a release is created, a GitHub Actions workflow automatically runs that does the following steps:
+
+1. Performs preprocessing to update hardcoded numbers in the codebase, like versions and copyright years
+1. Commits these preprocessing changes to the main branch
+1. Builds artifacts for each build target
+1. Uploads the `.zip` and `.rbxm` artifacts to the release on GitHub
+1. Publishes the `.tar` package to Wally
diff --git a/README.md b/README.md
index 0481696..3765465 100644
--- a/README.md
+++ b/README.md
@@ -23,20 +23,20 @@ These signals, transition callbacks, and state changes are processed in the foll
![SequenceDiagram](https://github.com/BusyCityGuy/finite-state-machine-luau/assets/55513323/9ace09e3-a16e-474b-83ca-aac91cd69492)
1. Fire `beforeEvent` signal
- - with arguments `eventName`, `beforeState`
+ - with arguments `eventName`, `beforeState`
1. Call `transition.beforeAsync()` (required, returns next state)
- - with the VarArgs from `:handle(eventName, transitionArgs...)`
+ - with the VarArgs from `:handle(eventName, transitionArgs...)`
1. Fire `leavingState` signal
- - with arguments `beforeState, afterState`
+ - with arguments `beforeState, afterState`
1. Update `_currentState` to next state
1. Fire `stateEntered` signal
- - with arguments `afterState, beforeState`
+ - with arguments `afterState, beforeState`
1. Call `transition.afterAsync()` (if specified)
- - with the VarArgs from `:handle(eventName, transitionArgs...)`
+ - with the VarArgs from `:handle(eventName, transitionArgs...)`
1. Fire `afterEvent` signal
- - with arguments `eventName, afterState, beforeState`
+ - with arguments `eventName, afterState, beforeState`
1. Fire `finished` signal if next state from `beforeAsync()` was `nil`
- - with argument `beforeState`
+ - with argument `beforeState`
Transitions can be asynchronous, which is supported by queuing each Event submitted via :handle() and processing them in First-In-First-Out (FIFO) order. The next Event starts processing immediately after the previous Event's handler fires `afterEvent`.
@@ -54,6 +54,10 @@ A simple state machine diagram for a light switch may look like this, where
![ExampleUsage](https://github.com/BusyCityGuy/finite-state-machine-luau/assets/55513323/3d5b2118-91ea-4427-ac2d-688fb0094d1f)
```luau
+local ReplicatedStorage = game:GetService("ReplicatedStorage")
+
+local StateQ = require(ReplicatedStorage.Packages.StateQ)
+
local LightState = {
On = "On",
Off = "Off",
@@ -110,17 +114,22 @@ light:handle(Event.SwitchOn) -- warns "Illegal event `SwitchOn` called during st
## Rojo users
If your project is set up to build with Rojo, the preferred installation method is using [Wally](https://wally.run/). Add this to your `wally.toml` file:
+
+```bash
> StateQ = "busycityguy/stateq@0.0.5"
+```
If you're not using Wally, you can add this repository as a submodule of your project by running the following command:
-> git submodule add https://github.com/BusyCityGuy/finite-state-machine-luau path/to/your/dependencies
+```bash
+> git submodule add path/to/your/dependencies
+```
If you want to avoid submodules too, you can download the `.zip` file from the [latest release](https://github.com/BusyCityGuy/finite-state-machine-luau/releases/latest) page.
## Non-Rojo users
-If you aren't using Rojo, you can download the `.rbxm` file from the [latest release](https://github.com/BusyCityGuy/finite-state-machine-luau/releases/latest) page and drag it into Roblox Studio.
+If you aren't using Rojo, you can download the `.rbxm` file from the [latest release](https://github.com/BusyCityGuy/finite-state-machine-luau/releases/latest) page and drag it into Roblox Studio, placing the `Packages` folder in `ReplicatedStorage`.
# Feedback
diff --git a/default.project.json b/default.project.json
index b77509c..024d819 100644
--- a/default.project.json
+++ b/default.project.json
@@ -1,9 +1,6 @@
{
- "name": "StateQ",
- "tree": {
- "$path": "src/StateQ",
- "Dependencies": {
- "$path": "Packages"
+ "name": "StateQ",
+ "tree": {
+ "$path": "src/StateQ"
}
- }
}
diff --git a/lune/analyze.luau b/lune/analyze.luau
index 89b420d..7db293a 100644
--- a/lune/analyze.luau
+++ b/lune/analyze.luau
@@ -18,7 +18,7 @@ local task = require("@lune/task")
local ANALYZE_PATHS: { { path: string, project: string?, sourceMap: string? } } = {
{
path = Path.join("src", "StateQ"),
- project = "default.project.json",
+ project = "rbxm.project.json",
sourceMap = "stateQSourcemap.json",
},
{
diff --git a/lune/build.luau b/lune/build.luau
index 3a79ef5..186ec5a 100644
--- a/lune/build.luau
+++ b/lune/build.luau
@@ -55,9 +55,9 @@ local build_targets = {
command = "rojo",
args = {
"build",
- "default.project.json",
+ "rbxm.project.json",
"--output",
- `{Path.join(ensurePath(buildFolder, "rbxm"), `{fileName}.rbxm`)}`,
+ Path.join(ensurePath(buildFolder, "rbxm"), `{fileName}.rbxm`),
},
},
{
@@ -66,7 +66,7 @@ local build_targets = {
args = {
"package",
"--output",
- `{Path.join(ensurePath(buildFolder, "wally"), fileName:lower())}`,
+ Path.join(ensurePath(buildFolder, "wally"), `{fileName:lower()}.tar`),
},
},
{
@@ -74,7 +74,7 @@ local build_targets = {
command = "zip",
args = {
"-r",
- `{Path.join(ensurePath(buildFolder, "zip"), fileName)}.zip`,
+ Path.join(ensurePath(buildFolder, "zip"), `{fileName}.zip`),
"src/StateQ",
"default.project.json",
"Packages",
diff --git a/rbxm.project.json b/rbxm.project.json
new file mode 100644
index 0000000..796b619
--- /dev/null
+++ b/rbxm.project.json
@@ -0,0 +1,9 @@
+{
+ "name": "Packages",
+ "tree": {
+ "$path": "Packages",
+ "StateQ": {
+ "$path": "src/StateQ"
+ }
+ }
+}
diff --git a/src/StateQ/init.luau b/src/StateQ/init.luau
index 883d044..ac91c21 100644
--- a/src/StateQ/init.luau
+++ b/src/StateQ/init.luau
@@ -110,10 +110,16 @@
light:handle(Event.SwitchOn) -- errors "Illegal event `SwitchOn` called during state `On`" with a stack trace
--]]
+-- This module should be in a folder called Packages that contains other module dependencies.
+-- If installed as an rbxm, the Packages folder should be included as the parent of this module already.
+-- If installed with Wally, this should already be automatically set up as a descendant of the Packages folder with references to dependencies created.
+-- If installed as a submodule, you'll need to edit your project.json file to set the path for this module inside of a Packages folder, and install dependencies there.
+local Packages = script.Parent
+
local Logger = require(script.Modules.Logger)
local Signal = require(script.Modules.Signal)
local ThreadQueue = require(script.Modules.ThreadQueue)
-local t = require(script.Dependencies.t)
+local t = require(Packages.t)
-- These types are added for readability to disambiguate what the string is meant to represent in types
type State = string
diff --git a/src/TestService/Source/Tests/StateQ.spec.luau b/src/TestService/Source/Tests/StateQ.spec.luau
index d0b4d03..e37539e 100644
--- a/src/TestService/Source/Tests/StateQ.spec.luau
+++ b/src/TestService/Source/Tests/StateQ.spec.luau
@@ -3,10 +3,10 @@
-- local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TestService = game:GetService("TestService")
-local JestGlobals = require(TestService.Dependencies.JestGlobals)
--- local Freeze = require(TestService.Dependencies.Freeze)
--- local Logger = require(ReplicatedStorage.Source.StateQ.Modules.Logger)
--- local StateQ = require(ReplicatedStorage.Source.StateQ)
+local JestGlobals = require(TestService.Source.DevPackages.JestGlobals)
+-- local Freeze = require(DevPackages.Freeze)
+-- local Logger = require(ReplicatedStorage.Source.Packages.StateQ.Modules.Logger)
+-- local StateQ = require(ReplicatedStorage.Source.Packages.StateQ)
-- Shortening things is generally bad practice, but this greatly improves readability of tests
-- local Dict = Freeze.Dictionary
diff --git a/src/TestService/Source/run.server.luau b/src/TestService/Source/run.server.luau
index 0919381..6306487 100644
--- a/src/TestService/Source/run.server.luau
+++ b/src/TestService/Source/run.server.luau
@@ -1,6 +1,6 @@
local TestService = game:GetService("TestService")
-local Jest = require(TestService.Dependencies.Jest)
+local Jest = require(TestService.Source.DevPackages.Jest)
local runCLI = Jest.runCLI
-- Jest.TestBootstrap:run({ TestService.Source.Tests })
diff --git a/test.project.json b/test.project.json
index b36d0d1..5768875 100644
--- a/test.project.json
+++ b/test.project.json
@@ -1,33 +1,32 @@
{
- "emitLegacyScripts": false,
- "name": "stateq-test",
- "tree": {
- "$className": "DataModel",
- "ReplicatedStorage": {
- "$className": "ReplicatedStorage",
- "Source": {
- "$className": "Folder",
- "StateQ": {
- "$path": "src/StateQ",
- "Dependencies": {
- "$path": "Packages"
- }
+ "emitLegacyScripts": false,
+ "name": "StateQ-Test",
+ "tree": {
+ "$className": "DataModel",
+ "ReplicatedStorage": {
+ "$className": "ReplicatedStorage",
+ "Source": {
+ "$className": "Folder",
+ "Packages": {
+ "$path": "Packages",
+ "StateQ": {
+ "$path": "src/StateQ"
+ }
+ }
+ }
+ },
+ "TestService": {
+ "$className": "TestService",
+ "$properties": {
+ "ExecuteWithStudioRun": true
+ },
+ "Source": {
+ "$className": "Folder",
+ "$path": "src/TestService/Source",
+ "DevPackages": {
+ "$path": "DevPackages"
+ }
+ }
}
- }
- },
- "TestService": {
- "$className": "TestService",
- "$properties": {
- "ExecuteWithStudioRun": true
- },
- "Source": {
- "$className": "Folder",
- "$path": "src/TestService/Source"
- },
- "Dependencies": {
- "$className": "Folder",
- "$path": "DevPackages"
- }
}
- }
}