--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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
- 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.
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.
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.
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
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
@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.
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.
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.
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.
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.
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.
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`.
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.
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.
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.
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.
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.
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.
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.
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.
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.