Releases: AlfaCode-Team/hkm-kernel
Release list
v1.17.0
Added
-
Pluginsandplugin_installed()— is a module actually installed in
this application? Answered from the compiledservice-manifest.php, whose
keys are thesolvesdomains of everything the project passed to
withModules([...]), so it needs no knowledge of any particular plugin and no
directory scan.plugin_installed('tenancy.routing')takes a domain or a
module.jsonname, and several at once;installed_plugins()returns the
whole map;Plugins::ensure(...)throws aKernelExceptionnaming what is
missing AND what IS registered, because the usual cause is a domain spelled
differently from the plugin'ssolvesrather than a plugin that is really
absent.A module that cannot work without another one should still declare it in its
ownmodule.jsonrequires[]—CompileServiceManifestStagefails the BOOT
there, which an operator sees at deploy time instead of when a request happens
to reach the feature. This is for whatrequires[]cannot cover: an OPTIONAL
integration that should light up when a plugin is present and degrade quietly
when it is not, and code running outside the module graph — a standalone CLI
entry point, a bootstrap file, a template — which has nomodule.jsonto
declare anything in. An absent plugin is therefore afalse, never a throw,
unlessensure()is the call.
What's Changed
🔄 Other Changes
- chore(homebrew): point the formula at v1.16.0 by @hakeemRash in #174
- release: v1.17.0 — ask whether a plugin is installed without guessing by @hakeemRash in #175
Full Changelog: v1.16.0...v1.17.0
v1.16.0
Added
RangeReadableStorage— an optional interface aStoragePortimplements
to report an object'ssize()and open part of it withreadRange().
readStream()was the only read the port declared, and on S3 it returns a
network body that can be neither stat'ed nor seeked, so a consumer honouring
Range(a PDF reader paging a large document) served ranges on the local
driver and silently sent whole objects on S3. Checked withinstanceof, like
DriverAware, so existingStoragePortfakes need no change; a consumer
falls back toreadStream()when the bound port does not implement it.
What's Changed
🔄 Other Changes
- chore(homebrew): point the formula at v1.15.0 by @hakeemRash in #171
- release: v1.16.0 — RangeReadableStorage, so storage consumers can serve ranges on S3 by @hakeemRash in #172
Full Changelog: v1.15.0...v1.16.0
v1.15.0
Added
- Scheduled tasks, declared in
module.json. A module listsschedule[]
entries — aname, a cron expression or alias such as@hourlyinat, and
EITHER ajobor acommand, with optionalqueue,timezoneand
withoutOverlapping.CompileScheduleManifestStageparses every expression
at boot, so an impossible schedule ("0 25 * * *") fails the boot naming the
field instead of silently never running; naming both a job and a command, or
neither, fails it too. One crontab line drives the application:
hkm cli schedule:runevery minute dispatches whatever is due — ajobis
pushed onto the queue, so it gets the worker's retries and timeout, and a
commandruns through the CLI pipeline exactly as if typed.schedule:list
shows what is declared.withoutOverlappingtakes aCachePortlock keyed by
task name, which is what makes the scheduler safe on several servers at once;
with no cache bound, a guarded task is skipped and logged rather than run
unguarded. MetricsPortandTracerPort, and anObservabilityStageon every
request. The kernel already propagated a correlation id; it had no seam for
the aggregate numbers or for where a request's time went. Both ports are
StatsD/OpenTelemetry-shaped and dependency-free. The stage sits inside
CorrelationIdStageand outside security and routing, so a denied request and
an unmatched one (labelledunmatched) are still measured, and it labels by
route TEMPLATE, never by path, so a metric cannot mint one series per id. With
neither port bound, shared no-op implementations cost onehrtime()pair per
request. An adapter must not throw.DriverAware— an optional interface aDatabasePortimplements to name
the SQL dialect behind it. Checked withinstanceof, likeRequestAware, so
the many in-memory test fakes of the port need no change; a consumer falls
back to configuration when the bound port does not implement it.hkm ppkg— the native package manager. The Composer-compatible package
manager frommodules/hkm-ppkg, now a submodule, runs under the launcher:
install,update,require,remove,autoload,audit,outdatedand
the rest, writing the samecomposer.lockandvendor/Composer does.
Composer PLUGINS are not run;hkm ppkg compatnames each one a project uses
and what it would have done.hkm ppkg test-env <dir>builds a plugin's test
vendor/from the kernel's own and the checkouts already on disk.- A feature test bench.
tests/Featureboots a real kernel and pushes real
Requests through the realHttpPipeline— manifests, matcher, dependency
graph, scoped containers and stages all real, only the database, cache and
queue faked — and runs as its ownFeaturesuite. It exists for the class of
defect a unit test cannot see: a controller signature a stage no longer
matches, or a route that resolves to nothing.
Changed
- The route compiler is split into
Kernel\Boot\Routing.
CompileRouteManifestStagewas 1,200 lines doing six jobs and could only be
tested by compiling a whole manifest. The jobs now live inRouteNormalizer,
DomainComposer,GroupExpander,RouteCompilerandRoutePolicy, each
tested on its own, and the stage keeps only their order. The public shape of
route-manifest.phpis unchanged; each dynamic route inroute-index.phpnow
also carries its route key, which is what lets a metric be labelled by
template. An index compiled by an older kernel still serves. - A fetched plugin is verified without Composer when that is possible.
hkm plugins installused to runcomposer installinside every plugin to
reach phpunit — a dependency resolution across a few dozenvcsrepositories,
throttled by GitHub's anonymous rate limit. It now first builds the test
environment from the kernel's ownvendor/plus the sibling checkouts on
disk, and falls back to Composer whenever that cannot be trusted: no phpunit,
or a test dependency with no checkout.HKM_PLUGIN_TESTS_COMPOSER=1forces the
Composer route. RichGrapharticles takeinLanguage— the language of the CONTENT,
which can differ from the page's — and an article with noauthorNamenames
the publishing Organization as its author, as Google recommends for content
that is contributed rather than bylined.
Fixed
- Error responses had no correlation id.
ErrorStagewrapped
CorrelationIdStage, so the request it caught an exception on was the one from
before the id was attached: every error envelope'srequestIdwas empty,
every notifier received an empty correlation id, and error responses carried
noX-Correlation-IDheader — the id was on every response except the ones
anyone would need it for. The two stages now run the other way round. hkm plugins updateoverwrote files a project had edited. It replaced
every published file whose bytes differed from the plugin's copy, with no way
to tell a project's customisation from a stale copy — a customised layout, view
override or translation was lost on the next update — and when two plugins
published the same path, each update overwrote the other's. The installer now
records a SHA-256 of every file it publishes invar/plugin-assets.json. A
file still exactly as published is refreshed; one that was edited, never
recorded, or written by another plugin is KEPT, and the plugin's version is
written beside it as<file>.plugin-new, listed with the other plugin named.
--overwriterestores the old behaviour for one run, andenablenever
overwrites. A manifest written before this release has no hashes, so its first
update keeps every file that differs rather than guessing.- Every page head carried two
<title>elements and two descriptions when
SeoHeadembedded a SiteSEO Open Graph document, which renders its own. Only
that document'sog:andtwitter:tags are kept. - Ctrl+C left the Vite dev server running. The frontend template's hot-file
cleanup installedSIGINT/SIGHUPlisteners, and Node removes its default
terminate the moment one exists — so the shell got its prompt back while the
orphaned server kept the port, and its readline failed withError: read EIO
on top of the next command typed. The handler now cleans up, restores the
default and re-raises, and registers once per process across dev-server
restarts.
Security
composer/composer2.10.2 → 2.10.3 in the kernel's development
dependencies, for CVE-2026-84361 — arbitrary command execution through a
malicious package's Perforce source URL. It was never in a release bundle,
which installs without development dependencies, butcomposer auditfailed
every build until it moved.
Removed
templates/app/apache.conf.exampleandtemplates/app/nginx.conf.example,
andhkm newno longer writes them into a new project. The nginx or Apache
configuration a deployment runs is generated by the Edge plugin
(hkm edge:apply).
What's Changed
🔄 Other Changes
- chore(homebrew): point the formula at v1.14.0 by @hakeemRash in #168
- release: v1.15.0 — scheduled tasks, observability ports, hkm ppkg, and edited plugin files are kept by @hakeemRash in #169
Full Changelog: v1.14.0...v1.15.0
v1.14.0
Added
-
hkm service— run a project's queue worker as a supervised service.
hkm worker --queue=mailsis a foreground process: it dies with the terminal,
it does not come back after a crash or a reboot, and nothing collects its
output. Every deployment therefore hand-wrote the same unit file. The command
generates it for whichever supervisor the host runs — systemd or launchd, with
--platformto override so a Mac can produce the Linux unit it will deploy —
in four verbs: preview (the default, which writes nothing),write,
install [--start]andremove. Scope is--systemor--user, defaulting
to system on Linux and to a user agent on macOS, where a LaunchDaemon running
as root is the wrong answer on a developer machine.--dry-run(-n) reports
every write and every command for the three mutating verbs and performs none
of them.Three things the generated unit gets right that a hand-written one usually
does not:ExecStartruns the LAUNCHER —hkm worker -p <root>, notphpplus an
absolutevendor/autoload.php. The launcher self-locates the kernel, so a
kernel upgrade that moves a version-stamped install directory cannot
silently break the queue.--exec=phpemits the direct form for a server
with no launcher installed, and states the pinned autoload's cost in the
unit itself.TimeoutStopSec/ExitTimeOutis 90s. The worker traps SIGTERM and
finishes the job in flight before exiting — that is what makes a redeploy
safe — and launchd's 20s default SIGKILLs it mid-transaction instead.PATHandHKM_PHP_BINare pinned. A service inherits none of a login
shell's PATH, and/opt/homebrew/binis on neither manager's default. The
entire diagnostic without the pin iserror: FileNotFound, with nothing
anywhere naming php. Found by running the generated unit, not by reading it.
Values reaching the unit are validated rather than interpolated: a queue name
may hold only[A-Za-z0-9._:-], so nothing can append an argument or a
directive;ExecStarttokens containing whitespace are quoted; plist strings
are XML-escaped.
Fixed
- The worker entry point ignored every command-line flag.
hkm workerand
hkm run --workerforward their arguments verbatim toapp/worker/run.php,
which read onlyWORKER_QUEUEfrom the environment — so
hkm worker --queue=mailswas accepted in silence and draineddefault
instead. That is the failure mode with no signal at all: no error, no warning,
a running worker, and the wrong queue. The entry point (and the scaffolding
template new projects get) now parses-q/--queue,-n/--max-iterations,
--memoryand-h/--help, each overriding the matching environment variable,
and rejects an argument it does not recognise rather than ignoring it. The
environment fallbacks moved fromgetenv()toenv()at the same time: the
loader injects.envinto$_ENVand deliberately skipsputenv(), so
getenv('WORKER_QUEUE')could not see a value set in the project's.env.
What's Changed
🔄 Other Changes
- chore(homebrew): point the formula at v1.13.1 by @hakeemRash in #165
- release: v1.14.0 — the worker takes its flags, and can be run as a service by @hakeemRash in #166
Full Changelog: v1.13.1...v1.14.0
v1.13.1
Changed
EventBus::dispatch()now returns the listener failures it isolated
(array<class-string, \Throwable>, empty on full success). Isolation was
right — one broken subscriber must not stop the others — but callers had no
way to tell it apart from success, and one of them was a transactional
outbox. A mis-scoped listener threw, the bus swallowed it exactly as designed,
the outbox marked the row dispatched becausedispatch()had returned
normally, and the row was consumed and never retried: a tenant membership was
lost permanently while the table recordedstatus=1, attempts=1, last_error=NULL. Adding the value is backward compatible — every existing
$bus->dispatch($e);ignores it and behaves exactly as before — but anything
that RECORDS delivery should now check it and re-queue rather than consume.
Fixed
- A listener that could not be resolved was reported as a broken constructor.
resolveListener()caught every container failure and fell back to
new $listenerClass(). For a listener with constructor arguments that threw
ArgumentCountError, so abindInternal()binding — which the container had
already refused with aScopeViolationExceptionnaming the scope, the class
and the fix — was logged as "Too few arguments to function
…::__construct()". Every reader then went to the listener's constructor,
which was correct, instead of to the binding, which was not; the same shape
had already been misdiagnosed twice before.newis now attempted only when
it can actually succeed (a constructor with no required parameters);
otherwise the container's own exception is rethrown untouched.
What's Changed
🔄 Other Changes
- chore(homebrew): point the formula at v1.13.0 by @hakeemRash in #162
- fix(events): stop a failed dispatch from looking like a delivered one by @hakeemRash in #163
Full Changelog: v1.13.0...v1.13.1
v1.13.0
Fixed
hkm install --owner=left every plugin file owned by the deploying user.
A project's plugins are not in the project:hkm plugins installkeeps one
copy per (plugin, version, origin) in the global store and links the project
at it, soplugins/Loggeris a symlink out of the tree. Both halves of the
hardening pass stopped at that boundary by design —hardenTreeskips
symlinks because a chmod would follow one and rewrite a target outside the
project, and the chown only walked the project root. The result was a project
that verified clean and could not serve: every file the pool has to read
first, every Provider and every controller a route resolves to, still belonged
to whoever ran the command, under a report that saidProject owned by deploy:www-data.--ownernow also chowns the store versions the project
links to, plus the directories between them and the store root so the trees it
just chowned can be reached. Only the versions THIS project links to: the store
is shared by every project on the machine, and claiming all of it for one
project's web account is not that command's call.--productionreported a reachable project while the plugins were
unreachable. The traversal check walked the parents of the project root only.
Since the store moved out of the project it defaults to$HOME/.cache, which a
deploy under sudo resolves to/root/.cache— 0700 on every mainstream distro
— so the chown succeeded on every entry and the site still could not read one
of them. The check now covers the store's own parents, with its own remedy:
relocate the store (hkm plugins store --set=, orHKM_PLUGIN_STORE) rather
than widen a home directory to reach a cache.- A plugin that gained an env var never got it.
hkm plugins enablereturns
early when the plugin and its dependencies are already wired, so a plugin
declaring a newconfig[]entry in a later version left an.envblock that
was now incomplete — and the boot failed on the missing key with nothing
pointing at the cause. Enabling an already-enabled plugin now tops up its
block. Safe by construction: the seeder only ever ADDS keys the file does not
already mention, in any form, so a real secret is never rewritten. .env.exampledocumented the Tenancy control-plane switch as a hostname.
TENANCY_CONTROL_PLANE=admin.example.comreads as "the control plane lives
here"; the plugin declares the key astype: bool, where any non-empty string
is truthy — so the example value silently turned tenant routing OFF for anyone
who uncommented it. Corrected to a bool, withTENANCY_CENTRAL_DOMAINS(a
real declared key that was missing) added beside it and the mode values named.
The plugin's ownmodule.jsonstays the authority; this is the example
catching up to it.- Re-seeding wrote a second block for the same plugin. The append was
unconditional, so a plugin seeded twice got two# ─── Auth ───headings, and
three after that. Every key was still present exactly once, so nothing broke —
the grouping the block exists to provide just quietly stopped being true. New
keys are now merged into the block the plugin already owns, keeping the blank
line that separates it from the next one.
Added
hkm env— audit and tidy a project's.env. A dotenv file accumulates:
a plugin seeds its block on enable, someone appends a key at the bottom to try
something, a second plugin declares a variable the first one already did. None
of that is an error anywhere. The loader resolves a repeated key silently, the
boot succeeds, and the value in effect is whichever line happens to be last —
a file that works and does not say what it is doing.hkm envreports duplicates with every occurrence's line number and marks
which one is live. That marker is the point:LoadEnvironment::setVar
overwrites on each call and the cascade reads a file top to bottom, so the
LAST active assignment wins — the opposite of what most people assume when
they append a key to the bottom of a .env.hkm env dedupeasks per key rather than choosing. The right survivor is
not derivable:DB_HOST=localhoston line 12 andDB_HOST=10.0.0.4on line
88 are both plausible, and the one in effect is as likely to be the accident
as the intent.--keep=effectiveis the scriptable form that cannot change
behaviour;--keep=first/--keep=lastare positional.hkm env groupreorders the file into blocks: a key a plugin declares in its
module.jsonconfig[]goes under that plugin, otherwise under the feature
its prefix names, otherwiseUngrouped. Comments attached to a key move with
it, comments attached to nothing are rescued into aNotesblock rather than
dropped, and the pass refuses to write unless every key AND every
informational comment that went in comes out again.- Every write leaves the previous file beside it as
.env.bak, at 0600.
- A project is found from anywhere inside it.
resolveRootchecked the exact
working directory, sohkm envin<project>/appanswered "'.' is neither a
project folder (with proj.json) nor a registered name" about a project one
directory up. It now walks up to the filesystem root, the way git, composer and
npm all find theirs — for every command that takes a[path|name], not just
env. An EXPLICIT path stays exact: the same resolver backs
hkm install --owner, and a command that chowns a tree must never quietly
retarget itself above where it was pointed.
What's Changed
🔄 Other Changes
- chore(homebrew): point the formula at v1.12.0 by @hakeemRash in #159
- chore(homebrew): point the formula at v1.12.1 by @hakeemRash in #158
- release: v1.13.0 — the account that serves a project can read its plugins, and .env can be audited by @hakeemRash in #160
Full Changelog: v1.12.1...v1.13.0
v1.12.1
Fixed
- A plugin fetch asked for a GitHub account, for a repo that is public.
FileManagerwas the one hyphenated plugin missing from the slug override
table, so it resolved tohkm-plugin-filemanager— a repository that does not
exist. GitHub answers 404 for "does not exist" and "not yours" alike; it
will not confirm a private repo to an anonymous request. Git cannot tell those
apart, assumed the second, and stopped to ask for a username and password that
no account could have satisfied. Added the override, plus tests pinning every
multi-word folder to its real hyphenated slug (and round-tripping back to the
PSR-4 folder name) so the next repo added with a hyphen cannot drift the same
way. - Git could block a deploy on a credential prompt. The plugin fetch inherited
the terminal, so an unreachable remote hunghkm installon a password box
until somebody killed it — on a deploy box or in CI, indefinitely. Every git
invocation now runs withGIT_TERMINAL_PROMPT=0and SSHBatchMode=yes: a bad
remote fails immediately and the call site names the plugin and URL, which is
the information actually needed.HKM_GIT_INTERACTIVE=1restores the prompt
for a genuinely private remote you intend to authenticate against by hand.
What's Changed
🔄 Other Changes
- release: v1.12.1 — a public plugin repo no longer asks for a GitHub account by @hakeemRash in #156
Full Changelog: v1.12.0...v1.12.1
v1.12.0
Fixed
-
A project installed with
--production --owner=still could not be served
by PHP-FPM. The pass only ever touchedvar/anduserdata/, so every
directory a request actually reads —app/public_html,src/,vendor/,
plugins/— kept the deploying user's ownership and whatever mode the clone
arrived with. The pool could write logs it was never going to reach the code
to produce. Three separate reasons a boot failed, each fixed:- the pass now covers the WHOLE project tree, and runs LAST — after
composer installand the plugin fetch, both of which createvendor/and
plugins/as whoever ran the command. Running at step 3, as it did, meant
the two largest directories in the project were created after the
permissions were "fixed". .envwas chmod'd0600. PHP-FPM running as another account cannot read
APP_KEYthrough that, and the boot fails on a file whose mode bits look
deliberate. It is now0640— group-readable, never group-writable, never
world-anything.- nothing reported that the pool could not TRAVERSE to the project. Reaching
app/public_html/index.phpneeds execute on every parent directory, and a
home directory is0700on a stock Debian install — unfixable from inside
the project, so the offending parents are now named (reported only, never
changed).
- the pass now covers the WHOLE project tree, and runs LAST — after
-
The installed kernel was left at whatever the installing account's umask
produced (tools/install.sh)./opt/hkm-kernelis shared infrastructure —
every PHP-FPM pool on the box loads its PHP out of that one tree, and none of
those pools runs as the account that installed it. Withumask 027or077
the whole tree landed 0750/0700 and every site died with "Permission denied"
on a kernel file, while the install reported success because the installer
could obviously read what it had just written. The installer now normalises
the tree it lays down: directories traversable, files readable, and anything
that WAS executable still executable.
Changed
hkm install --production/--owner=now apply a split-ownership model:
code owned by the deploy user and only READABLE through the web server's
group (2750/0640),var/anduserdata/group-writable (2770/0660).
EVERY directory carries setgid, code included: the group is the only thing
granting the pool access, so a file created later — a log written at 3am, a
file agit pulllands — would otherwise take the creating account's primary
group and drop out of the share, and each deploy would silently un-share
whatever it touched.
Code is never group-writable in either profile — an FPM pool that can rewrite
the PHP it executes turns any file-write bug into code execution. An
already-executable file keeps its exec bit (re-granted only where the profile
grants read, sobin/pspandvendor/bin/*survive at0750, not0751),
and.gitis skipped by both the chown and the chmod.- The pass re-stats what it changed and reports any mode the filesystem
refused, instead of reporting success for a chmod the kernel rejected.
What's Changed
🔄 Other Changes
- chore(homebrew): point the formula at v1.10.0 by @hakeemRash in #152
- chore(homebrew): point the formula at v1.10.1 by @hakeemRash in #153
- chore(homebrew): point the formula at v1.11.0 by @hakeemRash in #151
- release: v1.12.0 — a deployed project is readable by the account that serves it by @hakeemRash in #154
Full Changelog: v1.11.0...v1.12.0
v1.11.0
Fixed
- The migration engine only worked on MySQL (
modules/let-migrate). A
commit titled "refactor: remove deprecated methods" had restoredsrc/,
tests/and the README byte-for-byte to their state before a day of merged
PR work — undoing four driver fixes and a Laravel-parity alias, and deleting
the eight tests that covered them. Nothing was failing that the deletion
fixed. Restored and carried forward:ALTER TABLEcompiled MySQL syntax for every driver — additions batched
into one comma-separated statement, indexes added withADD KEY, and drops
running columns BEFORE the indexes over them. A rollback written in the
correct order was reordered by the compiler into one that could not run
anywhere but MySQL, in the one direction nobody exercises until they
uninstall a plugin.- PostgreSQL rejected
BOOLEAN DEFAULT 1, andmodifyColumn()emitted three
;-joined statements into a clause the extended query protocol refuses. - SQLite could not add a foreign key to an existing table, and
modifyColumn()
compiledCREATE TABLE "__tmp_users" ()— it was broken outright, because
the rebuild SQLite requires was driven from a blueprint holding only the
delta. It now reconstructs the full table from theSchemaInspector,
carrying existing indexes across and unwrapping defaults so a literal is
not re-quoted on every rebuild. - Seeding a second database in one run died with Cannot redeclare class —
reachable the moment one run seeds once per driver, which is what
hkm ground migratedoes.
- PostgreSQL: every schema lookup silently matched nothing. The driver and
inspector used libpq's$1placeholders, which PDO neither understands nor
rejects — sotableExists()answeredfalsefor a table with seven columns,
and the inspector reported no columns, indexes or foreign keys. Anything
guarded byhasTable(), and everything built on schema diffing or dumping,
was quietly wrong on that driver. Found only by executing against a live
server. - SQL Server emitted invalid T-SQL for every column addition and every
foreign key —ALTER TABLE … ADD COLUMN(T-SQL has noCOLUMNkeyword
there) andON DELETE RESTRICT(unimplemented; its actions areNO ACTION,
CASCADE,SET NULL,SET DEFAULT). Both are argued from the T-SQL
specification and are not verified against a live server — none was
reachable — but each replaces SQL the server rejects outright. MigrationConfig: singularpathoverrode pluralpathsinstead of
acting as its fallback, so a config carrying both silently ran one directory
and ignored the array — failing by doing less work rather than by erroring.Blueprint::dropColumn()accepted one column, sodropColumn('a', 'b')
silently dropped onlya. Now variadic, and thedrop*methods chain.
Added
useCurrent()/useCurrentOnUpdate()/bigIncrements()— Laravel
parity, so a ported migration compiles unchanged. Without them the failure is
a fatal Call to undefined method raised the moment the migration runs,
during a deploy.StatusRenderer—migrate:statusas normalised data, aligned table
lines and JSON from one source, so the human and--jsonviews cannot
disagree.tests/Live— the migration compiler executed against every reachable
engine. Configured withLETMIGRATE_DB_MYSQL/_PGSQL/_SQLSRV
(GROUND_DB_*honoured); each run uses its own scratch database and drops it.
A driver that is unconfigured or not answering SKIPS with the reason, never
counted as a pass. This release was verified on SQLite, MariaDB 12.3 and
PostgreSQL 18; SQL Server skipped, and says so.
Changed
docs/guides/18_MIGRATIONS.md—->useCurrent()and->useCurrentOnUpdate()
now exist, so the anti-pattern entry saying they do not is corrected. The
->index()half stands: an index is declared on the Blueprint, not the
column.
What's Changed
🔄 Other Changes
- Dev mac by @hakeemRash in #146
- Master by @hakeemRash in #147
- ci: bump actions/checkout from 5 to 7 in the actions group by @dependabot[bot] in #148
- Release v1.11.0 — the migration engine works on every driver again by @hakeemRash in #149
New Contributors
- @dependabot[bot] made their first contribution in #148
Full Changelog: v1.10.1...v1.11.0
v1.10.1
Fixed
-
hkm ground initgenerated a CI workflow that called a binary nothing
installs. The generated.github/workflows/ground.ymlran
vendor/bin/hkm-ground check --hereandvendor/bin/hkm-ground migrate --strict, but this package declares"bin": ["bin/hkm-cli", "modules/ground/bin/ground"]— so what a plugin actually gets in its
vendor/binisground. No package has ever shipped ahkm-ground
executable, andalfacode-team/groundis not published separately at all: it
is a path repo inside this repository, reachable only because the kernel
autoloads it.So every plugin that ran
ground initgot a workflow whose first step could
only ever exit 127,No such file or directory— and it failed at the step
AFTERcomposer installsucceeded, which reads like the plugin is broken
rather than like the workflow named the wrong file. Five plugin repositories
had already been scaffolded with it.The generator now emits
vendor/bin/ground. Existing checkouts need the two
lines changed by hand orground initre-run; nothing else in the workflow
moves.
What's Changed
🔄 Other Changes
- release: v1.10.1 — ground init generates a workflow that calls a real binary by @hakeemRash in #144
Full Changelog: v1.10.0...v1.10.1