Browse Source

Feat: Changelog, Release Notes (#1385)

* add changelog, use semver for update message

* use first line of release for short changelog
pull/1648/head
Bernd Storath 7 months ago
committed by Bernd Storath
parent
commit
f29e17068d
  1. 22
      CHANGELOG.md
  2. 5
      package.json
  3. 11
      src/app/layouts/Header.vue
  4. 20
      src/app/stores/global.ts
  5. 9
      src/package.json
  6. 11
      src/pnpm-lock.yaml
  7. 5
      src/server/api/release.get.ts
  8. 2
      src/server/utils/config.ts
  9. 35
      src/server/utils/release.ts

22
CHANGELOG.md

@ -0,0 +1,22 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
We're super excited to announce v15!
This update is an entire rewrite to make it even easier to set up your own VPN.
## Major Changes
- Almost all Environment variables removed
- New and Improved UI
## [14.0.0] - 2024-09-04
### Major changes
- `PASSWORD` has been replaced by `PASSWORD_HASH`

5
package.json

@ -1,8 +1,9 @@
{
"version": "1.0.1",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "docker compose -f docker-compose.dev.yml up",
"build": "docker build -t wg-easy ."
},
"packageManager": "pnpm@9.9.0"
"packageManager": "pnpm@9.10.0"
}

11
src/app/layouts/Header.vue

@ -63,18 +63,18 @@
</div>
<div class="text-sm text-gray-400 dark:text-neutral-400 mb-5" />
<div
v-if="latestRelease"
v-if="globalStore.latestRelease"
class="bg-red-800 dark:bg-red-100 p-4 text-white dark:text-red-600 text-sm font-small mb-10 rounded-md shadow-lg"
:title="`v${currentRelease} → v${latestRelease.version}`"
:title="`v${globalStore.currentRelease} → v${globalStore.latestRelease.version}`"
>
<div class="container mx-auto flex flex-row flex-auto items-center">
<div class="flex-grow">
<p class="font-bold">{{ $t('updateAvailable') }}</p>
<p>{{ latestRelease.changelog }}</p>
<p>{{ globalStore.latestRelease.changelog }}</p>
</div>
<a
href="https://github.com/wg-easy/wg-easy#updating"
:href="`https://github.com/wg-easy/wg-easy/releases/tag/${globalStore.latestRelease.version}`"
target="_blank"
class="p-3 rounded-md bg-white dark:bg-red-100 float-right font-sm font-semibold text-red-800 dark:text-red-600 flex-shrink-0 border-2 border-red-800 dark:border-red-600 hover:border-white dark:hover:border-red-600 hover:text-white dark:hover:text-red-100 hover:bg-red-800 dark:hover:bg-red-600 transition-all"
>
@ -92,9 +92,6 @@ const route = useRoute();
const isLoginPage = computed(() => route.path == '/login');
const currentRelease = ref<null | number>(null);
const latestRelease = ref<null | { version: number; changelog: string }>(null);
const theme = useTheme();
const uiShowCharts = ref(getItem('uiShowCharts') === '1');

20
src/app/stores/global.ts

