@open-xchange/app-suite-ui-sdk
v0.1.7
Published
Shared build, lint and test tooling for OX App Suite UI projects
Maintainers
Keywords
Readme
@open-xchange/app-suite-ui-sdk
Shared build, lint and test tooling for OX App Suite UI projects: core ui, plugins, customer plugins and services with a UI build.
A project depends on this one package instead of vite, eslint, stylelint, vitest, lefthook and their plugins. A tool bump or a security fix becomes one SDK release. Renovate then rolls it out to every project with one automerged MR.
Setup
pnpm add -D @open-xchange/app-suite-ui-sdk
pnpm exec ui-sdk doctor --fix
pnpm installdoctor --fix removes the tooling the SDK provides from package.json, points the scripts at ui-sdk, and writes the pnpm settings the SDK needs into pnpm-workspace.yaml. docs/migration.md walks through moving an existing project, including what doctor leaves to you.
For renovate, the project's renovate.json only extends the SDK preset:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["local>openxchange/appsuite/web-foundation/renovate:app-suite-ui-sdk"]
}The preset automerges updates, and after an SDK bump it runs ui-sdk doctor --fix in the same MR. SDK minor releases below 1.0.0 and SDK majors wait for review.
Commands
| Command | Runs | Default arguments |
|---|---|---|
| ui-sdk dev | vite dev server | |
| ui-sdk build | vite build | |
| ui-sdk test | vitest | --run |
| ui-sdk lint | eslint | . --cache |
| ui-sdk stylelint | stylelint | src/**/*.{scss,css} --allow-empty-input |
| ui-sdk msgmerge [po] [pot] | gettext msgmerge for each po file | src/i18n/*.po dist/ox.pot |
| ui-sdk doctor [--fix] | checks the project setup, --fix writes the changes | |
| ui-sdk audit [--fix] | pnpm audit, --fix first writes only the SDK security overrides into pnpm-workspace.yaml and reinstalls | |
Arguments after the command replace the defaults and go straight to the tool, for example ui-sdk lint src spec --fix. msgmerge needs gettext installed.
The tools come from the SDK's own dependencies. After ui-sdk doctor --fix, the publicHoistPattern makes pnpm link vite, vitest, eslint, stylelint and tsc into node_modules/.bin, so pnpm exec vite works too. Scripts use ui-sdk, which falls back to the SDK config when the project has none; plain vite does not.
Configs
Without a config file in the project, ui-sdk uses the SDK defaults. To change them, add the file and start from the SDK export:
// vite.config.js
import { defineConfig } from '@open-xchange/app-suite-ui-sdk/vite'
export default defineConfig({
id: 'gpg-ui',
name: 'GPG UI',
tailwind: true,
gettext: { poFiles: 'src/i18n/*.po', outFile: 'ox.pot', defaultDictionary: 'io.ox/gpg/i18n', defaultLanguage: 'en_US' },
vite: { build: { sourcemap: true } }
})To copy files into place when vite starts, for example assets from node_modules into public/, pass copy targets. Each match of src lands in dest under its own name, folders are copied as folders, and rename and transform change the file name and content:
defineConfig({
copy: [
{ src: 'node_modules/pdfjs-dist/cmaps', dest: 'public/pdfjs' },
{ src: 'node_modules/flag-icons/flags/4x3/*', dest: 'public/flags', rename: name => `${name}.webp`, transform: svg => toWebp(svg) }
]
})With an own vite config, import copyPlugin from '@open-xchange/app-suite-ui-sdk/vite-plugin-copy' gives the same plugin.
defineConfig builds the usual plugin setup: src as root and dist as output, the dev proxy to SERVER from .env, manifests, $ externals, css and po2json. Vitest runs with jsdom on spec/**/*_test.{js,mjs} and src/**/*.test.{js,mjs}. options.vite and options.test are merged over the defaults.
// eslint.config.js
import config from '@open-xchange/app-suite-ui-sdk/eslint'
export default [...config]// stylelint.config.js
import config from '@open-xchange/app-suite-ui-sdk/stylelint'
export default { ...config, rules: { ...config.rules } }# lefthook.yml
extends:
- node_modules/@open-xchange/app-suite-ui-sdk/configs/lefthook.ymlWhat doctor checks
packageManagerandengines.nodematch the SDK.devDependencieslists no tooling that the SDK or@open-xchange/lintprovides, and none of the tools the SDK replaced (release-it, lint-staged, simple-git-hooks, husky, eslint-config-standard). The same packages independenciesonly get a warning, because the project may need them at runtime.- Workspace packages from the
packageslist inpnpm-workspace.yamlget the same dependency check. Their scripts only get warnings, because the SDK is a dependency of the workspace root. A package without"private": trueis published and builds on its own, so it keeps its tooling and only gets warnings. - Scripts call
ui-sdkinstead of the tools.--fixrewrites single commands likevite buildoreslint srcand keeps their arguments. Scripts that chain commands, pipe or redirect get a warning and need a manual change. pnpm-workspace.yamlhaspublicHoistPattern(so editors and test files find eslint, stylelint, typescript, vite and vitest, and the project's own configs still find every SDK package doctor removed frompackage.json),allowBuilds,minimumReleaseAgeExcludefor@open-xchange/*(pnpm otherwise refuses a freshly released SDK version, which blocks the renovate bump), and the SDK security overrides.lefthook.yml(or.lefthook.yml) extends the SDK config.renovate.json(or.renovaterc.json,renovate.json5and the other names renovate reads) extends theapp-suite-ui-sdkpreset.--fixcreates the file when it is missing. An existing file without the preset only gets a warning, because projects keep their own rules there.- rlsy is installed globally, unless the project has its own
releasescript. That is a warning only.
ui-sdk doctor exits non-zero while errors remain, so a CI job that runs it fails on drift. Add that job to the project's pipeline.
Security overrides
pnpm reads overrides only from the project root, so the SDK cannot pin a patched transitive version for its consumers by itself. Instead, security-overrides.json lists the overrides, and doctor --fix writes them into the project's pnpm-workspace.yaml with a # managed by ui-sdk comment. Managed overrides that the SDK no longer lists are removed again. An override the project set itself always wins, and doctor only warns about it.
A daily scheduled pipeline (with SECURITY_OVERRIDES=true) finds the overrides itself: it installs the packed SDK into a temporary project, runs pnpm audit --fix there and adds every new override to the list. If it finds one, it opens an MR with a Security: commit that merges once its pipeline passes, test projects included.
To add an override by hand, add it here and commit it with the Security: prefix. A push to main with such a commit releases right away instead of waiting for the weekly release. The renovate SDK bump in each project then runs ui-sdk doctor --fix as a post-upgrade task and automerges. For an urgent fix in one project, run pnpm exec ui-sdk audit --fix.
Releasing
Releasing stays out of the SDK. Projects release with a globally installed rlsy (pnpm add -g rlsy) and get no release script.
The SDK itself releases once a week. A scheduled pipeline on main runs rlsy --yes, which picks the version from the commits since the last release and counts renovate's "Update dependency" commits as changes. A security change does not wait: when a push to main brings a commit with [SECURITY] (renovate's security updates) or a Security: commit, the release job releases right away. Running a pipeline on main by hand also releases, from the GitLab UI or with glab ci run -b main. The tag the release job pushes starts the publish job.
Decisions
Agreed on 2026-11-03.
- One package with everything. Projects without a vite build install the build tooling too. One version to bump matters more than install size.
- Tooling only. Runtime libraries stay in the projects or come from core ui as
$externals. - Exact pins, no SDK lockfile. pnpm ignores a dependency's lockfile, and bundling the dependencies fails on platform-specific binaries (rolldown, sass, lefthook). Each project's own lockfile keeps its build reproducible.
- One CLI.
ui-sdkwraps every tool, so all projects run the same scripts. It has no release command. - Config factories with overrides. Projects keep only what differs from the defaults.
doctor --fixwrites visible settings intopackage.jsonandpnpm-workspace.yamlinstead of injecting them at install time.- Same tools everywhere. lefthook, the SDK's pnpm and Node version, enforced by
ui-sdk doctorin CI. @open-xchange/lintstays its own package. The SDK pins it and re-exports it.- Semver, released weekly. Security changes release right away. A tool bump that breaks consumers is a major. Consumers automerge minor and patch updates and review majors.
- Test projects gate releases. Every SDK pipeline installs the packed SDK into the projects in test-projects and runs
doctor --fix,lint,stylelint,test,buildandauditthere. A core ui pipeline per SDK MR was too heavy for every dependency bump. - Renovate everywhere through the
app-suite-ui-sdkpreset in web-foundation/renovate. Every project has the shortrenovate.jsonfrom Setup. - e2e is out of scope and gets its own SDK.
- Rollout: core ui first, then the other vite projects, then the customer plugins.
Development
pnpm test # unit tests
pnpm test:projects # packs the SDK and runs it in each test project
pnpm test:projects vite-vue # only one projectpnpm test:projects copies each project from test-projects into a temporary directory, installs the packed SDK there, and deletes the directory afterwards. It needs network access for the install and the audit. The test projects:
customer-plugin: a drifted customer plugin with eslint 8, release-it and no build.vite-vue: vite with Vue, scss, po files and a unit test.vite-tailwind: vite with Tailwind.ui-like: shaped like core ui, with a workspace package, its own lefthook and renovate files, a chained build script, a runtime dependency and no vite config.
License
MIT
