mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
724c88c201
A mechanical prettier --write over server/src, server/tests, server/scripts and shared/src with the workspaces' .prettierrc; nothing but layout and import order changes, and every file keeps the same set of imports. shared is formatted throughout and its format baseline is empty. The server baseline keeps the 35 files that Prettier would grow past their lint:size limit (they need a split first, which is not a formatting change), and the generated entity files stay as their generator writes them.
158 lines
6.7 KiB
TypeScript
158 lines
6.7 KiB
TypeScript
import {
|
|
hostVersion,
|
|
normalizedHost,
|
|
isValidTrekRange,
|
|
minTrekOf,
|
|
hostSatisfies,
|
|
trekRangeBypassed,
|
|
bypassedRange,
|
|
__resetHostWarningForTests,
|
|
} from '../../../src/nest/plugins/install/host-compat';
|
|
|
|
import { describe, it, expect, afterEach, vi } from 'vitest';
|
|
|
|
/**
|
|
* The TREK-version gate (#plugins). Every install front door and the activation gate
|
|
* answer "can this plugin run here?" through these functions, so their edges are the
|
|
* edges of the whole feature.
|
|
*/
|
|
|
|
const APP_VERSION = process.env.APP_VERSION;
|
|
afterEach(() => {
|
|
if (APP_VERSION === undefined) delete process.env.APP_VERSION;
|
|
else process.env.APP_VERSION = APP_VERSION;
|
|
delete process.env.TREK_PLUGINS_IGNORE_TREK_RANGE;
|
|
__resetHostWarningForTests();
|
|
});
|
|
|
|
describe('isValidTrekRange', () => {
|
|
it('accepts the ranges a plugin actually declares', () => {
|
|
for (const r of ['>=3.2.0 <4.0.0', '^3.2.0', '~3.2', '>=3', '<4.0.0', '3.x', '*']) {
|
|
expect(isValidTrekRange(r), r).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('rejects a range no version can ever satisfy — validRange() alone would accept it', () => {
|
|
// Syntactically fine, semantically empty. A plugin declaring this would be
|
|
// uninstallable everywhere with nothing to tell its author why.
|
|
expect(isValidTrekRange('>=4.0.0 <3.0.0')).toBe(false);
|
|
});
|
|
|
|
it('rejects junk without letting semver.minVersion throw out of it', () => {
|
|
for (const r of ['latest', 'v3-ish', '3.2+', '', ' ', undefined, null, 42, {}]) {
|
|
expect(isValidTrekRange(r), String(r)).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('minTrekOf', () => {
|
|
it('reads the lower bound off the RANGE, not off the first version-shaped substring', () => {
|
|
// The regression this replaces: the old regex took the first X.Y.Z in the string, so
|
|
// "<4.0.0" reported a *minimum* of 4.0.0 — the exact inverse of what the plugin says.
|
|
expect(minTrekOf('<4.0.0')).toBeUndefined(); // lower bound is 0.0.0 → nothing to show
|
|
expect(minTrekOf('>=3.2.0 <4.0.0')).toBe('3.2.0');
|
|
expect(minTrekOf('^3.2.0')).toBe('3.2.0');
|
|
expect(minTrekOf('~3.2')).toBe('3.2.0');
|
|
expect(minTrekOf('>=3')).toBe('3.0.0');
|
|
expect(minTrekOf('>=3.2.0-beta.1')).toBe('3.2.0-beta.1');
|
|
});
|
|
|
|
it('has no lower bound to show for an unbounded range', () => {
|
|
expect(minTrekOf('*')).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('hostSatisfies', () => {
|
|
it('admits a host inside the range and refuses one outside it', () => {
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0', '3.3.0')).toBe(true);
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0', '3.2.0')).toBe(true);
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0', '3.1.9')).toBe(false);
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0', '4.0.0')).toBe(false); // the exclusive upper bound
|
|
});
|
|
|
|
it('treats a prerelease host as its target release — both edges matter', () => {
|
|
// Plain semver would fail 3.4.0-rc.1 against a range that names no prerelease, which
|
|
// would make EVERY plugin incompatible the moment TREK shipped an rc.
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0', '3.4.0-rc.1')).toBe(true);
|
|
// And includePrerelease would order 4.0.0-rc.1 BEFORE 4.0.0, so it would satisfy
|
|
// "<4.0.0" — loading a plugin that disclaims TREK 4 onto a build that already carries
|
|
// TREK 4's breaking changes. An rc of 4.0 IS 4.0 for compatibility purposes.
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0', '4.0.0-rc.1')).toBe(false);
|
|
expect(hostSatisfies('>=4.0.0', '4.0.0-rc.1')).toBe(true);
|
|
});
|
|
|
|
it('never blocks when the host version is not semver at all (APP_VERSION=dev)', () => {
|
|
// The Docker build ARG defaults to the literal "dev". An unversioned build must stay
|
|
// fully usable rather than refusing every plugin it has.
|
|
process.env.APP_VERSION = 'dev';
|
|
expect(normalizedHost()).toBeNull();
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0')).toBe(true);
|
|
expect(hostSatisfies(null)).toBe(true);
|
|
});
|
|
|
|
it('warns once — and only once — about an unparseable host, so a misconfig is visible', () => {
|
|
process.env.APP_VERSION = 'nonsense';
|
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
normalizedHost();
|
|
normalizedHost();
|
|
expect(warn).toHaveBeenCalledTimes(1);
|
|
expect(warn.mock.calls[0][0]).toMatch(/not a semver version/);
|
|
warn.mockRestore();
|
|
});
|
|
|
|
it('refuses an undeclared or unsatisfiable range rather than waving it through', () => {
|
|
// The caller decides what an absent range MEANS (reject at install, "unknown" at
|
|
// activation) — it must not pass by accident here.
|
|
expect(hostSatisfies(null, '3.3.0')).toBe(false);
|
|
expect(hostSatisfies('', '3.3.0')).toBe(false);
|
|
expect(hostSatisfies('>=4.0.0 <3.0.0', '3.3.0')).toBe(false);
|
|
});
|
|
|
|
it('reads the running version from APP_VERSION, falling back to the package version', () => {
|
|
process.env.APP_VERSION = '3.9.1';
|
|
expect(hostVersion()).toBe('3.9.1');
|
|
expect(hostSatisfies('>=3.2.0 <4.0.0')).toBe(true);
|
|
expect(hostSatisfies('>=4.0.0')).toBe(false);
|
|
|
|
delete process.env.APP_VERSION;
|
|
expect(hostVersion()).toMatch(/^\d+\.\d+\.\d+/); // package.json
|
|
});
|
|
});
|
|
|
|
/**
|
|
* TREK_PLUGINS_IGNORE_TREK_RANGE: an operator's explicit "install it anyway" for a plugin
|
|
* whose author has not updated its `trek` range. The gates warn instead of refusing, but
|
|
* the truth (hostSatisfies) never changes — the UI still has to say the plugin is
|
|
* outside its range.
|
|
*/
|
|
describe('range bypass (TREK_PLUGINS_IGNORE_TREK_RANGE)', () => {
|
|
it('is off by default and only an explicit truthy value turns it on', () => {
|
|
expect(trekRangeBypassed()).toBe(false);
|
|
process.env.TREK_PLUGINS_IGNORE_TREK_RANGE = '0';
|
|
expect(trekRangeBypassed()).toBe(false);
|
|
process.env.TREK_PLUGINS_IGNORE_TREK_RANGE = '1';
|
|
expect(trekRangeBypassed()).toBe(true);
|
|
});
|
|
|
|
it('reports a bypassed mismatch only when the bypass is on AND the range does not admit the host', () => {
|
|
process.env.APP_VERSION = '3.3.0';
|
|
expect(bypassedRange('>=4.0.0')).toBeNull(); // off → the gate refuses, nothing to report
|
|
process.env.TREK_PLUGINS_IGNORE_TREK_RANGE = 'true';
|
|
expect(bypassedRange('>=4.0.0')).toEqual({ trekRange: '>=4.0.0', hostVersion: '3.3.0' });
|
|
expect(bypassedRange('>=3.2.0 <4.0.0')).toBeNull(); // fits → not a bypass, a normal install
|
|
});
|
|
|
|
it('covers a plugin that declared no range at all — the same author oversight', () => {
|
|
process.env.APP_VERSION = '3.3.0';
|
|
process.env.TREK_PLUGINS_IGNORE_TREK_RANGE = '1';
|
|
expect(bypassedRange(null)).toEqual({ trekRange: null, hostVersion: '3.3.0' });
|
|
expect(bypassedRange(undefined)).toEqual({ trekRange: null, hostVersion: '3.3.0' });
|
|
});
|
|
|
|
it('leaves hostSatisfies telling the truth while bypassed', () => {
|
|
process.env.APP_VERSION = '3.3.0';
|
|
process.env.TREK_PLUGINS_IGNORE_TREK_RANGE = '1';
|
|
expect(hostSatisfies('>=4.0.0')).toBe(false);
|
|
});
|
|
});
|