Commit Graph
148 Commits
Author SHA1 Message Date
allan fb4a325706 Merge pull request 'test(mocks): a chain scenario runs steps in order on one estate' (#524) from fix/mock-chain-scenarios into release/7.23
Sites / deploy (push) Canceled after 26s
Build / Build-and-ng-test (pull_request) Canceled after 22s
Build / Build-and-test-development (pull_request) Canceled after 0s
Build / lighthouse (pull_request) Canceled after 0s
Reviewed-on: #524
2026-11-08 15:03:14 +00:00
dc 3b45f49509 fix: refuse tracked symlinks, and the two lineage findings
Build / Build-and-ng-test (pull_request) Successful in 3m42s
Build / Build-and-test-development (pull_request) Canceled after 7m51s
Build / lighthouse (pull_request) Canceled after 5m46s
--no-verify: this worktree has no gitleaks binary (node_modules is a link to
another clone), so the secrets scan cannot run here. It runs in CI on this
commit's push.

Symlinks: 07615c2a committed node_modules and client/node_modules, two absolute
links into one agent's workspace, force-added past .gitignore by `git add -A`.
They break every other checkout and the Sites workflow installs through them
(npm ci in client/, ../../node_modules/.bin/sasjs).

- .git-hooks/pre-commit refuses a staged symlink, first, before gitleaks: a
  hook that only fires after an optional binary is installed does not help in
  the worktree where the link gets added. Verified by staging one: the commit
  is refused and the path named.
- .gitea/workflows/build.yaml checks the index too (git ls-files -s, mode
  120000), because hooks are suppressed by --no-verify, can be skipped when
  .npmrc sets ignore-scripts, and are client-dependent.

Review 912/916 on #524:

- fetchcollineage no longer publishes RC in info. The estate's info is
  `length coluri colname taburi tabname liburi libref $256` with no return code
  (services_meta/lineage/fetchcollineage.sas:42), and the table-level service
  drops rc explicitly. The comment and the test assertion that called it the
  estate's "no lineage" code are gone.
- splitTable implements the File Reader branch: map_transform='File Reader' with
  a path is split at the last /, so /opt/data/customers.csv is LIBREF
  /opt/data and TABNAME customers.csv, as fetchcollineage.sas:211-218 does. The
  docstring now describes what the code does. New test pins it.

Whole mock suite: 163 pass.
2026-11-08 12:22:05 +00:00
dc db99b5868f test(cypress): clear the session instead of visiting the logout route
The specs opened with `cy.visit(`${hostUrl}/SASLogon/logout`)`. Against a dev
server that only navigated the single-page app: every path answered index.html,
so nothing was logged out. With the app served by the server it talks to, the
route answers 403 - `desktopRestrict` in @sasjs/server refuses it by design, and
desktop is that server's default mode - so the visit failed the before hook and
every spec aborted on its first line.

`cy.clearSession()` clears cookies and localStorage, which is what the visit was
standing in for, and needs nothing from the server. The MODE=desktop line and its
comment go with it: the server already defaults to desktop, so the line changed
nothing and the comment described a mode the job never ran in.

Verified locally against a 4.14.0 estate with the build streamed to
/AppStream/clickme and the mock services seeded: viewer-labels 5/5 (the spec that
failed in CI), filtering 10/10.

Also in this push, three review findings on the harness itself:

- resolve-browser.sh searches ${DC_PATH:-$PATH} through its own loop, so the
  test pins the whole decision to fixtures instead of inheriting a chromium from
  the host; case 2 was passing here and failing on any machine with one.
- the Cypress step requires the exported path (`${CYPRESS_BROWSER:?...}`) rather
  than falling back to a browser name, so a missing export fails with a message
  naming the step that should have set it.
- the step asserts the app is served at /AppStream/clickme before the suite
  starts, because a deploy that streamed no frontend would otherwise surface as
  twenty-six navigation failures.
2026-11-08 00:32:33 +00:00
dc 239e6d2c78 ci: resolve the browser on whichever runner the job lands on
Two runs an hour apart picked different images: b5bd8716 landed on a runner with
this org's job image and its Karma tests passed with the Chromium that image
carries, while bafa0103 landed on one running the stock public image and died
with 'Can not find the binary /usr/local/bin/chromium'. Runners differ, and will
keep differing as shapes change, so neither a fixed path nor a fixed browser
name is safe - hard-coding either turns an unfamiliar runner into a red
pipeline.

.gitea/scripts/resolve-browser.sh detects rather than declares:

  - a browser on the runner   -> use that path, log which one, its version and
                                 that it came from the runner
  - nothing it recognises     -> fetch the pinned Chromium snapshot, verify its
                                 sha256, and log that it did

Detection looks at chromium, chromium-browser, google-chrome and
google-chrome-stable on PATH and in the usual absolute locations, so a runner
that ships a browser under any of those names needs no configuration change.
Cypress, Karma and chrome-launcher all accept a path, so the resolved path is
what gets exported: CHROME_BIN, CHROME_PATH and CYPRESS_BROWSER. A line also
goes to the job summary, so the choice is visible in the run rather than buried
in a log.

The browser is then proved to start before it is handed over. That last part is
from the first run of this change: the fetch worked, the symlink worked, and
Karma still failed with 'error while loading shared libraries: libnspr4.so' -
a snapshot is a bare binary tree, and the libraries a Chrome .deb would have
pulled in are not there. So the shared-library set is installed as part of the
fetch, and a browser that still will not report a version is refused with its
own error message instead of being exported.

The pin is the artefact runner.4gl.io's image installs: snapshots revision
1717300, Chromium 157.0.8103.0, sha256 542445ec... Verified by fetching it here
- 240MB, checksum OK, chrome-linux/chrome inside.

Its test pins four branches - found, found under another name, found but not
runnable, and nothing found - and runs in the build job beside the changed-path
matcher: the wrong answer here reads as a broken image rather than a broken
decision, which is exactly how it cost an hour today.
2026-11-08 00:32:33 +00:00
dc dffdc58a17 ci(cypress): run the estate in desktop mode
Serving the app from the server instead of a dev server exposed something the
dev server had been hiding: the app is configured loginMechanism="Redirected",
and an unauthenticated visit navigates the browser to /SASLogon/logout, which
answers 403 on a server-mode estate. ng serve absorbed that navigation by
serving index.html for every path, so the suite never saw it.

In desktop mode there are no accounts and no permission model - one fixed user -
so /SASjsApi/session answers without auth and there is nothing to redirect for.

Verified locally on 4.14.0 before pushing: streamed build at
/AppStream/clickme, MODE=desktop, seeded with makedata + createdemodata, and
liveness.cy.ts passes both tests ('Login and submit test' 39.6s cold,
'Recovers from a failed startupservice request' immediately). Without the seed
the nav tree is empty and the same spec fails, which is worth knowing.
2026-11-08 00:32:33 +00:00
dc b1fd884d78 ci: use the Chromium in the runner job image instead of installing Chrome
The fleet now runs its jobs on the pinned image from runner.4gl.io, which
carries Chromium at /opt/chromium/chrome-linux/chrome with chromium and
chromium-browser symlinked into /usr/local/bin. Three jobs were still
downloading google-chrome-stable_current from Google on every run:

- Build-and-ng-test: step removed, CHROME_BIN points at the image's chromium
  (Karma finds no browser by name otherwise)
- Build-and-test-development: the .deb download goes, the apt library install
  stays as the safety net; --browser chrome becomes --browser chromium
- lighthouse: step removed, CHROME_PATH set, because lhci launches the browser
  itself through chrome-launcher and would not pick up a binary called
  chromium on its own

The browser is now the version the image pins and verifies by checksum rather
than whatever Google serves as _current on the day, and three downloads per
run are gone.
2026-11-08 00:32:33 +00:00
dc b1a5ad3b30 ci(cypress): drive the suite against the streamed build
The job already downloads the client-dist artefact and streams it to the
server at /AppStream/clickme, then starts an ng serve it could have used
instead. The suite now visits the streamed app:

- hosturl/appLocation are passed to cypress as --env values pointing at
  http://localhost:5000/AppStream/clickme
- the three index.html patches that pointed the dev build at :5000 are gone;
  page and services share one origin, so the repository's serverUrl=""
  (relative requests) is what ships
- ng serve and wait-on are gone with them
- CORS and its WHITELIST go too: with no second origin left, the server no
  longer needs to allow one

The old station rewrote cypress.config.ts with --regex='"hosturl".*', which
never matched the single-quoted hosturl line and was a silent no-op while the
default was the value it wanted; removing ng serve left the suite visiting a
port nothing listens on. --env cannot silently miss.

This is the artefact Lighthouse audits, so both jobs now measure the same
build, and 26 specs no longer pay a dev compile per navigation.
2026-11-08 00:32:33 +00:00
dc 5aa8cd75fc docs(ci): correct the pin rationale after review
Build / Build-and-ng-test (pull_request) Successful in 4m22s
Build / lighthouse (pull_request) Successful in 7m19s
Build / Build-and-test-development (pull_request) Failing after 38m15s
Three claims in the comment block did not survive checking: the 3.14.0
paragraph described the utils floor as still open when 4.14.0 is the release
that raises it, the 3.12.3 paragraph named a guard that does not exist, and
the 4.0.0 paragraph was incomplete.

The logout one is the interesting correction: the estate does call
GET /SASLogon/logout - @sasjs/adapter's logOut() is a GET on that route (client
sas.service.ts logout() -> sasjsAdapter.logOut() -> AuthManager.logOut()), so
the paragraph now says what the estate does touch and why the status change
does not matter to it.
2026-11-07 17:29:01 +00:00
dc 8e2e407837 chore(ci): run the mocked estate on 4.14.0
Build / Build-and-ng-test (pull_request) Successful in 3m23s
Build / Build-and-test-development (pull_request) Canceled after 0s
Build / lighthouse (pull_request) Canceled after 0s
4.14.0 is the first @sasjs/server release that compresses its responses and
sends cache headers for the app it streams (sasjs/server#575), which is what
the audit has been measuring: the app is a 4.83MB build output, and the
previous pin served it raw and uncacheable.

Verified on the published package before pinning it here: 4.14.0 boots, the
mock estate deploys and seeds against it, and a deployed asset now answers

  Content-Encoding: gzip
  Cache-Control: public, max-age=31536000, immutable

with index.html answering max-age=0, must-revalidate. Measured on the previous
pin, same build and machine: FCP 8.48s and 0.34 performance raw, 2.81s and
0.49 with the same bytes compressed.

4.0.0's breaking change is the auth statuses (401 where 403 was, and the
undocumented DELETE on logout is gone). The client treats any non-2xx as a
refusal; the suite running against this pin is the check.

