Skip to content

Commit b4b1ecf

Browse files
committed
feat: add min version of main app
1 parent 2b3c72c commit b4b1ecf

3 files changed

Lines changed: 48 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ Each feature folder needs a `feature.json` or `config.json` manifest:
185185
{
186186
"name": "foodorders",
187187
"version": "0.0.1",
188+
"minCoreVersion": "0.3.0",
188189
"navbar": {
189190
"insert": true,
190191
"href": "/foodorders",
@@ -202,7 +203,7 @@ Each feature folder needs a `feature.json` or `config.json` manifest:
202203
}
203204
```
204205

205-
On startup the app scans `installed_features/<featureName>`. Folder presence enables the feature. If the feature version is newer than `config/features/<featureName>.json`, or if the installed config does not exist yet, the feature files are copied into the application.
206+
On startup the app scans `installed_features/<featureName>`. Folder presence enables the feature. `minCoreVersion` defines the oldest compatible SimpleStrichliste version. Incompatible source versions are skipped and logged without stopping startup or replacing an already installed compatible version. If the feature version is newer than `config/features/<featureName>.json`, or if the installed config does not exist yet, the feature files are copied into the application.
206207

207208
Supported feature folders include application folders such as `api`, `lib`, `src`, `views`, `public`, and `config`. A top-level `templates` folder is installed into `config/templates`. Feature page translations live in:
208209

lib/features.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ const featureConfigDir = path.join(appRoot, 'config', 'features');
77
const featureMigrationRoot = path.join(appRoot, 'migrations', 'features');
88
const featureSeedRoot = path.join(appRoot, 'seeds', 'features');
99
const localsMapPath = path.join(appRoot, 'config', 'locals_map.js');
10+
const coreVersion = require(path.join(appRoot, 'package.json')).version;
1011

1112
const manifestCandidates = ['feature.json', 'config.json'];
1213

1314
/**
1415
* @typedef {Object} FeatureManifest
1516
* @property {String} name Safe feature identifier used for settings, config files, and install paths.
1617
* @property {String} version Feature version used to decide whether an installed feature should be updated.
18+
* @property {String} minCoreVersion Minimum SimpleStrichliste version required by the feature.
1719
* @property {Object} navbar Navbar metadata used when a feature adds a navigation entry.
1820
* @property {Object} adminCard Admin feature-card metadata for the admin overview.
1921
* @property {Object} db Database migration and seed file references.
@@ -108,6 +110,7 @@ const normalizeManifest = (manifest, fallbackName) => {
108110
...manifest,
109111
name,
110112
version: manifest.version || '0.0.0',
113+
minCoreVersion: manifest.minCoreVersion || '0.0.0',
111114
navbar: {
112115
insert: manifest.navbar?.insert === true,
113116
href: manifest.navbar?.href || `/${name}`,
@@ -167,6 +170,14 @@ const compareVersions = (left, right) => {
167170
return 0;
168171
};
169172

173+
/**
174+
* Checks whether a feature supports the currently running core application version.
175+
* @param {FeatureManifest} manifest
176+
* @returns {Boolean} True when the feature supports the running core version.
177+
*/
178+
const isCoreCompatible = (manifest) =>
179+
compareVersions(coreVersion, manifest.minCoreVersion) >= 0;
180+
170181
/**
171182
* Resolves a feature-provided relative install path and prevents writes outside the app root.
172183
* @param {String} relativePath Relative path from the application root.
@@ -296,6 +307,18 @@ const installFeatures = () => {
296307
? normalizeManifest(readJsonFile(installedConfigPath), sourceManifest.name)
297308
: null;
298309

310+
if (!isCoreCompatible(sourceManifest)) {
311+
const retainedVersion = currentManifest
312+
? ` Existing installed version ${currentManifest.version} remains active.`
313+
: '';
314+
process.log?.error?.(
315+
`[FEATURES] Skipping ${sourceManifest.name}@${sourceManifest.version}: requires ` +
316+
`SimpleStrichliste ${sourceManifest.minCoreVersion} or newer; running ${coreVersion}.` +
317+
retainedVersion
318+
);
319+
return;
320+
}
321+
299322
if (currentManifest && compareVersions(sourceManifest.version, currentManifest.version) <= 0) {
300323
return;
301324
}
@@ -364,4 +387,5 @@ module.exports = {
364387
loadFeatureDefinitions,
365388
getFeaturePublicFilePath,
366389
compareVersions,
390+
isCoreCompatible,
367391
};

test/features.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const test = require('node:test');
2+
const assert = require('node:assert/strict');
3+
4+
require('module-alias/register');
5+
6+
const { isCoreCompatible } = require('@lib/features');
7+
8+
test('features accept compatible minimum core versions', () => {
9+
assert.equal(isCoreCompatible({
10+
name: 'compatible',
11+
version: '1.0.0',
12+
minCoreVersion: '0.3.0',
13+
}), true);
14+
});
15+
16+
test('features reject newer minimum core versions', () => {
17+
assert.equal(isCoreCompatible({
18+
name: 'future-feature',
19+
version: '1.0.0',
20+
minCoreVersion: '999.0.0',
21+
}), false);
22+
});

0 commit comments

Comments
 (0)