This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
The following commit(s) were added to refs/heads/main by this push:
new c80a2e7 ci: add unit-test job + harden bundled_templates path for prod
c80a2e7 is described below
commit c80a2e7044e1f1517a8f2f10e8f7c34e135e7fd1
Author: Wu Sheng <[email protected]>
AuthorDate: Sun May 17 07:55:05 2026 +0800
ci: add unit-test job + harden bundled_templates path for prod
CI:
- New `Unit tests (workspaces)` job runs `pnpm -r run test:unit`
on PR + push to main; rolled into the `Required` gate so branch
protection blocks on test failures, not just type-check + build.
- test:unit scripts now bake in `vitest run` (one-shot); a new
test:unit:watch script keeps the dev-watch entry point. Avoids
fragile `--` flag forwarding through pnpm in CI.
Loaders (prod-bundle fix):
The earlier `'..', '..'` fix worked in dev (tsx-watched .ts files)
but broke under esbuild — every import collapses into
`/app/dist/server.js`, so `__dirname` is `/app/dist/` and two `..`
lands at `/bundled_templates` (does not exist; Dockerfile copies
templates to `/app/bundled_templates`).
logic/layers/loader.ts and logic/overview/loader.ts now probe
candidate directories in order and use the first that exists:
1. `<__dirname>/../../bundled_templates/<sub>` (dev: src layout)
2. `<__dirname>/../bundled_templates/<sub>` (prod: dist sibling)
3. `<cwd>/bundled_templates/<sub>` (CLI-style override)
Same behavior on disk for both environments, no Dockerfile change
needed. 40 BFF + 67 UI tests still green; tsc / vue-tsc clean.
---
.github/workflows/ci.yaml | 25 ++++++++++++++++++++++-
apps/bff/package.json | 3 ++-
apps/bff/src/logic/layers/loader.ts | 37 +++++++++++++++++++++++++++++------
apps/bff/src/logic/overview/loader.ts | 25 +++++++++++++++++------
apps/ui/package.json | 3 ++-
5 files changed, 78 insertions(+), 15 deletions(-)
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index ab7b0f8..9823ddb 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -92,13 +92,35 @@ jobs:
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @skywalking-horizon-ui/bff build
+ test:
+ name: Unit tests (workspaces)
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ - uses: pnpm/action-setup@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'pnpm'
+ - run: pnpm install --frozen-lockfile
+ # `pnpm -r` walks every workspace package and runs `test:unit` —
+ # currently @skywalking-horizon-ui/ui (vitest + jsdom) and
+ # @skywalking-horizon-ui/bff (vitest, node env). Packages without
+ # the script (api-client, design-tokens) are skipped automatically.
+ # The scripts already include `vitest run`, so no `--run` forwarding
+ # needed — keeps the CI line robust against pnpm-flag parsing quirks.
+ - run: pnpm -r run test:unit
+
required:
# Status-check name pinned to "Required" so Apache branch protection
# (`.asf.yaml: protected_branches.main.required_status_checks.contexts:
# [Required]`) can gate on a single rolled-up signal.
if: always() && !cancelled()
name: Required
- needs: [license-header, type-check, build-ui, build-bff]
+ needs: [license-header, type-check, build-ui, build-bff, test]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
@@ -108,3 +130,4 @@ jobs:
[[ ${{ needs.type-check.result }} == 'success' ]] || exit 1
[[ ${{ needs.build-ui.result }} == 'success' ]] || exit 1
[[ ${{ needs.build-bff.result }} == 'success' ]] || exit 1
+ [[ ${{ needs.test.result }} == 'success' ]] || exit 1
diff --git a/apps/bff/package.json b/apps/bff/package.json
index de0a505..1760b8e 100644
--- a/apps/bff/package.json
+++ b/apps/bff/package.json
@@ -10,7 +10,8 @@
"cli:hash": "tsx src/cli/hash.ts",
"type-check": "tsc --noEmit",
"lint": "eslint . --ext .ts,.cjs,.mjs --fix --ignore-path
../../.gitignore",
- "test:unit": "vitest --root src/"
+ "test:unit": "vitest run --root src/",
+ "test:unit:watch": "vitest --root src/"
},
"dependencies": {
"@fastify/cookie": "^11.0.1",
diff --git a/apps/bff/src/logic/layers/loader.ts
b/apps/bff/src/logic/layers/loader.ts
index 979a382..b95990e 100644
--- a/apps/bff/src/logic/layers/loader.ts
+++ b/apps/bff/src/logic/layers/loader.ts
@@ -306,12 +306,37 @@ export const BOOSTER_ENDPOINT_DEP_DEFAULTS:
EndpointDependencyConfig = {
};
const __dirname = dirname(fileURLToPath(import.meta.url));
-// Bundled layer JSONs live alongside other static templates under
-// `apps/bff/src/bundled_templates/`. In the long run these should be
-// served by OAP; for now they ship inside the BFF so a fresh deploy
-// renders something sensible without any operator setup. Two levels
-// up from logic/layers/ to reach apps/bff/src/.
-const CONFIG_DIR = join(__dirname, '..', '..', 'bundled_templates', 'layers');
+/** Locate bundled_templates/layers/ at runtime.
+ *
+ * - **Dev** (tsx-watched source files): this file lives at
+ * `apps/bff/src/logic/layers/loader.ts`, so two `..` reaches
+ * `apps/bff/src/bundled_templates/`.
+ * - **Prod** (esbuild bundle): every import collapses into
+ * `/app/dist/server.js`, so `__dirname` is `/app/dist/` and only
+ * one `..` reaches the Dockerfile's `/app/bundled_templates/`.
+ *
+ * We probe candidates in order — first existing wins. `process.cwd()`
+ * is the last fallback for operators running the bundle from an
+ * arbitrary working dir with the templates beside them. */
+function locateConfigDir(): string {
+ const candidates = [
+ join(__dirname, '..', '..', 'bundled_templates', 'layers'),
+ join(__dirname, '..', 'bundled_templates', 'layers'),
+ join(process.cwd(), 'bundled_templates', 'layers'),
+ ];
+ for (const dir of candidates) {
+ try {
+ readdirSync(dir);
+ return dir;
+ } catch {
+ /* try next */
+ }
+ }
+ // Last-resort default — first candidate. The first readdir of an
+ // empty result will surface the underlying ENOENT to the operator.
+ return candidates[0];
+}
+const CONFIG_DIR = locateConfigDir();
let cache: Map<string, LayerTemplate> | null = null;
diff --git a/apps/bff/src/logic/overview/loader.ts
b/apps/bff/src/logic/overview/loader.ts
index 0e93363..ae78189 100644
--- a/apps/bff/src/logic/overview/loader.ts
+++ b/apps/bff/src/logic/overview/loader.ts
@@ -35,12 +35,25 @@ import type {
} from '@skywalking-horizon-ui/api-client';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
-// Bundled overview dashboards live under `bundled_templates/overviews/`
-// (sibling to `bundled_templates/layers/`). Co-locating the static
-// templates keeps the future "operator-editable / OAP-served" migration
-// to one directory swap. Two levels up from logic/overview/ to reach
-// apps/bff/src/.
-const CONFIG_DIR = path.join(__dirname, '..', '..', 'bundled_templates',
'overviews');
+/** Mirrors the dev-vs-prod path probing in `logic/layers/loader.ts`
+ * — see that file for the rationale. */
+function locateConfigDir(): string {
+ const candidates = [
+ path.join(__dirname, '..', '..', 'bundled_templates', 'overviews'),
+ path.join(__dirname, '..', 'bundled_templates', 'overviews'),
+ path.join(process.cwd(), 'bundled_templates', 'overviews'),
+ ];
+ for (const dir of candidates) {
+ try {
+ fs.readdirSync(dir);
+ return dir;
+ } catch {
+ /* try next */
+ }
+ }
+ return candidates[0];
+}
+const CONFIG_DIR = locateConfigDir();
let cache: OverviewDashboard[] | null = null;
diff --git a/apps/ui/package.json b/apps/ui/package.json
index 0edc7a8..a0a32b6 100644
--- a/apps/ui/package.json
+++ b/apps/ui/package.json
@@ -10,7 +10,8 @@
"preview": "vite preview",
"type-check": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix
--ignore-path ../../.gitignore",
- "test:unit": "vitest --environment jsdom --root src/"
+ "test:unit": "vitest run --environment jsdom --root src/",
+ "test:unit:watch": "vitest --environment jsdom --root src/"
},
"dependencies": {
"@skywalking-horizon-ui/api-client": "workspace:*",