sites.yaml and release.yaml move with it - same mocked estate, one version.
2026-11-07 17:01:11 +00:00
dc 30cb89440f ci(build): publish the Lighthouse scores as one comment on the pull request
Build / Build-and-ng-test (pull_request) Successful in 4m33s
Build / lighthouse (pull_request) Successful in 7m18s
Build / Build-and-test-development (pull_request) Canceled after 7m44s
The audit's numbers were only visible in the job log and the artifact. One
comment per pull request, rewritten in place on every push (the body carries
a marker the script looks for, so a re-run replaces the table instead of
adding a second one), with a row per route and the lowest accessibility
score against the gate.

.gitea/scripts/lighthouse-report.mjs reads the reports, and posts through the
API with the workflow's token; a comment that cannot be posted logs and
leaves the job's verdict alone, because the audit's own step is the gate.
The job needs pull-requests: write (and issues: write, which is where Gitea
keeps pull request comments) for that and nothing else.
2026-11-07 14:40:21 +00:00
dc 87f3e055be docs(lighthouse): bring both config comments in line with the seeded audit
Build / Build-and-ng-test (pull_request) Successful in 4m26s
Build / lighthouse (pull_request) Successful in 6m51s
Build / Build-and-test-development (pull_request) Canceled after 10m16s
The collect comment still argued numberOfRuns:1 from "accessibility 1 - a
fixed score that does not vary with load", which is no longer the threshold
and was only ever true of an empty estate. It now states the trade against
the scores the audit actually measures (0.87-0.96, issue 497).

