ci(demo): publish release branches to demo.datacontroller.io
Demo preview / deploy (push) Successful in 3m47s
Build / Build-and-ng-test (pull_request) Successful in 5m12s
Lighthouse Checks / lighthouse (pull_request) Successful in 20m18s
Build / Build-and-test-development (pull_request) Successful in 30m56s

Pushes to release/** now build the mocks as a browser-hosted site (SASjs CLI
6.1.0, streamConfig.mockWeb) and publish it to the Surfer app behind
demo.datacontroller.io, so the demo always matches the release branch.

The browser build seeds its drive from the compiled services, which carries no
DC data, so the job runs makedata on a throwaway SASjs Server and ships that
drive as mock-snapshot.json.
This commit is contained in:
dc committed 2026-10-24 16:49:02 +00:00
1 parent 37abc3adb7
commit a128b609f8
5 files changed
+254 -9

No files matched your search

+114
View File
@@ -0,0 +1,114 @@
name: Demo preview
on:
push:
branches:
- 'release/**'
paths-ignore:
- '**.md'
workflow_dispatch:
env:
NODE_VERSION: '24.15.0'
APP_LOC: /Public/app/dc
DEMO_HOST: https://demo.datacontroller.io
# One demo site, so two pushes must not upload over each other. A newer push
# supersedes the deploy in flight rather than interleaving with it.
concurrency:
group: demo-preview
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Write .npmrc file
run: |
touch client/.npmrc
echo '${{ secrets.NPMRC}}' > client/.npmrc
- name: Install client dependencies
run: |
cd client
# Decrypt and Install sheet
echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
npm ci
- name: Build frontend
# The site serves this build. `sasjs build` rewrites its index.html to
# point the adapter at the in-browser backend.
run: |
cd client
npm run postinstall
npm run build
- name: Install the SASjs CLI
# Pinned in sas/package.json - the browser build needs CLI >= 6.1.0
run: |
cd sas
npm ci
- name: Start SASjs server
# Needed only to run makedata: the drive the site ships with is captured
# from a real server (see the snapshot step below).
run: |
curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
unzip -q linux.zip
touch .env
echo RUN_TIMES=js >> .env
echo NODE_PATH=node >> .env
# The server refuses to run as root unless RUN_AS names an account
echo RUN_AS=root >> .env
nohup ./api-linux > server.log 2>&1 &
for i in $(seq 1 60); do
if curl -sf -o /dev/null http://localhost:5000/; then
echo "SASjs server answered on attempt ${i}"
exit 0
fi
sleep 2
done
echo "SASjs server did not come up within 120s"
cat server.log
exit 1
- name: Deploy the mocked services and seed the database
run: |
cd sas/mocks/sasjs
# Start from a clean appLoc so the snapshot below is this build alone,
# not a merge with whatever a previous run left on the drive.
curl -sS -X DELETE "http://localhost:5000/SASjsApi/drive/folder/?_folderPath=$APP_LOC"
../../node_modules/.bin/sasjs cbd -t server-ci
# makedata replies with HTML (it is normally called as a URL redirect),
# which the CLI reports as "invalid Json string" - ignore that.
../../node_modules/.bin/sasjs request services/admin/makedata -t server-ci -d deploy/makedata.json -o ./makedata_out.json || true
- name: Build the browser-hosted site
run: |
# DC's data files exist only after makedata has run, so the drive is
# captured first and shipped as the site's starting drive. services/web9
# is the streamed frontend, which is already the site itself.
node sas/mocks/snapshot.js sasjs_root/drive "$APP_LOC" sas/mocks/sasjsbuild/mock-snapshot.json services/web9
cd sas/mocks/sasjs
../../node_modules/.bin/sasjs b -t server-ci
- name: Publish to demo.datacontroller.io
env:
SURFER_USER: allan
SURFER_PASSWORD: ${{ secrets.DEMO_DATACONTROLLER_IO }}
SURFER_HOST: ${{ env.DEMO_HOST }}
SURFER_MESSAGE: Build from ${{ gitea.ref_name }} @ ${{ gitea.sha }}
run: |
# `deploy` uploads the folder as an archive and swaps the site in one
# step, so a failed upload leaves the current site serving.
npx --yes @cloudron/surfer@7.2.0 deploy \
--server "$SURFER_HOST" \
--username "$SURFER_USER" \
--password "$SURFER_PASSWORD" \
--message "$SURFER_MESSAGE" \
sas/mocks/sasjsbuild/mock
+4
View File
@@ -133,6 +133,10 @@ There is also a SAS 9 mock variant under `sas/mocks/sas9/` (target `sas9-mocks`)
After changing mock services, re-run `sasjs cbd -t server-ci` from `sas/mocks/sasjs/`. After changing the frontend, rebuild (`cd client && npm run build`) then re-deploy so the streaming app picks up the new `client/dist/`. If you are using `ng serve` (step 3), only the mock services need re-deploying; the frontend hot-reloads automatically.
### Demo site
A push to a `release/**` branch builds the mocks as a browser-hosted site (SASjs CLI >= 6.1, `streamConfig.mockWeb`) and publishes it to [demo.datacontroller.io](https://demo.datacontroller.io) - workflow file `.gitea/workflows/demo.yaml`, using the `DEMO_DATACONTROLLER_IO` Surfer app password (user `allan`). The services run in a service worker and the drive lives in the visitor's IndexedDB, so there is no server. The build runs `makedata` on a throwaway SASjs Server first and ships that drive as `sasjsbuild/mock-snapshot.json`; without it the site opens with no data. The mock licence is minted for HTTP only, so the HTTPS demo shows the free-tier banner.
## GUI Elements
For documentation on the Clarity Design System, including a list of components and example usage, see [our website](https://vmware.github.io/clarity).
+92
View File
@@ -0,0 +1,92 @@
// Collect the SASjs Server drive for an app into a mock-snapshot.json, which
// `sasjs build` ships as the browser build's starting drive (the alternative is
// a drive seeded from the compiled services alone, i.e. no DC data).
//
// Usage: node sas/mocks/snapshot.js <drive-root> <appLoc> <out-file>
// drive-root the SASjs Server drive folder (the `drive` folder of a server install)
// appLoc the deployed app location, e.g. /Public/app/dc
// out-file where to write the snapshot, e.g. sas/mocks/sasjsbuild/mock-snapshot.json
//
// Every file is written as a string because the browser runtime's drive holds
// text only; a file that does not survive a UTF-8 round trip is skipped rather
// than mangled.
const fs = require('fs')
const path = require('path')
const [driveRoot, appLoc, outFile, excludeArg] = process.argv.slice(2)
if (!driveRoot || !appLoc || !outFile) {
console.error(
'Usage: node sas/mocks/snapshot.js <drive-root> <appLoc> <out-file> [exclude,exclude]'
)
process.exit(1)
}
const app = '/' + appLoc.replace(/^\/+|\/+$/g, '')
const driveFolder = path.join(
driveRoot,
'files',
...app.split('/').filter(Boolean)
)
const driveKey = `/sasjs/drive/files${app}`
// Comma-separated paths relative to the app, e.g. "services/web9" - the
// streamed frontend is copied into the drive as a service folder, is already the
// site itself, and would otherwise duplicate the whole app inside the snapshot.
const excludes = (excludeArg || '')
.split(',')
.map((value) => value.trim().replace(/^\/+|\/+$/g, ''))
.filter(Boolean)
if (!fs.existsSync(driveFolder)) {
console.error('No drive folder at ' + driveFolder)
process.exit(1)
}
const files = {}
const skipped = []
const walk = (folder) => {
for (const entry of fs.readdirSync(folder, { withFileTypes: true })) {
const absolute = path.join(folder, entry.name)
if (entry.isDirectory()) {
walk(absolute)
continue
}
const relative = path
.relative(driveFolder, absolute)
.split(path.sep)
.join('/')
if (
excludes.some(
(prefix) => relative === prefix || relative.startsWith(prefix + '/')
)
) {
continue
}
const buffer = fs.readFileSync(absolute)
const text = buffer.toString('utf8')
if (!Buffer.from(text, 'utf8').equals(buffer)) {
skipped.push(relative)
continue
}
files[`${driveKey}/${relative}`] = text
}
}
walk(driveFolder)
fs.mkdirSync(path.dirname(path.resolve(outFile)), { recursive: true })
fs.writeFileSync(path.resolve(outFile), JSON.stringify({ files }, null, 2))
console.log(
`Snapshot: ${Object.keys(files).length} file(s) from ${driveFolder} -> ${outFile}` +
(skipped.length
? `, skipped ${skipped.length} non-text file(s): ${skipped.slice(0, 3).join(', ')}`
: '')
)
+43 -8
View File
@@ -6,7 +6,7 @@
"": {
"name": "dc-sas",
"dependencies": {
"@sasjs/cli": "6.0.0",
"@sasjs/cli": "6.1.0",
"@sasjs/core": "6.0.0"
}
},
@@ -165,14 +165,14 @@
}
},
"node_modules/@sasjs/cli": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-6.0.0.tgz",
"integrity": "sha512-nsdL5J+Y5FHZL4pieqQHjsNmY2DZ4gQcGAupR0dv8aGlweEHqdVErtx3B7+EmIiyfRt+oE3YEofzF41WBWL7tg==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-6.1.0.tgz",
"integrity": "sha512-i2CdGS69EM6Sn7XfR1ltBk9Bxz6gEV9xTGSWUmk4eVe+XnJBlYHyg93VgMcx7sWCtzRF9Dv7N2p7eXEc2DHfbQ==",
"license": "ISC",
"dependencies": {
"@sasjs/adapter": "5.1.1",
"@sasjs/core": "6.0.0",
"@sasjs/utils": "4.0.1",
"@sasjs/adapter": "^5.1.1",
"@sasjs/core": "^6.0.0",
"@sasjs/utils": "^4.1.0",
"axios": "1.20.0",
"chalk": "4.1.2",
"dotenv": "18.0.8",
@@ -196,7 +196,42 @@
"node": ">=22"
},
"peerDependencies": {
"@sasjs/lint": "4.3.0"
"@sasjs/lint": "^4.3.0"
}
},
"node_modules/@sasjs/cli/node_modules/@sasjs/utils": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-4.1.0.tgz",
"integrity": "sha512-aC5pOugg6/2jKQR7ZaYsLtbsFy/iVacsrN3/Kzh6BouJUszuVrr13HMrQUSKcWL+rJzxYof7ymmmCl+vE6YwAw==",
"license": "ISC",
"dependencies": {
"@fast-csv/format": "5.0.7",
"@types/fs-extra": "11.0.4",
"@types/prompts": "2.4.9",
"chalk": "4.1.1",
"consola": "3.4.2",
"fs-extra": "11.4.1",
"jwt-decode": "4.0.0",
"prompts": "2.4.2"
},
"engines": {
"node": ">=22"
}
},
"node_modules/@sasjs/cli/node_modules/@sasjs/utils/node_modules/chalk": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@sasjs/core": {
+1 -1
View File
@@ -30,7 +30,7 @@
},
"private": true,
"dependencies": {
"@sasjs/cli": "6.0.0",
"@sasjs/cli": "6.1.0",
"@sasjs/core": "6.0.0"
},
"overrides": {