DependencyControl provides versioning, automatic script update, dependency management and script management services to Aegisub macros and modules.
Features:
- A lightweight package manager lets users conveniently install scripts right from inside Aegisub
- Loads modules used by an automation script, pulls missing requirements from the internet and informs the user about missing and outdated modules that could not be updated automatically
- Checks scripts and modules for updates and automatically installs them
- Offers convenient macro registration with user-customizable submenus
- Provides configuration, logging services, file operations and a unit test framework for your scripts
- Supports optional modules and private module copies for cases where an older or custom version of a module is required
- Resolves circular dependencies (limitations apply)
DependencyControl is self-contained: it bundles a JSON library (dkjson), though if you have another json module installed, it is used instead. It also now ships with pure-FFI implementations of functionality previously provided by ffi-experiments modules (DownloadManager, BadMutex, PreciseTimer).
- Prerequisites
- DependencyControl for Users
- DependencyControl for Script Authors
- Namespaces and Paths
- The Updater Feed
- Reference
- CLI
- Release Automation
DependencyControl downloads and verifies packages, which on some systems relies on a couple of standard shared libraries. What (if anything) you need to install depends on your platform and on whether you use DependencyControl inside Aegisub or through its standalone CLI.
On Windows and macOS there is nothing to install beyond Aegisub v3.4.0+ itself — or a release of arch1t3cht's Aegisub fork based on v3.4.0+ (older versions may work, but you're on your own if you run into issues).
On Linux, two additional system libraries are used to download and verify packages. Both are almost certainly already installed on any distribution, so you would only need to add them on a very bare-bones system:
- libcurl 7.30.0 or newer (
libcurl.so.4) — downloads packages and updates. - OpenSSL 1.1.0 or newer (
libcrypto.so.1.1orlibcrypto.so.3) — computes the SHA-1 hashes DependencyControl relies on, both to verify downloaded files and internally for its locking between concurrent Aegisub instances.
DependencyControl also guards against server-side request forgery — a hostile feed trying to point a download at an address inside your own network — through the blockPrivateHosts setting. For that guard to cover redirects as well, libcurl 7.80.0 or newer is needed; with an older libcurl the address you start from is still checked, but the targets of any redirects along the way are not.
The CLI runs outside Aegisub and needs its own Lua toolchain (LuaJIT, LuaRocks, and a set of rocks) on top of the system libraries above. See CLI → Toolchain setup for the full requirements and install commands.
As an end-user you don't get to decide whether your scripts use DependencyControl or not, but you can control many aspects of its operation. The updater works out-of-the-box (for any script with an update feed) and is run automatically.
-
Download the latest DependencyControl release and unpack its contents into your Aegisub user automation directory:
- On Windows:
%AppData%\Aegisub\automation - On Linux:
~/.aegisub/automation - On OSX:
~/Library/Application Support/Aegisub/automation
Do NOT unpack the file into the automation directory within the Aegisub installation folder, as this will break the updater.
- On Windows:
-
Restart Aegisub or re-scan your autoload directory from within the Aegisub Automation Manager.
DependencyControl comes with sane default settings, so if you're happy with that, there's no need to read further. If you want to disable the updater, use custom menus or want to tweak another aspect of DependencyControl, read on.
DependencyControl stores its configuration as a JSON file in the config folder of your Aegisub user directory:
- On Windows:
%AppData%\Aegisub\config\l0.DependencyControl.json - On Linux:
~/.aegisub/config/l0.DependencyControl.json - On OSX:
~/Library/Application Support/Aegisub/config/l0.DependencyControl.json
The DependencyControl Toolbox macro provides a GUI for common management tasks; advanced options still require manual JSON editing.
There are 2 kinds of configuration:
Settings in the top-level config object affect all scripts and DependencyControl's own behavior. As of v0.7.0 they're grouped into four sections — updates, feeds, logging, and paths. A flat config from an earlier version is migrated into this layout automatically the first time v0.7.0 loads it, so there's nothing to convert by hand. DependencyControl stamps the file with a $schema link to the config schema, which a JSON-aware editor uses to validate and autocomplete your edits.
updates — the update engine
- str mode ["auto-update"]: Which update contexts may install and update at all. Each value includes the ones before it:
"off"— no installs or updates at all"user-requested"— only actions you start yourself (e.g. via the Toolbox)"dependency-resolution"— also installing/updating modules a script depends on"auto-update"(default) — also background scheduled update checks
- int checkInterval [302400 (3.5 days)]: Minimum seconds between two automatic update checks of a given script.
- int waitTimeout [60]: Seconds to wait for another process's in-progress update to finish before giving up.
- int orphanTimeout [50]: Seconds after which an updater lock whose owner has vanished (e.g. a crashed Aegisub) is treated as stale and may be taken over.
- str feedTrustPromptThreshold ["auto-update"]: How freely DependencyControl may ask you to trust an as-yet-untrusted feed before installing from it. Takes the same context values as
mode("off"never asks; the default allows asking in every context). When a context isn't allowed to prompt, the install fails or is skipped rather than silently using the untrusted feed. - str packageChoicePromptThreshold ["user-requested"]: How freely DependencyControl may ask you to choose among equally-ranked sources for a requirement. Same context values (the default asks only for actions you start yourself). When a context can't prompt, a stable but arbitrary tie-breaker decides instead.
- bool offerAllSources [false]: By default the package picker only appears when two or more sources are genuinely tied (same trust band and version). Set this to
trueto be offered every eligible source whenever there's more than one, including lower-ranked and untrusted ones. - bool blockPrivateHosts [true]: Refuse feed and package downloads from hosts that resolve to a private, loopback, or link-local address (an SSRF safeguard). Turn off to use feeds on your local network.
feeds — feed trust, discovery, and caching
- arr extraFeeds []: Your own additional feeds. They're trusted and used as discovery roots: checked for updates to any script, and their advertised
knownFeedsare crawled to surface further feeds. - arr trustedFeeds []: Feeds you trust as package sources but that are not discovery roots. They let a package install from them without a warning, but aren't themselves crawled for updates.
- arr blockedFeeds []: Feeds that must never be used as a package source, overriding all trust and merged with DependencyControl's own block list. Each entry is an object:
url(required);matchMode("prefix", the default, matches every feed URL starting withurlcase-insensitively, while"exact"matches only that one URL); and an optionalreason. - str fetchUntrustedFeeds ["always"]: What to do when discovery reaches an untrusted feed:
"always"fetches it,"never"skips it, and"prompt"asks first (falling back to"never"where nothing can prompt, e.g. a headless run). - obj crawlLimits: Bounds on how far untrusted feed discovery expands. Keys:
per-feed[25] (untrusted feeds a single feed may contribute),per-root[50] (budget per configured discovery root), anddepth[7]. Any unset budget uses its default. - int cacheMaxAge [3600]: Seconds a cached feed snapshot stays fresh before it's re-fetched.
- int maxFeedSize [5000000]: Largest single feed response, in bytes, before the fetch is aborted — a guard against a hostile or runaway feed. Set
0to remove the limit. - int feedFetchTimeout [15]: Longest time, in seconds, to spend fetching a single feed before the transfer is aborted. Set
0to remove the timeout.
logging — verbosity and retention
- int defaultLevel [3]: Default trace level of DependencyControl's messages; higher levels log more. Setting it above your Aegisub Trace level keeps these messages out of the log window.
- bool toFile [true]: Write log messages to a file in the log directory. This is valuable for debugging, especially during script initialization when the Aegisub log window isn't available.
- int maxFiles [200]: Old log files are purged once the limit on file count, age, or cumulative size is exceeded.
- int maxAge [604800 (1 week)]: Delete log files whose last-modified date is older than this many seconds.
- int maxSize [10000000 (10 MB)]: Cumulative byte-size limit across all log files.
paths — base directories (each accepts Aegisub path tokens such as ?user and ?data)
- str config ["?user/config"]: Directory for DependencyControl's config files; also the directory offered to automation scripts for their own config (they may or may not use it).
- str log ["?user/log"]: Directory for DependencyControl's log files.
- str cache ["?user/cache"]: Base directory for on-disk caches. Each cache lives under a
<namespace>/<name>subdirectory (e.g. the feed cache at<cache>/l0.DependencyControl/feeds).
Changes made in the macros and modules sections of the configuration file affect only the script or module in question.
Available Fields:
- str customMenu: If you want to sort your automation macros into submenus, set this to the submenu name (use
/to denote submenu levels). - str userFeed: When set the updater will use this feed exclusively to update the script in question (instead of other feeds)
- int lastUpdateCheck [auto]: This field is used to store the (epoch) time of the last update check.
- obj currentSource [auto]: Remembers which source last satisfied this package and how sticky that choice is (see Remembering your source choice). Managed automatically; don't edit it.
- int logLevel [3]: sets the default trace level for log messages from this script (only applies to messages sent through a Logger instance provided by DependencyControl to the script)
- bool logToFile [false]: set the user preference wrt/ whether log messages of this script should be written to disk or not (same restrictions as above apply, may be overridden by the script)
author,configFile,feed,moduleName,name,namespace,url,requiredModules,version,unmanaged,provides: These fields hold aspects of the script's version record. Don't change them (they will be reset anyway)
When a script or module needs to be installed or updated, more than one feed may be able to supply it. DependencyControl chooses the source by trust first, version second, so an unexpected or compromised feed can't win just by advertising a higher version number. Candidates are ranked best-first:
- The package's own / declared feed: the feed an installed package advertises, or the feed the depending script declares for the dependency — as long as it still offers the package and remains trusted.
- Other trusted feeds: offering the package directly by name.
- Trusted feeds offering a provider: a different module that declares it
providesthe required name (see Providing module aliases). - Untrusted feeds (by name, then via a provider): interactive installs ask for confirmation before using them. Silent installs/updates are refused until the feed is added to the trusted list.
Within a tier the highest satisfying version wins. Feeds are fetched lazily in this order, so closer, more-trusted sources are tried before DependencyControl reaches further out.
The feeds DependencyControl advertises in its own feed are trusted out of the box, as is anything you add yourself. Tune this in your global configuration:
trustedFeeds: additional feed URLs you trust as package sources.extraFeeds: extra feeds to check for updates (these count as trusted, too).blockedFeeds: feeds that must never be used, overriding everything else (applied in addition to DependencyControl's own block list). Entries match case-insensitively by URL prefix, so a host root blocks every feed under it.userFeed(per script): pin a single feed to be used exclusively for that script.
The DependencyControl Toolbox's Manage Feeds macro lets you review, trust, block, and discover feeds interactively, so most of this can be done without editing the config by hand.
When a package can be installed from more than one source and DependencyControl asks you to pick one, the picker also lets you decide how long that choice should stick. The buttons map to:
- Just This Once: use the picked source for this install only; you'll be asked again next time there's a choice.
- Remember: keep using the picked source as long as it stays available. If it later disappears (e.g. the feed stops offering the package), DependencyControl asks you to choose again — or, during a non-interactive update, falls back to asking next time and proceeds with its own ranked pick for now.
- Pin/Lock: pin this source. DependencyControl will keep using it without asking and will refuse to silently switch — if a pinned source becomes unavailable, a required dependency fails (so you can fix the pin) while an optional one is skipped.
- Auto-Pick: stop asking for this package and always take the top-ranked source automatically.
- Cancel: cancel the operation without installing.
A remembered choice follows the source even if its feed URL changes, so moving a feed won't break it. When the picked source is reached through a provider, it's the provider that's remembered, so version bumps update it in place instead of switching to a different one.
Load DependencyControl at the start of your macro and create a version record. Script and version information is automatically pulled from the script_* variables (the additional script_namespace variable is required).
Here's an example of a macro that requires several modules - some of which have a version record as well as some that don't.
script_name = "Move Along Path"
script_description = "Moves text along a path specified in a \\clip. Currently only works on fbf lines."
script_version = "0.1.2"
script_author = "line0"
script_namespace = "l0.MoveAlongPath"
local DependencyControl = require("l0.DependencyControl")
local version = DependencyControl{
feed = "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/DependencyControl.json",
{
"aegisub.util",
{"a-mo.LineCollection", version = "1.0.1", url = "https://github.com/torque/Aegisub-Motion"},
{"a-mo.Line", version = "1.0.0", url = "https://github.com/TypesettingTools/Aegisub-Motion"},
{"a-mo.Log", url = "https://github.com/torque/Aegisub-Motion"},
{"l0.ASSFoundation", version = "0.1.1", url = "https://github.com/TypesettingTools/ASSFoundation",
feed = "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"},
{"l0.ASSFoundation.Common", version = "0.1.1", url = "https://github.com/TypesettingTools/ASSFoundation",
feed = "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"},
"YUtils"
}
}
local util, LineCollection, Line, Log, ASS, Common, YUtils = version:requireModules()Specifying a feed in your own version record provides DependencyControl with a source to download updates to your script from. Specifying feeds for required modules managed by DependencyControl allows the Updater to discover those modules and fetch them when they're missing from the user's computer. However, you can omit the feed URLs for required modules when your own feed already has references to them.
To register your macros use the following code snippets instead of the usual aegisub.register_macro() calls:
For a single macro that should be registered using the script_name as automation menu entry, use:
version:registerMacro(myProcessingFunction)For a script that registers several macros using its own submenu use:
version:registerMacros{
{script_name, "Opens the Move Along Path GUI", showDialog, validClip},
{"Undo", "Reverts lines to their original state", undo, hasUndoData}
}Using this method for macro registration is a requirement for the custom submenus feature to work with your script and lets DependencyControl hook your macro processing function to run an update check when your macro is run.
Creating a record for a module is very similar to how it does for macros, with the key difference being that name and version information is passed to DependencyControl correctly and a moduleName is required.
local DependencyControl = require("l0.DependencyControl")
local version = DependencyControl{
name = "ASSFoundation",
version = "0.1.1",
description = "General purpose ASS processing library",
author = "line0",
url = "http://github.com/TypesettingTools/ASSFoundation",
moduleName = "l0.ASSFoundation",
feed = "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json",
{
"l0.ASSFoundation.ClassFactory",
"aegisub.re", "aegisub.util", "aegisub.unicode",
{"l0.ASSFoundation.Common", version = "0.1.1", url = "https://github.com/TypesettingTools/ASSFoundation",
feed = "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"},
{"a-mo.LineCollection", version = "1.0.1", url = "https://github.com/TypesettingTools/Aegisub-Motion"},
{"a-mo.Line", version ="1.0.0", url = "https://github.com/TypesettingTools/Aegisub-Motion"},
{"a-mo.Log", url = "https://github.com/TypesettingTools/Aegisub-Motion"},
"ASSInspector.Inspector",
{"YUtils", optional = true},
}
}
local createASSClass, re, util, unicode, Common, LineCollection, Line, Log, ASSInspector, YUtils = version:requireModules()A reference to the version record must be added as the .version field of your returned module for version control to work. A module should also register itself to enable circular dependency support. The :register() method returns your module, so the last lines of your module should look like this:
MyModule.version = version
return version:register(MyModule)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.
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
moduleNamemust 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.
provides also works across the dependency graph at install time. When a script's requiredModules names a module that no feed offers directly, DependencyControl can install a module that lists that name in its provides instead — much like Debian's Provides: or Arch's provides. For example, a script that requires json can be satisfied by installing l0.dkjson, which provides it. A candidate must actually be installable (its platforms must include yours and its version must meet the requirement), a directly-named module is always preferred over a provider, and the usual package-source precedence decides which feed it comes from. Once a provider is installed for a requirement, DependencyControl keeps using and updating that same module rather than switching to a different provider, as long as it can still satisfy the requirement.
A provider can pin which versions of an aliased module it stands in for by giving the table entry an npm-style version range via provides = {{name = "json", version = "~1.2"}}. This lets one provider cover a span of releases without being re-published for every patch bump of the aliased module. If not specified, the provider is assumed to satisfy any version requirement for that name.
For module authors: the provides you declare in your version record is mirrored into your feed automatically when you run the update-feed CLI, so a published feed advertises it without manual upkeep (you may also add it to a feed by hand).
A package can change the feed it updates from by shipping a release whose record points feed at the new URL; DependencyControl picks that up the next time it updates the package. Because source selection is trust-aware (see How DependencyControl Selects Package Sources), plan migrations with that in mind:
- If the new URL is already trusted (listed in DependencyControl's feed, or added by users), the move is seamless.
- If the new URL is not yet trusted, automatic updates to it are held back. To avoid breaking them, keep serving from your old, already-trusted feed in parallel during the transition and/or get the new URL added to DependencyControl's trusted list. Individual users can also add it to their own
trustedFeeds.
DependencyControl strictly enforces a namespace-based file structure for modules as well as automation macros in order to ensure there are no conflicts between scripts that happen to have the same name.
Automation scripts must define their namespace in the version record whereas for modules the module name (as you would use in a require statement) defines the namespace.
Rules for a valid namespace:
- contains at least one dot
- must not start or end with a dot
- must not contain series of two or more dots
- the character set is restricted to:
A-Z,a-z,0-9,.,_,- - should be descriptive (this is more of a guideline)
Examples:
l0.ASSFoundationl0.ASSFoundation.Common(for a separately version-controlled 'submodule')l0.ASSWipea-mo.LineCollection
The namespace of your script translates into a subtree of the user automation directory you can use to store your files in:
- On Windows:
%AppData%\Aegisub\automation - On Linux:
~/.aegisub/automation - On OSX:
~/Library/Application Support/Aegisub/automation
DependencyControl will not outright refuse to work with scripts that ignore this restriction, however it's designed in such a way that downloading to locations outside of your tree is impossible (which means your package won't be able to use the auto-updater).
Automation Scripts use the ?user/automation/autoload directory, which has a flat file structure. You may not use subdirectories and your file names must start with the namespace of your script.
Examples:
l0.ASSWipe.lual0.ASSWipe.Addon.moon
Modules use the ?user/automation/include folder, which has a nested file structure. To determine the base name for your main entry point file and sub-directory, the dots in your namespace are replaced with the path separator (\ on Windows, / on other platforms).
Tests live under ?user/automation/tests/DepUnit/modules or ?user/automation/tests/DepUnit/macros, depending on whether a module or an automation script is being tested. Test files are resolved as require identifiers (DepUnit.modules.<namespace> / DepUnit.macros.<namespace>), so the dots in the namespace always become path separators — the tree is nested for both modules and macros, even though the autoload folder itself is flat. A test suite is a single file at the root of the namespace path; a multi-file suite adds sibling files under a folder of the same name.
Our example module ASSFoundation with namespace l0.ASSFoundation writes (among others) the following files:
?user/automation/include/l0/ASSFoundation.lua?user/automation/include/l0/ASSFoundation/ClassFactory.lua?user/automation/include/l0/ASSFoundation/Draw/Bezier.lua?user/automation/tests/DepUnit/modules/l0/ASSFoundation.lua
An automation script MyScript with namespace l0.MyScript lives at ?user/automation/autoload/l0.MyScript.lua (flat) but its test suite is nested at ?user/automation/tests/DepUnit/macros/l0/MyScript.lua.
If you want DependencyControl auto-update your package(s) on the user's system, you'll need to supply update information in an updater feed, which is a JSON file with the following layout:
(// denotes a comment explaining the property above)
{
"dependencyControlFeedFormatVersion": "0.3.0",
// The version of the feed format. This example uses the simple v0.3.0 layout, which remains fully supported.
// The current version is 0.4.0, which adds optional per-file-type template maps, author-defined
// variables and local path resolution for CI/CLI tooling (see the template section below).
"name": "line0's Aegisub Scripts",
"description": "Main repository for all of line0's automation macros.",
"maintainer": "line0",
// The title and description of your repository as well as the name of the maintainer. May be used by GUI-driven management tools, package managers, etc...
"knownFeeds": {
"a-mo": "https://raw.githubusercontent.com/TypesettingTools/Aegisub-Motion/DepCtrl/DependencyControl.json",
"ASSFoundation": "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"
},
// A hash table of known feed URLs. Can be referenced with @{feed:name} and will be used to discover other repositories the user can install automation scripts and modules from. At the very least this should contain the repo URLs for the required modules in your repo, but may be used to advertise other unrelated repos you trust.
"baseUrl": "https://github.com/TypesettingTools/line0-Aegisub-Scripts",
// baseUrl is a template variable that can be referenced in other string fields of the template. It's useful when you have several scripts which all have their documentation hosted on the same site (so they start with the same URL). For more Information about templates, see the section below.
"url": "@{baseUrl}",
// The address where information about this repository can be found. In this case it references the baseUrl template variable and expands to "https://github.com/TypesettingTools/line0-Aegisub-Scripts".
"fileBaseUrl": "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/@{channel}/@{namespace}",
// A special rolling template variable. See the templates section below for more information.
"macros": {
// the section where all automation scripts tracked by this feed go. The key for each value is the namespace of the respective script. Below this level, this namespace is available as the @{namespace} and @{namespacePath} template variable
"l0.ASSWipe": { /* ... */ },
"l0.Nudge": { /* ... */ }
},
"modules": {
// Your modules go here. If your feed doesn't track any modules, you may omit this section (same goes for the macros object)
"l0.ASSFoundation": { /* ... */ }
}An automation script or module object looks like this:
"l0.ASSWipe": {
"url": "@{baseUrl}#@{namespace}",
"author": "line0",
"name": "ASSWipe",
"description": "Performs script cleanup, removes unnecessary tags and lines.",
// These script information fields should be identical to the values defined in your
// DependencyControl version record.
"channels": {
// a list of update channels available for your script (think release, beta and alpha).
// The key is a channel name of your choice, but should make sense to the user picking one.
"master": {
// This example only defines one channel, which is set up to track
// the HEAD of a GitHub repository.
"version": "0.1.3",
// The current script version served in this channel.
// Must be identical to the one in the version record.
"released": "2015-02-26",
// Release date of the current script version (UTC/ISO 8601 format)
"default": true,
// Marks this channel as the default channel in case the user doesn't have picked a specific one.
// Must be set to true for **exactly** one channel in the list.
"platforms": ["Windows-x86", "Windows-x64", "OSX-x64"]
// Optional: A list of platforms you serve builds for. You should omit this property for regular scripts
// and modules that use only Lua/Moonscript and no binaries. If this property is absent,
// the platform check will be skipped. The platform names are derived from the output of
// ffi.os()-ffi.arch() in luajit.
"files": [
// A list of files installed by your script.
{
"name": ".lua",
// the file name relative to the path assigned to the script by your namespace choice
// (see the Namespaces and Paths section for more information). Available as the @{fileName} template variable
// for use in the url field below.
"url": "@{fileBaseUrl}@{fileName}",
// URL from which the **raw** file can be downloaded from (no archives, no javascript
// redirects, etc...). In this case the templates expand to
// "https://raw.githubusercontent.com/TypesettingTools/line0-Aegisub-Scripts/master/l0.ASSWipe.lua"
"sha1": "A7BD1C7F0E776BA3010B1448F22DE6528F73B077"
// The SHA-1 hash of the file being currently served under that url. Will be checked
// against the downloaded file, so it must always be present and valid or the update process
// will fail on the user's end.
},
{
"name": ".lua",
"type": "test",
// Optional, defaults to "script". Specify "test" to denote a unit test.
// Currently only "script" and "test" are available, unknown script types will be skipped.
"url": "@{fileBaseUrl}.Tests.lua",
"sha1": "27745AB9CF04A840CF3454050CA9D38FA345CEBB"
},
{
"name": ".Helper.dll",
"url": "@{fileBaseUrl}@{fileName}",
"sha1": "0B4E0511116355D4A11C2EC75DF7EEAD0E14DE9F",
"platform": "Windows-x86"
// Optional. When this property is present, the file will only be downloaded to the users
// computer if his platform matches to this value.
}
],
"requiredModules": [
// an exhaustive list of modules required by this script. Must be identical to the required
// module entries in your DependencyControl record, but you may not use short style here.
// (see the DependencyControl for Script Authors section for more information)
{
"moduleName": "a-mo.LineCollection",
"name": "Aegisub-Motion (LineCollection)",
"url": "https://github.com/torque/Aegisub-Motion",
"version": "1.0.1",
"feed": "@{feed:a-mo}"
},
{
"moduleName": "l0.ASSFoundation",
"name": "ASSFoundation",
"url": "https://github.com/TypesettingTools/ASSFoundation",
"version": "0.1.1",
"feed": "@{feed:ASSFoundation}"
},
{
"moduleName": "aegisub.util"
},
]
}
},
"changelog": {
// a change log that allows users to see what's new in this and previous versions. The changelog
// is shared between all channels. Only the entries with a version number equal or below
// the version the user just updated to will be displayed.
"0.1.0": [
"Sync with ASSFoundation changes",
// one entry for each line
"Start versioning with DependencyControl"
],
"0.1.3": [
"Enabled auto-update using DependencyControl",
"Changed config file to \\config\\l0.ASSWipe.json (rename ASSWipe.json to restore your existing configuration)",
"DependencyControl compatibility fixes"
]
}
}Full JSON Schema documents (which you can use to validate your feeds) are provided for the following feed versions:
- v0.4.0 (current; also validates v0.3.0 and legacy v0.2.0 feeds)
- v0.3.0 (also validates legacy v0.2.0 feeds)
To make maintaining an update feed easier, you can use several template variables that will be expanded when used inside string values (but not keys).
Regular Variables: These reference a specific key or value and are available at the same depth and further down the tree from the point on where they were created.
Variables extracted at the same depth are expanded in a specific order. As a consequence only references to variables of lower order are expanded in values that are assigned to a variable themselves.
Depth 1: Feed Information
@{feedName}: The name of the feed@{baseUrl}: The baseUrl field@{feed:<feedName>}: A reference to a feed URL in the knownFeeds table
Depth 2: Section Information (v0.4.0)
@{scriptTypeSection}: the name of the feed section the package lives in (macrosormodules)@{scriptType}: the matching script type identifier (automationormodule)
Depth 3: Script Information
@{namespace}: the script namespace@{namespacePath}: the script namespace with all.replaced by/@{scriptName}: the script name
Depth 5: Version Information
@{channel}: the channel name of this version record@{version}: the version number as a SemVer string
Depth 7: File Information
@{platform}: the platform defined for this file, otherwise an empty string@{fileName}: the file name
"Rolling" Variables: These variables can be defined at any depth in the JSON tree — the feed root, a macros/modules section container (v0.4.0), a package, or a channel — and are continuously expanded using the variables available. You can reference a rolling variable in itself, which will substitute the template for the contents the variable had at the parent-level.
@{fileBaseUrl}: the base URL to construct file download URLs from@{localFileBasePath}(v0.4.0): the on-disk counterpart of@{fileBaseUrl}, resolved relative to the feed file when a feed is expanded in local mode; it lets CI/CLI tooling (such as the DepCtrl CLI) locate every packaged file's source in your repository
Per-file-type template maps (v0.4.0): the fileBaseUrls and localFileBasePaths properties hold one full URL/path template per file type (script, test), usually ending in @{fileName}. At each file record, the entry matching the file's type becomes the effective @{fileBaseUrl}/@{localFileBasePath} value, so file entries can simply declare "url": "@{fileBaseUrl}". A file type without a map entry falls back to the scalar fileBaseUrl/localFileBasePath. The maps roll like the scalar variables, so defining them once at the feed root (or on a section container, when your macros and modules trees follow different layouts) describes every package's file locations in one place.
Author-defined variables (v0.4.0): a root-level vars object defines your own template variables. A string value is substituted as @{name}; an object value is a lookup table indexed as @{name:key}, where the key part may itself be a variable. DependencyControl's own feed uses this to derive release-tag names per channel:
"vars": { "tagSuffix": { "alpha": "-alpha", "stable": "" } },
"fileBaseUrls": { "script": "@{fileBaseUrl}v@{version}@{tagSuffix:@{channel}}/@{scriptTypeSection}/@{namespacePath}@{fileName}" }For an example that serves updates from the HEAD of a GitHub repository's main branch, see line0's script feed. An example that shows a feed making use of tagged releases is also available. DependencyControl's own feed exercises the full v0.4.0 feature set: root-level template maps, a section-scoped override for the differently-structured macros tree, and channel-keyed release-tag suffixes.
The full API reference — every class, method, parameter, and return, for every module — is published at typesettingtools.github.io/DependencyControl, showing the latest release (with a version picker for earlier releases and the in-development main). To build it from your own checkout, or to get the same annotations as editor IntelliSense, see MoonCATS below.
The l0.MoonCats module extracts the LuaCATS annotations from MoonScript sources into LuaLS .d.lua type definitions and rendered API documentation — see the generate-types and generate-docs CLI commands for the packaged workflows. The module itself is a pure text transform available to other tooling: MoonCats!\extractPackage sources takes a list of {requireId, source} pairs and returns one definition per module plus a diagnostics collection, resolving cross-module references (re-exported classes, package-wide type aliases) across the whole set; renderDocs sources, opts runs the same parse and returns documentation pages, an index, and site scaffolding instead; extractModule is the single-module convenience. Parsing is AST-assisted via the moonscript rock's own parser, so string literals and comment-lookalike content never confuse the extraction.
DependencyControl ships a CLI launcher (depctrl.lua) for running tests, building release bundles, and deploying to a local Aegisub installation — all without a running Aegisub process. All commands read their package list from a feed JSON file and can operate on any DepCtrl-managed package, not only DependencyControl itself.
Beyond the native runtime libraries the CLI shares with Aegisub (see Prerequisites), running the CLI needs a standalone Lua toolchain:
- 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 - Your
LUA_PATH/LUA_CPATHmust letluajitfind the LuaRocks-installed modules (luarocks --lua-version=5.1 path --binprints the correct values).
Every command needs three core rocks: moonscript (to load the .moon sources), LuaFileSystem, and argparse.
luarocks --lua-version=5.1 install moonscript
luarocks --lua-version=5.1 install luafilesystem
luarocks --lua-version=5.1 install argparseIndividual commands pull in a few more rocks; install a group only if you run the commands that need it. The test workflow installs the full superset.
- Feed validation: commands that refresh or write the feed (led by
update-feed) validate it against the bundled JSON schema usinglua-schema, withlpegfor its pattern rules. Withoutlua-schemapresent, validation is skipped with a warning rather than failing. testwith the mock HTTP server: test suites that exercise download and update code spin up a small mock web server built onluasocket,copas, andpegasus. DependencyControl's own suite needs all three; a package whose tests don't reach the network does not.
# feed validation
luarocks --lua-version=5.1 install lua-schema
luarocks --lua-version=5.1 install lpeg
# mock HTTP server for network tests
luarocks --lua-version=5.1 install luasocket
luarocks --lua-version=5.1 install copas
luarocks --lua-version=5.1 install pegasusGeneral form:
luajit depctrl.lua <command> [options]The feed is resolved in this order: --feed flag → DependencyControl.json in the current working directory. All commands accept --target-module and --target-macro to restrict processing to specific packages; without them the command operates on every package in the feed.
luajit depctrl.lua test [--feed <path>] [--report-dir <dir>]
[--target-module <ns>] [--target-macro <ns>]Loads every matching package from the feed, runs its DepUnit test suite (if one is registered), and writes a per-package CTRF JSON report. Exit code 0 = all tested packages passed, 1 = one or more failures or load errors.
Packages without a test suite are skipped with a notice; packages that fail to load are counted as failures. Log files and config/feed caches are written to a per-run throwaway workspace under the system temp directory rather than touching your real Aegisub configuration.
The feed must have correct localFileBasePaths (or localFileBasePath) entries so the CLI can resolve source files on disk.
| Option | Default | Description |
|---|---|---|
--feed |
DependencyControl.json in CWD |
Path to the feed JSON file |
--report-dir |
ctrf/ |
Directory for per-package CTRF JSON reports |
--target-module |
(all modules) | Module namespace to test; repeatable |
--target-macro |
(all macros) | Macro namespace to test; repeatable |
luajit depctrl.lua bundle [--feed <path>] [--out-dir <dir>]
[--target-module <ns>] [--target-macro <ns>]Copies every file listed in the feed into a dist/ subfolder of <dir>, then packages dist/ into a zip archive named <feedName>-v<version>[-<branch>-g<hash>].zip in <dir>. dist/ is wiped and recreated on each run. The git branch and hash suffix is omitted when HEAD is exactly on a tag.
| Option | Default | Description |
|---|---|---|
--feed |
DependencyControl.json in CWD |
Path to the feed JSON file |
--out-dir |
CWD | Root for the dist/ folder and the zip file |
--target-module |
(all modules) | Restrict to this module namespace; repeatable |
--target-macro |
(all macros) | Restrict to this macro namespace; repeatable |
Exit code 0 = success, 1 = one or more errors.
luajit depctrl.lua deploy [--feed <path>] [--out-dir <dir>] [--clobber | --no-clobber]
[--target-module <namespace>] [--target-macro <namespace>]Copies every file listed in the feed directly into <dir> using the Aegisub install layout — macros into <dir>/automation/autoload/, modules into <dir>/automation/modules/, test files into <dir>/automation/tests/DepUnit/…. Useful for testing against a locally installed Aegisub without going through a full release build.
| Option | Default | Description |
|---|---|---|
--feed |
DependencyControl.json in CWD |
Path to the feed JSON file |
--out-dir |
CWD | Deployment root — typically the Aegisub user directory |
--clobber |
false | Overwrite existing files in the deployment directory |
--no-clobber |
(default) | Skip files that already exist at the destination |
--target-module |
(all modules) | Restrict to this module namespace; repeatable |
--target-macro |
(all macros) | Restrict to this macro namespace; repeatable |
Exit code 0 = success, 1 = one or more errors.
luajit depctrl.lua update-feed [--feed <path>] [--channel <name>] [--dry-run] [--add-files]
[--mark-released] [--release-date <date>]
[--target-module <ns>] [--target-macro <ns>]Refreshes each targeted package's channel in place: recomputes SHA-1 hashes from the local source files, updates the version, dependencies and provided aliases from the package's DependencyControl record, and flags files that have vanished locally with delete: true. Any package that changed has its released date reset to null to mark the build as pending. The feed is validated against the bundled schema before processing.
With --add-files, files found on disk that the targeted channel doesn't list are added to it, complete with computed SHA-1 hashes. Discovery works by inverting the effective per-file-type localFileBasePaths templates — every template whose only unexpanded variable is @{fileName} is matched against the files below it — so it requires the feed to declare localFileBasePaths (at any level; see the template section above). New packages are not discovered; add those to the feed by hand.
With --mark-released, each targeted channel still marked unreleased (released is null) is stamped with the release date — today (UTC) by default, or the date passed to --release-date (giving a date implies --mark-released). A channel that already carries a date keeps it. This is the last step of a release, recording which builds have shipped — the release workflow runs it so version tooling can later tell a released version from a pending one, passing one date so every stamp in a run agrees.
| Option | Default | Description |
|---|---|---|
--feed |
DependencyControl.json in CWD |
Path to the feed JSON file |
--channel |
(each package's default) | Channel to update |
--dry-run |
false | Print what would change without writing back |
--add-files |
false | Add entries for on-disk files missing from the channel |
--mark-released |
false | Stamp the release date on channels still unreleased |
--release-date |
today (UTC) | Date to stamp with --mark-released |
--target-module |
(all modules) | Restrict to this module namespace; repeatable |
--target-macro |
(all macros) | Restrict to this macro namespace; repeatable |
Exit code 0 = success, 1 = one or more packages had errors.
luajit depctrl.lua bump-version [--feed <path>] [--channel <name>] (--major | --minor | --patch)
(<namespace>… | --all-changed)Bumps the given packages — or, with --all-changed, every package changed since its last release — to the repository's lockstep version, then refreshes the feed. Versions are managed in lockstep: a release only advances the packages that actually changed, and they all move to the same repo-level version. The versions and release state are read from the working channel (--channel, or the channel marked default: true). The target version is derived from that channel's release dates: if the highest version present has already been released, a new cycle starts and the version is bumped by the chosen component; otherwise the packages join the release already in progress. If a later bump reaches past the in-progress version, every package already bumped into that cycle is carried up with it, so a release never spans two versions.
Each version lives in its package's source, on the line marked -- @{<namespace>:version} (e.g. -- @{l0.MoonCats:version}) — the feed template variable the literal feeds, keyed by the package's own namespace just like everything else DependencyControl tracks. The command rewrites the literal there and updates the channel's version and file hash in the feed to match. version is the only property synced this way — every other record field flows source → feed through update-feed, never the reverse — so it is the only recognized marker. Before touching anything the command verifies the feed's hashes already match the sources and bails if not, so its only feed change is the version bump — run update-feed and commit first if it reports the feed out of sync.
| Option | Default | Description |
|---|---|---|
--feed |
DependencyControl.json in CWD |
Path to the feed JSON file |
--channel |
(the default channel) | Channel whose versions and release state drive it |
--major |
— | Start a new cycle by bumping the major component |
--minor |
— | Start a new cycle by bumping the minor component |
--patch |
— | Start a new cycle by bumping the patch component |
--all-changed |
false | Bump every package changed since its last release |
Exit code 0 = success (including nothing to bump), 1 = the feed was out of sync or a package/marker couldn't be resolved.
luajit depctrl.lua merge-feed [--feed <src>] --into <dst> --from <channel> --to "<channels>"
--default-channel <name> [--released <date>] [--dry-run]Copies the --from channel of every package in the source feed into each --to channel of the destination feed (updated in place), then writes the destination through the feed's canonical formatter. Each destination channel's default flag is set (true only for --default-channel) and its release date stamped when --released is given; file hashes are copied as-is, so no sources are read. Channels not named in --to are left untouched — they keep their previously published versions — and a package missing from the destination is added carrying only the --to channels. Top-level feed metadata and shared package fields track the source. This is how the release workflow promotes the dev feed's channel onto the published stable/alpha channels while leaving the channel it isn't releasing in place.
| Option | Default | Description |
|---|---|---|
--feed |
DependencyControl.json in CWD |
Source feed to copy from |
--into |
(required) | Destination feed, updated in place |
--from |
(required) | Source channel to copy |
--to |
(required) | Destination channel(s), space-separated |
--default-channel |
(required) | Which destination channel becomes default: true |
--released |
(unset) | Release date to stamp on the destination channel(s) |
--dry-run |
false | Compute the merge without writing the destination |
Exit code 0 = success, 1 = a feed couldn't be loaded or a required option was missing.
luajit depctrl.lua release-notes [--feed <path>] [--channel <name> | --version <ver>] [--title <text>] [--output <path>]Renders a GitHub-flavored markdown release body from one version's changelog across every package that has entries for it, grouping them into sections — New Features, Bug Fixes, Changes, Other Changes — by a conventional-commit-style marker at the start of each changelog entry:
<type>[(<scope>)][!]: <message>
type is feat, fix, or change (case-insensitive); an unrecognized leading token leaves the entry uncategorized under Other Changes. (scope) is an optional area tag such as (Updater) that becomes the entry's bold lead-in. A trailing ! flags a breaking change: the entry stays in its type's section but is tagged name is treated as primary — its unscoped entries render with no lead-in, while other packages fall back to their namespace's last segment, so every line stays attributed in the aggregated notes.
The same markers drive the changelog the Toolbox prints to Aegisub's log on update: there the machine type token is dropped and each entry is rendered under a glyph-headed category (✨ Features, 🐛 Fixes, 🔧 Changes), breaking entries tagged
Pass --version for an exact version, or --channel (the default channel if omitted) to use the highest version that channel advertises. Notes go to stdout unless --output writes them to a file, which receives only the notes and none of the setup logging — so the release workflow uses --output to fill the body of the GitHub Release it cuts.
| Option | Default | Description |
|---|---|---|
--feed |
DependencyControl.json in CWD |
Path to the feed JSON file |
--channel |
(the channel marked default) | Channel whose version's changelog to render |
--version |
(derived from --channel) |
Exact version to render (overrides --channel) |
--title |
(none) | Optional top-level # heading to prepend |
--output |
(stdout) | Write the notes to this file instead of stdout |
Exit code 0 = success (including an empty result), 1 = the feed couldn't be loaded or no version could be resolved.
luajit depctrl.lua generate-types [--feed <path>] [--out-dir <dir>] [--check] [--target-module <ns>]Extracts the LuaCATS annotations from the feed's module sources into LuaLS .d.lua definition files (one per module, mirroring the require path under the output directory), powered by the MoonCATS module. Pointing the Lua language server's Lua.workspace.library setting at the definition tree gives any Lua or MoonScript project full IntelliSense — completion, signature help, and type checking — for every DependencyControl-managed module; this repository's own .vscode/settings.json wires up types/ that way. For browsable API documentation rendered from the same annotations, see generate-docs below.
Beyond copying annotation blocks, the extractor synthesizes what LuaLS can't infer from MoonScript: a constructor call signature (@overload) for every class built from its new parameters, a typed per-enum class for each exported Enum so members complete and type-check, @field declarations for data members and instance defaults, and cross-module types for re-exported classes. Macros are skipped — they aren't require-able and have no type definition to generate.
With --check, nothing is written; instead the extraction's findings act as an annotation linter: missing annotation blocks on public members, @param tags that disagree with the real signature in name or count, and explicit value returns without a @return are reported as errors and fail the run. Note that a MoonScript function's implicit final-expression return is deliberately not flagged as missing a @return, so a clean check is not proof of complete return documentation.
| Option | Default | Description |
|---|---|---|
--feed |
DependencyControl.json in CWD |
Path to the feed JSON file |
--out-dir |
types in CWD |
Root directory for the generated definition tree |
--check |
false | Lint annotations only; write nothing, exit 1 on errors |
--target-module |
(all modules) | Restrict to this module namespace; repeatable |
Exit code in generate mode: 0 = success, 1 = a module failed to parse, a definition failed to emit, or a file couldn't be written (annotation lint findings are reported but don't fail generation). In --check mode: 1 when any error-severity finding exists.
luajit depctrl.lua generate-docs [--feed <path>] [--out-dir <dir>] [--site <flavor>] [--site-name <title>]
[--include-private] [--target-module <ns>]Renders browsable API documentation straight from the module sources' LuaCATS annotations — one markdown page per module, plus an index grouped by package. Because it reads the annotations themselves (via MoonCATS) rather than the generated type definitions, it documents everything the annotations carry, including details that don't survive into definition files or third-party doc generators: constructor parameter tables with descriptions, deprecation notices with their reasons, per-value enum member descriptions from @alias variant lines, and MoonScript's real instance-vs-class member distinction. Every method shows its call signature in both MoonScript and Lua form, parameter and field types cross-link to the page that defines them, and private members are omitted unless --include-private renders them with a badge.
For a standalone API site, --site mkdocs emits a ready-to-serve, self-contained MkDocs project, and --site mdbook the mdBook equivalent. The default, --site none, is the advanced path DependencyControl's own site takes: it writes only an embeddable reference section, which a committed mkdocs.yml folds into a larger, hand-written site through the mkdocs-literate-nav plugin, keeping docs/ and mkdocs.yml as committed source with only the generated docs/reference/ gitignored.
Published API docs are versioned on GitHub Pages by the Docs workflow (.github/workflows/docs.yml), which regenerates the docs and deploys them with mike: every merge to main updates /main/, every v* tag publishes an immutable /<tag>/ copy and moves the latest alias, and a manual workflow dispatch on any branch publishes a /<branch>/ preview. The site's root URL redirects to latest once the first release exists, and to /main/ before then, so it is never a dead link. The Material theme's version picker lists all published versions; a stale preview can be dropped with mike delete <branch> --push from the repository root (where the committed mkdocs.yml lives). Serving requires the repository's Pages source to be set to the gh-pages branch (root).
| Option | Default | Description |
|---|---|---|
--feed |
DependencyControl.json in CWD |
Path to the feed JSON file |
--out-dir |
docs/reference in CWD |
Embeddable reference dir, or a standalone site root |
--site |
none |
none embed section; mkdocs/mdbook standalone |
--site-name |
DependencyControl API |
Site title |
--include-private |
false | Render private members (badged) instead of omitting them |
--target-module |
(all modules) | Restrict to this module namespace; repeatable |
Exit code 0 = success, 1 = a module failed to parse or a file couldn't be written.
DependencyControl ships two reusable GitHub Actions workflows for a publish-branch release model — used both by this repo and by any package repo that wants the same setup:
reusable-publish-release.yml— run on demand; copies your dev feed's channel onto the published channel(s) of a separate publish branch, tags the versions they reference, records the release date back on the dev branch (for a stable release) sobump-versioncan tell what has shipped, and can cut a GitHub Release per channel with notes rendered from the changelog.reusable-update-feed.yml— on every push to the dev branch, refreshes the feed's file hashes and versions from the sources and commits the result back, so pull requests only have to author metadata.
The feed URL baked into installed configs points at one branch, so that branch must only ever advertise released, downloadable versions. The workflows keep the dev and published states on separate branches:
- a dev branch (
main) whoseDependencyControl.jsoncarries a single dev channel — the working state, edited freely in pull requests; - a publish branch (
publish) whoseDependencyControl.jsoncarries the user-facing channels (e.g.stableandalpha) — written only by the release workflow, and the branch the feed URL serves.
A release copies the dev channel onto a chosen published channel and leaves the channels it isn't releasing untouched, so the published channels can sit at different versions. Each channel's files resolve from a v<version> tag (with the channel's tag suffix) that the release workflow creates.
DependencyControl's own feed URL served from master through v0.6.x; from v0.7.0 it serves from publish, the workflow's default. Because clients older than v0.7.0 can't read the current feed format, a one-time resolved feed left on master advertises v0.7.0 so they can upgrade onto publish. Any branch name works, as long as it's the one your feed URL points at.
-
Set up the branches. Make your dev branch the default. The first release creates the publish branch for you, so you needn't create it by hand. In your feed, give each package a single dev channel (its name is the
source-channelinput, defaultmain); the release workflow creates the published channels. -
Add two thin caller workflows that pin a DependencyControl version and pass your layout. A publish caller (
.github/workflows/release.yml):name: Release on: workflow_dispatch: inputs: ref: description: Ref to release default: main channels: description: Channel(s) to publish (space-separated) default: stable permissions: contents: write jobs: publish: permissions: contents: write uses: TypesettingTools/DependencyControl/.github/workflows/reusable-publish-release.yml@v1 with: ref: ${{ github.event.inputs.ref }} channels: ${{ github.event.inputs.channels }} default-channel: stable release-channels: stable secrets: app-private-key: ${{ secrets.CI_APP_PRIVATE_KEY }} # only if the publish branch is protected
An update-feed caller (
.github/workflows/update-feed.yml):name: Update feed on: push: branches: [main] paths: ["**/*.moon", "DependencyControl.json"] permissions: contents: write jobs: sync: if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} permissions: contents: write uses: TypesettingTools/DependencyControl/.github/workflows/reusable-update-feed.yml@v1
-
Version with
bump-version. Bump the changed packages (lockstep) before releasing; the update-feed workflow propagates the new versions into your dev feed, and the release workflow tags and publishes them.
Each workflow pushes to the branch it maintains — the publish workflow to the publish branch and its version tags, the update-feed workflow to the dev branch. With no protection they push using the built-in GITHUB_TOKEN and need no credential setup. To protect those branches, note that GITHUB_TOKEN cannot be granted bypass: a ruleset's bypass list accepts roles, teams, GitHub Apps, and deploy keys — but not the github-actions[bot] identity the token runs as. Give the workflows a GitHub App on the bypass list instead:
- Create a GitHub App with repository permission Contents: read & write and no webhook, then generate a private key and note the App's Client ID.
- Install the App on the package repository.
- Add the App to the bypass list of each protected branch's ruleset — the publish branch, the dev branch, or both — plus any tag protection that the publish workflow's version tags would trip.
- Store the credentials as repository Actions entries: the Client ID as a variable (it isn't sensitive) such as
DEPCTRL_CI_APP_CLIENT_ID, and the private key as a secret such asDEPCTRL_CI_APP_PRIVATE_KEY. - Pass them to whichever caller pushes a protected branch — the publish and update-feed callers both accept
app-client-id: ${{ vars.DEPCTRL_CI_APP_CLIENT_ID }}and theapp-private-key: ${{ secrets.DEPCTRL_CI_APP_PRIVATE_KEY }}secret.
Each workflow mints a short-lived token from the App for its pushes and falls back to GITHUB_TOKEN when these inputs are absent, so you can develop against unprotected branches and add the App only when you lock them down.
Both workflows fetch the depctrl CLI from this repo (depctrl-ref, default latest — the newest DependencyControl release), so you never vendor it. The publish workflow's inputs:
| Input | Default | Description |
|---|---|---|
ref |
(required) | Commit or branch to release (its source is tagged) |
channels |
(required) | Space-separated channels to publish (e.g. stable alpha) |
default-channel |
(required) | Which published channel is marked default: true |
source-channel |
main |
The dev feed channel to copy from |
dev-branch / publish-branch |
main / publish |
Branch names |
stable-channel |
stable |
Publishing this channel records the release date on the dev branch |
release-channels |
(none) | Published channels to also cut a GitHub Release for |
depctrl-ref |
latest |
DependencyControl ref for the CLI (latest, a tag, or a branch) |
app-client-id |
(none) | GitHub App Client ID for branch-protection bypass |
reusable-update-feed.yml takes the matching dev-branch, depctrl-ref, and app-client-id inputs plus the app-private-key secret. See each workflow file's header for the full contract.