]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commit
refactor(charging-station): config assets single source of truth (#2157)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Thu, 29 Oct 2026 23:43:13 +0000 (00:43 +0100)
committerGitHub <noreply@github.com>
Thu, 29 Oct 2026 23:43:13 +0000 (00:43 +0100)
commit26dfa87ec2ad901249b88b90923e19f7797fac61
tree5d3fdbb33737ed188c6d2db512b3c7ae3ca43b2b
parent696754a298ed0b446fa5fc633e0192767ccebbdf
refactor(charging-station): config assets single source of truth (#2157)

* refactor(charging-station): config assets single source of truth

Replace every join(dirname(fileURLToPath(import.meta.url)), "assets", ...)
asset path with a single resolveAssetsDir() helper in src/resolveAssetsDir.ts.
It honors the EVSE_ASSETS_DIR override, otherwise resolves the assets folder
that sits beside the helper - dist/assets in the bundle, src/assets via tsx.

- Configuration.ts, HelpersConfig.ts (templates, id-tags, EV profiles),
  Bootstrap.ts (assetsDir) and UIMCPServer.ts (schema dir) now use it.
- start:dev(+debug/prof) run src/start.ts through tsx with no build step, so
  dev reads and reloads src/assets directly; production start stays bundled.
- README documents editing src/assets (dist is a build artifact) plus override.

Configuration defaults are preserved: src/assets/config.json is absent from
sources, so the runtime still falls back to the default configuration.

* [autofix.ci] apply automated fixes

* refactor(charging-station): route remaining schema loaders and config diagnostics through resolveAssetsDir

Address PR review: MCPResources.getSchemaBaseDir and OCPPServiceUtils.parseJsonSchemaFile now resolve via resolveAssetsDir() (honoring EVSE_ASSETS_DIR); drop the assets/ prefix from createPayloadOptions so relativePath is relative to the assets dir. The missing-config error now interpolates the resolved configurationFile path.

* [autofix.ci] apply automated fixes

* test(charging-station): add unit tests for resolveAssetsDir assets-dir resolver

Add a unit test for the single source of truth that resolves the assets
directory (EVSE_ASSETS_DIR override; absolute paths used as-is; relative
paths resolved against cwd; module-side fallback). It locks the documented
absolute-used-as-is contract and closes the coverage gap on the resolver.

* chore(charging-station): gitignore dev-generated station config state

Dev mode now resolves assets from src/assets (single source of truth), so the simulator's provisioned station configs and .simulator-state.json are written to src/assets/configurations/ at runtime. Previously (bundle dev) this state landed in dist/assets (already ignored). Ignore the generated directory to keep the working tree clean.

* fix(charging-station): route cert-manager base path via resolveAssetsDir; qualify README asset paths

Review follow-up (MEDIUM findings):
- OCPP20CertificateManager: BASE_CERT_PATH now join(resolveAssetsDir(),
  'configurations') instead of hardcoded 'dist/assets/configurations'
  (which resolved to a nonexistent directory in tsx/dev/tests mode).
  Certificate-manager test cleanup paths now use resolveAssetsDir() too.
- README: charging-station configuration paths now expressed as
  <assets-dir>/configurations, where <assets-dir> is dist/assets in
  production and src/assets in development (locations, hierarchy, section
  header), so developers no longer look in dist/ in dev mode.

* test(assets): relocate resolveAssetsDir test to mirror source tree

resolveAssetsDir.ts lives at the src/ root (required by its
<module-dir>/assets resolution), so its test belongs at the tests/
root, not tests/utils/. Move tests/utils/resolveAssetsDir.test.ts to
tests/resolveAssetsDir.test.ts and adjust the relative import depth.

* docs(assets): make JSDoc asset-path references mode-neutral

Cross-validation of the asset-centralization review findings surfaced
JSDoc comments still assuming the production layout. Express them in
mode-neutral terms consistent with resolveAssetsDir():
- HelpersConfig: drop "bundled" from the id-tags/ev-profiles resolvers;
  buildTemplateName references the station-templates subdirectory of the
  assets directory.
- Statistics: template-index reconstruction path now
  <assets-dir>/configurations/ instead of dist/assets/configurations/.

* test(assets): make path assertions platform-robust (Windows)

Asset-path centralization (resolveAssetsDir) makes asset paths absolute,
which exposed POSIX-only path handling in two tests on Windows:
- OCPP20CertificateManager: the serial-sanitization test parsed the cert
  path with split('/').pop(), which on Windows returns the whole
  drive-qualified path (contains ':'); use path.basename() instead.
- resolveAssetsDir: the absolute-override tests used a POSIX-only
  '/tmp/...' fixture that isAbsolute() rejects on Windows; build the
  fixture with path.resolve(cwd, ...) so it is absolute on every platform.

* fix(assets): address PR #2157 review findings

- hermetic tests: tests/stubs/assets fixture (junctions to read-only src/assets sub-trees + writable configurations/) wired via EVSE_ASSETS_DIR in test scripts
- centralize OCPP schema base dir (getOcppSchemaBaseDir) used by MCPResources, UIMCPServer, OCPPServiceUtils
- isPathWithinBase: platform-safe sep-based prefix guard
- resolveAssetsDir tests: strict module-relative assertions
- README: EVSE_ASSETS_DIR layout/writability/pre-start, prod/dev asset-dir mapping, persistState generation
- parseJsonSchemaFile: document SyntaxError throw

* [autofix.ci] apply automated fixes

* docs: relocate EVSE_ASSETS_DIR contract from README to resolveAssetsDir JSDoc

The README is user-facing documentation; the expected asset layout,
writability requirement, and set-before-start timing for the
EVSE_ASSETS_DIR override are developer-facing details. Document the
full contract in the resolveAssetsDir.ts module JSDoc instead, and
keep the README limited to the user workflow (edit src/assets, dist
rebuilt by pnpm build, configurations/ persists across builds).

* test: run the suite hermetically against the assets fixture

The suite asserted configuration defaults while reading whatever
resolveAssetsDir() found: pnpm test from the repo root worked, but
standalone runs (node --import tsx --test <file>) hit the
user-generated src/assets content — a user config.json failed the
suite, and the certificate-manager test wrote into
src/assets/configurations.

- src/resolveAssetsDir.ts: fail fast with AssetsDirError when the
  EVSE_ASSETS_DIR override points to a missing or non-directory path
  (the <module-dir>/assets fallback stays the trusted default);
  export OCPP_SCHEMA_DIR (<assets-dir>/json-schemas/ocpp)
- OCPP 1.6 / 2.0 payload validators use OCPP_SCHEMA_DIR instead of
  hardcoded relative strings
- tests/stubs/testEnvBootstrap.mjs (new): node --import module for the
  test scripts — sets EVSE_ASSETS_DIR to the absolute fixture path
  (honoring an explicit user override), NODE_ENV=test, and ensures the
  fixture layout
- tests/stubs/setupAssetsFixture.mjs: split into a non-destructive,
  concurrency-safe ensureAssetsFixture() and the direct-entry
  full-regeneration setupAssetsFixture()
- package.json: test / test:debug / test:coverage load the bootstrap
  via --import instead of a cwd-relative cross-env EVSE_ASSETS_DIR
- Configuration.test.ts and OCPP20CertificateManager.test.ts import the
  bootstrap first: standalone runs of these files are hermetic too
- tests/resolveAssetsDir.test.ts: real temp dirs (mkdtemp) instead of
  relative paths never created on disk; AssetsDirError tests
- README: the <assets-dir>/configurations note now matches the runtime
  (mkdir recursive on write, not a startup generation); idTagsFile /
  evProfilesFile documented relative to <assets-dir> instead of
  src/assets

* fix: restore --test-reporter-destination in the test:coverage script

The script rewrite accidentally renamed the longstanding
--test-reporter-destination flag to --test-coverage-destination,
which Node 22.x/24.x do not know (CI failed with
'node: bad option: --test-coverage-destination=coverage/lcov.info').
Verified: the exact script runs on Node 22.23.4 and 26.12.1 and
produces a populated lcov file.

* refactor: move AssetsDirError to src/exception per review findings

- Relocate AssetsDirError to src/exception/AssetsDirError.ts to follow the
  repository error-class convention (BaseError, OCPPError, TimeoutError +
  barrel); re-export it from resolveAssetsDir.ts so the module API is
  unchanged.
- Correct the assertAssetsDir JSDoc: call sites span process startup,
  per-station setup, and per-transaction paths; the per-call cost is one
  statSync only when an override is set (no syscall on the no-override
  fallback).
- Assert the full AssetsDirError message (name + message) in
  tests/resolveAssetsDir.test.ts, following the repository's strict
  error-message assertion convention.

* fix: derive station configuration and diagnostics paths from the resolved assets layout

ChargingStation: derive the per-station configuration file from the
template file's parent directory (the assets directory) instead of a
string replace of 'station-templates', which broke EVSE_ASSETS_DIR
overrides whose path contains 'station-templates'.

OCPP16 GetDiagnostics: resolve the log scan and the diagnostics
archive against the working directory (where winston writes the logs
and where the archive is created) instead of module-relative, so the
command works in dev-from-source; drop the now-unused fileURLToPath
import.

README: note in the <assets-dir> definition that EVSE_ASSETS_DIR may
point to a custom assets directory (absolute, or relative to the
working directory).

* refactor: address remaining review findings on assets resolution

- Rename OCPP_SCHEMA_DIR to OCPP_SCHEMA_SUBDIR (it is a relative path
  fragment of the assets directory, not a directory path)
- Drop the unused AssetsDirError re-export (canonical barrel: src/exception)
- JSDoc: document the layout as a contract not validated by the resolver;
  correct the ui-protocol/ claim (source-tree-only, artifact for humans)
- README: source-managed content rebuild wording; backtick <assets-dir>
  in 3 table cells; idTagsFile/evProfilesFile are file names at the root
  of the assets directory (basename-only resolution)
- Bootstrap JSDoc: unset-or-empty override; warn about stale fixture
  content on manual full-suite runs; document the first-import contract
  and the string-equality fixture check
- Tests: cover the relative-missing override branch (raw and resolved
  paths in the error message); assert AssetsDirError messages by
  substrings per the suite convention instead of duplicating the format

* [autofix.ci] apply automated fixes

* refactor: centralize asset path derivation in resolveAssetsDir helpers

- add getAssetFilePath, getConfigurationsDir, getStationTemplatesDir and getConfigFilePath so every <assets-dir>/<subpath> is derived from one join primitive

- Bootstrap, HelpersConfig, Configuration and OCPP20CertificateManager use the derived helpers instead of join(resolveAssetsDir(), ...)

- AssetsDirError: drop the redundant name override (BaseError sets name via new.target.name)

- README: fix the \`<assets-dir>\`/configurations wording to <assets-dir>/configurations

- tests: cover getOcppSchemaBaseDir with and without EVSE_ASSETS_DIR

- apply pnpm format (prettier + eslint) on the changed files

* refactor: derive the OCPP 1.6 schema path via getAssetFilePath

parseJsonSchemaFile now uses the single join primitive from resolveAssetsDir, dropping the last direct join(resolveAssetsDir(), ...) call site and the unused node:path import. No behavior change.

* fix(assets): preserve nested persistence and cache paths

* chore: leave changelog updates to automation

* fix(ocpp20): canonicalize certificate asset roots

* test(assets): handle physical cwd through aliases

* fix(ocpp20): use native certificate root resolution

* fix(assets): preserve selected filesystem root

Cache physical asset identity without changing raw overrides.
Keep logical template subpaths consistent across selected aliases.

* fix: normalize aliased template identities consistently

* test: anchor alias lifecycle fixture to native temp root

* fix: share pinned asset roots with workers

Keep later workers on the selected assets revision after an alias
retarget. Preserve logical template namespaces across isolates.

* fix(test): avoid removing live fixture links

Keep ensure non-destructive. Let explicit setup repair conflicting
entries before readers start.

* ci: select stable Node versions for latest jobs

The latest alias can select prereleases rejected by the existing
engine check. Use a checked SemVer range without renaming checks.

* docs: clarify custom assets layout

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
28 files changed:
.github/workflows/ci.yml
.gitignore
README.md
package.json
src/charging-station/Bootstrap.ts
src/charging-station/ChargingStation.ts
src/charging-station/HelpersConfig.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
src/charging-station/ocpp/2.0/OCPP20CertificateManager.ts
src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts
src/charging-station/ocpp/OCPPServiceUtils.ts
src/charging-station/ui-server/UIMCPServer.ts
src/charging-station/ui-server/mcp/MCPResources.ts
src/exception/AssetsDirError.ts [new file with mode: 0644]
src/exception/index.ts
src/resolveAssetsDir.ts [new file with mode: 0644]
src/types/Statistics.ts
src/utils/Configuration.ts
tests/charging-station/Bootstrap.test.ts
tests/charging-station/ChargingStation-ConfigurationPersistence.test.ts [new file with mode: 0644]
tests/charging-station/ocpp/2.0/OCPP20CertificateManager.test.ts
tests/resolveAssetsDir.test.ts [new file with mode: 0644]
tests/stubs/assets/.gitignore [new file with mode: 0644]
tests/stubs/setupAssetsFixture.mjs [new file with mode: 0644]
tests/stubs/setupAssetsFixture.test.ts [new file with mode: 0644]
tests/stubs/testEnvBootstrap.mjs [new file with mode: 0644]
tests/utils/Configuration.test.ts