Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cd03290
fix: add missing SemanticVersioning module
line0 May 23, 2026
f147a45
fix: broken script type handling in update feeds
line0 May 23, 2026
bf66595
fix: module loader running depctrl initializer hooks on already initi…
line0 May 23, 2026
f2e1fa8
fix: update failing due to unsupported string index in capitalization…
line0 May 23, 2026
977f37a
test: add first test
line0 May 23, 2026
75b6c43
ci: enforce LF + EOF newline for consistent hash calculation
line0 May 23, 2026
7f2530a
build: bump version; update feed
line0 May 23, 2026
ff8495a
fix: typo in ModuleLoader breaking optional module presence checks
line0 May 23, 2026
d0cbeee
refactor: tighten version number validation in updater
line0 May 23, 2026
d16fd64
fix: broken version number display in version errors
line0 May 23, 2026
167d427
fix: typos
line0 May 23, 2026
a1fc3f8
fix: semver parsers allowing invalid values
line0 May 23, 2026
5bc3f45
fix: semantic version toString roundtrip exception when invalid semve…
line0 May 23, 2026
c08a79c
fix: update errors missing information about whether or not a record …
line0 May 24, 2026
80ead7c
fix: broken skip lists in module loader
line0 May 24, 2026
c86e07e
feat: port Logger improvements from sqlite branch
line0 May 26, 2026
5a582f0
fix: nil dereference crash in feed template variable expansion
line0 May 26, 2026
4383018
refactor: add basic type annotations
line0 May 26, 2026
d55348e
refactor: port over FileOps improvements from sqlite branch
line0 May 26, 2026
ad04372
refactor port new ConfigHandler/ConfigView from sqlite branch
line0 May 26, 2026
a3106c9
refactor: add unit tests and extend testing framework (WIP)
line0 May 27, 2026
daa023a
refactor: remove dependency on aegisub.re and aegisub.util
line0 May 28, 2026
228cf7c
refactor: lift dependency on PreciseTimer
line0 May 28, 2026
fc48086
feat: provide an alternative to BadMutex that only uses OS APIs
line0 May 28, 2026
29bca61
feat: provide an alternative to DownloadManager that uses only OS API…
line0 May 29, 2026
2ac3d7a
feat: allow modules to provide import aliases; ship a copy of dkjson …
line0 May 30, 2026
5afefe2
feat: CLI/CI test runner (WIP)
line0 May 31, 2026
c9d561a
fix: logger trying to clean log files when the log dir doesn't exist …
line0 May 31, 2026
09f8db2
fix: broken home directory expansion on linux
line0 May 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
50 changes: 50 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Tests

on:
pull_request:
branches:
- 'master'
workflow_dispatch:
inputs:
ref:
description: 'Release tag or branch to scan.'
required: true
default: 'master'

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ref || github.ref }}

- uses: leafo/gh-actions-lua@v13
with:
luaVersion: "luajit-2.1.0-beta3"
# DepCtrl relies on Lua 5.2 features (table.unpack, __pairs/__len)
luaCompileFlags: "XCFLAGS=-DLUAJIT_ENABLE_LUA52COMPAT"

- uses: leafo/gh-actions-luarocks@v6
- run: luarocks install moonscript
- run: luarocks install luafilesystem

- name: Run tests
run: lua run-tests.lua