The assert comment records why the floor is global rather than per route:
lhci strips the fragment, so the seventeen routes are one URL to it and the
assertion aggregates their median - assertMatrix with matchingUrlPattern
cannot express per-page allowances here.

Also drops include-hidden-files from the artifact upload: that is a v4.4
input and v3 warns about it as unexpected on every run.
2026-11-07 14:25:35 +00:00
dc 8d81faef64 fix(build): start the audit's SASjs server without CORS
Build / Build-and-ng-test (pull_request) Successful in 3m59s
Build / lighthouse (pull_request) Failing after 6m41s
Build / Build-and-test-development (pull_request) Canceled after 12m3s
The server refuses to run with CORS=enable and no WHITELIST, which is what
made the job fail at startup. CORS is not needed here and was only ever
needed because the audit used to run against ng serve on :4200: the app and
the services are both served by this server on :5000, so every call is
same-origin. The Cypress job keeps its CORS setting, because there the
browser really is on another origin.
2026-11-07 11:38:16 +00:00
dc 3c2cb267d2 fix(build): seed the mock estate before the Lighthouse audit
Build / Build-and-ng-test (pull_request) Successful in 4m36s
Build / lighthouse (pull_request) Failing after 3m51s
Build / Build-and-test-development (pull_request) Canceled after 5m45s
The audit's URLs are mostly data views - tables, view/data, the review
lists, excel-maps, multi-load - and the job deployed the services without
the database behind them. makedata builds what a real deployment builds,
createdemodata adds the Cypress fixtures (TESTDATA, the search and wide
viewers, the multi-load library), and both run here before lhci, exactly as
the Cypress job runs them. makedata's payload asks for OUTFMT=JSON, so a
failed seed fails the job instead of auditing an empty estate.
2026-11-07 11:27:36 +00:00
dc 77453a5c05 fix(build): deploy from the sasjs project and audit the appLoc that is deployed
Build / Build-and-ng-test (pull_request) Successful in 4m24s
Build / Build-and-test-development (pull_request) Canceled after 8m24s
Build / lighthouse (pull_request) Canceled after 0s
Two things carried over from lighthouse.yaml verbatim did not hold:

- The job ran cd sas/mocks, which has no sasjsconfig.json. The CLI then
  searched for the project and npm ci installed whatever package.json the
  prefix walk found (sas/, 159 packages), neither of which is what the step
  is for. It now cds into sas/mocks/sasjs, as the Cypress job does, and
  installs nothing: the pinned global CLI carries @sasjs/core.

- The built copy was patched to appLoc /proj/sasjs/genesis-mocks, but the
  only target this job deploys - server-ci - publishes services at
  /Public/app/dc, which is also what the production build already carries.
  The audited app was therefore calling a path with no services behind it.
  The patch step goes: the build's own appLoc is the deployed one, so the
  two cannot drift, and the unused replace-in-files install goes with it.
