Commit Graph
1831 Commits
Author SHA1 Message Date
allan 8646381044 Merge pull request 'fix(mocks): restore the sas9 separators and convert the sasjs lineage capture' (#479) from fix/463-sas9-getdata-separators into release/7.23
Sites / deploy (push) Successful in 4m12s
Build / Build-and-ng-test (pull_request) Successful in 4m42s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m26s
Build / Build-and-test-development (pull_request) Successful in 27m10s
Reviewed-on: #479
2026-11-06 09:07:21 +00:00
dc caa58771bf fix(mocks): emit the sasjs lineage capture through webout()
Build / Build-and-ng-test (pull_request) Successful in 3m42s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m16s
Build / Build-and-test-development (pull_request) Successful in 29m9s
The capture assigned its body to _webout in one template literal, and that
literal was not JSON: raw line breaks inside the graphviz label and an
unescaped quote in labelloc = "t". fromsas, clickableids, info and flatdata
are members now, written as string literals that keep the label's quotes and
line breaks, with the envelope supplied by the runtime.

sas/mocks/test/sasjs-webout.test.js holds the six services that set their own
content headers, and pins this capture's members. CONTRIBUTING.md lists the
same six.
2026-11-06 09:03:01 +00:00
dc 67631133f9 test(mocks): pin the missing-separator branches and check a delimited drive
Build / Build-and-ng-test (pull_request) Successful in 4m57s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m56s
Build / Build-and-test-development (pull_request) Successful in 32m56s
Each fault shape the tree repairs now has a case that builds it - drive,
OMSOBJ, BUILTIN and DOMAIN - so a heuristic that stops detecting one fails the
suite instead of leaving only the exact-value assertion behind.

A drive letter is path-initial when nothing word-like precedes it, so a path
after <, >, =, [ or a comma is checked too; prose that puts a word before a
colon still is not a drive.
2026-11-06 02:53:11 +00:00
dc f1ba6362cc test(mocks): build the UNC cases from the code point
Build / Build-and-ng-test (pull_request) Successful in 4m17s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m8s
Build / Build-and-test-development (pull_request) Canceled after 15m30s
The two cases added for a path after a tag and after a comma were written as
single backslashes in the source, so each one evaluated to a value with no
separator at all and pinned nothing. The value is now assembled from
String.fromCharCode, with an assertion that it starts with the two separators
the case needs.
2026-11-06 02:33:19 +00:00
dc 755083bb49 test(mocks): fail a service that emits no member, and drop the history from the header
Build / Build-and-ng-test (pull_request) Successful in 6m17s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m30s
Build / Build-and-test-development (pull_request) Canceled after 13m34s
A service whose payload does not arrive as (kind, name, rows) contributed no
members and passed the tree-wide test with nothing checked; the loop now
records the file with no members captured.

The header describes what the values and the test do. The ticket numbers it
carried are gone: AGENTS.md allows a tracker reference only as a link, and the
behaviour reads without one.
2026-11-06 02:13:24 +00:00
dc 0e74ab58a1 test(mocks): pin the tree's scope and name the service that cannot run
Build / Build-and-ng-test (pull_request) Successful in 3m46s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m1s
Build / Build-and-test-development (pull_request) Successful in 29m25s
A file that does not emit through webout() and is not a listed helper fails a
scope test, so a renamed emitter shrinks the run set loudly instead of leaving
the replay over an empty set. Every webout kind is captured, a service that
needs a global the sandbox lacks names itself in the error, and the header
states the scope the scan covers.
2026-11-06 01:34:26 +00:00
dc c9119101a4 test(mocks): describe the separator rule as it applies to the webout() tree
Build / Build-and-ng-test (pull_request) Successful in 3m44s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m5s
Build / Build-and-test-development (pull_request) Successful in 29m15s
The header described the captures as live template literals, which this tree
no longer carries; the values now travel through webout() and the same
backslash is one escape away from the same damage.
2026-11-06 00:55:17 +00:00
dc 676cdf6fdd test(mocks): run only the services that emit, and widen the UNC exception
Build / Build-and-ng-test (pull_request) Successful in 5m23s
Build / Build-and-test-development (pull_request) Canceled after 2m39s
Lighthouse Checks / lighthouse (pull_request) Canceled after 8m1s
The replay covers the files that call webout(), so a shared module added to
the tree is skipped instead of failing with a require error, while the
_webout assertion still reads every file - a service that writes its own body
cannot slip past the filter.

A UNC prefix is legal after any character that is not a word character, a
colon or another separator, so a path inside a tag or after a comma keeps its
prefix instead of reading as a doubled separator.
2026-11-06 00:47:13 +00:00
dc 88cc68e2f9 test(mocks): anchor the doubled-separator rule and resolve sibling requires
Build / Build-and-ng-test (pull_request) Successful in 3m47s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m2s
Build / Build-and-test-development (pull_request) Successful in 26m56s
The whole-tree check treated any two consecutive backslashes as a fault, so a
UNC library path (\\server\share\...) failed on a correct value. A run of two
is now legal only where a UNC path puts it - the start of a value or of a quoted
path - and two separators anywhere else, plus any run of three, are still a
fault. faultsIn gains a case for each direction.

The sandbox require delegated relative requests to this test file's own
require, so a service requiring a sibling module resolved it under
sas/mocks/test. A relative request now resolves from the service file; builtins
and absolute paths delegate unchanged.
2026-11-06 00:15:03 +00:00
dc bafbe201ce fix(mocks): restore the two separators the sas9 getdata recording lost
Build / Build-and-ng-test (pull_request) Successful in 4m47s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m51s
Build / Build-and-test-development (pull_request) Successful in 29m4s
getdata's ENGINEHOST metadata rows carried a Windows path and an account
name with no separators, which SAS writes and the rest of this tree
already has: the rows read C:DataControllerDC996664mpe_x_test.sas7bdat
and BUILTINAdministrators, against refreshlibinfo's C:\DataController\
DC996664 library and BUILTIN\Administrators owner in the same recording.

Both values now carry single separators.

sas/mocks/test/sas9-separators.test.js replays all 36 files of the tree
under a webout() stub and fails on a doubled separator, or a missing one
in an OMSOBJ URI, a drive path or a BUILTIN/DOMAIN account. It also pins
the two corrected rows and asserts that no service assigns _webout.
2026-11-05 23:18:52 +00:00
dc 109711b9a0 fix(viewer): key the export menu so Handsontable keeps it
Sites / deploy (push) Successful in 3m19s
The CSV / Excel / PDF export submenu never reached the screen. HOT's
ExportFile plugin contributes a context-menu entry under the reserved key
`export_file`, and ContextMenu's ItemsFactory.setPredefinedItems folds the
plugin's entry over the grid's entry at the same key. That folded entry
carries `hidden() { return this.getSettings().exportFile === undefined }`,
and no grid in this app sets `exportFile`, so the whole item - submenu and
all - was filtered out of the menu. Seen on the 7.22.0 build: the viewer's
context menu offers Copy with header, Copy header only and Show labels, and
nothing else, so the client-side PDF export could not be reached at all.

Give the item a key of the app's own, EXPORT_MENU_KEY, and use it at the
three grids that build the menu (viewer, editor, viewboxes). The submenu
keys move with it.

export-menu.spec.ts pins both halves against a real grid, because the
merge happens inside the plugin at menu-build time and no unit test of the
item object can see it: configured under `export_file` the item is absent,
configured under EXPORT_MENU_KEY it is present.
2026-11-05 22:24:45 +00:00
dc 6bf7f9ba0d ci(docs): give the SAS docs deploy the Cloudron password that exists
Sites / deploy (push) Successful in 3m19s
The 7.22 release's sas-docs job failed with surfer's "Run surfer config first,
or provide --server ... --password" message, and its log shows SURFER_PASSWORD
empty - the CODE_DATACONTROLLER_IO secret resolves to nothing. Authenticate
with WEBDOC_DATACONTROLLER_IO, the password the typedoc job in this same
workflow already uses: a Cloudron app password belongs to the user, and the
same user owns both docs apps, so one credential covers both.
2026-11-05 21:29:35 +00:00
dc 205adf0a2b ci: put the remaining workflows on the node version the build uses
build.yaml, lighthouse.yaml and sites.yaml were moved to Node 24.15.0 on
2026-10-28 (d84a1d50); release.yaml, release-assets.yaml and docs.yaml were left
on 24.5.0. The release workflow builds the same client and runs the same Cypress
suite as build.yaml, so it should not be the one place pinning a different
runtime - and docs.yaml builds the frontend for typedoc with it too.
2026-11-05 21:29:23 +00:00
dc f3d1eb11d1 ci(release): attach cypress evidence only on failure
Sites / deploy (push) Successful in 3m17s
The release workflow recorded video for every spec and uploaded
cypress-videos.zip on every run, green or red. Recording eleven specs costs
encode time each run and the recording is only ever opened after a failure, so
run with video=false,screenshotOnRunFailure=true - the same pair build.yaml
already passes - and zip screenshots plus videos only under if: failure().
2026-11-05 21:17:10 +00:00
semantic-release-bot e07246ac68 chore(release): 7.22.0 [skip ci]
# [7.22.0](https://git.datacontroller.io/dc/dc/compare/v7.21.0...v7.22.0) (2026-11-05)

### Bug Fixes

* **browse:** accept legal folder names, cap the walk, and lock the read ([3572d56](3572d56cfb))
* **build:** build the mock sites with a CLI whose runtime provides webout() ([b1eab98](b1eab98200))
* **build:** publish the changed-path decision and pin the matcher with tests ([ecc8702](ecc87021c3))
* **build:** treat a sas/package.json bump as a Cypress input ([48df795](48df795202))
* **client:** cancel a queued dropdown reposition on destroy ([590b841](590b841f24))
* **client:** drop the synthetic window resize dispatches ([2535baa](2535baa5f7))
* **client:** keep the ARIA pass on the viewbox and multi-dataset grids too ([cb30469](cb30469e79))
* **client:** restrict the table pass to Handsontable's own tables, add the viewer fallback ([46c6aec](46c6aec37a))
* **client:** scope the ARIA pass to each grid and drive it from the render hooks ([bf1e7d6](bf1e7d6b15))
* **client:** stop synthesising aria-labels; name the filter modal's submit in the test ([32d3d49](32d3d49b59))
* **client:** strip only this grid's own wrapper, and pin the re-strip after a data load ([f282d1a](f282d1a44b))
* **deploy:** ACTION cannot seed by default, and the probe stops overwriting ([e84cfaa](e84cfaaabd))
* **deploy:** ask for the admin group before the staging directory on SASjs ([5c64691](5c646913e4))
* **deploy:** await nothing, and encode the values in the makedata URL ([d67a0e5](d67a0e5d5f))
* **deploy:** drop the deprecated service-pack deploy from the setup screen ([59e09e2](59e09e26a5))
* **deploy:** gate the browse actions on the settings file ([5eb8492](5eb8492742))
* **deploy:** make the folder picker work on both estates ([fb38309](fb38309e98))
* **deploy:** match the documented SYSSCPL set and stop guessing at the cap ([5f338ff](5f338ff437))
* **deploy:** read a URL-supplied ACTION before defaulting, and show locked folders ([addb4a5](addb4a56ea))
* **deploy:** stop the makedata poll outliving its screen, and align the release gates ([c808c41](c808c41333)), closes [#407](#407)
* **deploy:** unclamp the folder picker row and probe in the chosen context ([1330925](133092502c))
* **deps:** bump @sasjs/core to 7.0.1 so special missings can be restored ([3d2eefd](3d2eefdd7b)), closes [sasjs/core#499](sasjs/core#499)
* **deps:** bump @sasjs/core to 7.1.3 so a filter abort names the clause ([c7c1815](c7c181518c))
* **editor:** a container resize must not clear an active sort ([5bd7b5d](5bd7b5d724)), closes [#246](#246) [#246](#246)
* **editor:** drop the stale case hint, and narrow the strip to our own form ([bd2739c](bd2739cde4))
* **editor:** one predicate for the CASE marker, and no stacking in the tooltip ([18b0546](18b0546dcb))
* **embed:** a rejected mapping must not block the page ([d845b9e](d845b9e9b7)), closes [dc/dc#404](#404)
* **embed:** read a month-name date from VA as a date, not a character value ([30cefcf](30cefcf2dc))
* **getgroupmembers:** widen membername to $256 for long names ([196d6c9](196d6c931d))
* **lint:** enable noSingleAsteriskComments and noUnusedLibnames ([9071801](9071801ac5)), closes [#376](#376)
* **lint:** make noBannedStrings an error and clear its 13 findings ([0b85f7b](0b85f7bf1e)), closes [#376](#376)
* **lint:** make noUndeclaredMacros an error, and fix what it found ([af28a79](af28a79bd5)), closes [#376](#376)
* **lint:** make noUnusedMacros an error and clear its 54 findings ([c8c0858](c8c0858295)), closes [#376](#376)
* **lint:** rename the short and mixed-case helper macros, and clear the last warnings ([7da67eb](7da67eb17b)), closes [#376](#376)
* **makedata:** let the browse actions live as long as the service does ([912fae4](912fae448b))
* **mocks:** 0 is a closed TX_TO, a libref boundary, and the union of the row keys ([67d9cfd](67d9cfd1bd))
* **mocks:** abort like mp_abort, lock like mp_lockanytable, derive saslibs ([ca60208](ca602082ff))
* **mocks:** answer the deploy screen's DIRLIST and WRITECHECK actions ([e1c3c6e](e1c3c6e872))
* **mocks:** derive startupservice's maps and timezone instead of hardcoding ([87fd46c](87fd46c825))
* **mocks:** fold the audit window, and append MPE_AUDIT under its lock ([5ecc7be](5ecc7bebec))
* **mocks:** keep the OMS and path separators the captures lost ([7d9d466](7d9d466c3e))
* **mocks:** one sasDatetimeNow, a documented TX_TO=0, and a shaped fallback export ([e53c50d](e53c50d4e2))
* **mocks:** read hook columns in either case, and guard duplicates first ([349a2f3](349a2f314c))
* **mocks:** read the metadata member from the response, not the function's arity ([09e9f19](09e9f192c5))
* **mocks:** refuse a runtime whose webout() cannot carry column metadata ([482435f](482435f318))
* **mocks:** resolve the audit table once, record lower-case md5, and report a failed audit write ([1a9f594](1a9f59478b))
* **mocks:** run approval hooks, as mpe_runhook does ([f0208bb](f0208bb0b0))
* **mocks:** separate a body read failure from a missing showmeta floor ([744c89b](744c89b919))
* **mpe_abortmessage:** decide once whether the payload was escaped ([af89126](af89126acf))
* **mpe_abortmessage:** read program and state by name, and trim the prefix ([68b89ca](68b89caf63))
* **mpe_assert_abort:** anchor the cut on the whole escaped separator ([e74a7ba](e74a7ba115))
* **mpe_assert_abort:** compare the hook's message, not the log extract ([8b426c2](8b426c27fd))
* **mpe_assert_abort:** compare the unescaped message, cut at the last marker ([e68d221](e68d221354))
* **mpe_assert_abort:** cut at the separator, not the last marker ([4c7e869](4c7e869369))
* **mpe_assert_abort:** name the separator and tolerate a carriage return ([7b09ade](7b09ade97c))
* **mpe_assert_abort:** size the capture buffer for the whole message ([5d32de9](5d32de9df9))
* **mpe_assert_abort:** strip the separator tail, not every trailing escape ([b29bc1e](b29bc1e7ae))
* **mpe_execute:** assemble the abort text in a macro that can be tested ([e2e9103](e2e91039cb))
* **mpe_execute:** keep a child abort message intact when reporting it ([ebfd395](ebfd39582f))
* **mpe_execute:** keep the child message in the fallback, and test the doubled separator ([11587e2](11587e2f24))
* **mpe_execute:** make the abort-text fallback reachable ([24b8cbe](24b8cbe9dc))
* **mpe_execute:** mask a child abort message before it aborts the caller ([20dbf00](20dbf00fb0))
* **mpe_execute:** one symbol for the abort text, and no values in the call ([700b906](700b906d69))
* **multi-dataset:** a sheet that failed to extract is not a clean finish ([3129648](312964864e))
* **multi-dataset:** show the file size and loading stage while the file loads ([45d6c02](45d6c025c9)), closes [#121](#121)
* **report:** treat a blank PDF footer parameter as its default ([0f729d7](0f729d7101))
* **test:** let the dirlist and writecheck results reach the caller on Viya ([ed678f0](ed678f0b46))
* **test:** run metanav.cy.ts in CI and open a metadata object ([02a8590](02a8590310))
* **va-host:** bound the early replay even when the frame is not postable ([f6d5a04](f6d5a0495e))
* **va-host:** deliver in load mode, honour dataType, and survive odd prompt names ([7d4155d](7d4155dc6c))
* **va-host:** deliver the load-mount message once, and document report.json from the file ([90784b3](90784b342f))
* **va-host:** keep the deploy password off argv, and verify the app is served ([7c63a5c](7c63a5c93f))
* **va-host:** post to the frame's own origin, and key the replay to the document ([9cec292](9cec292681))
* **viewer:** clamp over-tall PDF rows, always emit an otherwise-empty page ([10229d5](10229d5efe))
* **viewer:** defer the blob URL revoke, reject a selection inside skipped columns ([b5bb704](b5bb704a4f))
* **viewer:** keep the PDF export working where hiddenColumns is not enabled ([f84d4ca](f84d4ca7af))
* **viewer:** review findings - CSV skipped column, PDF row cap before reading ([2c4be5e](2c4be5e94b))

### Features

* **admin:** find and test a writeable location during deploy ([442c595](442c595c2b))
* **deploy:** pick and validate the DC location from the deploy screens ([6eeab2e](6eeab2ed16))
* **editor:** convert CASE rule values on entry ([c4eef49](c4eef498b2))
* **logs:** read the logs modal with the shared log parser ([aabc578](aabc578d5e))
* **mocks:** add the control-table services the screens call ([a1615ee](a1615ee8a7))
* **mocks:** build every JS stored program's response with webout() ([ce6bb5d](ce6bb5d4cd))
* **mocks:** emit the lineage, usernav and libinfo responses through webout() ([1f54fad](1f54fad3fe))
* **mocks:** emit the metanav responses through webout() ([e1d877f](e1d877f21d))
* **mocks:** emit the sas9 services through webout() ([7be3f84](7be3f84073)), closes [#461](#461)
* **mocks:** restore a load, and record the diffs it needs ([1a60c68](1a60c689f4))
* **mocks:** use the server's webout() in the JS stored programs ([ccb67ed](ccb67ed5a0))
* **va-host:** emulate a VA report that embeds DC, as a deployable AppStream app ([2185db9](2185db911d))
* **viewer:** download a server-side PDF report ([67ca129](67ca129c4d))
* **viewer:** export the grid as PDF from the context menu ([e58e735](e58e735610))

### Reverts

* keep the helpers nested inside makedata_body ([4424171](4424171b2f))
v7.22.0
2026-11-05 21:11:44 +00:00
allan 39e8ffdbdd Merge pull request 'feat: Release/7.22' (#407) from release/7.22 into main
Release / Build-production-and-ng-test (push) Successful in 4m25s
Release / Build-and-test-development (push) Successful in 21m8s
Release / release (push) Successful in 4m37s
Reviewed-on: #407
2026-11-05 20:42:41 +00:00
allan 6f7f2d189f Merge pull request 'fix(deploy): drop the browse gate, and ask for the admin group before the directory' (#478) from fix/7.22-browse-window-and-order into release/7.22
Sites / deploy (push) Successful in 3m6s
Build / Build-and-ng-test (pull_request) Successful in 3m46s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m4s
Build / Build-and-test-development (pull_request) Successful in 29m18s
Reviewed-on: #478
2026-11-05 18:54:47 +00:00
dc 5c646913e4 fix(deploy): ask for the admin group before the staging directory on SASjs
Build / Build-and-ng-test (pull_request) Successful in 4m15s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m31s
Build / Build-and-test-development (pull_request) Successful in 33m3s
The SASjs Server / SAS9 screen put the DC Staging Directory first and the admin
group second, the reverse of the Viya screen, so the operator was asked to pick
a location before being told who administers DC. It now asks for the admin group
first and the directory last, with the copy reordered to match.

A rendered-order spec covers it: it reads the labels off the rendered template
and fails if the directory comes first again.
2026-11-05 18:21:54 +00:00
dc 912fae448b fix(makedata): let the browse actions live as long as the service does
DIRLIST and WRITECHECK were refused once the appLoc had a settings.sas, on the
argument that a failed run could leave that file behind. That is what makes the
gate wrong: a failed run is exactly when the deploy screen still needs to list
and probe directories, and any appLoc configured earlier refuses them for good.
The window needs no separate clock - a successful run deletes this program, so
once DC is configured there is nothing left here to call.

Removed from both variants (server checks the drive over HTTP, Viya checks the
filesystem; neither is needed), and the two tests that asserted the refusal go
with it. The macros themselves are covered by mpe_dirlist.test.sas and
mpe_writecheck.test.sas.

Seen on sas.4gl.io: a deployment to /Public/app/dc477 answered BROWSE with
"DIRLIST and WRITECHECK are only available until DC is configured".
2026-11-05 18:21:54 +00:00
allan eef91ce989 Merge pull request 'fix(7.22): clear the makedata poll on destroy, and close the release-gate findings' (#477) from fix/7.22-release-followups into release/7.22
Sites / deploy (push) Successful in 3m6s
Build / Build-and-ng-test (pull_request) Successful in 6m18s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m47s
Build / Build-and-test-development (pull_request) Successful in 27m24s
Reviewed-on: #477
2026-11-05 17:58:28 +00:00
dc c808c41333 fix(deploy): stop the makedata poll outliving its screen, and align the release gates
Build / Build-and-ng-test (pull_request) Successful in 4m20s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m36s
Build / Build-and-test-development (pull_request) Successful in 33m14s
The release pipeline went red at 28157fae: Karma aborted with "Some of your
tests did a full page reload!" after test 753 of 861. The new-window spec calls
runMakedataInNewWindow, which starts the five-second makedata poll; the service
stub answers that makedata is gone, so the first tick assigns window.location.href
and the page reloads under the running spec. A local run finishes in under five
seconds, so the timer never fires there - only CI saw it.

- the poll is cleared in ngOnDestroy, so a timer cannot reload the page after
  the user has moved on; a spec covers that
- the spec that exercises the new-window URL no longer starts the real poll

Three open findings on #407, all taken:

- lighthouse.yaml: sas/package.json joins the change-detection pattern, because
  the job pins @sasjs/cli from that file
- release-assets.yaml: webpack is installed at the version the client lockfile
  resolves rather than whatever is current at release time
- build.yaml: the client audit runs with --audit-level=low, which is what the
  comment above it has always claimed
2026-11-05 17:09:52 +00:00
dc ed678f0b46 fix(test): let the dirlist and writecheck results reach the caller on Viya
Both test programs pointed _webout at a temporary file so the macros under
test could write their JSON answer, then tried to hand the fileref back with
`filename _webout "&_webout_path" mod;`. On Viya's JES _webout is a filesrvc
fileref, whose pathname() is empty, so that statement fails with "Invalid
physical name" and the temporary file stays in place. The term program's
%webout(OPEN) assigns _webout itself (filesrvc on JES, temp elsewhere), so the
restore is not needed at all; the two programs now leave the fileref to it.

Observed on the nextviya estate: the suite reports 64/64 of the tests that
produced results, with mpe_dirlist and mpe_writecheck absent from the results
JSON and "Invalid physical name" in their logs.
2026-11-05 17:09:49 +00:00
allan 28157faeca Merge pull request 'fix(deploy): unclamp the folder picker row and probe in the chosen context' (#476) from fix/deploy-folder-overlay into release/7.22
Build / Build-and-ng-test (pull_request) Failing after 2m13s
Build / Build-and-test-development (pull_request) Skipped
Sites / deploy (push) Successful in 3m16s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m36s
Reviewed-on: #476
2026-11-05 15:52:29 +00:00
dc e8cdc37a05 test(deploy): match the picker's requests with regexps, and sign in on a secured estate
Build / Build-and-ng-test (pull_request) Successful in 5m10s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m34s
Build / Build-and-test-development (pull_request) Successful in 32m58s
The spec failed on CI: the browse button never appeared. Two causes, both
found by reproducing the CI estate locally (seeded drive):

- The intercept pattern '**/drive/folder**' does not match the request the
  app makes ('/SASjsApi/drive/folder?_folderPath=...') in this Cypress
  version, so the app saw the real drive, loaded the startup service and
  never routed to the deploy screen. Both intercepts are now regexps, which
  match deterministically. Verified on the seeded estate: the app routes to
  #/deploy and the agreement card renders.

- A secured estate shows a login form first. The boot helper now waits for
  one of the states it can act on (a visible login form, the agreement card,
  or the deploy screen), signs in when the form is visible, then waits for
  the deploy screen. The login form stays in the DOM hidden on other routes,
  so visibility - not presence - decides.
2026-11-05 15:10:51 +00:00
dc 677b03eddf test(deploy): bind the picker in the Viya order spec, and drop fixed waits
Build / Build-and-ng-test (pull_request) Successful in 4m13s
Lighthouse Checks / lighthouse (pull_request) Successful in 7m56s
Build / Build-and-test-development (pull_request) Failing after 37m51s
Review findings on #476.

- The 'hands the picker the context' test declared only AutomaticComponent
  and HostComponent with NO_ERRORS_SCHEMA, so app-dc-location stayed an
  unknown element: the [computeContextName] binding was never applied and
  the assertion just repeated the fallback case. DcLocationComponent is
  now declared (SasService is already stubbed), and the test asserts the
  picker's own input - both before a selection (the configured fallback)
  and after one is chosen. Verified it bites: removing the binding from
  the template fails it.

- The Cypress spec set the path nowhere before clicking Validate, so the
  assertion leaned on the field arriving pre-filled; a blank field would
  short-circuit with 'Type or pick a folder first' and fail for a reason
  unrelated to the layout. It now types the path explicitly.

- The spec slept a fixed 4s after accepting the agreement, which turns a
  loaded CI runner into an intermittent failure. Both tests now share one
  boot helper whose readiness gate is the Browse button appearing.
2026-11-05 14:13:16 +00:00
dc 133092502c fix(deploy): unclamp the folder picker row and probe in the chosen context
Build / Build-and-ng-test (pull_request) Successful in 3m43s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m35s
Build / Build-and-test-development (pull_request) Canceled after 24m24s
Two defects on the deploy screens, found on staging.

1. The folder picker's outer row reused Clarity's 'clr-input-wrapper'
   class, whose 'max-height: var(--clr-forms-input-wrapper-height)'
   clamps the row to the input's height. The row is flex-wrap: wrap, so
   once the Browse/Validate buttons wrapped (or the row was simply
   taller than one input) they overflowed the clamp, and the folder
   browser panel - positioned after the collapsed box - rendered on
   top of the input, the buttons and the surrounding text. Measured on
   staging: panel top 256 against input 248-272 and buttons 280-304;
   with the class dropped the row contains all its children and the
   panel starts at 312.

2. The Viya screen asked for the DC directory before the compute
   context, but the folder listing and the write check run inside the
   chosen context, as its batch identity - so the probes could inspect
   a file system the deployment itself would not use. The fields are
   reordered (admin group, then batch id / compute context, then DC
   Loc last) and the picker now runs DIRLIST and WRITECHECK with the
   selected context in the adapter request config, which every Viya
   executor path honours. The SAS 9 / SASjs screens pass no context
   and behave exactly as before.

Tests: a Cypress spec drives the real deploy screen estate - the
picker row must contain its own children and the panel must start
below them; both tests fail with the clamp reintroduced. A rendered
TestBed spec pins the Viya field order (admin group < compute context
< DC Loc) and fails with the old order. Unit specs pin the probe
config: no context on SAS 9 / SASjs, the chosen context on Viya.
2026-11-05 13:44:31 +00:00
allan 1ea1965721 Merge pull request 'feat(mocks): restore a load, and record the diffs it needs' (#474) from feat/mocks-restore into release/7.22
Sites / deploy (push) Successful in 3m23s
Build / Build-and-ng-test (pull_request) Successful in 3m41s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m36s
Build / Build-and-test-development (pull_request) Successful in 33m22s
Reviewed-on: #474
2026-11-05 10:54:20 +00:00
allan 3573ce260f Merge pull request 'feat(mocks): add the control-table services the screens call' (#473) from feat/mocks-control-table-services into release/7.22
Sites / deploy (push) Canceled after 0s
Build / Build-and-ng-test (pull_request) Canceled after 0s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
Reviewed-on: #473
2026-11-05 10:54:10 +00:00
dc 1a9f59478b fix(mocks): resolve the audit table once, record lower-case md5, and report a failed audit write
Build / Build-and-ng-test (pull_request) Successful in 6m28s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m32s
Build / Build-and-test-development (pull_request) Successful in 27m3s
Three review findings.

- KEY_HASH was upper-cased after the digest. SAS md5() writes lower-case hex,
  so the mock's key hash did not match what a real audit table holds. The
  digest is stored as it comes out, and the test asserts lower case.

- restore resolved MPE_TABLES.AUDIT_LIBDS per table, while the approval always
  appended to the control library's MPE_AUDIT: a table with its own audit table
  had its diffs written where restore never looked, and restore then refused
  with "No entry for <load>", which no later approval could fix. Resolution now
  lives in dcMockUtils.auditLibdsFor, called by both sides, and the approval
  reads and saves that table in its own libref.

- An audit failure was only logged to the server console, leaving an approval
  with no diffs and no visible reason. It is still not allowed to undo the
  approval, but once the control tables are written the failure is raised as an
  abort that names the table and says the load can no longer be rewound.

Two tests: one approves a table whose MPE_TABLES row names a different audit
table and asserts the rows land there and not in the control library; one points
AUDIT_LIBDS at a libref with no data folder and asserts the approval still
lands and the response says the diffs could not be recorded.
2026-11-05 10:44:40 +00:00
dc 5ecc7bebec fix(mocks): fold the audit window, and append MPE_AUDIT under its lock
Two review findings on this branch.

- editors/restore.js spread every audit row for the load into Math.min(...).
  MPE_AUDIT is append-only, so a long-lived estate would eventually exceed the
  engine's argument limit and the RangeError would surface as a failed restore
  of a perfectly restorable load. Folded into a reduce with an Infinity seed.

- auditors/postdata.js appended to MPE_AUDIT as a read-modify-write outside any
  lock. postdata.sas locks the table it writes - MPE_REVIEW around its proc
  append, MPE_SUBMIT around its update - so the audit append now takes the
  MPE_AUDIT lock around a fresh read, the concat and the save, rather than
  reading the table up front and saving a copy that may be missing another
  approval's rows.

53 mock tests pass (node --test sas/mocks/test/).
2026-11-05 10:44:40 +00:00
dc 1a60c689f4 feat(mocks): restore a load, and record the diffs it needs
`editors/restore` had no mock, and the reason it could not have had one is that
`postdata` wrote no audit rows: an approval applied the load and left nothing
to rewind.  Both halves are here.

postdata (final approval): writes MPE_AUDIT the way %mp_storediffs does - one
row per column, keyed by the md5 of the business-key values joined by '|', with
move_type A/D/M, is_pk on the key columns, is_diff on a changed one, and the
old and new value on their own typed columns.  An append carries every column
on the new side, a delete every column on the old side (which is what lets a
restore put the row back), and a modification the keys plus only the columns
that actually changed.  A failure writing the audit is logged and does not undo
an approval that has already landed.

restore: mpe_checkrestore's two decidable checks (the table has an audit table;
the load has entries in it) with their messages, then mp_stripdiffs' window -
the load's timestamp and every change to that table from then on, because a
restore reverts this load *and all subsequent ones*.  Per business key the
pre-window state is the earliest event: an append means the row is staged for
deletion (`_____DELETE__THIS__RECORD_____=YES`), a delete means it is staged
with the values the delete recorded, and a modification means each column takes
its earliest old value while untouched columns keep the current one.  The
result goes through the same stageSubmission the editors use, so it arrives as
a new submission with the reason `Restoring <loadref>` and is approved like any
other - restore.sas never writes the base table either.

The access and group/RLS checks are not emulated, consistent with the rest of
the mocks: nothing enforces MPE_SECURITY there.

Tests: 52 pass across the seven mock suites, 7 of them new.  They drive a real
approve and then read the staged rows back: append staged for deletion,
modification staged with the pre-load value, delete staged with the row's
values, and restoring the first of two loads reverting the second as well.
With the base postdata and no restore service, all 7 fail.
2026-11-05 10:44:40 +00:00
dc 67d9cfd1bd fix(mocks): 0 is a closed TX_TO, a libref boundary, and the union of the row keys
Build / Build-and-ng-test (pull_request) Successful in 5m40s
Lighthouse Checks / lighthouse (pull_request) Successful in 7m54s
Build / Build-and-test-development (pull_request) Successful in 32m44s
Four review findings.

- isCurrentOn counted TX_TO 0 as current. 0 is 01JAN1960 in SAS, so a real row
  with that window has been superseded; only a missing, null or blank TX_TO is
  open besides 31DEC9999. The fixtures are seeded with 31DEC9999 (253717919999)
  through an OPEN constant instead, and the 0 clause is gone.

- The MPE_DATADICTIONARY filter compared DD_SOURCE against the libref without a
  boundary, so DC_JSLIB2.TABLE was excluded along with DC_JSLIB.TABLE. It now
  tests for the libref plus the dot, as the libds rewrite does.

- The schema-less export took its column list from Object.keys(rows[0]), so a
  column that first appears in a later row was silently missing from the backup.
  The list is the union of the keys across the exported rows, in first-seen
  order.

- control-tables.test.js imported readTable and never used it.
2026-11-05 10:44:28 +00:00
dc e53c50d4e2 fix(mocks): one sasDatetimeNow, a documented TX_TO=0, and a shaped fallback export
Build / Build-and-ng-test (pull_request) Successful in 5m56s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m3s
Build / Build-and-test-development (pull_request) Successful in 29m43s
Three review findings on this branch.

- dcMockUtils defined sasDatetimeNow twice: once beside isCurrentRow with the
  epoch written inline and once in the mp_lockanytable section using
  SAS_EPOCH_MS. The second is the one the file already had; the duplicate is
  gone, so the epoch has one expression.

- isCurrentRow treated TX_TO=0 as current without saying why, and reached that
  through Number(null) === 0. Both conventions are real: makedata writes
  31DEC9999 for a row that was never superseded, and the fixtures in
  control-tables.test.js use 0 for the same thing. The clause stays and the
  comment now says which convention is which, plus that a missing, null or
  blank TX_TO (a table with no version column) is current too. Reading the
  column moved into isCurrentOn, because Array.filter passes the index as a
  second argument and would have handed a `column` parameter the row number.

- admin/exportconfig used the schema only when it existed: with no columns in
  the table file it fell back to Object.keys(rows[0]), which exported the
  TX_/VER_/SELECTBOX_RK columns the spec drops and quoted every number. The
  fallback now applies the same drop prefixes and infers numeric typing from
  the rows, so a schema-less estate produces the same shape as a seeded one. A
  test writes a table file with rows and no columns and asserts both.

All 46 mock tests pass (node --test sas/mocks/test/).
2026-11-05 09:39:25 +00:00
allan 5bf836e206 Merge pull request 'ci(build): restore the Angular cache after postinstall, where it survives' (#475) from ci/angular-cache-after-install into release/7.22
Sites / deploy (push) Successful in 3m54s
Build / Build-and-ng-test (pull_request) Successful in 5m26s
Lighthouse Checks / lighthouse (pull_request) Successful in 7m58s
Build / Build-and-test-development (pull_request) Successful in 33m8s
Reviewed-on: #475
2026-11-05 09:25:44 +00:00
dc 00d37577f7 ci(build): restore the Angular cache after postinstall, where it survives
Build / Build-and-ng-test (pull_request) Successful in 5m42s
Lighthouse Checks / lighthouse (pull_request) Successful in 7m56s
Build / Build-and-test-development (pull_request) Successful in 32m57s
postinstall runs scripts/strip-clr-base64-fonts.mjs, which edits Clarity's CSS
inside node_modules and then deletes client/.angular/cache - because the
builder treats node_modules as immutable and would otherwise serve styles built
from the unstripped file. Measured on the Angular 20 builder: a marker
@font-face injected into clr-ui.min.css was absent from a warm build's CSS
(28s) and present in a cold one (88s).

The restore used to run before the install, so that clear deleted the entry
before anything could use it - run c732d75 restored 396MB and logged "cleared
/workspace/.../client/.angular/cache" minutes later. It now runs immediately
after "Install dependencies": the clear has already happened, and every entry
in the cache was produced from a stripped stylesheet, because every run strips
before it builds. The invariant the clear exists to enforce is preserved.

No restore-keys on this cache, deliberately: because the builder cannot see a
change inside node_modules, a prefix match could restore entries compiled
against a different Clarity after a lockfile bump. Exact key only, so a
dependency change misses and rebuilds.

Measured locally with the tree matching the lockfile (CI=true): cold with the
cache cleared 90s, warm with no source change 27s, warm with version.ts
regenerated as a new commit does 58s. So a build that is 99-109s in CI should
land near 58s; the rest needs the version out of the compiled graph, which is
an application change.

The strip script's comment described webpack 5; it now describes what was
measured, and notes that CI restores after it runs.
2026-11-05 08:21:21 +00:00
allan 1e2a6bb168 Merge pull request 'ci(build): cache the client npm downloads, and keep Angular's disk cache in CI' (#472) from ci/cache-client-builds into release/7.22
Sites / deploy (push) Successful in 3m56s
Build / Build-and-ng-test (pull_request) Successful in 4m45s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m21s
Build / Build-and-test-development (pull_request) Successful in 26m47s
Reviewed-on: #472
2026-11-04 22:33:03 +00:00
dc a1615ee8a7 feat(mocks): add the control-table services the screens call
Build / Build-and-ng-test (pull_request) Successful in 5m35s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m36s
Build / Build-and-test-development (pull_request) Successful in 28m53s
Three services the Angular app calls had no mock at all, so on the mock
estate the Excel-map screen, the system screen's refresh and the config
export either failed or showed nothing:

- editors/getxlmaps - the rules and info of one map.  Reads MPE_XLMAP_RULES
  and MPE_XLMAP_INFO for the current rows of that XLMAP_ID, orders by sheet
  and range id, and coalesces the target to DC_JSLIB.MPE_XLMAP_DATA - the
  same five columns, order and fallback getxlmaps.sas uses.
- admin/refreshcatalog - a libref is optional and must be well formed
  (Invalid libref otherwise), and the reply is sasparams with the
  completion message the screen displays.  A mock estate has no catalogue
  to rewrite, which the service says rather than pretending.
- admin/exportconfig - a member per non-empty table, named
  <libref>.<TABLE>.csv, with the per-table treatment exportconfig.sas
  applies: TX_* dropped, a version filter, the `where also` exclusions,
  and the control-library name rewritten to the target libref (including
  the DC_MACROS value and the LIBREF.TABLE of a map target).  Zipped as
  DCBACKUP.zip.

The version test and the control-table reader were local to startupservice;
they now live in dcMockUtils (isCurrentRow, sasDatetimeNow, and
makeControlTableLoader for controlTable), so the rule that decides a row is
current exists once.

The harness gains the session's stpsrv_header.txt and _SASJS_USERNAME, so
a service that streams a file can be exercised: exportconfig is asserted
both ways - the zip where the runtime has Buffer, the content listing where
it does not.

Tests: 45 pass across the six mock suites, 8 of them new.  With the three
services removed, all 8 new cases fail.
2026-11-04 22:15:54 +00:00
dc ce8c41c5db ci(build): drop the Angular cache step, which bought nothing across runs
Build / Build-and-ng-test (pull_request) Successful in 6m10s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m27s
Build / Build-and-test-development (pull_request) Successful in 26m56s
Run c732d75 restored both entries on runner2 and the build stayed at 99s (it
was 99-109s cold); the tz suite's 62s -> 25s came from the disk cache inside
the job, not from the restore. postinstall rewrites
src/environments/version.ts with the commit hash and a timestamp on every run,
and the app shell imports it, so the application and test graphs are
invalidated regardless of what was restored.

The npm cache stays, and it is the one that pays: the client install fell from
80s to 25s in that run. angular.json keeps cli.cache.environment=all, which is
what makes the within-job reuse possible.
2026-11-04 22:11:15 +00:00
dc c732d7593c ci(build): restore the caches after the secret scan
Build / Build-and-ng-test (pull_request) Successful in 4m38s
Build / Build-and-test-development (pull_request) Canceled after 2m5s
Lighthouse Checks / lighthouse (pull_request) Canceled after 6m42s
The gitleaks step scans the working tree with --no-git, and the Angular cache
holds webpack/babel JSON full of `{ token: ... }` fragments. Restoring it
before the scan put those files in the tree and the gate failed on run 3174
with a wall of generic-api-key findings in
client/.angular/cache/20.3.41/babel-webpack/*.json.

Both cache steps now run immediately after the scan. The npm download cache
only helps the client's npm ci, which is after that point anyway; the Angular
cache only helps the tests and the build, which are later still.
2026-11-04 22:04:31 +00:00
dc f88e053887 ci(build): state the cache invariant in the step comments, not one run's numbers
Build / Build-and-ng-test (pull_request) Failing after 1m23s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Canceled after 2m36s
The comments cited run 3170 and a timing measured on one 4 vCPU box. Both go
stale and read as a guarantee about a particular runner. They now state the
invariant the workflow relies on: an unreachable cache server degrades restore
and save to a warning, so the job stays green. The timing figures stay in the
PR description, where they are evidence for this change rather than a promise
the workflow makes.
2026-11-04 21:59:45 +00:00
dc 768755dd37 ci(build): namespace the cache keys by base branch
Build / Build-and-ng-test (pull_request) Successful in 5m16s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m34s
Build / Build-and-test-development (pull_request) Successful in 29m6s
act_runner's cache handler scopes entries to the repository (each entry is
stamped with its repo and checked on read), not to a ref, so a pull request
into one branch could seed the entry another branch's build restores. The base
branch is now part of both keys and both restore-key prefixes.

A save against an unreachable cache server is already a warning, not a
failure: run 3170 landed on runner4.4gl.io and logged four
'::warning::Failed to save: reserveCache failed: Request timeout' lines with the
job completing green.
2026-11-04 21:23:33 +00:00
dc 5f8f668c66 ci(build): keep Angular's disk cache in CI so the cache step has something to save
Build / Build-and-ng-test (pull_request) Successful in 6m6s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m20s
Build / Build-and-test-development (pull_request) Canceled after 8m43s
The first run of these cache steps showed the npm entry saving (234 MB) and the
Angular one reporting 'Path Validation Error: Path(s) specified in the action
for caching do(es) not exist'. Angular keeps its persistent cache in memory
when CI is set, so no .angular/cache directory existed to upload; the disk
cache is only used outside CI by default.

client/angular.json now sets cli.cache.environment to "all", which is the
supported switch for that. Verified locally with CI=true: after removing
.angular/cache, a build recreates it (396 MB).
2026-11-04 21:08:40 +00:00
dc 9d30e1855f ci(build): restore the npm downloads and the Angular build cache
Lighthouse Checks / lighthouse (pull_request) Successful in 10s
Build / Build-and-ng-test (pull_request) Successful in 5m47s
Build / Build-and-test-development (pull_request) Canceled after 11m42s
The runner's cache server is reachable from job containers on two of the three
boxes now, so these are the first cache entries in this repo that can actually
come back. Probe evidence, four dispatches of a throwaway two-job workflow:

- save on runner.4gl.io, restore on runner2.4gl.io: miss (each act_runner runs
  its own cache server, so entries belong to the box that saved them)
- save and restore on runner2.4gl.io: cache-hit=true, the stamp came back
- save on runner4.4gl.io: "Failed to save: reserveCache failed: Request
  timeout" - that box still runs the old runner config

Two caches, both keyed on client/package-lock.json so a dependency change
invalidates them, with prefix restore-keys so the previous entry still lands:

- ~/.npm, for the client's npm ci (81s cold, measured on run 3132)
- client/.angular/cache, for the production build (109s cold, 118s -> 33s with
  a warm cache on a 4 vCPU box) and the two Karma runs, which share it

Per run the arithmetic while runner4 is still on the old config is about
1/3 x (+80s: two restores and two saves timing out at 20s each) plus
2/3 x (-100s), so it is a net improvement already and a clear one once the
third box is deployed. On an old box both steps only warn and the job stays
green.

actions/cache does not save a second entry for an existing key, so the Angular
cache refreshes when the lockfile changes; the v1- prefix is there to force a
reset by hand if the win decays.
2026-11-04 20:51:03 +00:00
allan 40624c0a4c Merge pull request 'fix(deploy): make the folder picker work on both estates' (#471) from fix/deploy-picker into release/7.22
Sites / deploy (push) Successful in 3m55s
Build / Build-and-ng-test (pull_request) Successful in 7m21s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m32s
Build / Build-and-test-development (pull_request) Successful in 33m11s
Reviewed-on: #471
2026-11-04 20:44:56 +00:00
dc 5f338ff437 fix(deploy): match the documented SYSSCPL set and stop guessing at the cap
Build / Build-and-ng-test (pull_request) Failing after 16m15s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 20m24s
Review findings on #471.

The platform rule now checks the documented tokens exactly
(['WIN', 'DNTHOST']) instead of their first letters, so an unlisted token
beginning with W or D no longer silently gets the drive path back.

brDirList raised TRUNCATED whenever it saw rows.length >= maxobs, which
included the check made after the last permitted row was added: a listing
of exactly maxobs folders with a file sorting after them reported itself
truncated although nothing was refused.  The flag is now set only where a
row is actually refused, and the early exit remains the walk bound.

Two new cases pin the shape the picker depends on: a listing of exactly
MAXOBS entries is TRUNCATED='N', and a DEPTH=2 browse numbers children at
1 and grandchildren at 2 with no LEVEL 0 row.  Both, plus the existing
parent-level and write-probe cases, fail against release/7.22's service.

Tests: 37 pass across the five mock suites, client spec 9/9.
2026-11-04 20:32:32 +00:00
allan f0769b46aa Merge pull request 'ci(build): skip the cold build when the Cypress job will not run' (#470) from ci/faster-build-job into release/7.22
Sites / deploy (push) Successful in 4m39s
Build / Build-and-ng-test (pull_request) Successful in 5m28s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m3s
Build / Build-and-test-development (pull_request) Canceled after 7m6s
Reviewed-on: #470
2026-11-04 20:28:42 +00:00
dc ecc87021c3 fix(build): publish the changed-path decision and pin the matcher with tests
Build / Build-and-ng-test (pull_request) Successful in 6m32s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m59s
Build / Build-and-test-development (pull_request) Successful in 27m9s
Two review findings on the first two commits.

The two jobs no longer decide separately. Build-and-ng-test now publishes
`client` as a job output and Build-and-test-development consumes it instead of
re-diffing the base branch: a base commit landing between the two decisions
could make the Cypress job expect an artifact the build job had already
skipped, and the failure would read as a missing artifact. An empty output
(the build job could not decide) still runs everything.

The path list is now a PATHS variable the script can be asked about directly
(`--match` with a file list on stdin), and tests/cypress-changes.test.sh pins
one case per alternative plus the negatives (a sas/ service, docs, the
Lighthouse workflow, a "clientx/" directory, the sas lockfile). The build job
runs the test, so dropping an alternative is red rather than a suite that
silently stops covering a class of change. Checked the test fails when the
pattern is mutated.
2026-11-04 19:17:35 +00:00
dc 48df795202 fix(build): treat a sas/package.json bump as a Cypress input
Build / Build-and-ng-test (pull_request) Successful in 4m56s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m12s
Build / Build-and-test-development (pull_request) Successful in 33m35s
The Cypress job installs the CLI from the version sas/package.json pins
(`CLI_VERSION=$(node -p "...dependencies['@sasjs/cli']")` in "Deploy mocked
services"), so bumping that pin changes what the suite runs without touching
anything under sas/mocks/. A pull request that only bumped it would have been
skipped by both jobs.

The sas lockfile stays out of the set: the job installs that CLI globally by
version string, so the lockfile cannot change what it resolves.
2026-11-04 18:36:02 +00:00
dc fb38309e98 fix(deploy): make the folder picker work on both estates
Build / Build-and-ng-test (pull_request) Successful in 5m45s
Lighthouse Checks / lighthouse (pull_request) Successful in 11m11s
Build / Build-and-test-development (pull_request) Successful in 27m14s
Three defects behind staging's "Invalid parent" and the picker that could
not list a subfolder.  Found by running the real mpe_dirlist on
sas.4gl.io, not by reading it.

1. The browser host answers SYSSCPL "browser".  The client looked for the
   first letter of a unix token (L/H/A/S) and fell through to the Windows
   default for anything else, so the deploy screen pre-filled
   C:\DataController on a POSIX runtime, which the server then refused as
   an invalid parent.  Windows is now recognised by its own tokens (WIN,
   DNTHOST); anything else, including a value the list has never seen,
   gets the POSIX path.

2. mpe_dirlist emitted a non-blank parent's children at LEVEL 0, because
   mp_dirlist numbers a folder's contents with the folder's own level.
   The picker keeps LEVEL>0 for a non-root listing, so every row was
   discarded and a subfolder could never be browsed.  The children are
   shifted to start at 1; a blank parent still lists the platform roots
   at 0.  Verified on the estate: /tmp at DEPTH=1 returns levels [1],
   a blank parent returns [0, 1] (the root, then its children).

3. The mock's write probe generated one name and wrote it, truncating a
   file that was already called that; mpe_writecheck tries up to ten
   names with fileexist()=0 first.  The mock now does the same and
   reports 'No free name for the write probe' when all ten are taken.

The mock's DIRLIST now matches the estate's shape too: children at
LEVEL 1, roots at 0 followed by their children, and the cap stops the
walk at MAXOBS rather than slicing afterwards.  Tests: 35 pass across
the five mock suites (makedata-actions 15, two of them new: the probe
collision and a folder that cannot be written to), sasjs lint clean,
client spec 8/8 with the new platform cases and 2 failing without the
fix.
2026-11-04 18:31:12 +00:00
dc 6830d873e5 ci(build): skip the cold build when the Cypress job will not run, and stop installing Cypress in the lint job
Build / Build-and-ng-test (pull_request) Successful in 5m29s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m58s
Build / Build-and-test-development (pull_request) Canceled after 16m5s
The build job spent 402s on every pull request (run 3132, release/7.22).
Two of its steps were work nobody consumed:

- `npx cypress install` (11s, measured 17:07:05-17:07:16) downloaded and
  unzipped the Cypress binary into a job that only runs Karma, eslint, the
  licence checker, the mock service tests and `ng build`. Cypress runs in
  Build-and-test-development, which installs its own binary.
- The production build (109s) and the client-dist upload exist only to feed
  the Cypress job, which already skips pull requests that touch nothing it
  exercises. On those pull requests the artifact was uploaded and never
  downloaded.

The changed-path decision now lives in .gitea/scripts/cypress-changes.sh and
both jobs call it, so the build job's gate cannot drift from the Cypress job's
skip. The script keeps the existing fail-open rule: anything it cannot decide
(no base ref, failed fetch, failed diff) writes no output, and every caller
gates on `!= 'false'`, so the work still runs.
2026-11-04 18:13:23 +00:00