Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
461 commits
Select commit Hold shift + click to select a range
f7f68c8
fix(examples): tweet runs on any machine and documents how (#4247)
bpamiri Oct 4, 2026
3c6a775
ci(cli): CLI suite watchdog trips on any progress gap and stops curl …
bpamiri Oct 4, 2026
dd64a8c
fix(cli): the starter app runs its tests on the test datasource (#4256)
bpamiri Oct 4, 2026
8a12ac4
fix(cli): browser setup launches the browser before reporting ready (…
bpamiri Oct 4, 2026
c889155
fix(test): keep the framework test runners off an app primary datasou…
bpamiri Oct 4, 2026
97eacad
fix(cli): upgrade check names the file a missing-code finding checked…
bpamiri Oct 4, 2026
a472b2b
docs(testing): how to clear the per-request query cache in specs (F6)…
bpamiri Oct 4, 2026
4af17bf
fix(model): stop where strings erroring on LIKE ... ESCAPE and on low…
bpamiri Oct 4, 2026
fc486ea
fix(cli): browser setup uses the platform classpath separator (#4261)
bpamiri Oct 4, 2026
df7e333
fix(cli): bound docs fetch downloads and local test fixtures so a sta…
bpamiri Oct 4, 2026
d6e8534
fix(migrator): treat an empty default on non-string columns as no def…
bpamiri Oct 4, 2026
9ab2f0a
fix(cli): apply prints its report once; new --help lists its options …
bpamiri Oct 4, 2026
8c7801e
feat(cli): wheels framework install — install the framework into an a…
bpamiri Oct 4, 2026
6632bb1
fix(cli): upgrade check finds 3.x WireBox bootstraps and box.json plu…
bpamiri Oct 4, 2026
fc1c0a2
fix(cli): review follow-ups for the CLI fixture timeouts (#4269)
bpamiri Oct 4, 2026
058deed
fix(test): a test run that did not finish or a browser spec that did …
bpamiri Oct 4, 2026
0be2256
docs(consumer-ai): mailers topic; jobs.md names its mailer and the wo…
bpamiri Oct 4, 2026
d5f399d
fix(cli): wheels new picks a free shutdown port (#4270)
bpamiri Oct 4, 2026
8a582c9
docs(consumer-ai): auth.md describes the hashing wheels generate auth…
bpamiri Oct 4, 2026
c38b2f2
fix(cli): framework install review folds — no double-print, app-root …
bpamiri Oct 4, 2026
c51945e
feat(cli): the starter app runs on wheels start with SQLite (#4268)
bpamiri Oct 4, 2026
89c54ef
fix(cli): wheels new avoids ports other projects pin (#4276)
bpamiri Oct 4, 2026
f1623fd
docs: jobs, spec environment, transactional enqueue and generator com…
bpamiri Oct 4, 2026
eaff960
feat(cli): wheels jobs work --stop-when-empty for one-shot batches (#…
bpamiri Oct 4, 2026
815c19d
fix(job): throw Wheels.Job.EnqueueFailed when a job cannot be written…
bpamiri Oct 4, 2026
ae806c9
fix(model): qualify a column compared with a function call when the q…
bpamiri Oct 4, 2026
762d1db
fix(controller): hint app.controllers.Controller for a nested control…
bpamiri Oct 4, 2026
fe9c54c
docs(web/guides): testing examples use testClient, and rollback can't…
bpamiri Oct 4, 2026
264bc6f
fix(model): compare date values in the query builder and dynamic find…
bpamiri Oct 4, 2026
3eeedeb
fix(cli): new apps declare the root route before the wildcard (#4284)
bpamiri Oct 4, 2026
6512849
fix(cli): generated API session controllers answer in JSON without an…
bpamiri Oct 4, 2026
bea3fd3
docs: JSON API topic, pagination() for JSON, and UTC model timestamps…
bpamiri Oct 4, 2026
dbdca62
docs: testing guides cover CSRF tokens, 303 redirects and escaped mes…
bpamiri Oct 4, 2026
e70917d
fix(cli): api-resource routes stay out of other generators' marked bl…
bpamiri Oct 4, 2026
219140d
fix(middleware): pass middleware results to the controller (#4289)
bpamiri Oct 4, 2026
9a3c271
test(cli): widen the pinned-guide scan and pin the apply summary coun…
bpamiri Oct 4, 2026
bb2f3ce
feat(cli): token auth scaffold indexes its token digest and documents…
bpamiri Oct 4, 2026
035cbad
docs: 4.1 to 4.2 upgrade guide covers the remaining behaviour changes…
bpamiri Oct 4, 2026
5123f02
docs(web/guides): test-client POSTs need the CSRF token (v4.0 and v4.…
bpamiri Oct 4, 2026
2a7458d
fix(model): stop where strings erroring when a function call is compa…
bpamiri Oct 4, 2026
db77fdd
fix(cli): wheels start names who holds its port; wheels new picks a f…
bpamiri Oct 4, 2026
f547f98
feat(cli): generate migration points column-change names at generate …
bpamiri Oct 4, 2026
01d5ce5
feat(cli): upgrade check flags persisted reads, legacy test runners a…
bpamiri Oct 4, 2026
4c58aa0
fix(controller): fail a handler-less verifies() with 400, not a silen…
bpamiri Oct 4, 2026
386a5ea
docs(web/guides): a failed verifies() without a handler returns 400 (…
bpamiri Oct 4, 2026
9370449
fix(test): default coreTestDataSourceName after the settings load (#4…
bpamiri Oct 4, 2026
2548cc3
test(job): the core job and .env specs carry their own fixtures (#4300)
bpamiri Oct 4, 2026
9394d45
fix(events): classify production error-page HTTP status instead of al…
bpamiri Oct 4, 2026
6f35e5b
test: skip repository-only specs when the framework suite runs in an …
bpamiri Oct 4, 2026
6c0dcc2
test(view): the form helper specs pin the framework's form settings (…
bpamiri Oct 4, 2026
917a509
docs: production error pages return 404/403/406 for Wheels errors (4.…
bpamiri Oct 4, 2026
42d40d4
test: fail repository runs that cannot see the repository (#4305)
bpamiri Oct 4, 2026
db4ae39
fix(middleware): a middleware registered by path no longer needs an i…
bpamiri Oct 4, 2026
f2d487a
fix(test): create MockBox's stub directory before mocking in an app (…
bpamiri Oct 4, 2026
e5060c9
ci: retry the release builds' and compat matrix's external downloads …
bpamiri Oct 4, 2026
1447275
fix(cli): generate presence validation only for required columns (#4285)
bpamiri Oct 5, 2026
2bfdafe
docs(web/guides): generated model columns are required by default (4.…
bpamiri Oct 5, 2026
e66c0cd
docs: a tenant database must be the same type as the primary (#4309)
bpamiri Oct 5, 2026
d51e427
fix(job): defer an enqueue inside a tenant-datasource transaction to …
bpamiri Oct 5, 2026
58798b5
fix(cli): upgrade check points persisted readers at status, not remov…
bpamiri Oct 5, 2026
82ba204
docs(web/guides): deferred enqueue on another datasource (4.1 to 4.2 …
bpamiri Oct 5, 2026
3a66bb6
ci: generate the AJP check's server.json and httpd.conf at run time (…
bpamiri Oct 5, 2026
e67334f
ci: refuse to republish an already-released version (#4314)
bpamiri Oct 5, 2026
8166a56
fix(cli): wheels browser install runs setup (#4315)
bpamiri Oct 5, 2026
95e1d93
fix(cli): print only user help text in wheels <command> --help (#4205)
bpamiri Oct 5, 2026
4c05693
ci: retry the test images' Lucee extension and driver downloads (#4316)
bpamiri Oct 5, 2026
fecea1f
perf(router): index dynamic routes by method and first two path segme…
bpamiri Oct 5, 2026
2b223c2
perf(controller): apply a per-class cached method integration instead…
bpamiri Oct 5, 2026
5d3f074
fix(model): bind Oracle integers beyond 64 bits as text (#4173)
bpamiri Oct 5, 2026
187a581
fix(model): bind decimals beyond 15 significant digits exactly (#4190)
bpamiri Oct 5, 2026
bd9dd92
feat: run migrations once per wheels deploy on one host (migrate: blo…
bpamiri Oct 5, 2026
cac1788
perf(core): key the promoted-globals memo on wheels.Global instead of…
bpamiri Oct 5, 2026
e5ad2c7
fix(cli): describe packages install as an alias of add (#4211)
bpamiri Oct 5, 2026
4cdf712
fix: stop singularize() turning psychoanalyses into psychoanalyasis (…
bpamiri Oct 5, 2026
3d83e59
feat(controller): isSafeRedirectUrl() checks that a return-to URL sta…
bpamiri Oct 5, 2026
011a3c0
fix(migrator): fail clearly on a column type the adapter does not map…
bpamiri Oct 5, 2026
d3c2b32
feat(migrator): run migrations under a cross-process lock (#4208)
bpamiri Oct 5, 2026
cf7e468
docs(web/guides): upgrade-guide lines for the controller integration …
bpamiri Oct 5, 2026
5f8be6b
fix(cli): link the 4.2 guides from GuidesLink and stop its latest ver…
bpamiri Oct 5, 2026
7080e70
perf(view): reduce linkTo/URLFor overhead on JVM engines (#4192)
bpamiri Oct 5, 2026
abea0a2
docs(web/guides): isSafeRedirectUrl reserved-name line; retype the mi…
bpamiri Oct 5, 2026
ae66636
docs(jobs): maxRetries counts the retries after the first run (#4280)
bpamiri Oct 5, 2026
9091bc6
fix: singularize databases to database instead of databasis (#4191)
bpamiri Oct 5, 2026
6dbb62a
feat(model): opt-in transaction-scoped advisory locks for withAdvisor…
bpamiri Oct 5, 2026
10936d1
test(wheelstest): shared route snapshot/restore/clear helpers (PR1: v…
bpamiri Oct 5, 2026
04b96b7
docs(web/guides): advisory-lock transaction note and isSafeRedirectUr…
bpamiri Oct 5, 2026
bd2b487
fix(job): match the EnqueueFailed hint to the deferred enqueue (#4312)
bpamiri Oct 5, 2026
7a89e85
feat(model): run IN lists past SQL Server's parameter limit as one ST…
bpamiri Oct 5, 2026
914231f
perf: memoise pluralize() and singularize() per application (#4184)
bpamiri Oct 5, 2026
242d6c3
feat(cli): wheels migrate unlock shows and clears the migration lock …
bpamiri Oct 5, 2026
70644ec
test(mapper): migrate mapper-area specs to shared route helpers (PR2)…
bpamiri Oct 5, 2026
9c540f9
fix(model): take SQL Server advisory locks for the session, outside a…
bpamiri Oct 5, 2026
752eebe
docs(changelog): drop trailing periods added to nine fragments (#4328)
bpamiri Oct 5, 2026
61e1b05
fix(test): auto-detect TestClient/BrowserTest base URL from the serve…
bpamiri Oct 5, 2026
d6831b0
test(dispatch,global,security): migrate leak-class specs to shared ro…
bpamiri Oct 5, 2026
727c33b
feat(model): run long date and timestamp IN lists on SQL Server throu…
bpamiri Oct 5, 2026
c325b7a
fix(model): take MySQL and PostgreSQL advisory locks with the shared …
bpamiri Oct 5, 2026
b4b5ed6
fix(model): retry a failed SQL Server probe after a back-off (#4332)
bpamiri Oct 5, 2026
b5ccb4e
fix(model): release withAdvisoryLock(transaction=true) lock when the …
bpamiri Oct 5, 2026
e88227a
fix(model): compare SQL Server date and time conditions in the column…
bpamiri Oct 5, 2026
7e82f21
docs(changelog): fold the abort-release fix into the transaction-lock…
bpamiri Oct 5, 2026
c9722ed
fix(model): roll back a Wheels transaction when the request ends with…
bpamiri Oct 5, 2026
5e04f3f
docs(web/guides): abort inside a Wheels transaction rolls back on Box…
bpamiri Oct 5, 2026
179125c
fix(core): run abort-surviving cleanup in a catch-free finally; guard…
bpamiri Oct 5, 2026
ca4db7b
refactor(model): drop the redundant abort rollback from the transacti…
bpamiri Oct 5, 2026
35fa491
docs(web): bring the wheels.dev home page up to date with 4.x (#4339)
bpamiri Oct 5, 2026
f75dc78
feat(cli): wheels lookup searches the API reference and guides offlin…
bpamiri Oct 5, 2026
e127df7
feat(web): self-host the Inter and JetBrains Mono brand fonts (#4341)
bpamiri Oct 5, 2026
b5b6eb0
fix(cli): migrate unlock's refusal names the holder and --force in it…
bpamiri Oct 5, 2026
60f3af3
fix(model): read the sort direction in an order string whatever its c…
bpamiri Oct 5, 2026
58667cc
fix(web): keep the packages grid inside the page; add italic Inter an…
bpamiri Oct 5, 2026
987c3ae
feat(cli): wheels new writes .mcp.json and .opencode.json by default …
bpamiri Oct 5, 2026
a31fb4c
docs: refresh context7.json versions and folders (#4351)
bpamiri Oct 5, 2026
85ae232
docs(cli): show the full .opencode.json entry in the MCP integration …
bpamiri Oct 5, 2026
ca1ea83
feat(model): report what the last save wrote with savedChanges() (#4349)
bpamiri Oct 5, 2026
033f834
feat(web): llms.txt, llms-full.txt, llms-small.txt and per-page Markd…
bpamiri Oct 5, 2026
1bce259
fix(test): warn when a test run switches the live application's datas…
bpamiri Oct 5, 2026
b2c0cc8
fix(web): return a real 404 for unknown paths on wheels.dev, blog and…
bpamiri Oct 5, 2026
a01ae17
fix(test): match the test-runner path prefix only in the test-context…
bpamiri Oct 5, 2026
f3f7967
feat(test): $testClient().fromAddress() sets the request client addre…
bpamiri Oct 5, 2026
435c778
build(web): make the astro specifiers match the Astro 6 the sites bui…
bpamiri Oct 5, 2026
1993b8b
build: refresh tooling lockfiles (#4363)
bpamiri Oct 5, 2026
7dafd74
docs(examples): bring the example READMEs to Wheels 4.x and drop twee…
bpamiri Oct 5, 2026
bf6d57e
docs(web): name the right version on the 4.1 and 4.2 guides landing p…
bpamiri Oct 5, 2026
5c2746d
test(cli): NewPinnedPortsSpec judges a moved shutdown port by the CLI…
bpamiri Oct 5, 2026
59c4049
ci(rustcfml): skip known-bad engine releases in the version check; lo…
bpamiri Oct 5, 2026
b745451
feat(cli): wheels jobs enqueue adds a job from the command line, now …
bpamiri Oct 5, 2026
8d6d799
test: put back the model class state four specs change (#4365)
bpamiri Oct 5, 2026
726b400
feat(test): assert how many SQL statements a spec's call sends (#4357)
bpamiri Oct 5, 2026
5845af8
docs: cover soft delete, dependents, bulk writes, channels and upgrad…
bpamiri Oct 5, 2026
2ddcd28
perf(plugins): skip $initializeMixins work when the class has no mixi…
bpamiri Oct 5, 2026
33e258f
fix(test): isolate the testbox/tests_testbox runner URLs and refuse u…
bpamiri Oct 5, 2026
8afa24a
fix(controller): cookie-stored CSRF tokens work on Adobe CF (#4380)
bpamiri Oct 5, 2026
af79953
feat(model): add escapeForLike() to escape LIKE wildcards in a search…
bpamiri Oct 5, 2026
006710f
feat(events): development errors as JSON or Markdown for agents and s…
bpamiri Oct 5, 2026
0efe228
fix(model): skip commit callbacks when callbacks = false (#4378)
bpamiri Oct 5, 2026
eb99bc9
fix(model): run insertAll() and upsertAll() batches in one transactio…
bpamiri Oct 5, 2026
1252e48
feat(model): add public forgetCachedQueries() to clear the per-reques…
bpamiri Oct 5, 2026
fe819db
docs(web/blog): 2.x to 4.x: why it's two passes, and the map
bpamiri Oct 5, 2026
24fab28
build(web): upgrade Astro to 7 and Starlight to 0.42 (#4362)
bpamiri Oct 5, 2026
a5c5a4e
fix(cli): deploy binds space-form values, rejects an unknown role, pr…
bpamiri Oct 5, 2026
f9200c3
ci: pin GitHub-hosted Linux jobs to ubuntu-24.04 (#4402)
bpamiri Oct 5, 2026
3067d1f
fix(cli): start changes nothing when it has nothing to start, and hon…
bpamiri Oct 5, 2026
1de7926
fix(cli): deploy build push honours the builder block; logs and prune…
bpamiri Oct 5, 2026
42d1d29
test(channel): stop creating MockBox's stub directory in a channel sp…
bpamiri Oct 5, 2026
f966923
fix(cli): surface runner errors, exit non-zero on refusals, and fix c…
bpamiri Oct 5, 2026
0735e92
chore(docs): auto-refresh blog visual baseline after content change (…
wheels-bot[bot] Oct 5, 2026
3d34720
fix(docs): run verify:docs and its harness tests in a throwaway CLI h…
bpamiri Oct 5, 2026
791d77c
docs(cli): align command-line guides with actual CLI behaviour (#4404)
bpamiri Oct 5, 2026
ec87099
fix(cli): upgrade check dates the renderPage rename to 2.0 and flags …
bpamiri Oct 5, 2026
848a7d6
fix(migrator): stop migrate diff misreporting SQLite date columns and…
bpamiri Oct 5, 2026
0412b95
fix(cli): show applied-at in db status and correct command help text …
bpamiri Oct 5, 2026
e168f29
test(cli): point the test-runner endpoint spec at a small spec direct…
bpamiri Oct 5, 2026
18dde9f
docs: note bulk-write transactions and callbacks=false in the app gui…
bpamiri Oct 5, 2026
3988455
fix(migrator): read the pre-3.0 null column option as an alias of all…
bpamiri Oct 5, 2026
49c638e
fix(model): clearChangeInformation(property) refreshes only that prop…
bpamiri Oct 5, 2026
af18152
test(model): fix escapeForLikeSpec adapter-restore when the key was a…
bpamiri Oct 5, 2026
f1be9dc
fix(test): restore processRequest() state on every exit, not only on …
bpamiri Oct 5, 2026
7aeb4e3
fix(docs): remove the verify:docs CLI home on Ctrl-C, keep downloads …
bpamiri Oct 5, 2026
fc2585e
docs(web/blog): announce the first 2.x to 4.x series post (#4442)
bpamiri Oct 5, 2026
5cdd658
feat: add appCacheFetch() and friends for caching your own data (#4388)
bpamiri Oct 5, 2026
886b616
docs(blog): record discussion announcements (#4446)
wheels-bot[bot] Oct 5, 2026
6473785
fix(model): quote order="table.column" the way order="column" is quot…
bpamiri Oct 5, 2026
d7ad970
feat(test): add a clock seam and travelTo() for deterministic time te…
bpamiri Oct 5, 2026
90bfaa9
fix(docs): start the fixture server before tutorial CLI steps that ne…
bpamiri Oct 5, 2026
50a9cc5
fix(docs): bound every harness command so a stuck wheels can't hang t…
bpamiri Oct 6, 2026
47308cc
fix(sse): resume from the lastEventId url param and trim the events f…
bpamiri Oct 6, 2026
43d052b
fix(cli): cover subfolder models in migrate diff and write the seed s…
bpamiri Oct 6, 2026
0acd228
fix(di): key the Injector's circular-dependency guard per thread (#4449)
bpamiri Oct 6, 2026
a722945
fix(model): clear the whole per-request query cache on any ORM write …
bpamiri Oct 6, 2026
1056eea
fix(docs): run the verify-docs counts test serially and show its outp…
bpamiri Oct 6, 2026
9fc4177
docs(cli): describe deploy build, logs and prune as they now work (#4…
bpamiri Oct 6, 2026
ee93247
fix(model): keep soft deletes reversible and make softDelete=false a …
bpamiri Oct 6, 2026
648d3c8
fix(docs): stagger wheels JVM starts so concurrent blocks don't race …
bpamiri Oct 6, 2026
f64bc0c
feat(model): report unknown arguments to framework declarations (#4393)
bpamiri Oct 6, 2026
05eeac7
feat(job): enqueue(transactional = false) survives a rollback (#4400)
bpamiri Oct 6, 2026
d74df20
fix(controller): read the flash cookie's value when this request wrot…
bpamiri Oct 6, 2026
32b1bdd
fix(model): dependent deletes follow callbacks = false; check depende…
bpamiri Oct 6, 2026
b4b611b
docs(test): describe recorded queries and appCache keys accurately in…
bpamiri Oct 6, 2026
29d87d0
docs: softDelete=false alone purges already soft-deleted rows (#4454)
bpamiri Oct 6, 2026
735bf0f
docs: order="table.column" is quoted like order="column" (#4456)
bpamiri Oct 6, 2026
ae1bbe5
docs: note callbacks=false and declare-time checks for dependent= (#4…
bpamiri Oct 6, 2026
f5c9025
feat(test): TestClient sends the CSRF token to protected actions, lik…
bpamiri Oct 6, 2026
141644a
feat(model): foreignType on polymorphic associations (#4398)
bpamiri Oct 6, 2026
c610da9
fix(model): polymorphic associations find underscore-shaped columns (…
bpamiri Oct 6, 2026
78851a3
docs(web): use a variable receiver for wheels.Job in the 4.2 job exam…
bpamiri Oct 6, 2026
d825b50
docs: use named arguments in the agent-docs examples and fix the 4.1 …
bpamiri Oct 6, 2026
d9dd420
test: guard against method calls on a parenthesized new (#4462)
bpamiri Oct 6, 2026
cc40e8e
fix(controller): carry the CSRF cookie in base64url so no engine can …
bpamiri Oct 6, 2026
240c81c
docs: polymorphic associations now resolve underscore-shaped columns …
bpamiri Oct 6, 2026
9d869ea
docs(web/blog): Pass 1: 2.x to 3.0, the layout move
bpamiri Oct 6, 2026
67fb202
chore(docs): auto-refresh blog visual baseline after content change (…
wheels-bot[bot] Oct 6, 2026
3686d31
docs(web/blog): announce the second 2.x to 4.x series post (#4466)
bpamiri Oct 6, 2026
b83975f
docs(blog): record discussion announcements (#4468)
wheels-bot[bot] Oct 6, 2026
f67eeda
docs(web/blog): Pass 2: 3.x to 4.1 with `wheels upgrade check`
bpamiri Oct 7, 2026
cfb68d8
chore(docs): auto-refresh blog visual baseline after content change (…
wheels-bot[bot] Oct 7, 2026
8b87be6
docs(web/blog): announce the third 2.x to 4.x series post (#4472)
bpamiri Oct 7, 2026
b723c9c
docs(blog): record discussion announcements (#4474)
wheels-bot[bot] Oct 7, 2026
420ffb4
fix(job): fence job completion, retry and failure with a per-claim to…
bpamiri Oct 7, 2026
ba2d9a1
feat(job): de-duplicate enqueue with uniqueKey (#4488)
bpamiri Oct 8, 2026
bff44fe
fix: build app paths from public/Application.cfc's own directory (#4500)
bpamiri Oct 8, 2026
c575f04
feat(job): heartbeat() and idempotency-aware reaping (#4490)
bpamiri Oct 8, 2026
7a4f74a
feat(job): per-host concurrency cap, drain/resume and JobRunner.tick(…
bpamiri Oct 8, 2026
631f1a8
feat(cli): wheels jobs drain [--wait] and resume for deploys (#4492)
bpamiri Oct 8, 2026
47ce1a8
fix(job): run processQueue() through the job worker (#4495)
bpamiri Oct 8, 2026
d1b8df2
feat(job): recurring schedules (cron and intervals) with one job per …
bpamiri Oct 8, 2026
3a76104
fix(job): explain uniqueKey index failures on old SQL Server levels (…
bpamiri Oct 8, 2026
db327a1
ci: let the install smoke accept a LuCLI tested with the release line…
bpamiri Oct 8, 2026
79df940
test(cli): keep the shared sshd fixture independent of the checkout t…
bpamiri Oct 8, 2026
e63973b
feat(job): exclusive jobs and concurrency keys on a shared lease lock…
bpamiri Oct 8, 2026
f3ed23b
ci: update brew after tapping homebrew/core in the Linuxbrew install …
bpamiri Oct 8, 2026
87bbd01
feat(job): lifecycle hooks, an app-wide failure hook, and a stored re…
bpamiri Oct 8, 2026
368535c
docs(web/blog): The file the upgrade doesn't touch: `public/Applicati…
bpamiri Oct 8, 2026
5b293a4
docs(web/blog): announce the fourth 2.x to 4.x series post (#4511)
bpamiri Oct 8, 2026
89ec145
chore(docs): auto-refresh blog visual baseline after content change (…
wheels-bot[bot] Oct 8, 2026
d009c42
docs(blog): record discussion announcements (#4514)
wheels-bot[bot] Oct 8, 2026
1c0ff9a
chore(docs): auto-refresh blog visual baseline after content change […
wheels-bot[bot] Oct 8, 2026
a6c2d00
fix(job): reclaim a job when its heartbeats stop, not only after 2x i…
bpamiri Oct 8, 2026
7f4dc07
fix(cli): let wheels jobs work run each job with its own timeout (#4518)
bpamiri Oct 8, 2026
c284bc0
feat(cli): wheels jobs install and jobsAutoCreateTables (#4499)
bpamiri Oct 8, 2026
405e6b0
feat(job): run job schedules from the worker poll and JobRunner.tick(…
bpamiri Oct 8, 2026
be711e6
ci: pin the LuCLI runtime to wheels-dev/LuCLI 0.6.2.3 (#4519)
bpamiri Oct 9, 2026
85f0d8c
feat(job): token-gated /wheels/jobs/tick route and wheels jobs tick (…
bpamiri Oct 9, 2026
aa1ea2e
fix(job): store job timestamps in UTC on the database's clock (#4521)
bpamiri Oct 9, 2026
3a810db
docs(web): add the 4.2 "Running Jobs on Multiple Servers" guide (#4501)
bpamiri Oct 9, 2026
3b03968
ci: run the core suite on SQLite with the engine JVM in America/New_Y…
bpamiri Oct 9, 2026
056404d
feat(job): configure the jobs tick route's batch size, timeout and qu…
bpamiri Oct 9, 2026
c3f96d7
fix(job): run a cron step on a single value to the end of the field (…
bpamiri Oct 9, 2026
915a96c
fix(job): report a missing job schema from inside a PostgreSQL transa…
bpamiri Oct 9, 2026
5a0627d
fix(migrator): run a CockroachDB migration step's transaction at seri…
bpamiri Oct 9, 2026
a8443c3
docs(router): note that constraint regexes use single backslashes in …
bpamiri Oct 9, 2026
df14b5e
ci: evaluate RustCFML prereleases in the daily version check (#4534)
bpamiri Oct 9, 2026
e3e6466
fix(job): convert epoch timestamps to local time correctly on RustCFM…
bpamiri Oct 9, 2026
8267db1
ci: file a rejected RustCFML release even when the stable fallback fa…
bpamiri Oct 9, 2026
96b31d5
fix(model): name a where= value that has no model column to bind agai…
bpamiri Oct 9, 2026
b2ebab1
fix(controller): include the global helpers when each controller obje…
bpamiri Oct 9, 2026
cba28f4
docs(web/blog): Validation conditions now fail closed: migrating your…
bpamiri Oct 9, 2026
cc7e93f
chore(docs): auto-refresh blog visual baseline after content change (…
wheels-bot[bot] Oct 9, 2026
f2f8eba
docs(web/blog): announce the fifth 2.x to 4.x series post (#4538)
bpamiri Oct 9, 2026
b55e2a8
ci: mirror the Docker Hub images CI pulls to GHCR (#4540)
bpamiri Oct 9, 2026
ad1e7b4
docs(blog): record discussion announcements (#4542)
wheels-bot[bot] Oct 9, 2026
0fc0f7f
ci: dry-check the mirror list on PRs and write tags only after merge …
bpamiri Oct 9, 2026
fda214f
ci: pin peter-evans/create-pull-request to v8.1.1 (supports actions/c…
bpamiri Oct 10, 2026
7de48c5
chore(rustcfml): bump engine pin to v0.730.0 (#4545)
bpamiri Oct 10, 2026
c3665d9
test(cli): stop NewShutdownPortSpec racing on the chosen port (#4548)
bpamiri Oct 10, 2026
c1e1f81
docs(api): add the 4.2.0 API reference JSON and v4-2-0 tree (#4550)
bpamiri Oct 11, 2026
ef1ef0b
docs(web): make the 4.2 guides, API reference and home page links cur…
bpamiri Oct 11, 2026
1c8ea50
chore(release): promote changelog fragments into 4.2.0 (#4549)
bpamiri Oct 11, 2026
2cbc6c4
chore(release): list the 4.2.0 security fixes with their advisories (…
bpamiri Oct 11, 2026
7ce2eb6
fix(controller): require the CSRF cookie payload struct, not any JSON…
bpamiri Oct 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
82 changes: 82 additions & 0 deletions .ai/wheels/cross-engine-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,36 @@ var fn = obj["dynamicMethod"];
var result = fn();
```

### Quoted Named Argument in a Direct Call (Adobe CF 2023)

A named argument whose *name* is a quoted string literal — `obj.method("name" = value)` — is a **compile error on Adobe CF 2023 only** (`MissingNameException: Invalid construct: Either argument or name is missing` / "each parameter must have a name"). Lucee 6/7, BoxLang, **and Adobe CF 2025** all accept it. This is the inverse footprint of the related invariants: the parenthesized-`new` receiver (invariant 16a) fails on both Adobe engines, the zero-arg `application`-scope call (16b/16b-ext) is Adobe 2025 only, and this one is the lone **Adobe 2023-only** shape — so an Adobe 2025 smoke, or Lucee-only local verification, does **not** cover it.

**Context-independent (unlike the closure-sensitive 16b-ext).** Probe-verified on adobe2023/62023, the quoted named-arg call fails identically in every call context tested — a single-expression arrow (`(x) => obj.method("n" = x)`), a block-bodied arrow, a regular `function()` closure, and a plain statement in a non-closure method body all throw the same `MissingNameException`. There is no "safe" context; only changing the call shape (below) helps.

You only reach for a quoted argument name when the name is otherwise illegal as a bare token — a `$`-prefixed or hyphenated argument. Measured boundaries that compile on adobe2023 (do **not** "fix" these):
- an *unquoted* named argument, `obj.method(name = value)`, compiles on every engine;
- a quoted key in a *struct literal*, `{"$x" = 1}`, compiles everywhere — it is a struct key, not a function-argument name;
- passing the same `$`-prefixed value via `argumentCollection` compiles everywhere.

Adobe misattributes the error to the enclosing `describe(...)` / `function` line, and because the core suite compiles via `directory="wheels.tests.specs"`, a single occurrence zeroes the **entire adobe2023 leg** (`tests="0"` on every database) while Lucee/BoxLang/Adobe 2025 stay green.

```cfm
// WRONG — MissingNameException on adobe2023 (compiles on Lucee/BoxLang/adobe2025)
var out = c.URLFor(controller = "posts", action = "index", "$argsResolved" = true);

// RIGHT — the $-prefixed name lives on a struct key, not an argument name
var args = {controller = "posts", action = "index"};
args["$argsResolved"] = true;
var out = c.URLFor(argumentCollection = args);
```

**Reference**: `vendor/wheels/tests/specs/view/linkToUrlForDifferentialSpec.cfc` ([#4192](https://github.com/wheels-dev/wheels/pull/4192)). It was pre-existing and only the adobe2023 matrix legs caught it. Bisect against a running container (~13s vs ~19min), using the Adobe 2023 port `62023`:

```bash
curl -s "http://localhost:62023/wheels/core/tests?db=sqlite&format=json&cli=true" | \
python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('totalPass','COMPILE FAIL'), d.get('RootCause',{}).get('snippet',''))"
```

### Method Reference Extraction Loses Receiver (BoxLang)

BoxLang implements method dispatch with JavaScript-style semantics: pulling a method off an object into a local variable produces a bare function reference with no bound receiver. Calling that local then runs the function in an empty context, and any in-component call inside (helpers prefixed with `$`, `this.x()`, etc.) fails to resolve. The lookup-and-call **must** stay in a single expression for BoxLang to bind the receiver.
Expand Down Expand Up @@ -230,6 +260,58 @@ try {

**Reference example**: `$restoreEmailViewVariables()` in [`vendor/wheels/controller/miscellaneous.cfc`](../../vendor/wheels/controller/miscellaneous.cfc) — the `sendEmail` variables-scope restore runs from `finally` via a `public` `$`-prefixed helper (mixin invariant: helpers must be public). Found while addressing review on [#2922](https://github.com/wheels-dev/wheels/pull/2922).

### `continue` and `break` in One `try`/`catch` Inside a Loop: `continue` Exits the Loop (Lucee 6/7)

Inside a loop, a `try`/`catch` that contains **both** a `continue` and a `break` makes the `continue` leave the loop, as if it were a `break`. The `break` doesn't have to run: an `if (false) break;` in the `catch` is enough. Where the two keywords sit doesn't matter either: `continue` in the `try` and `break` in the `catch`, or both in the `catch`.

```cfm
// WRONG: on Lucee 6/7 this returns "first", not "first,second,third"
local.trace = "";
for (local.item in ["first", "second", "third"]) {
local.trace = ListAppend(local.trace, local.item);
try {
if (true) {
continue;
}
} catch (any e) {
break;
}
}

// RIGHT: no continue; let if/else carry control to the end of the iteration
for (local.item in local.items) {
try {
if (skip(local.item)) {
recordSkip(local.item);
} else {
process(local.item);
}
} catch (any e) {
if (stopOnError) break;
}
}
```

**Measured** with the same probe spec (compat-matrix dispatch, SQLite):

| Engine (build) | `continue` + `break` in one `try`/`catch` inside a loop |
|---|---|
| Lucee 6.2.5.48 | **`continue` exits the loop** |
| Lucee 7.0.1.100 (the CI pin) | **`continue` exits the loop** |
| Lucee 7.0.0.395 (local) | **`continue` exits the loop** |
| Adobe CF 2023.0.11, 2025.0.06 | correct |
| BoxLang 1.11.0+52 | correct |

The RustCFML v0.693.0 lane reported no failures in the same run; its per-spec result wasn't checked individually.

**Measured as correct on every engine above:**
- the same `try`/`catch` with a `continue` but no `break`;
- a loop **inside** a `try` that uses both `continue` and `break` for that loop. This covers a `for`-in loop that continues and then breaks, a `while` with `break` around a nested `for` with `continue`, and one loop with both.

The failing shape reproduces with `for`-in and index loops, with or without an outer `try`/`finally`.

**Where it bit:** `TenantMigrator.migrateAll()`. A `continue` in the inner `try` (after recording a failed tenant), next to `if (arguments.stopOnError) break;` in its `catch`, recorded only the first failed tenant with `stopOnError = false`. The fix uses `if`/`else` ([#4169](https://github.com/wheels-dev/wheels/pull/4169)). A source sweep of `vendor/wheels/` when this was found turned up no other occurrence of the failing shape.

### `DirectoryCreate()` Second Argument Is Lucee-Only

Lucee accepts `DirectoryCreate(path, createPath, mode)` and recurses parent directories when `createPath=true`. Adobe CF's signature varies by version and at least some Adobe builds reject any second argument with `"The function takes 1 parameter"` (issue #2567).
Expand Down
2 changes: 1 addition & 1 deletion .ai/wheels/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,5 @@ Fixtures at `cli/lucli/tests/_fixtures/deploy/configs/` (`minimal.yml`, `full.ym

## Reference docs

- User guides: `web/sites/guides/src/content/docs/v4-0-0/deployment/` (first-deploy, production-config, accessories, secrets, hooks, migrating-from-kamal, security-hardening, docker-deployment)
- User guides: `web/sites/guides/src/content/docs/v4-2-0/deployment/` (first-deploy, production-config, accessories, secrets, hooks, migrating-from-kamal, security-hardening, docker-deployment)
- In-source CLI docs: `cli/lucli/services/deploy/cli/docs/` (per-verb)
6 changes: 3 additions & 3 deletions .ai/wheels/testing/browser-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ component extends="wheels.wheelstest.BrowserTest" {
// from inside the suite body via this helper.
browserDescribe("Login flow", () => {
it("can load a page and read its title", () => {
if (this.browserTestSkipped) return;
this.browser.visitUrl("data:text/html,<title>Hi</title><h1>x</h1>")
.assertTitleContains("Hi");
});
Expand Down Expand Up @@ -63,8 +62,9 @@ bash tools/test-local.sh # skips browser specs if JARs missin
- **`##` in selectors** — CFML requires `##` to emit literal `#`. `"##email"` → `"#email"` at runtime.
- **`client` is a Lucee reserved scope.** `var client = ...` in a closure throws "client scope is not enabled". Use `var c = ...` or `var bc = ...`. (Generalized rule: see CLAUDE.md anti-pattern #11.)
- **Data URLs work for most tests** — no server needed for ~95% of DSL coverage. Full HTTP integration (cookies, form submits, redirects) needs a running fixture app; that wiring is the same as Wheels Web app bootstrap (separate server + baseUrl).
- **`this.browserTestSkipped`** — when Playwright JARs aren't installed (fresh CI, clean machine), `beforeAll` sets this flag and `browserDescribe`'s hooks short-circuit. All `it`s should check `if (this.browserTestSkipped) return;` to stay green on CI.
- **CI runs browser tests** — `pr.yml` and `snapshot.yml` install Playwright JARs + Chromium (cached via `browser-manifest.json` hash). Browser specs run as part of the normal test suite. `WHEELS_BROWSER_TEST_BASE_URL=http://localhost:60007` is set automatically. The base URL is resolved at instance time through a layered lookup (`this.baseUrl` → Wheels setting → JVM property `wheels.browserTest.baseUrl` → env var → CGI auto-detect → `http://localhost:8080`); per-spec `this.baseUrl` takes priority over the env var. Set `this.baseUrl` in the component pseudo-constructor (outside any function), not inside `beforeAll()` — `super.beforeAll()` calls `$resolveBaseUrl()` and caches the result, so a `this.baseUrl =` assignment that runs after `super.beforeAll()` is silently ignored.
- **`this.browserTestSkipped`** — when Playwright JARs aren't installed (fresh CI, clean machine), `beforeAll` sets this flag and `browserDescribe`'s hooks short-circuit: each spec is reported as **Skipped** with `this.browserTestSkipReason` (never as a pass). `it`s in a plain `describe()` call `browserSpecGuard();` first (skip with reason, or Error on a launch failure); a bare `if (this.browserTestSkipped) return;` reads as a pass.
- **`this.browserLaunchError`** — Playwright installed but the browser could not start (missing system libraries, broken driver): `browserDescribe` reports each spec as an **error** with this message. `WHEELS_BROWSER_SKIP_LAUNCH_FAILURES=true` turns that into a skip. The decision is `$browserSpecGate()`.
- **CI runs browser tests** — `pr.yml` and `snapshot.yml` install Playwright JARs + Chromium (cached via `browser-manifest.json` hash). Browser specs run as part of the normal test suite. `WHEELS_BROWSER_TEST_BASE_URL=http://localhost:30007` is set automatically. The base URL is resolved at instance time through a layered lookup (`this.baseUrl` → Wheels setting → JVM property `wheels.browserTest.baseUrl` → env var → CGI auto-detect → `http://localhost:8080`); per-spec `this.baseUrl` takes priority over the env var. Set `this.baseUrl` in the component pseudo-constructor (outside any function), not inside `beforeAll()` — `super.beforeAll()` calls `$resolveBaseUrl()` and caches the result, so a `this.baseUrl =` assignment that runs after `super.beforeAll()` is silently ignored.
- **Isolated application context (#3374)** — `BrowserTest.$startBrowserContext()` sends `X-Wheels-Test-Context` (Playwright `extraHTTPHeaders`) plus a `WHEELS_TEST_CONTEXT` cookie so fixture HTTP binds `<this.name>_wheelsTest`, not the live app. `Application.cfc` must include `vendor/wheels/events/testcontext.cfm` after `config/app.cfm` (ships in `wheels new`). Without that snippet, browser tests still work via the #3373 live-scope swap.
- **Fixture routes** — `/_browser/login-as` and `/_browser/logout` are mounted automatically in test mode. They must come before `.wildcard()` in routes.cfm. In the Routes UI (`/wheels/routes`) all `/_browser/*` routes appear under the **Internal** tab, not Application. The `/_browser/login-as` handler is configurable: `set(browserLoginAsHandler = "AuthFixture##loginAs")` in `config/settings.cfm` substitutes that `Controller##action` at route-registration time (default is `BrowserTestLogin##create`). Env-gating is handled by `wheels.middleware.BrowserTestFixtureGuard` on the whole `/_browser` scope — custom handlers do not need to re-implement the guard. Empty string or absent setting falls back to the default. (#2830)
- **Dialogs are Lucee-only** — `acceptDialog`, `dismissDialog`, `dialogMessage` use `createDynamicProxy` which is Lucee-specific. Specs skip gracefully on other engines.
7 changes: 7 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,10 @@ indent_style = space

[{index.cfm,rewrite.cfm,root.cfm,wheels/index.cfm}]
insert_final_newline = false

# Per-request/lifecycle event templates emit zero output: they are $include'd into
# the response buffer, so a trailing newline after </cfsilent> leaks as whitespace
# before renderText output and breaks XML. Keep them newline-free (demo app/events
# and the CLI generator templates). See #3882.
[**/events/{onabort,onapplicationend,onapplicationstart,onrequestend,onrequeststart,onsessionend,onsessionstart}.cfm]
insert_final_newline = false
4 changes: 0 additions & 4 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,3 @@ LUCEE_EXTENSIONS=465E1E35-2425-4F4E-8B3FAB638BD7280A;name=H2;version=1.3.172
cfconfig_adminPassword=commandbox
WHEELS_ENV=development
BASE_URL=http://127.0.0.1:8080

# Consumed by wheels.tests.specs.global.EnvFileParsingSpec: a .env value of
# numeric 1 must survive parsing as a VALUE, not become the boolean true.
WHEELS_SPEC_NUMERIC_ONE=1.0
1 change: 1 addition & 0 deletions .github/RELEASE_PLAYBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ If any pin to `<X.Y.Z`, open issues on those repos to widen the constraint.
| `release.yml` fails: "CHANGELOG contains TBD" | Forgot to update changelog | Add release date to `# [X.Y.Z]` line, push, re-run workflow |
| `release.yml` fails: "release-notes range … bleeds into a later version section" | Separator below the target version is `----` (4 dashes), not `---` — the awk extraction overruns into the previous version | Change that separator line to exactly `---`, push, re-run |
| `release.yml` fails: "version contains -SNAPSHOT" | `wheels.json` has a `-snapshot` suffix on main | Strip the suffix in `wheels.json`, push (release.yml asserts clean main versions) |
| `release.yml` fails: "Release vX.Y.Z is already published" or "Tag vX.Y.Z already points at …" | `wheels.json` on main still names a released version (e.g. a fix merged to main after the release). A published version's assets never change (#4221) | Bump the version in `wheels.json` (a patch release) and push. If the release is a partial upload from a run that failed mid-upload, delete that release (and its tag, if it points at an older commit) by hand, then re-run |
| brew tap PR doesn't open after release | `DOWNSTREAM_DISPATCH_TOKEN` expired or unset | Rotate token in repo secrets; re-run `release.yml` workflow_dispatch |
| develop-bump PR doesn't open after GA | `DOWNSTREAM_DISPATCH_TOKEN` expired/unset, OR the `bump-develop` dispatch step warned and exited 0 | Fire it manually: Actions → "Bump Develop Version" → "Run workflow" → enter the just-released version (e.g. `4.0.0`). The workflow has a `workflow_dispatch` fallback for exactly this case. |
| `brew install wheels` post-release fails | Formula sha256 mismatch | Re-run the tap bump workflow with `workflow_dispatch` to recompute |
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/setup-lucli/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ runs:
mkdir -p "$install_dir"
# Installed as plain `lucli`: a versioned file name would be taken
# for a module alias.
curl --fail --location --retry 3 \
curl --fail --location --retry 3 --retry-delay 2 --retry-all-errors \
"https://github.com/${LUCLI_REPO}/releases/download/v${LUCLI_VERSION}/lucli-${LUCLI_VERSION}-${suffix}" \
--output "$install_dir/lucli"
if command -v sha256sum >/dev/null 2>&1; then
Expand Down
6 changes: 3 additions & 3 deletions .github/actions/setup-wheels-test-env/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ inputs:
port:
description: 'Port to bind Lucee to'
required: false
default: '60007'
default: '30007'
install-playwright:
description: 'Install Playwright + Chromium (set "false" to skip if no browser tests run)'
required: false
Expand Down Expand Up @@ -65,7 +65,7 @@ runs:
SHA=$(echo "$row" | jq -r '.sha256')

echo "Downloading ${FILE}..."
curl -sL "$URL" -o ~/.wheels/browser/lib/"$FILE"
curl -sL --retry 3 --retry-delay 2 --retry-all-errors "$URL" -o ~/.wheels/browser/lib/"$FILE"

ACTUAL=$(sha256sum ~/.wheels/browser/lib/"$FILE" | cut -d' ' -f1)
if [ "$ACTUAL" != "$SHA" ]; then
Expand All @@ -81,7 +81,7 @@ runs:
- name: Download SQLite JDBC driver
shell: bash
run: |
curl -sL "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.50.3.0/sqlite-jdbc-3.50.3.0.jar" \
curl -sL --retry 3 --retry-delay 2 --retry-all-errors "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.50.3.0/sqlite-jdbc-3.50.3.0.jar" \
-o /tmp/sqlite-jdbc.jar

- name: Start Lucee server
Expand Down
5 changes: 3 additions & 2 deletions .github/scripts/blog-announce-on-publish.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
# `announcement:` block still lacks a `discussionUrl` (backfill).
# 2. Run web/scripts/blog-announce-discussion.mjs over the candidates. The
# dedupe already lives in that script: before creating a discussion it
# searches existing Announcements by exact title and/or the post's blog URL,
# and on a hit writes that URL back instead of creating a duplicate.
# lists the Announcements category and matches the post's slug marker,
# exact title or blog URL, and on a hit writes that URL back instead of
# creating a duplicate.
# 3. If the frontmatter gained a discussionUrl, commit it on
# docs/bot-blog-announce-<shortsha> and open a tiny PR to develop (same
# pattern as the publish workflow — never push to develop directly).
Expand Down
42 changes: 42 additions & 0 deletions .github/workflows/ajp-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: TestClient works behind an AJP front end

# Optional, manual regression check (#4210): runs the internal TestClient specs through Apache
# httpd -> mod_proxy_ajp -> Lucee 7's AJP connector, the shape of IIS+BonCode / mod_jk
# deployments, and asserts they pass. See tools/ci/ajp/run.sh for what the servlet reports there.
on:
workflow_dispatch:

permissions:
contents: read

jobs:
ajp:
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- name: Checkout Repository
uses: actions/checkout@v5

- name: Build the Lucee 7 image
run: |
MAX_ATTEMPTS=3
ATTEMPT=1
until docker compose build lucee7; do
if [ "$ATTEMPT" -ge "$MAX_ATTEMPTS" ]; then
echo "::error::docker compose build failed after ${MAX_ATTEMPTS} attempts"
exit 1
fi
ATTEMPT=$((ATTEMPT + 1))
sleep 15
done

- name: Run the TestClient specs through httpd and AJP
run: AJP_HTTPD_PORT=9590 AJP_RESULT_JSON=ajp-result.json bash tools/ci/ajp/run.sh wheels-ajp

- name: Upload the result
if: always()
uses: actions/upload-artifact@v6
with:
name: ajp-result
path: ajp-result.json
if-no-files-found: ignore
11 changes: 8 additions & 3 deletions .github/workflows/blog-announce-on-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ name: Blog — Announce on Publish
# the Discussion already existed but the publish never landed, so the next
# day's run re-announced and duplicated the thread. Announcing only on
# push-to-develop means a post is durable before any announcement is made,
# and the dedupe in web/scripts/blog-announce-discussion.mjs (exact title /
# blog-URL search before create) is a second line of defence.
# and the dedupe in web/scripts/blog-announce-discussion.mjs (before create it
# lists the category and matches the post's slug marker, title or blog URL) is
# a second line of defence.

on:
push:
Expand All @@ -32,7 +33,7 @@ concurrency:
jobs:
announce:
name: Announce published posts
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Generate App token
Expand All @@ -57,6 +58,10 @@ jobs:
with:
node-version: 22

# Fail closed: never announce with a dedupe that doesn't pass its own tests.
- name: Test the announcement helper
run: node --test web/scripts/blog-announce-discussion.test.mjs

- name: Announce and write back discussion URLs
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/blog-publish-scheduled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ concurrency:
jobs:
publish:
name: Publish posts scheduled for today
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Generate App token
Expand Down
Loading
Loading