Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 6 additions & 2 deletions .github/workflows/reusable-publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ jobs:

# Copy the source channel onto the published channel(s), preserving the others, then guard that
# every tag the published feed references exists before pushing. On the first release the publish
# branch doesn't exist yet, so seed the worktree from the released commit and let the final push
# create it.
# branch doesn't exist yet, so seed the worktree from the released commit with an empty feed and
# let the final push create it.
- name: Publish to the publish branch
run: |
set -euo pipefail
Expand All @@ -211,6 +211,10 @@ jobs:
else
echo "Publish branch '${{ inputs.publish-branch }}' doesn't exist yet — bootstrapping it from the released commit."
git worktree add --detach _publish HEAD
# That tree holds the dev feed, whose channels releases are cut from, so hand the merge an
# empty feed to fill instead: a dev channel copied in here would keep the `default: true`
# it carries there and go on claiming it alongside every channel published afterwards.
jq '{dependencyControlFeedFormatVersion, macros: {}, modules: {}}' DependencyControl.json > _publish/DependencyControl.json
fi

rsync -a --delete \
Expand Down
65 changes: 41 additions & 24 deletions DependencyControl.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,26 @@
"url": "@{baseUrl}#@{namespace}",
"channels": {
"main": {
"version": "0.8.1",
"released": "2026-07-24",
"version": "0.9.0",
"released": null,
"default": true,
"files": [
{
"name": ".moon",
"url": "@{fileBaseUrl}",
"sha1": "F7B62EAC79C4FBA001B50C12506231C204CC7E80"
"sha1": "9F1500392C356076F0DFF32EE9BA690366CE0379"
},
{
"name": ".moon",
"url": "@{fileBaseUrl}",
"sha1": "9329D7020A486DE3F5202963522327AB709FF815",
"sha1": "DED2B18E6F5C3BC5644FDA1E7DD514C8C579CE83",
"type": "test"
}
],
"requiredModules": [
{
"moduleName": "l0.DependencyControl",
"version": "0.7.0"
"version": "0.9.0"
}
]
}
Expand Down Expand Up @@ -109,6 +109,10 @@
],
"0.8.1": [
"fix: The Install Script macro no longer fails while scanning feeds that set URL templates per section, DependencyControl's own feed among them."
],
"0.9.0": [
"feat: A new Package Source Configuration macro lets you choose where a package's next update comes from — the source kind, channel, how firmly the choice is kept, and either a known feed, a custom feed URL, or a providing module.",
"fix: The Install, Uninstall and Update pickers now show each package's update channel beside its version, which was missing from the lists in most circumstances."
]
}
}
Expand All @@ -121,14 +125,14 @@
"url": "@{baseUrl}#@{namespace}",
"channels": {
"main": {
"version": "0.8.2",
"released": "2026-08-17",
"version": "0.9.0",
"released": null,
"default": true,
"files": [
{
"name": ".moon",
"url": "@{fileBaseUrl}",
"sha1": "29CCC772940BFB3F7A92FD93EA0306905A05897F"
"sha1": "89BA192E65D3446B8A066F4A5816EBA8560773E0"
},
{
"name": "/Constants.moon",
Expand All @@ -138,12 +142,12 @@
{
"name": "/ConfigHandler.moon",
"url": "@{fileBaseUrl}",
"sha1": "6C8ACA7DD2BBA8AD22AB7E488F918297B820EDF9"
"sha1": "B19475DF8C0D1C02491ED36BAB1355AC1016280E"
},
{
"name": "/ConfigView.moon",
"url": "@{fileBaseUrl}",
"sha1": "BCCB1B7E7A85245BCDD87CE0CCCC1955CB161827"
"sha1": "2530A6192FCCBA3B9BE4D946BEAA359B429F14B4"
},
{
"name": "/hash.moon",
Expand Down Expand Up @@ -173,7 +177,7 @@
{
"name": "/FeedInventory.moon",
"url": "@{fileBaseUrl}",
"sha1": "4EB32746AE7D048F15355350827C9A470CB5C384"
"sha1": "4E2075F12A5EB4A726A218F3F76CDEEAC6E0F104"
},
{
"name": "/FeedManager.moon",
Expand Down Expand Up @@ -254,12 +258,12 @@
{
"name": "/ScriptUpdateRecord.moon",
"url": "@{fileBaseUrl}",
"sha1": "9553BD9771DFEF0E35BA04814E2EAFFA0E27C51A"
"sha1": "1D1BB6F7CEDCBDC92925E412D3EFD0DF3400353F"
},
{
"name": "/SemanticVersion.moon",
"url": "@{fileBaseUrl}",
"sha1": "663BB4263B99C01E69AD897DAD7CF6420E17A666"
"sha1": "D13E41C35B1B287752420C73D124EC41D3861BF7"
},
{
"name": "/Stub.moon",
Expand All @@ -279,12 +283,12 @@
{
"name": "/UpdateFeed.moon",
"url": "@{fileBaseUrl}",
"sha1": "521AD8915BF09A02167ACDF73CE4C39E928F504A"
"sha1": "F136EA50649C7A4056453CE08F8498102334294E"
},
{
"name": "/UpdateTask.moon",
"url": "@{fileBaseUrl}",
"sha1": "4C1B8BFBE29120014A182F37F1D9E7AA0E456C07"
"sha1": "F0E0D701FD8325B407A508C5A6EEFA522158ECB3"
},
{
"name": "/Updater.moon",
Expand Down Expand Up @@ -344,7 +348,7 @@
{
"name": "/config-schema.moon",
"url": "@{fileBaseUrl}",
"sha1": "CC74ACBE7A7CD8761AD714564F3805AC96F68067"
"sha1": "25A8986ED53685D995338E4FE0A9446D0A284E6B"
},
{
"name": "/Finalizer.moon",
Expand All @@ -359,7 +363,7 @@
{
"name": "/domain.moon",
"url": "@{fileBaseUrl}",
"sha1": "C5D9BC686DFA4A85A28EE6E2E392B1E012A83909"
"sha1": "97AB343E57DACCD4CF7C97DA6CDBDC5F03CEC68E"
},
{
"name": "/environment.moon",
Expand Down Expand Up @@ -392,7 +396,7 @@
{
"name": "/ConfigView.moon",
"url": "@{fileBaseUrl}",
"sha1": "A0E00272169CA1FFAADF0A672C468A975D9B9F82",
"sha1": "2B269DFD47F980F17C4A72A84BCA9255A2C214BC",
"type": "test"
},
{
Expand Down Expand Up @@ -422,7 +426,7 @@
{
"name": "/FeedInventory.moon",
"url": "@{fileBaseUrl}",
"sha1": "5D820751E60E8609CD65427ED01DD312E869001B",
"sha1": "D4ADBFEFE046D722631BE0DB0A39D8DDC72E61C4",
"type": "test"
},
{
Expand Down Expand Up @@ -524,13 +528,13 @@
{
"name": "/ScriptUpdateRecord.moon",
"url": "@{fileBaseUrl}",
"sha1": "287B0C04CC3A4BA92FE30E2258D7EFEA922AFE58",
"sha1": "1C7EB37806E609FC57ACE47BA96F0F268F3E205E",
"type": "test"
},
{
"name": "/SemanticVersion.moon",
"url": "@{fileBaseUrl}",
"sha1": "E242FE3C0332C60468E4B3F76DCFDE1D631172F9",
"sha1": "F6FBCEC0B847AA71A4CF58967C8D6AAC2D8E26EF",
"type": "test"
},
{
Expand All @@ -548,13 +552,13 @@
{
"name": "/UpdateFeed.moon",
"url": "@{fileBaseUrl}",
"sha1": "4A3CE78253A52C70D6C28394BF724F9D0552DB0E",
"sha1": "6E2D5DDD51871129F3E4FCE504AFD8467B38AC61",
"type": "test"
},
{
"name": "/UpdateTask.moon",
"url": "@{fileBaseUrl}",
"sha1": "D1C04F53DE0609804FE0E9B63AF5DD1EEA2D1D5B",
"sha1": "E380E80D439F78699805E8A5FD9D9A38D84A82BF",
"type": "test"
},
{
Expand Down Expand Up @@ -608,7 +612,7 @@
{
"name": "/config-schema.moon",
"url": "@{fileBaseUrl}",
"sha1": "C65BC75E4ACF11F2CA174255CB6792C3C4FAD7A7",
"sha1": "8EE4EA529ABF8F8AF4C386B4C4713D92ED163089",
"type": "test"
},
{
Expand Down Expand Up @@ -786,6 +790,19 @@
"fix(CLI): `generate-types` and `generate-docs` skip files a feed marks deleted instead of reporting each one as an unreadable source",
"fix(CLI): a deleted file's entry can no longer shadow the source that replaced it when a test suite resolves its modules from the feed.",
"fix(Updater): Updates no longer fail with 'the update task is already running' for modules that require one another (such as Aegisub-Motion's 'Tags' and 'Transform')."
],
"0.9.0": [
"fix: A feed that flags more than one channel as its default no longer sends an install to a different channel from one session to the next. The pick is stable now and the feed is reported as faulty, and installs that a CI mishap left on the internal 'main' channel move to 'stable'.",
"fix(Updater): Asking for a specific update channel now takes effect on a package that already has one recorded, and a module that provides another is judged on the channel recorded for it rather than its feed's default. A recorded channel the feed no longer offers falls back to the default instead of failing the update — unless you pinned the package's source, which is reported as unavailable rather than quietly moved.",
"feat(Updater)!: Where a package will update from and where its installed copy came from are now recorded separately, as `configuredSource` and `currentSource`, so a source change you have made is distinguishable from one an update has applied. Both are written as soon as an install finishes — a fresh install used to record neither — and the origin stores the feed URL outright, so changing a feed override afterwards no longer rewrites where the copy came from. Existing configs migrate automatically.",
"fix(Updater): A second update attempt in the same session works again. A package remembered the feeds its first attempt had consulted and skipped them next time, so a retry passed over the very feed that offers it and reported that none was available.",
"fix(CLI): `merge-feed` now clears the `default` flag from every channel but the one it is told is the default, so publishing can't leave a feed with two default channels. It also copies each section's own file templates, which a feed merged into a fresh file used to lose.",
"fix(CLI): `serve-updates` no longer fails on a feed that already matches its working copy.",
"feat(CLI): `serve-updates` now accepts an optional `--port` argument, so a test feed URL stays valid across runs.",
"feat(CLI): `--serve-channel` can be given more than once to graft the dev build onto several channels at once, the first of which becomes the served feed's default.",
"fix: A package's settings are no longer reverted by a second DependencyControl instance writing the same config file (e.g. during a DependencyControl update, in another automation script context, or from another Aegisub process). Each save now writes only the fields it changed since it last read the file.",
"fix: A regression introduced in v0.7.0 was fixed that prevented the new version number of updated packages from being written into the DependencyControl config file when nothing else in its DependencyControl record changed.",
"fix: Changes to settings that affect feed fetching (private host blocking, feed size and timeout limits) now take effect immediately instead of requiring an automation scripts reload."
]
}
},
Expand Down
34 changes: 26 additions & 8 deletions depctrl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,12 @@ local serveCmd = parser:command("serve-updates",
serveCmd:option("-f --feed",
"Source feed JSON path, next to its files (a resolvable-channel feed, e.g. alpha)"):default("DependencyControl.json")
serveCmd:option("--lifetime", "Seconds to serve before the server self-terminates"):argname("<seconds>"):default("3600")
serveCmd:option("--port",
"Port to listen on, so a feed URL stays valid across runs (default: any free port)"):argname("<port>")
serveCmd:option("--serve-channel",
"Graft the feed's dev channel onto this channel before serving, so a client tracking it matches")
:argname("<name>")
"Graft the feed's dev channel onto this channel before serving, so a client tracking it matches. "
.. "When specified multiple times, the first is served as the default channel")
:argname("<name>"):count("*")
serveCmd:option("--from-channel", "Dev channel to graft from when --serve-channel is given"):argname("<name>"):default("main")

local bumpCmd = parser:command("bump-version",
Expand Down Expand Up @@ -580,24 +583,39 @@ elseif args.command == "serve-updates" then

-- Optionally graft the dev channel onto a channel a client tracks (e.g. main -> alpha), so an older
-- Aegisub can be tested against this feed without merging channels by hand first.
local feedToServe = refreshedPath
if args.serve_channel then
local feedToServe = refreshed.changed > 0 and refreshedPath or feedPath
local serveChannels = args.serve_channel or {}
if #serveChannels > 0 then
local mergedPath = FileOps.joinPath(dirname(refreshedPath), "merged.json")
local merged, mergeErr = UpdateFeed(nil, false, refreshedPath):mergeChannels(UpdateFeed(nil, false, refreshedPath), {
local merged, mergeErr = UpdateFeed(nil, false, feedToServe):mergeChannels(UpdateFeed(nil, false, feedToServe), {
from = args.from_channel,
to = { args.serve_channel },
to = serveChannels,
defaultChannel = serveChannels[1],
outPath = mergedPath,
})
if not merged or #merged == 0 then
io.stderr:write(("serve-updates: couldn't graft channel '%s' -> '%s' (%s)\n"):format(
args.from_channel, args.serve_channel, merged and "no package uses that channel" or tostring(mergeErr)))
args.from_channel, table.concat(serveChannels, "', '"),
merged and "no package uses that channel" or tostring(mergeErr)))
os.exit(1)
end
feedToServe = mergedPath
end

-- Bind first so the feed's file URLs can carry the real port.
local listener = assert(socket.bind("127.0.0.1", 0))
local requestedPort = 0
if args.port then
requestedPort = tonumber(args.port)
if not requestedPort or requestedPort % 1 ~= 0 or requestedPort < 1 or requestedPort > 65535 then
io.stderr:write(("serve-updates: --port must be a whole number in [1, 65535], got '%s'.\n"):format(tostring(args.port)))
os.exit(2)
end
end
local listener, bindErr = socket.bind("127.0.0.1", requestedPort)
if not listener then
io.stderr:write(("serve-updates: couldn't listen on port %d (%s).\n"):format(requestedPort, tostring(bindErr)))
os.exit(1)
end
local _, port = listener:getsockname()
local base = "http://127.0.0.1:" .. port

Expand Down
Loading
Loading