Bump Harbor versions and add direct .ru routing
This commit is contained in:
26
.codex/skills/manage-harbor-versions/SKILL.md
Normal file
26
.codex/skills/manage-harbor-versions/SKILL.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: manage-harbor-versions
|
||||||
|
description: Check and bump Harbor component versions for every runtime, UI, API, dependency, packaging, or deployment-config change in this repository. Use before completing implementation work, release preparation, or any change that can alter the shipped Mac client, Gateway client, or Gateway backend.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Manage Harbor Versions
|
||||||
|
|
||||||
|
Treat `src/shared/versions.js` as the only component-version source. Do not use the root package version as a release version.
|
||||||
|
|
||||||
|
## Required workflow
|
||||||
|
|
||||||
|
1. Inspect the complete diff and choose the comparison base, normally `HEAD` for working-tree changes or the target branch for a review.
|
||||||
|
2. Run `npm run version:harbor -- affected <base>`.
|
||||||
|
3. Classify the highest compatibility impact:
|
||||||
|
- `major`: changes an ecosystem contract or requires all cooperating components and clients to update;
|
||||||
|
- `minor`: changes one component and its tightly linked components while remaining compatible with other clients on the same major;
|
||||||
|
- `hotfix`: changes only the affected component without requiring linked components or other clients to update.
|
||||||
|
4. Run one explicit bump command:
|
||||||
|
- `npm run version:harbor -- bump major`
|
||||||
|
- `npm run version:harbor -- bump minor <components...>`
|
||||||
|
- `npm run version:harbor -- bump hotfix <components...>`
|
||||||
|
5. Run `npm run version:harbor -- check <base>` and the repository tests before completion.
|
||||||
|
|
||||||
|
Valid component names are `mac`, `gateway-client`, and `gateway-backend`. A major bump always updates all three components. A minor bump for either Gateway component automatically updates both Gateway client and Gateway backend. A hotfix updates only the named component.
|
||||||
|
|
||||||
|
Do not bump documentation- or test-only changes. If the version contract is new and the base has no `src/shared/versions.js`, keep the initial versions and let the checker report that no baseline exists.
|
||||||
4
.codex/skills/manage-harbor-versions/agents/openai.yaml
Normal file
4
.codex/skills/manage-harbor-versions/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Manage Harbor Versions"
|
||||||
|
short_description: "Check and bump Harbor component versions."
|
||||||
|
default_prompt: "Use $manage-harbor-versions to classify changes and update the required Harbor component versions."
|
||||||
@@ -3,3 +3,5 @@
|
|||||||
Use the checked-in `workpack/` directory as the only roadmap source. Do not require or read the original archive.
|
Use the checked-in `workpack/` directory as the only roadmap source. Do not require or read the original archive.
|
||||||
|
|
||||||
Follow `workpack/AGENTS.md` for every roadmap task, including status updates. Completed tasks must not be selected or implemented again unless the user explicitly asks to reopen one.
|
Follow `workpack/AGENTS.md` for every roadmap task, including status updates. Completed tasks must not be selected or implemented again unless the user explicitly asks to reopen one.
|
||||||
|
|
||||||
|
For every runtime, UI, API, dependency or deployment-config change, use `.codex/skills/manage-harbor-versions/SKILL.md`. Before completion, classify the affected components, bump the required version level and run `npm run version:harbor -- check <base>`. Documentation- and test-only changes do not require a bump.
|
||||||
|
|||||||
@@ -219,6 +219,8 @@ cd ~/.vpn-proxy-client
|
|||||||
|
|
||||||
Компонентные версии меняются в `src/shared/versions.js`. У всех компонентов должен совпадать `major`, у Gateway client и backend — `major.minor`; `hotfix` может отличаться. Runtime-значения доступны через `GET /api/version`.
|
Компонентные версии меняются в `src/shared/versions.js`. У всех компонентов должен совпадать `major`, у Gateway client и backend — `major.minor`; `hotfix` может отличаться. Runtime-значения доступны через `GET /api/version`.
|
||||||
|
|
||||||
|
Для изменения версии используйте `npm run version:harbor -- affected HEAD`, затем `npm run version:harbor -- bump <major|minor|hotfix> [компонент]` и `npm run version:harbor -- check HEAD`. Правила выбора уровня закреплены в обязательном repo skill `manage-harbor-versions`.
|
||||||
|
|
||||||
## Настройки `.env`
|
## Настройки `.env`
|
||||||
|
|
||||||
Для большинства установок достаточно стандартных значений.
|
Для большинства установок достаточно стандартных значений.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"dev": "vite --host 0.0.0.0",
|
"dev": "vite --host 0.0.0.0",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"test": "node --test",
|
"test": "node --test",
|
||||||
|
"version:harbor": "node scripts/harbor-version.mjs",
|
||||||
"start": "node src/server/index.js"
|
"start": "node src/server/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
177
scripts/harbor-version.mjs
Normal file
177
scripts/harbor-version.mjs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import { execFileSync } from 'node:child_process';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||||
|
import { parseVersion, versionCompatibility } from '../src/shared/versions.js';
|
||||||
|
|
||||||
|
const COMPONENTS = ['macClient', 'gatewayClient', 'gatewayBackend'];
|
||||||
|
const VERSION_FILE = 'src/shared/versions.js';
|
||||||
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
|
const aliases = {
|
||||||
|
mac: 'macClient',
|
||||||
|
'mac-client': 'macClient',
|
||||||
|
client: 'gatewayClient',
|
||||||
|
'gateway-client': 'gatewayClient',
|
||||||
|
backend: 'gatewayBackend',
|
||||||
|
'gateway-backend': 'gatewayBackend',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function versionsFromSource(source) {
|
||||||
|
return Object.fromEntries(COMPONENTS.map((component) => {
|
||||||
|
const match = new RegExp(`${component}:\\s*'(\\d+\\.\\d+\\.\\d+)'`).exec(source);
|
||||||
|
if (!match) throw new Error(`Не найдена версия ${component}`);
|
||||||
|
return [component, match[1]];
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function affectedComponents(files) {
|
||||||
|
const affected = new Set();
|
||||||
|
const add = (...components) => components.forEach((component) => affected.add(component));
|
||||||
|
for (const file of files) {
|
||||||
|
if (file === VERSION_FILE) continue;
|
||||||
|
if (/^(package-lock\.json|src\/shared\/)/.test(file)) add(...COMPONENTS);
|
||||||
|
else if (/^(src\/web\/|public\/|index\.html$|vite\.config\.js$)/.test(file)) {
|
||||||
|
add('macClient', 'gatewayClient');
|
||||||
|
} else if (/^src\/server\//.test(file)) add('macClient', 'gatewayBackend');
|
||||||
|
else if (/^(Dockerfile\.client|docker-compose\.client\.yml|entrypoint\.client\.sh|scripts\/(install-macos-client|harbor-network-monitor)\.sh)$/.test(file)) {
|
||||||
|
add('macClient');
|
||||||
|
} else if (/^(Dockerfile|Dockerfile\.runtime-base|docker-compose\.gateway\.yml|entrypoint\.sh|scripts\/(deploy-gateway|build-runtime-base|build-on-107-deploy-111)\.sh)$/.test(file)) {
|
||||||
|
add('gatewayBackend');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return COMPONENTS.filter((component) => affected.has(component));
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatVersion({ major, minor, hotfix }) {
|
||||||
|
return `${major}.${minor}.${hotfix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bumpVersions(versions, level, requested = []) {
|
||||||
|
const current = Object.fromEntries(COMPONENTS.map((component) => {
|
||||||
|
const parsed = parseVersion(versions[component]);
|
||||||
|
if (!parsed) throw new Error(`Некорректная версия ${component}: ${versions[component]}`);
|
||||||
|
return [component, parsed];
|
||||||
|
}));
|
||||||
|
if (level === 'major') {
|
||||||
|
const major = Math.max(...COMPONENTS.map((component) => current[component].major)) + 1;
|
||||||
|
return Object.fromEntries(COMPONENTS.map((component) => [component, `${major}.0.0`]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const targets = new Set(requested.map((target) => aliases[target] || target));
|
||||||
|
if (!targets.size) throw new Error(`${level} требует хотя бы один компонент`);
|
||||||
|
for (const target of targets) {
|
||||||
|
if (!COMPONENTS.includes(target)) throw new Error(`Неизвестный компонент: ${target}`);
|
||||||
|
}
|
||||||
|
if (level === 'minor' && (targets.has('gatewayClient') || targets.has('gatewayBackend'))) {
|
||||||
|
targets.add('gatewayClient');
|
||||||
|
targets.add('gatewayBackend');
|
||||||
|
}
|
||||||
|
if (!['minor', 'hotfix'].includes(level)) throw new Error(`Неизвестный уровень: ${level}`);
|
||||||
|
|
||||||
|
const next = { ...versions };
|
||||||
|
if (level === 'minor' && targets.has('gatewayClient')) {
|
||||||
|
const minor = Math.max(current.gatewayClient.minor, current.gatewayBackend.minor) + 1;
|
||||||
|
next.gatewayClient = `${current.gatewayClient.major}.${minor}.0`;
|
||||||
|
next.gatewayBackend = `${current.gatewayBackend.major}.${minor}.0`;
|
||||||
|
targets.delete('gatewayClient');
|
||||||
|
targets.delete('gatewayBackend');
|
||||||
|
}
|
||||||
|
for (const target of targets) {
|
||||||
|
const value = current[target];
|
||||||
|
next[target] = level === 'minor'
|
||||||
|
? `${value.major}.${value.minor + 1}.0`
|
||||||
|
: formatVersion({ ...value, hotfix: value.hotfix + 1 });
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function git(args) {
|
||||||
|
return execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function changedFiles(base) {
|
||||||
|
const tracked = git(['diff', '--name-only', base, '--']).split('\n');
|
||||||
|
const untracked = git(['ls-files', '--others', '--exclude-standard']).split('\n');
|
||||||
|
return [...new Set([...tracked, ...untracked].filter(Boolean))];
|
||||||
|
}
|
||||||
|
|
||||||
|
function baselineVersions(base) {
|
||||||
|
try {
|
||||||
|
return versionsFromSource(git(['show', `${base}:${VERSION_FILE}`]));
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareVersions(before, after) {
|
||||||
|
const left = parseVersion(before);
|
||||||
|
const right = parseVersion(after);
|
||||||
|
if (!left || !right) return -1;
|
||||||
|
for (const key of ['major', 'minor', 'hotfix']) {
|
||||||
|
if (right[key] !== left[key]) return right[key] > left[key] ? 1 : -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCompatibility(versions) {
|
||||||
|
const compatibility = versionCompatibility(versions);
|
||||||
|
if (!compatibility.major) throw new Error('У всех компонентов должен совпадать major');
|
||||||
|
if (!compatibility.gateway) throw new Error('Gateway client и backend должны совпадать по major.minor');
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeVersions(versions) {
|
||||||
|
const file = path.join(root, VERSION_FILE);
|
||||||
|
let source = fs.readFileSync(file, 'utf8');
|
||||||
|
for (const component of COMPONENTS) {
|
||||||
|
source = source.replace(
|
||||||
|
new RegExp(`(${component}:\\s*')\\d+\\.\\d+\\.\\d+(')`),
|
||||||
|
`$1${versions[component]}$2`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fs.writeFileSync(file, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
function printVersions(versions) {
|
||||||
|
for (const component of COMPONENTS) console.log(`${component}: ${versions[component]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function main([command = 'check', ...args]) {
|
||||||
|
const source = fs.readFileSync(path.join(root, VERSION_FILE), 'utf8');
|
||||||
|
const current = versionsFromSource(source);
|
||||||
|
validateCompatibility(current);
|
||||||
|
|
||||||
|
if (command === 'bump') {
|
||||||
|
const next = bumpVersions(current, args[0], args.slice(1));
|
||||||
|
validateCompatibility(next);
|
||||||
|
writeVersions(next);
|
||||||
|
printVersions(next);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = args[0] || 'HEAD';
|
||||||
|
const affected = affectedComponents(changedFiles(base));
|
||||||
|
if (command === 'affected') {
|
||||||
|
console.log(affected.length ? affected.join('\n') : 'Нет изменений, требующих bump.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (command !== 'check') throw new Error(`Неизвестная команда: ${command}`);
|
||||||
|
|
||||||
|
const baseline = baselineVersions(base);
|
||||||
|
if (!baseline) {
|
||||||
|
console.log('Version contract создаётся впервые; baseline для bump отсутствует.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const missing = affected.filter((component) => compareVersions(baseline[component], current[component]) <= 0);
|
||||||
|
if (missing.length) throw new Error(`Не повышена версия: ${missing.join(', ')}`);
|
||||||
|
console.log(affected.length ? `Version check: ${affected.join(', ')}` : 'Version check: bump не требуется.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
||||||
|
try {
|
||||||
|
main(process.argv.slice(2));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[harbor-version] ${error.message}`);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,9 +48,11 @@ export function buildGatewayConfig(subscriptionConfig, selectedTag, { clientDire
|
|||||||
set_system_proxy: false,
|
set_system_proxy: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const directRules = [{ domain_suffix: ['ru'], outbound: 'direct' }];
|
||||||
const rules = clientMode
|
const rules = clientMode
|
||||||
? [{ inbound: [MIXED_INBOUND], outbound: outboundTag }]
|
? [...directRules, { inbound: [MIXED_INBOUND], outbound: outboundTag }]
|
||||||
: [
|
: [
|
||||||
|
...directRules,
|
||||||
{ inbound: [TPROXY_INBOUND], outbound: outboundTag },
|
{ inbound: [TPROXY_INBOUND], outbound: outboundTag },
|
||||||
{ inbound: [MIXED_INBOUND], outbound: outboundTag },
|
{ inbound: [MIXED_INBOUND], outbound: outboundTag },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export const HARBOR_VERSIONS = Object.freeze({
|
export const HARBOR_VERSIONS = Object.freeze({
|
||||||
macClient: '0.1.0',
|
macClient: '0.2.0',
|
||||||
gatewayClient: '0.1.0',
|
gatewayClient: '0.2.0',
|
||||||
gatewayBackend: '0.1.0',
|
gatewayBackend: '0.2.0',
|
||||||
});
|
});
|
||||||
|
|
||||||
export function parseVersion(value) {
|
export function parseVersion(value) {
|
||||||
|
|||||||
@@ -38,15 +38,12 @@ function VersionBadge({ code, component, componentKey, version, singBox, incompa
|
|||||||
|
|
||||||
function description(key) {
|
function description(key) {
|
||||||
if (key === 'major') {
|
if (key === 'major') {
|
||||||
return 'Общий уровень совместимости Harbor. При его изменении обновляются Mac и вся Gateway-инфраструктура.';
|
return 'Уровень совместимости всей экосистемы. Все совместно работающие компоненты и клиенты должны иметь одинаковый major.';
|
||||||
}
|
|
||||||
if (key === 'minor' && componentKey === 'macClient') {
|
|
||||||
return 'Линия Mac-клиента. Gateway может менять minor без обязательного обновления Mac.';
|
|
||||||
}
|
}
|
||||||
if (key === 'minor') {
|
if (key === 'minor') {
|
||||||
return 'Линия Gateway. Gateway client и backend должны совпадать по major.minor.';
|
return 'Уровень конкретного компонента и тесно связанной с ним части системы. Остальные клиенты с тем же major могут обновляться отдельно.';
|
||||||
}
|
}
|
||||||
return `Совместимое исправление только компонента ${component}; hotfix может обновляться независимо.`;
|
return 'Локальное совместимое исправление этой версии. Не требует обновлять связанные компоненты или другие клиенты.';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -199,16 +199,16 @@ p {
|
|||||||
z-index: 40;
|
z-index: 40;
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-items: end;
|
justify-items: end;
|
||||||
gap: 2px;
|
gap: 4px;
|
||||||
color: var(--client-muted);
|
color: var(--client-muted);
|
||||||
font: 600 9px/1 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace;
|
font: 650 12px/1 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
.harbor-version {
|
.harbor-version {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
opacity: 0.56;
|
opacity: 0.56;
|
||||||
transition: color 240ms ease, opacity 240ms ease;
|
transition: color 240ms ease, opacity 240ms ease;
|
||||||
}
|
}
|
||||||
@@ -227,7 +227,7 @@ p {
|
|||||||
.harbor-version-code {
|
.harbor-version-code {
|
||||||
width: 1.3ch;
|
width: 1.3ch;
|
||||||
color: var(--client-accent);
|
color: var(--client-accent);
|
||||||
font-size: 8px;
|
font-size: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,8 +239,8 @@ p {
|
|||||||
.harbor-version-part {
|
.harbor-version-part {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-width: 1ch;
|
min-width: 1ch;
|
||||||
padding: 3px 1px;
|
padding: 5px 2px;
|
||||||
border-radius: 4px;
|
border-radius: 5px;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -260,16 +260,16 @@ p {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: calc(100% + 7px);
|
bottom: calc(100% + 7px);
|
||||||
width: min(250px, calc(100vw - 28px));
|
width: min(300px, calc(100vw - 28px));
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 5px;
|
gap: 7px;
|
||||||
padding: 9px 10px;
|
padding: 12px 13px;
|
||||||
border-radius: 10px;
|
border-radius: 12px;
|
||||||
background: color-mix(in oklch, var(--client-panel) 84%, transparent);
|
background: color-mix(in oklch, var(--client-panel) 84%, transparent);
|
||||||
box-shadow: 0 9px 30px oklch(0.08 0.015 145 / 0.16);
|
box-shadow: 0 9px 30px oklch(0.08 0.015 145 / 0.16);
|
||||||
backdrop-filter: blur(12px) saturate(0.9);
|
backdrop-filter: blur(12px) saturate(0.9);
|
||||||
color: var(--client-muted);
|
color: var(--client-muted);
|
||||||
font-size: 9px;
|
font-size: 11px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@@ -283,12 +283,12 @@ p {
|
|||||||
|
|
||||||
.harbor-version-tooltip strong {
|
.harbor-version-tooltip strong {
|
||||||
color: var(--client-text);
|
color: var(--client-text);
|
||||||
font-size: 9px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.harbor-version-tooltip small {
|
.harbor-version-tooltip small {
|
||||||
color: var(--client-accent);
|
color: var(--client-accent);
|
||||||
font-size: 8px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.harbor-version-tooltip .is-warning {
|
.harbor-version-tooltip .is-warning {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ test('client exposes one local proxy and routes it through the selected VPN', ()
|
|||||||
assert.deepEqual(config.inbounds.map((inbound) => inbound.tag), ['mixed-in']);
|
assert.deepEqual(config.inbounds.map((inbound) => inbound.tag), ['mixed-in']);
|
||||||
assert.equal(config.inbounds[0].listen_port, 8082);
|
assert.equal(config.inbounds[0].listen_port, 8082);
|
||||||
assert.deepEqual(config.route.rules, [
|
assert.deepEqual(config.route.rules, [
|
||||||
|
{ domain_suffix: ['ru'], outbound: 'direct' },
|
||||||
{ inbound: ['mixed-in'], outbound: 'test-vpn' },
|
{ inbound: ['mixed-in'], outbound: 'test-vpn' },
|
||||||
]);
|
]);
|
||||||
assert.equal(config.route.final, 'test-vpn');
|
assert.equal(config.route.final, 'test-vpn');
|
||||||
@@ -37,6 +38,7 @@ test('client keeps its local proxy but routes directly when Harbor Gateway is ah
|
|||||||
const config = buildGatewayConfig(subscriptionConfig, 'test-vpn', { clientDirect: true });
|
const config = buildGatewayConfig(subscriptionConfig, 'test-vpn', { clientDirect: true });
|
||||||
|
|
||||||
assert.deepEqual(config.route.rules, [
|
assert.deepEqual(config.route.rules, [
|
||||||
|
{ domain_suffix: ['ru'], outbound: 'direct' },
|
||||||
{ inbound: ['mixed-in'], outbound: 'direct' },
|
{ inbound: ['mixed-in'], outbound: 'direct' },
|
||||||
]);
|
]);
|
||||||
assert.equal(config.route.final, 'direct');
|
assert.equal(config.route.final, 'direct');
|
||||||
|
|||||||
@@ -21,11 +21,12 @@ const subscriptionConfig = {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
test('gateway routes transparent and proxy traffic only through the selected VPN', () => {
|
test('gateway routes .ru domains directly and other traffic through the selected VPN', () => {
|
||||||
const config = buildGatewayConfig(subscriptionConfig, 'test-vpn');
|
const config = buildGatewayConfig(subscriptionConfig, 'test-vpn');
|
||||||
|
|
||||||
assert.deepEqual(config.route.rule_set, []);
|
assert.deepEqual(config.route.rule_set, []);
|
||||||
assert.deepEqual(config.route.rules, [
|
assert.deepEqual(config.route.rules, [
|
||||||
|
{ domain_suffix: ['ru'], outbound: 'direct' },
|
||||||
{ inbound: ['tproxy-in'], outbound: 'test-vpn' },
|
{ inbound: ['tproxy-in'], outbound: 'test-vpn' },
|
||||||
{ inbound: ['mixed-in'], outbound: 'test-vpn' },
|
{ inbound: ['mixed-in'], outbound: 'test-vpn' },
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
createStateSnapshot,
|
createStateSnapshot,
|
||||||
normalizeStoredState,
|
normalizeStoredState,
|
||||||
} from '../../src/shared/contracts/state.js';
|
} from '../../src/shared/contracts/state.js';
|
||||||
|
import { HARBOR_VERSIONS } from '../../src/shared/versions.js';
|
||||||
|
|
||||||
const root = path.resolve(import.meta.dirname, '../..');
|
const root = path.resolve(import.meta.dirname, '../..');
|
||||||
|
|
||||||
@@ -163,7 +164,7 @@ setInterval(() => {}, 60_000);
|
|||||||
assert.deepEqual(version, {
|
assert.deepEqual(version, {
|
||||||
apiVersion: 1,
|
apiVersion: 1,
|
||||||
location: 'mac',
|
location: 'mac',
|
||||||
components: { macClient: '0.1.0' },
|
components: { macClient: HARBOR_VERSIONS.macClient },
|
||||||
runtime: { singBox: '1.12.13' },
|
runtime: { singBox: '1.12.13' },
|
||||||
});
|
});
|
||||||
assertStateSnapshot(initial);
|
assertStateSnapshot(initial);
|
||||||
|
|||||||
39
test/version-script.test.js
Normal file
39
test/version-script.test.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import { affectedComponents, bumpVersions } from '../scripts/harbor-version.mjs';
|
||||||
|
|
||||||
|
const versions = {
|
||||||
|
macClient: '2.4.3',
|
||||||
|
gatewayClient: '2.7.1',
|
||||||
|
gatewayBackend: '2.7.8',
|
||||||
|
};
|
||||||
|
|
||||||
|
test('version paths map to the components actually shipped by this repository', () => {
|
||||||
|
assert.deepEqual(affectedComponents(['src/web/App.jsx']), ['macClient', 'gatewayClient']);
|
||||||
|
assert.deepEqual(affectedComponents(['src/server/index.js']), ['macClient', 'gatewayBackend']);
|
||||||
|
assert.deepEqual(affectedComponents(['package-lock.json']), [
|
||||||
|
'macClient',
|
||||||
|
'gatewayClient',
|
||||||
|
'gatewayBackend',
|
||||||
|
]);
|
||||||
|
assert.deepEqual(affectedComponents(['README.md', 'test/server/version.test.js']), []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('version bumps follow ecosystem, linked-component and local scopes', () => {
|
||||||
|
assert.deepEqual(bumpVersions(versions, 'major'), {
|
||||||
|
macClient: '3.0.0',
|
||||||
|
gatewayClient: '3.0.0',
|
||||||
|
gatewayBackend: '3.0.0',
|
||||||
|
});
|
||||||
|
assert.deepEqual(bumpVersions(versions, 'minor', ['gateway-backend']), {
|
||||||
|
macClient: '2.4.3',
|
||||||
|
gatewayClient: '2.8.0',
|
||||||
|
gatewayBackend: '2.8.0',
|
||||||
|
});
|
||||||
|
assert.deepEqual(bumpVersions(versions, 'hotfix', ['mac']), {
|
||||||
|
macClient: '2.4.4',
|
||||||
|
gatewayClient: '2.7.1',
|
||||||
|
gatewayBackend: '2.7.8',
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user