chore(deps): track client/.npmrc and run the generator explicitly
Build / Build-and-ng-test (pull_request) Successful in 5m5s
Build / Build-and-test-development (pull_request) Failing after 4m25s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m21s

Dependency install hooks now stay off in client/, as they already are at the root
and in sas/: client/.npmrc is committed with ignore-scripts and save-exact, the
root install script runs `npm run postinstall` after installing, and both CI
installs pass --ignore-scripts and then run the generator by hand. The client's
own postinstall is what creates src/environments/version.ts and the hot icons, so
it has to run - it is third-party hooks that do not.

CI writes client/.npmrc from the NPMRC secret; the second job now appends rather
than overwrites so the tracked policy survives (the first already appended).

The archiver guard reads the archive back instead of checking its size: a matcher
that takes the top-level file and misses the nested directory clears any size
threshold, and that is exactly the failure it exists to catch. It now asserts
a.txt and sub/b.txt are both present.
This commit is contained in:
dc committed 2026-10-31 03:33:19 +00:00
1 parent 786d15ab0f
commit c97082e468
5 files changed
+32 -16

No files matched your search

+9 -5
View File
@@ -51,9 +51,10 @@ jobs:
gpg --batch --yes --passphrase-fd 0 \
--output ./libraries/sheet-crypto.tgz \
--decrypt ./libraries/sheet-crypto.tgz.gpg
# no --ignore-scripts here: the client's install scripts generate
# src/environments/version.ts, which the app imports
npm ci
# dependency hooks stay off (client/.npmrc); the client's own
# generator runs explicitly, because the app imports the file it makes
npm ci --ignore-scripts
npm run postinstall
- name: Check audit
# Audit should fail and stop the CI on any vulnerability in root and sas, and on low+ in client
@@ -130,7 +131,7 @@ jobs:
- name: Write .npmrc file
run: |
touch client/.npmrc
echo '${{ secrets.NPMRC}}' > client/.npmrc
echo '${{ secrets.NPMRC}}' >> client/.npmrc
- name: Install system dependencies
run: |
@@ -153,7 +154,10 @@ jobs:
cd client
# Decrypt and Install sheet
echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
npm ci
# dependency hooks stay off (client/.npmrc); the client's own
# generator runs explicitly, because the app imports the file it makes
npm ci --ignore-scripts
npm run postinstall
- name: Build frontend for web streaming
# The server-ci target streams client/dist as the 'clickme' web app (see
-1
View File
@@ -20,7 +20,6 @@ sasjsbuild
sasjsresults
.env*
.sasjsrc
client/.npmrc
*~
.lighthouseci
audit.log
+5
View File
@@ -0,0 +1,5 @@
ignore-scripts=true
save-exact=true
# dependency install hooks are skipped: the client's own postinstall generates
# src/environments/version.ts and the hot icons, so run `npm run postinstall`
# after installing (the root install script does this for you)
+17 -9
View File
@@ -3,13 +3,17 @@
* The override forces minimatch 11 where readdir-glob asks for ^10. An
* incompatible API would only show up at runtime, in the one place that walks a
* directory through readdir-glob: archiver's directory(), which exceljs uses to
* build an xlsx. This archives a small tree and checks the archive holds both
* files, so a broken override fails here rather than in a user's download.
* build an xlsx.
*
* The archive is read back and both entries are asserted, because the failure
* worth catching is a matcher that takes the top-level file and silently misses
* the nested one.
*/
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { ZipArchive } from 'archiver'
import unzipper from 'unzipper'
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dc-arch-'))
fs.writeFileSync(path.join(dir, 'a.txt'), 'hello')
@@ -24,16 +28,20 @@ archive.on('error', (err) => {
console.error('archiver error: ' + err.message)
process.exit(1)
})
sink.on('close', () => {
const bytes = fs.statSync(out).size
/* two tiny files plus central directory; an empty or truncated archive is far
smaller, which is what a broken glob matcher would produce */
if (bytes < 200) {
console.error('archive is too small: ' + bytes + ' bytes')
sink.on('close', async () => {
const listing = await unzipper.Open.file(out)
const names = listing.files.map((f) => f.path)
const missing = ['a.txt', 'sub/b.txt'].filter((n) => !names.includes(n))
if (missing.length) {
console.error(
'archiver directory walk missed: ' + missing.join(', ') + ' (got: ' + names.join(', ') + ')'
)
process.exit(1)
}
console.log('archiver directory walk ok: ' + bytes + ' bytes')
console.log('archiver directory walk ok: ' + names.join(', '))
})
archive.pipe(sink)
archive.directory(dir, false)
archive.finalize()
+1 -1
View File
@@ -15,7 +15,7 @@
"got": "11.8.6"
},
"scripts": {
"install": "cd client && npm i && cd ../sas && npm i",
"install": "cd client && npm i && npm run postinstall && cd ../sas && npm i",
"build-frontend": "cd client && npm run build",
"lint": "cd client && npm run lint",
"lint:fix": "cd client && npm run lint:fix",