- name: Publish test report
uses: ctrf-io/github-test-reporter@v1
if: ${{ !cancelled() }}
with:
report-path: ./ctrf/*.json
pull-request: ${{ github.event_name == 'pull_request' && true || false }}
overwrite-comment: ${{ github.event_name == 'pull_request' && true || false }}
annotate: false
use-suite-name: true
status-check: true
summary-report: true
failed-report: true
upload-artifact: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/ctrf/*.json
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"files.insertFinalNewline": true
}
118 changes: 87 additions & 31 deletions DependencyControl.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,26 @@
"fileBaseUrl": "@{fileBaseUrl}macros-v@{version}-@{channel}/macros/@{namespace}",
"channels": {
"alpha": {
"version": "0.1.3",
"released": "2016-01-27",
"version": "0.2.0",
"released": "2026-05-23",
"default": true,
"files": [
{
"name": ".moon",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "3677B2817C3D1FFE86981C8ABCC092B3D2CCEE7B"
"sha1": "5F7E0EEFC89E71F427819EEF69630455C0CC2304"
}
],
"requiredModules": [
{
"moduleName": "l0.DependencyControl",
"version": "0.6.1"
"version": "0.7.0"
}
]
}
},
"changelog": {
"0.1.0": [
"initial release"
],
"0.1.0": ["initial release"],
"0.1.1": [
"The Install/Uninstall/Update dialogs now sort scripts by name.",
"DependencyControl and its requirements no longer appear in the uninstall menu."
Expand All @@ -63,6 +61,9 @@
],
"0.1.3": [
"Fixed an issue where trying to uninstall an unmanaged script resulted in an error unrelated to the intended error message."
],
"0.2.0": [
"Now registers the DepCtrl-internal test suite as a macro."
]
}
}
Expand All @@ -76,71 +77,97 @@
"fileBaseUrl": "@{fileBaseUrl}v@{version}-@{channel}/modules/@{scriptName}",
"channels": {
"alpha": {
"version": "0.6.3",
"released": "2016-02-06",
"version": "0.7.0",
"released": "2026-05-23",
"default": true,
"files": [
{
"name": ".moon",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "76C22149258CB1189265A367C1B28046F54F8FB3"
"sha1": "36104C47B776412EBF36AAA00D583180BF4507D5"
},
{
"name": "/Common.moon",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "7262886AEB9F106E95697E86FF0D44738415DBA6"
},
{
"name": "/ConfigHandler.moon",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "97BCD3207FE8158261FA7851057464535FCEFBC6"
"sha1": "1FEC3583C37E4A997E806D5B17A338390657BA53"
},
{
"name": "/FileOps.moon",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "D999D34DB93BA76EF0E991CEB1CD63F5CC5F8E68"
"sha1": "5A54D4B942F34C005ABC977B7655C2B849EC8889"
},
{
"name": "/Logger.moon",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "1E479FE95F0DFBEE8B098302AB589F32D0C40A00"
"sha1": "C4980A42A5AE9C8E24BE04DD12006D118606DBA1"
},
{
"name": "/ModuleLoader.moon",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "F35D88A9902FF9BC912D34299733D37FC15A36DF"
},
{
"name": "/Record.moon",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "796A430D14CACA3E2E15DBDD23F01DC4DC9E4B19"
},
{
"name": "/SemanticVersioning.moon",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "C8DE63A2BE75B1135CEED3ED4ADF7025C927706C"
},
{
"name": "/UnitTestSuite.moon",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "ADAB6EFB05E08A7828DCA01BC1FC43D6482979A1"
"sha1": "BF316812E9ACF6C73570337C2FCA89FD33189A2B"
},
{
"name": "/UpdateFeed.moon",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "1EE16D9D551FF82C2D7E448F2CD980E528874108"
"sha1": "7B64A01259AAA32E963708AE26BCF090AFC1E0DD"
},
{
"name": "/Updater.moon",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "A4AE061724E68B2EFBB7495A477263E1746E228A"
"sha1": "6647D7CAB70637E2B961EF334153718B06EA1027"
},
{
"name": ".moon",
"type": "test",
"url": "@{fileBaseUrl}/Tests.moon",
"sha1": "1ED8961CAFCADA7E4C04778227EECDC18E509B8D"
}
],
"requiredModules": [
{
"moduleName": "requireffi.requireffi",
"version": "0.1.1",
"feed": "@{feed:ffi-experiments}"
},
{
"moduleName": "DM.DownloadManager",
"version": "0.3.1",
"feed": "@{feed:ffi-experiments}"
},
{
"moduleName": "BM.BadMutex",
"version": "0.1.3",
"feed": "@{feed:ffi-experiments}"
},
{
"moduleName": "PT.PreciseTimer",
"version": "0.1.5",
"feed": "@{feed:ffi-experiments}"
}
]
}
},
"changelog": {
"0.7.0": [
"The previously monolithic `DependencyControl.moon` has been broken up into focused sub-modules as groundwork for a future SQLite-based script registry backend: `Record` (version record management), `ModuleLoader` (module loading and dependency resolution), `SemanticVersioning` (version number handling), and `Common` (shared enums and utilities).",
"Script types (automation macros vs. modules) and record types are now represented by proper enums (`ScriptType`, `RecordType`) instead of bare booleans, making the API more explicit and extensible.",
"UpdateFeed: Fixed two regressions caused by the refactoring, both of which caused the update process to fail.",
"Global initialization has been moved into a dedicated setup method, reducing implicit global state for loggers and configuration.",
"DepCtrl now refuses to load if the installed Moonscript is below the minimum required version with a helpful error message directing users to update their Aegisub build.",
"ModuleLoader: Fixed a regression where DepCtrl init hooks were called again on already-initialized modules, causing errors in modules that mutate their exported state on first call (e.g. BadMutex).",
"Common: Fixed a long-standing bug that guaranteed the `capitalize()` function to fail, that was never caught because it was unused until the refactoring.",
"Updater: Fixed a potential issue where a multi-assignment statement could corrupt record fields after an unsuccessful update."
],
"0.6.4": [
"Logger: Fixed a crash when `logEx()` is called without format arguments — `msg:format(...)` is now skipped when no varargs are supplied.",
"Logger: `fileBaseName` now falls back to `\"UNKNOWN\"` when `script_namespace` is nil, preventing errors during Logger initialization in contexts where no namespace is available.",
"Logger/UpdateFeed: Fixed chained method calls on file handles (`handle:write():flush()` and `handle:write():close()`) that could silently swallow errors"
],
"0.6.3": [
"Fixed a v0.6.2 regression that caused DependencyControl to fail loading the first time after a scheduled self-update."
],
Expand Down Expand Up @@ -195,6 +222,35 @@
"The update feed format has been updated to v0.2.0 and introduces a new template variable to reference knownFeeds specifed at the top level."
]
}
},
"l0.dkjson": {
"url": "http://dkolf.de/dkjson-lua/",
"author": "David Kolf",
"name": "dkjson",
"description": "David Kolf's JSON module for Lua, vendored with and managed by DependencyControl.",
"fileBaseUrl": "@{fileBaseUrl}v@{version}-@{channel}/modules/@{scriptName}",
"channels": {
"release": {
"version": "2.10.0",
"released": "2026-05-30",
"default": true,
"files": [
{
"name": ".moon",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "94DDD16A2B34530F50F664F6D0F7A4B6B962A886"
},
{
"name": "/vendor/dkjson.lua",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "A0597E1AEEB14D42DABB3A0E8C05129EB024EDCA"
}
]
}
},
"changelog": {
"2.10.0": ["Vendored dkjson v2.10 with a DependencyControl version record and json/dkjson self-registration."]
}
}
}
}
86 changes: 82 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ __Features__:
__Requirements__:

* Aegisub > 3.2.0 (e.g. [Plorkyeran's](http://plorkyeran.com/aegisub/) r8792+ or [my](http://files.line0.eu/builds/Aegisub/) git builds)
* [LuaJSON](https://github.com/harningt/luajson)
* [DownloadManager](https://github.com/torque/ffi-experiments/releases) v0.3.0
* [BadMutex](https://github.com/torque/ffi-experiments/releases) v0.1.2
* [PreciseTimer](https://github.com/torque/ffi-experiments/releases) v0.1.4

DependencyControl is self-contained: it bundles a JSON library (dkjson) and ships pure-FFI
implementations of the timer, mutex and download manager it needs, so no extra modules have to be
installed. If you do have the native [ffi-experiments](https://github.com/torque/ffi-experiments)
modules ([DownloadManager](https://github.com/torque/ffi-experiments/releases),
[BadMutex](https://github.com/torque/ffi-experiments/releases)) or another `json` module installed,
those are used in preference to the bundled fallbacks automatically.

----------------------------------

Expand Down Expand Up @@ -184,6 +187,29 @@ MyModule.version = version
return version:register(MyModule)

```

##### Providing module aliases

A module may declare additional names it can satisfy via a `provides` field. Once DependencyControl is loaded, any `require` for one of those names — including a bare, non-namespaced name — resolves to your module, *unless* a real module of that name is already available (yours is only a fallback). This lets a library stand in for a commonly-required dependency without every consuming script having to know your module's namespace.

```lua
local version = DependencyControl{
name = "dkjson",
version = "2.10.0",
moduleName = "l0.dkjson",
-- this module can satisfy `require("json")`:
provides = {"json"},
}
```

Notes:

* Each entry is a name string (or a table `{name = "json"}`, which may offer further customization options in the future).
* Provided names may be bare/non-namespaced even though your own `moduleName` must be a valid
(dotted) namespace.
* Resolution only applies after DependencyControl itself has been loaded, and always defers to a
genuinely installed module of that name — so users can still bring their own.

---------------------------------------------

### Namespaces and Paths ###
Expand Down Expand Up @@ -575,3 +601,55 @@ Reference documentation for the UnitTestSuite module is available in the [source
#### UpdateFeed ####

tbd

----------------------------------

### Running the Test Suite ###

DependencyControl ships a headless test runner (`run-tests.lua`) that executes the full unit
test suite from the command line — locally or in CI — **without** an Aegisub process. An
`aegisub` global shim and bundled FFI implementations of the timer, mutex and download manager
stand in for the host application, and JSON is vendored (dkjson), so the only external
dependencies are LuaJIT and two LuaRocks modules.

#### Prerequisites ####

* _LuaJIT_ on your `PATH`, built with `DLUAJIT_ENABLE_LUA52COMPAT`
* _LuaRocks_, configured for Lua v5.1, which _LuaJIT_ is ABI-compatible with. You may have to select the Lua version explicitly via `luarocks --lua-version=5.1`
* The [moonscript](https://luarocks.org/modules/leafo/moonscript) and [luafilesystem](https://luarocks.org/modules/hisham/luafilesystem) rocks, installed into that 5.1 tree:

```sh
luarocks --lua-version=5.1 install moonscript
luarocks --lua-version=5.1 install luafilesystem
```

* Your `LUA_PATH` / `LUA_CPATH` must let `luajit` find the LuaRocks-installed modules (`luarocks --lua-version=5.1 path --bin` prints the correct values).

#### Running ####

From the repository root:

```sh
luajit run-tests.lua
```

The runner resolves its own location to find the bundled `modules/` directory, so it works
regardless of the current working directory. It exits `0` when every test passes and `1` otherwise.

Log files and config/feed caches are written to a per-run throwaway workspace created under
your system temp directory, rather than touching your real Aegisub configuration. Each Aegisub
path token (`?user`, `?temp`, ...) gets its own subdirectory there. Set `DEPCTRL_TEMP_DIR` to
choose a different base directory for that workspace.

#### Test report ####

After running, the suite writes a [CTRF](https://ctrf.io) report — a JSON test-result format
understood by ready-made CI reporters. By default it lands at `./ctrf/DependencyControl.json`
relative to the runner/repository root; pass a different path as the first CLI argument:

```sh
luajit run-tests.lua path/to/report.json
```

The same report can be produced programmatically from any `UnitTestSuite` via
`suite\writeResults(path)` (or `suite\toCtrf!` for the raw table).
Loading
Loading