@ -2,8 +2,8 @@ import { defineStore } from 'pinia';
export const useGlobalStore = defineStore('Global', () => {
const uiShowCharts = ref(getItem('uiShowCharts') === '1');
const currentRelease = ref<null | number>(null);
const latestRelease = ref<null | { version: number; changelog: string }>(
const currentRelease = ref<null | string>(null);
const latestRelease = ref<null | { version: string; changelog: string }>(
null
);
const features = ref({
@ -35,18 +35,18 @@ export const useGlobalStore = defineStore('Global', () => {
locale.value = lang.value!;
}
// this is still called on client. why?
const { data: release } = await api.getRelease();
if (
Number(release.value!.currentRelease) >=
release.value!.latestRelease.version
) {
if (!release.value) {
return;
}
if (!release.value.updateAvailable) {
return;
}
currentRelease.value = Number(release.value!.currentRelease);
latestRelease.value = release.value!.latestRelease;
currentRelease.value = release.value.currentRelease;
latestRelease.value = release.value.latestRelease;
}
async function fetchFeatures() {
@ -65,6 +65,8 @@ export const useGlobalStore = defineStore('Global', () => {
updateCharts,
sortClient,
features,
currentRelease,
latestRelease,
fetchRelease,
fetchFeatures,
};

9
src/package.json

@ -1,9 +1,6 @@
{
"release": {
"version": "14"
},
"name": "wg-easy",
"version": "1.0.1",
"version": "14.0.0",
"description": "The easiest way to run WireGuard VPN + Web-based Admin UI.",
"private": true,
"type": "module",
@ -37,6 +34,7 @@
"nuxt": "^3.13.0",
"pinia": "^2.2.2",
"qrcode": "^1.5.4",
"semver": "^7.6.3",
"tailwindcss": "^3.4.10",
"timeago.js": "^4.0.2",
"vue": "latest",
@ -47,11 +45,12 @@
"@nuxt/eslint-config": "^0.5.5",
"@types/debug": "^4.1.12",
"@types/qrcode": "^1.5.5",
"@types/semver": "^7.5.8",
"eslint": "^9.9.1",
"eslint-config-prettier": "^9.1.0",
"prettier": "^3.3.3",
"typescript": "^5.5.4",
"vue-tsc": "^2.1.4"
},
"packageManager": "pnpm@9.9.0"
"packageManager": "pnpm@9.10.0"
}

11
src/pnpm-lock.yaml

@ -62,6 +62,9 @@ importers:
qrcode:
specifier: ^1.5.4
version: 1.5.4
semver:
specifier: ^7.6.3
version: 7.6.3
tailwindcss:
specifier: ^3.4.10
version: 3.4.10
@ -87,6 +90,9 @@ importers:
'@types/qrcode':
specifier: ^1.5.5
version: 1.5.5
'@types/semver':
specifier: ^7.5.8
version: 7.5.8
eslint:
specifier: ^9.9.1
version: 9.9.1([email protected])
@ -1242,6 +1248,9 @@ packages:
'@types/[email protected]':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
'@types/[email protected]':
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
'@typescript-eslint/[email protected]':
resolution: {integrity: sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -5687,6 +5696,8 @@ snapshots:
'@types/[email protected]': {}
'@types/[email protected]': {}
'@typescript-eslint/[email protected](@typescript-eslint/[email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])':
dependencies:
'@eslint-community/regexpp': 4.11.0

5
src/server/api/release.get.ts

@ -1,7 +1,12 @@
import { gt } from 'semver';
export default defineEventHandler(async () => {
// TODO: cache this
const latestRelease = await fetchLatestRelease();
const updateAvailable = gt(latestRelease.version, RELEASE);
return {
currentRelease: RELEASE,
latestRelease: latestRelease,
updateAvailable,
};
});

2
src/server/utils/config.ts

@ -5,7 +5,7 @@ import type { Database } from '~~/services/database/repositories/database';
import { parseCidr } from 'cidr-tools';
import { stringifyIp } from 'ip-bigint';
export const RELEASE = packageJson.release.version;
export const RELEASE = packageJson.version;
export const SERVER_DEBUG = debug('Server');

35
src/server/utils/release.ts

@ -1,26 +1,27 @@
type GithubRelease = {
tag_name: string;
body: string;
};
export async function fetchLatestRelease() {
try {
const response = await $fetch<Record<string, string>>(
'https://wg-easy.github.io/wg-easy/changelog.json',
const response = await $fetch<GithubRelease>(
'https://api.github.com/repos/wg-easy/wg-easy/releases/latest',
{ method: 'get' }
);
const releasesArray = Object.entries(response).map(
([version, changelog]) => ({
version: parseInt(version, 10),
changelog: changelog,
})
);
releasesArray.sort((a, b) => {
return b.version - a.version;
});
if (releasesArray.length === 0) {
throw new Error('Changelog is empty');
if (!response) {
throw new Error('Empty Response');
}
return releasesArray[0]!;
const changelog = response.body.split('\r\n\r\n')[0] ?? '';
return {
version: response.tag_name,
changelog,
};
} catch (e) {
SERVER_DEBUG('Failed to fetch latest releases: ', e);
return { version: 0, changelog: '' };
throw createError({
statusCode: 500,
statusMessage: 'Failed to fetch latest release',
});
}
}

Loading…
Cancel
Save