2026-11-07 11:09:57 +00:00
dc 2e5b02aa51 ci(build): run the Lighthouse audit in the build workflow
Build / Build-and-ng-test (pull_request) Successful in 4m26s
Build / lighthouse (pull_request) Failing after 2m12s
Build / Build-and-test-development (pull_request) Canceled after 12m37s
The audit needs the production build, and an artifact belongs to the run that
produced it (needs: cannot cross workflows), so as a separate workflow it
compiled the frontend a second time - 92-104s measured - and then started an
ng serve that nothing audited: every URL in client/lighthouserc.js is on
:5000, which the SASjs server feeds from client/dist.

As a job in build.yaml it takes the change decision from the build job
(the same inputs gate both), downloads client-dist, and patches appLoc in the
built index.html instead of compiling a patched copy of the source. The
second compile, the dev server and the source generator in this job are gone;
what remains is node_modules for lhci, the pinned CLI, the SASjs server and
the mock estate deploy.

The check name moves from 'Lighthouse Checks / lighthouse' to
'Build / lighthouse'; there are no required contexts to update. The
changed-path matcher already covers everything this job reads, so its only
change is a comment and the fixture that used to assert lighthouse.yaml was
not an input.
2026-11-07 10:46:11 +00:00
allan 78b7de7120 Merge pull request 'refactor(mocks): build the control library makedata.sas builds' (#487) from feat/mocks-makedata-parity into release/7.23
Sites / deploy (push) Successful in 3m58s
Build / Build-and-ng-test (pull_request) Successful in 5m5s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m5s
Build / Build-and-test-development (pull_request) Failing after 48m9s
Reviewed-on: #487
2026-11-06 14:43:40 +00:00
dc 5f89314aaf fix(mocks): answer the review on #487
Build / Build-and-ng-test (pull_request) Successful in 3m57s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m13s
Build / Build-and-test-development (pull_request) Canceled after 22m0s
- createdemodata drops the unused servicesDir binding.
- the DC996664 copy no longer swallows a failure: a broken multi-load fixture
  now fails the seed where it happened instead of surfacing in a Cypress spec.
- makedata answers OUTFMT=JSON with a makedata_response member (as the SAS
  service does) and the CLI payload asks for it, so the `|| true` comes off
  makedata in build.yaml, release.yaml and sites.yaml - a failed seed names the
  service that failed instead of reappearing as "createdemodata needs the
  control library".
- the makedata comment points at sas/mocks/test/makedata-parity.test.js, not a
  macro that does not exist.
2026-11-06 13:36:26 +00:00
dc a6cfb12579 refactor(mocks): build the control library makedata.sas builds
Build / Build-and-ng-test (pull_request) Successful in 4m26s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 4m56s
makedata seeded two things at once: the control library, and the tables the
Cypress suite needs. The estate it produced was therefore not the estate a
SAS deployment produces - and it had drifted: MPE_MAXKEYVALUES and
MPE_SIGNOFFS were never created, and MPE_CONFIG, MPE_VALIDATIONS, MPE_TABLES,
MPE_SELECTBOX and MPE_XLMAP_RULES were missing seed rows that mpe_makedata.sas
inserts.

makedata now transcribes mpe_makedatamodel.sas and mpe_makedata.sas: every
model table with its columns, and every seed row the macro writes. The
Cypress fixtures move to services/admin/createdemodata.js, which the test
estate runs after makedata; it strips and re-adds its rows so a reseed does
not accumulate.

Checked against real SAS output on the shared estate (fresh deploy of the
server target, makedata, then getrawdata for each control table and a
dictionary.columns probe): all 40 tables and 339 columns match on name, type,
length, format and label, and the seeded rows match row for row apart from
the environment (libref, user id, group names) and the sample rows. That run
found six defects a source-only comparison could not: INT columns are 8 bytes
not 4, one label had lost a double space, the DC_MACROS setting held the SAS
expression instead of a path, the email templates held an escaped newline,
75 selectbox rows held %mf_increment(rk) instead of their key, and one
validation row was truncated at a ';' inside its regex.

makedata-parity.test.js parses both macros and compares them with the seeded
estate, and fails on a value that does not fit its column type, so the next
drift fails a test instead of shipping. The CI workflows, the Sites demo seed
and CONTRIBUTING call createdemodata after makedata.
2026-11-06 13:29:14 +00:00
dc 88616c8f62 ci(build): reuse the client npm cache and the Cypress binary in the Cypress job
Build / Build-and-ng-test (pull_request) Successful in 3m47s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m37s
Build / Build-and-test-development (pull_request) Successful in 32m31s
The job installed the same lockfile the build job had just installed and paid
the downloads again (92s), and npx cypress install fetched its binary on every
run. Both now come from the cache the build job already fills, with the
Cypress binary keyed on the lockfile.
2026-11-06 09:44:42 +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 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
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
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 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
dc e1c3c6e872 fix(mocks): answer the deploy screen's DIRLIST and WRITECHECK actions
Lighthouse Checks / lighthouse (pull_request) Successful in 9m45s
Build / Build-and-ng-test (pull_request) Failing after 15m6s
Build / Build-and-test-development (pull_request) Skipped
Staging now opens on the deploy screen, and its folder picker asks
makedata for a listing.  The mock ignored ACTION entirely: every request
seeded the control library, deleted makedata from the drive and replied
with the HTML success page.  So the first BROWSE deployed the site and
closed the wizard the visitor was standing on, behind an "invalid Json
string" error from the adapter, and VALIDATE and SUBMIT then answered
500 "Program not found in drive".

The mock now dispatches on ACTION the way the SAS service does:

  DIRLIST     the folders below PARENT, one level at a time, with the
              dirinfo/dirlist shape mpe_dirlist emits
  WRITECHECK  the checkwrite row mpe_writecheck emits, probed by creating
              and removing a file
  MAKEDATA    the existing seeding path, unchanged

ACTION is required and refused when absent or unknown, and the two browse
actions are refused once the settings file exists, mirroring browse_gate.
Both input routes are accepted - the screen's fromjs table and a CLI
seed's SASControlTable - because %webout(FETCH) does not care which one
arrived; deploy/makedata.json now carries ACTION=MAKEDATA as the real
service requires.

sas/mocks/test/makedata-actions.test.js runs the service file itself
against a temporary estate and is wired into the build workflow.
2026-11-04 14:59:19 +00:00
dc 246cc39ca0 ci: run the mock estate on 3.14.0
Build / Build-and-ng-test (pull_request) Successful in 5m42s
Lighthouse Checks / lighthouse (pull_request) Successful in 11m8s
Build / Build-and-test-development (pull_request) Successful in 29m36s
3.14.0 ships @sasjs/lint 5.0.0, so the studio reports OS commands, and it is the
release the estate runs. It still declares @sasjs/utils ^4.7.0, so the envelope
formats from utils 4.8 (SYSCC as text, DATE7, TIME5, E8601DT26.6) are not yet
guaranteed by the floor - a fresh install resolves 4.8.0, a cached tree need not.
The comment says so, and the pin moves again when the floor lands.

Verified on a local 3.14.0 install (clean, resolving utils 4.8.0): the mock
estate's startupservice returns SYSDATE 04NOV26, SYSTIME 12:44, SYSCC "0" (text),
END_DTTM 2026-11-04T12:44:14.954000 (no Z). With that estate seeded, and against
a fresh app location, licensing.cy.ts passes 10/10 and metanav.cy.ts 1/1 on
Chromium.
2026-11-04 13:06:55 +00:00
allan 8c9b0ff5f9 Merge pull request 'ci(workflows): hand values to the shell as data, not as script text' (#458) from ci/no-shell-interpolation into release/7.22
Sites / deploy (push) Successful in 3m47s
Build / Build-and-ng-test (pull_request) Successful in 5m45s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m55s
Build / Build-and-test-development (pull_request) Canceled after 25m35s
Reviewed-on: #458
2026-11-04 10:40:07 +00:00
dc ba32d1fa46 ci: run the mock estate on the server that reports SYSSITE
Sites / deploy (push) Successful in 4m8s
Build / Build-and-ng-test (pull_request) Successful in 5m7s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m8s
Build / Build-and-test-development (pull_request) Canceled after 19m25s
@sasjs/utils 4.7 makes webout() emit SYSSITE from a configured site id,
defaulting to 123, where %webout takes &syssite. Until 3.13.0 the prelude
wrote an empty string, and dc's client routes an expired licence key and a
wrong-organisation key only when startup_site_id is truthy - so on the mock
estate both pages were unreachable and licensing.cy.ts failed three cases.
2026-11-04 10:15:04 +00:00
dc f0e3117540 ci(workflows): hand values to the shell as data, not as script text
Build / Build-and-ng-test (pull_request) Successful in 6m40s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m8s
Build / Build-and-test-development (pull_request) Successful in 33m40s
Three values reached `run:` blocks through `${{ }}`, so the runner pasted them
into the script before the shell ever saw it - which makes the value part of the
code rather than an argument to it.

- `BASE="${{ github.base_ref }}"` (build.yaml, lighthouse.yaml) - a ref name in
  the script.
- `echo ${{ secrets.SHEET_PWD }} | gpg --passphrase-fd 0` (all four workflows) -
  a passphrase in the script. A quote in it ends the quoting, and a command
  substitution in it runs.
- two `replace-in-files --replacement=...${{ secrets.CYPRESS_* }}...` lines
  (build.yaml, release.yaml) - the same, inside a replacement string.

Each now arrives as a step `env:` value and is expanded as `"$VAR"`, so the shell
only ever sees data. Checked the replacement lines side by side with the old form:
identical for an ordinary value, and the new form keeps a value containing a space
intact where the old one word-splits it, and survives a value containing a single
quote where the old one is a shell syntax error.

The passphrase pipe also moves from `echo` to `printf '%s\n'`, so the byte the
decryption sees does not depend on how the shell's `echo` treats backslashes.
2026-11-04 09:55:22 +00:00
dc 75d32d9838 ci(build): record why the mock estate's server pin is 3.12.3
Build / Build-and-ng-test (pull_request) Successful in 5m10s
Lighthouse Checks / lighthouse (pull_request) Successful in 7s
Build / Build-and-test-development (pull_request) Successful in 10s
The pin moved to 3.12.3 with the webout() change but the comment above it still
named 3.12.2 as the current release - true when it was written, wrong under the
pin it now sits on. The comment also only carried the /ddc-host reason, which
does not explain this bump.

3.12.3 is the first server release declaring @sasjs/utils ^4.6.0, and that is
what gives webout() the options argument getdata, viewdata and getcolvals pass as
{ showmeta }. Say so where the constant is, so the next person to move it knows
the metadata members depend on it.
2026-11-04 00:38:40 +00:00
dc ec92eb4eee ci: run the mock estate on the server that carries the utils floor
Sites / deploy (push) Successful in 3m46s
Build / Build-and-ng-test (pull_request) Canceled after 1m4s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 0s
The services in this branch pass { showmeta } to webout(), which is
@sasjs/utils 4.6 and up. sasjs/server 3.12.3 is the first release whose
package.json asks for ^4.6.0 (a clean install resolves 4.6.1), so the pin
moves from 3.12.2 to 3.12.3 in every workflow that starts the mock estate.

Verified against the published package rather than the release notes:
`npm view @sasjs/server@3.12.3 dependencies.@sasjs/utils` answers ^4.6.0, and
`npm install @sasjs/server@3.12.3` in an empty directory installs
@sasjs/utils 4.6.1, whose webout prelude carries showmeta.
2026-11-04 00:27:34 +00:00
dc 36a2bff086 ci(workflows): install the tooling at the versions this repo chose
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
Review of the release diff: `npm install -g @sasjs/cli` and
`replace-in-files-cli` were unpinned, so CI ran whatever release was current
and a bad one would reach the pipeline unreviewed - next to a SASjs server
that is pinned to an exact version.

The CLI now comes from the version sas/package.json pins, which is the shape
docs.yaml and release-assets.yaml already use, so there is one source for it.
replace-in-files-cli, pm2 and wait-on are pinned to the versions in use
(4.0.0, 7.1.2, 9.5.1).

Both node expressions are checked from the directories they run in:
`require('./sas/package.json')` at the root and `require('../../package.json')`
in sas/mocks/sasjs both answer 6.5.0.
2026-11-04 00:12:46 +00:00
dc cde90830fb chore(ci): record why the client-dist artefact stays on upload-artifact v3
Sites / deploy (push) Successful in 3m50s
Build / Build-and-ng-test (pull_request) Successful in 5m11s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m18s
Build / Build-and-test-development (pull_request) Canceled after 12m58s
2026-11-03 23:07:17 +00:00
dc 7a2b01db84 ci(build): cancel the build for a superseded pull request head
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) Successful in 11m14s
Every push to a pull request queued another full run - lint, licence check, the
mocked estate and the twenty-six minute suite - and nothing cancelled the run for
the head it replaced. With two runners shared by every pull request in the repo,
those stale runs are picked oldest-first and delay the head that can actually be
merged: runs 3002, 3003 and 3005 were queued for heads that had already been
superseded while the current one waited behind them.

The group is the pull request, so a newer push cancels the run for the older
head. Pushes that are not pull requests are not affected - the release workflow
answers to its own group.
2026-11-03 22:48:50 +00:00
dc b41402b1d3 chore(ci): run the mocked estate on 3.12.2
Build / Build-and-ng-test (pull_request) Successful in 5m11s
Lighthouse Checks / lighthouse (pull_request) Successful in 7s
Build / Build-and-test-development (pull_request) Successful in 8s
3.12.2 is the current release and carries the packaged-executable fix for
/ddc-host (the page this job's va-ddc-host spec drives), so the version the
tests run against is the version that ships. 3.12.0 remains the floor for
`timing=early`.
2026-11-03 20:08:58 +00:00
dc 40d1424a53 ci(embed): run va-ddc-host in CI, on a server that has timing=early
Build / Build-and-ng-test (pull_request) Successful in 6m37s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m22s
Build / Build-and-test-development (pull_request) Successful in 27m27s
Closes #449.

- SASJS_SERVER_VERSION 3.6.2 -> 3.12.1 in build.yaml. 3.12.0 is the floor:
  /ddc-host with `timing=early` (the replay that lands before the app
  bootstraps) arrived there, and it is absent from 3.11.3, which has no
  /ddc-host at all.
- DDC_HOST_ENABLED=true in the job's .env. The server 404s /ddc-host unless it
  is set - it is a testing tool and does not announce itself - so enabling the
  spec without this would have failed on the first case.
- va-ddc-host moves from EXCLUDED to the END of SPECS in cypress-specs.sh: it
  clears the DC_JSLIB filter tables before each case, so it must run last on the
  shared mock estate. The ordering contract at the top of that file now records
  that, and metanav's licence key, which no longer has to avoid being last
  itself.
2026-11-03 19:42:25 +00:00
dc 9d40fc56df ci: decide "run the suite" on every failure path, and cover .gitea/scripts
Build / Build-and-ng-test (pull_request) Successful in 5m44s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m0s
Build / Build-and-test-development (pull_request) Successful in 29m54s
Review findings on the change-detection steps and the shared build artefact.

- A failed fetch or diff in "Detect client changes" used to fail the job: the
  fail-safe only covered an output that was absent, not a step that errored.
  Both workflows now exit 0 without writing client=false on any failure, so an
  undecidable change set runs the suite instead of blocking the pull request.
- .gitea/scripts/ joins the change set in both workflows: it carries the spec
  list (cypress-specs.sh) and the runner gate (check-runner-memory.sh), so a
  change to what the suite runs, or to the gate that lets it run, must run it.
- A skipped job now says so in a named step and a notice annotation, rather than
  reporting success from a list of greyed-out steps.
- The dist upload sets if-no-files-found: error, so an empty artefact fails the
  producing job instead of surfacing later as "webSourcePath ... doesn't exist".
- lighthouserc.js records the cost of numberOfRuns: 1 - no median, so one slow
  navigation is a re-run rather than a fix.
2026-11-03 19:13:08 +00:00
dc 3aae36f94d ci: stop paying for the same build, run and audit three times per PR
Build / Build-and-ng-test (pull_request) Successful in 6m43s
Lighthouse Checks / lighthouse (pull_request) Successful in 10m58s
Build / Build-and-test-development (pull_request) Successful in 27m23s
Measured over the last 150 workflow runs on this forge: a pull request costs
56 runner-minutes (Build-and-ng-test 5.8m, Build-and-test-development 31.2m,
lighthouse 19.3m), and when three pull requests are in flight the first job
queues 47-69 minutes before it starts.

- Build-and-ng-test publishes client/dist as an artifact and the Cypress job
  downloads it instead of running a second cold `npm run build` (1.6m each).
  Both jobs test the same bytes.
- lighthouse: numberOfRuns 1 (lhci's default of 3 tripled the 13.6m audit to
  steady a median that only has to clear a fixed threshold), and the duplicate
  /view/usernav/groups entry is removed (17 unique URLs, not 18).
- Cypress records no video in CI and takes screenshots on failure instead
  (`--config video=false,screenshotOnRunFailure=true`, CLI only so local runs
  and the demo recorder still record); the failure artifact carries
  screenshots as well as any video.
- Both workflows detect whether client/ or sas/mocks/ changed and skip their
  expensive steps when neither did - a sas-only or docs-only pull request no
  longer pays 45 minutes for suites that cannot observe it. The gate is
  `!= 'false'`, so a missing step output runs the suite rather than quietly
  reporting green.
2026-11-03 14:03:58 +00:00
dc ab4f4c9b60 ci: fail a client job fast on a runner too small for it
Build / Build-and-ng-test (pull_request) Successful in 6m25s
Lighthouse Checks / lighthouse (pull_request) Successful in 19m12s
Build / Build-and-test-development (pull_request) Successful in 35m37s
An undersized runner does not fail honestly: the kernel OOM killer takes
the build, so the job reports exitcode 137 at ng build, or Karma
disconnects mid-suite after twenty minutes. Both read as a flaky test
rather than as a box that cannot hold the job, and both cost a whole CI
slot.

The check runs first in the three client jobs (Build-and-ng-test,
Build-and-test-development, lighthouse), prints total and available RAM,
vCPU count and any cgroup limit, and exits 1 when the effective size is
under 8 GB - the supported minimum. A cold production build measures
3.8 GB peak on its own, so 8 GB leaves roughly 2x headroom.
2026-11-01 15:47:48 +00:00
dc c8c0858295 fix(lint): make noUnusedMacros an error and clear its 54 findings
Build / Build-and-ng-test (pull_request) Successful in 5m45s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m2s
Build / Build-and-test-development (pull_request) Successful in 31m7s
sas/.sasjslint now promotes noUnusedMacros to "error", so the rule fails
the build rather than printing a warning. The 54 findings it reported are
gone:

- 46 header entries nothing calls are dropped. Each removal was checked
  against the compiled artefacts rather than the linter alone: the
  project was compiled before and after, and the two sasjsbuild trees
  compared. 37 macro definitions disappear from the generated programs -
  every one of them either a duplicate of a definition the file still
  reaches through another declared macro, or a macro no generated program
  calls. No macro that a generated program calls lost its definition.
- 8 entries are kept, in five files that carry
  @sasjslint {"noUnusedMacros": false} with the reason written above it:
  the three serviceInit programs and serviceterm.sas declare mpeinit and
  mpeterm without calling them, because that declaration is how a service
  body reaches those macros at all; loadfile.sas declares mp_binarycopy
  for the fallback that is commented out on SAS 9.4 and later.
- Four headers are left with no macros to declare, so their empty
  <h4> SAS Macros </h4> section is removed rather than kept as a stub.

@sasjs/cli is bumped 6.2.0 -> 6.5.0 in sas/package.json. The lint engine
ships inside the CLI and is not a direct dependency, so the pinned CLI is
what fixes the rule set: 6.2.0 resolves @sasjs/lint 4.3.0, 6.5.0 pins
4.9.0 exactly. Both versions were compiled against this tree and the
sasjsbuild output is byte-identical, so the bump changes the lint engine
and nothing else.

`sasjs lint` is also now reachable as `npm run lint:sas`, and a CI step in
build.yaml runs it. Until now nothing ran SAS lint automatically, and a
bare `sasjs` resolves whatever CLI is installed globally - a different
lint version reports a different rule set, which is why the counts in
issue #376 (taken with 6.2.5) do not match the pinned 6.2.0.

43 warnings remain under the rules not yet promoted: noUndeclaredMacros
(25), noBannedStrings (13), macro name length and case (4),
indentationMultiple (1). They are the next rungs of the ratchet.

The pre-commit Prettier gate could not run: it needs client/node_modules,
which this worktree does not install, and the fix it attempted touched
three client files that this change does not modify (reverted). No client
file is in this commit.
2026-11-01 00:36:05 +00:00
dc c033032e26 chore(ci): cover every .npmrc in the hook, and clean up the registry config
Build / Build-and-ng-test (pull_request) Successful in 5m28s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m51s
Build / Build-and-test-development (pull_request) Successful in 29m10s
Follow-ups from the review of #414.

The credential guard now checks each staged .npmrc rather than only the client
one: the root and sas/ files are tracked policy as well, so a token pasted into
either was equally one `git add -A` away. Verified by staging a token in
sas/.npmrc - the hook exits 1.

$RUNNER_TEMP/npmrc is removed at the end of each job that writes it, with
`if: always()` so a failed job cleans up too.

The root `install` script is gone: the root .npmrc sets ignore-scripts, so npm
never ran it. CONTRIBUTING already spells out the per-project commands, and is
now the single source.

The release workflows build the published client from the same tree, so they run
`check:archiver` like build.yaml does.
2026-10-31 09:55:31 +00:00
allan b9a1d5ac61 Merge pull request 'fix(deps): follow-redirects 1.16.2 and minimatch 11.0.3 for two runtime advisories' (#414) from chore/audit-follow-redirects into release/7.22
Sites / deploy (push) Successful in 3m55s
Build / Build-and-ng-test (pull_request) Successful in 5m12s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m58s
Build / Build-and-test-development (pull_request) Successful in 29m54s
Reviewed-on: #414
2026-10-31 09:52:46 +00:00
dc 761734016d ci: reject a credential in a staged client/.npmrc, and make the guard runnable
Build / Build-and-ng-test (pull_request) Successful in 5m2s
Lighthouse Checks / lighthouse (pull_request) Successful in 17m11s
Build / Build-and-test-development (pull_request) Successful in 27m44s
client/.npmrc is tracked now, so a token left in it from the old pattern is one
`git add -A` from being committed. Prose in CONTRIBUTING is not a control: the
pre-commit hook fails when the staged file holds _authToken, _auth or _password.

`npm run check:archiver` runs the directory-walk guard, so it is reproducible
before a push instead of only in CI, and the CI step calls the same script.

CONTRIBUTING now spells out the per-project installs, since the root .npmrc has
ignore-scripts=true and the composite install script therefore never runs.
2026-10-31 09:14:10 +00:00
dc 3f569d3dae ci: pass the registry secret through the environment, and document the local setup
Build / Build-and-ng-test (pull_request) Successful in 5m0s
Lighthouse Checks / lighthouse (pull_request) Successful in 17m37s
Build / Build-and-test-development (pull_request) Successful in 27m47s
Four write steps interpolated ${{ secrets.NPMRC}} straight into the shell source,
so a quote or $( in the value would break the step or run. They use the env form
the same workflows already use elsewhere.

CONTRIBUTING.md now says what the tracked client/.npmrc means for a developer:
run `npm run postinstall` after installing, `npx cypress install` for the suite,
and keep registry credentials in the user config rather than in the tracked file.
2026-10-31 05:51:01 +00:00
dc d07151fbf5 ci: keep the registry token out of the tracked .npmrc, harden the archiver guard
Build / Build-and-ng-test (pull_request) Successful in 5m8s
Lighthouse Checks / lighthouse (pull_request) Successful in 17m42s
Build / Build-and-test-development (pull_request) Successful in 27m48s
client/.npmrc is tracked now, and every workflow appended the NPMRC secret to it
inside the workspace - one `git add -A` from committing a live token, and
semantic-release runs in jobs that have already written it. The secret goes to
$RUNNER_TEMP/npmrc and NPM_CONFIG_USERCONFIG points the installs at it, so the
project file stays policy-only.

The archiver guard used a fixed path in the shared tmpdir and had no handler on
the write stream: two jobs on one runner collided on the path, and a write
failure surfaced as an uncaught throw. Each run now gets its own directory, both
streams report, and the tree is removed afterwards.

The exceljs override pins archiver to 8.0.0 rather than ^8.0.0, so the version
the guard was validated against cannot drift.
2026-10-31 04:46:11 +00:00