@itrocks/skin
v0.1.0
Published
Enables it.rocks applications to be skinned with custom templates, styles, and images
Maintainers
Readme
skin
Enables it.rocks applications to be skinned with custom templates, styles, and images.
Status
The deterministic resolver validates file-specific and package-wide skin rules. The composed runtime integrations transparently replace final HTML templates, CSS stylesheets, and JPG or PNG images while preserving their public paths and native template or HTTP handling. The package is validated and ready for use; implementation evidence is tracked in the development plan.
Installation
npm i @itrocks/skinApplications declare package-wide and file-specific replacements in the skin section of their config.yaml, or in
the config.yaml of a package that contributes a skin. The framework merges package and application configuration
through @itrocks/config.
YAML keys and values beginning with @ must be quoted:
skin:
'@itrocks/home': /app/home
'@itrocks/list/feed.html': /app/list/my-feed.html
'@itrocks': '@my/skin'Only final .html, .css, .jpg, and .png files used at runtime are eligible. Authoring files under src/ and
SCSS sources are outside this package's responsibility.
Paths beginning with / will be relative to the application root. Paths beginning with ./ will keep the existing
@itrocks/config behaviour and be resolved relative to the package that declares them.
Package skins and application overrides
A standalone skin package depends on @itrocks/skin, publishes a replacement tree, and contributes a package rule:
# @demo/blue-skin/config.yaml
skin:
'@itrocks/home': ./contentThe replacement tree keeps the published paths of the artifacts it replaces, including build directories:
content/
└── cjs/
├── container.html
└── output.htmlAn application can keep that package skin and override one published artifact. Application configuration is loaded
last by @itrocks/config, and the exact rule has priority over the package rule:
# application config.yaml
skin:
'@itrocks/home/output.html': ./output.htmlCopyable configuration layouts are available in the minimal application and the standalone skin package.
Resolver
Create the resolver from the merged skin configuration, validate it once during bootstrap, then resolve final files
before reading or serving them:
import { appDir } from '@itrocks/app-dir'
import { config } from '@itrocks/config'
import { SkinResolver } from '@itrocks/skin'
const resolver = new SkinResolver(config.skin ?? {}, appDir)
const validation = await resolver.validate()
if (!validation.valid) {
throw new Error(validation.issues.map(issue => issue.message).join('\n'))
}
const resolution = resolver.resolve(templateFile, 'template')
const file = resolution.replacement ?? resolution.originalresolve() accepts template, style, and image resources. It ignores files under src/ and unsupported
extensions. Exact published paths win over build-directory aliases, which win over package rules. Package rules are
partial: when the corresponding target artifact is absent, resolution silently falls back to the original artifact.
An exact file rule remains strict because it explicitly promises one replacement file.
Targets and source rules containing traversal or mixed separators are rejected. Validation also resolves symbolic links and rejects any source or target that escapes its allowed package or application root.
Validation issues contain a stable code, the offending rule, and an actionable message. Applications should
fail bootstrap when valid is false rather than accepting traffic with an invalid rule or target root.
Source rules and replacement layouts
The following rule forms are accepted. <relative> is the complete published path below the source package, such as
cjs/views/feed.html, css/theme.css, or images/logo.png.
| Source rule | Scope | Paths searched below the target |
|---------------------------------|--------------------------|----------------------------------------------------|
| @itrocks/home/<relative> | One exact artifact | The configured target file |
| @itrocks/home/views/feed.html | One HTML or CSS alias | The configured target file |
| @itrocks/home | One package | <relative> |
| @itrocks | Every package in a scope | @itrocks/home/<relative>, then home/<relative> |
HTML aliases omit one initial cjs/ or html/ directory. CSS aliases omit one initial cjs/ or css/ directory.
Images have no alias. A complete exact rule takes priority over an alias, which takes priority over a package rule,
which takes priority over a namespace rule.
Unscoped packages use the equivalent home/<relative> and home source forms. Namespace rules apply only to scoped
packages and therefore always start with @.
Folder rules accept these target roots:
| Configured target | Resolved target root |
|-------------------|-----------------------------------------|
| /app/skin | <appDir>/app/skin |
| @my/skin | <appDir>/node_modules/@my/skin |
| @myappnamespace | <appDir>/node_modules/@myappnamespace |
For example, @itrocks: @my/skin searches a request for @itrocks/home/cjs/page.html first at
node_modules/@my/skin/@itrocks/home/cjs/page.html, then at node_modules/@my/skin/home/cjs/page.html. If neither
file exists, it uses the original node_modules/@itrocks/home/cjs/page.html without an error.
Diagnostics
Runtime diagnostics are disabled by default through the exported debug boolean. Enable them explicitly while
investigating replacement searches:
import { debug, setDebug } from '@itrocks/skin'
console.log(debug) // false
setDebug(true)setDebug(false) disables them again. The setter keeps the exported live debug value synchronized for both
CommonJS and ES module consumers. Existing applications can alternatively enable the composed integrations through
configuration:
skinDiagnostics: trueThe composed template and Fastify integrations then write one console.debug entry whenever a matching rule starts a
replacement search. Each entry contains the original physical file, the matching source-to-target configuration, all
candidate replacement files in search order, and the selected replacement or explicitly labelled original fallback.
Disable debugging after diagnosis because physical paths are intentionally included.
Library callers can collect structured events without console output:
const resolver = new SkinResolver(config.skin ?? {}, appDir, {
diagnostic: event => audit.push(event)
})Structured events expose rule, candidates, and final in addition to the normal resolution result.
Runtime integration
The package composes @itrocks/template:Template with its skin-aware implementation through config.yaml. It resolves
the requested final template and optional container, then delegates parsing unchanged to Template. Includes inherit
the composed class, relative includes use the replacement directory, and collected head dependencies keep the native
template-engine behaviour.
The composed SkinFastifyServer intercepts only final .css, .jpg, and .png requests. It resolves their physical
replacement, translates it back to an it.rocks static path, and delegates the response to Fastify. Public URLs, MIME
types, caching statuses, missing-file responses, and unconfigured assets therefore retain the native server behaviour.
Package rules preserve the complete published path, including images referenced relative to a replaced CSS file. An exact CSS rule does not infer image replacements; each image needs its own exact rule. SCSS, JavaScript, TypeScript, SVG, WOFF2, and front-script discovery remain untouched.
The dedicated integration subpaths avoid circular facade loading.
The base configuration also seeds an empty skin object before dependent skin packages are merged. This lets
@itrocks/config rebase their nested ./... target paths against the declaring package.
Compatibility
The release is validated against the package's current runtime stack:
| Component | Supported | Validated |
|-----------|-----------|-----------|
| Node.js | >=24 | 24.19.0 |
| TypeScript | ^7.0 | 7.0.2 |
| Fastify | 5.x | 5.12.0 through @itrocks/fastify 0.2.7 |
| Template engine | current @itrocks/template | 0.2.3 |
Development
Run the complete resolver, template, HTTP, merged-configuration, and bootstrap suite:
npm testInspect the publication allow-list before publishing:
npm pack --dry-run --jsonThe archive contains config.yaml, the README, license, compiled JavaScript, and declarations. TypeScript sources,
tests, source maps, examples, documentation sources, and caches are excluded.
