fix(themes): enforce strict semver versions
Require SemVer 2.0.0 when saving, validating, or publishing a theme, and validate again at the workshop upload boundary. This rejects partial versions, v-prefixed versions, leading-zero components, and arbitrary strings while preserving valid prerelease and build metadata.
This commit is contained in:
1 parent
07cbfa39a6
commit
51f5fa3e46
3 files changed
+32
-5
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { config } from './config';
|
||||
import { normalizeThemeVersion } from './themeV2';
|
||||
import {
|
||||
reloadThemes,
|
||||
themesDirectory,
|
||||
@@ -727,6 +728,13 @@ export async function publishThemeToWorkshop(options: {
|
||||
throw new Error('You must be logged in to EZPPFarm to publish themes.');
|
||||
}
|
||||
|
||||
const version = normalizeThemeVersion(options.version ?? '1.0.0');
|
||||
if (!version) {
|
||||
throw new Error(
|
||||
'Theme version must use semantic versioning, for example 1.2.3 or 1.2.3-beta.1.'
|
||||
);
|
||||
}
|
||||
|
||||
onProgress('Packaging theme archive...', 5);
|
||||
|
||||
const baseThemesDir = await themesDirectory();
|
||||
@@ -771,7 +779,7 @@ export async function publishThemeToWorkshop(options: {
|
||||
themeId: options.workshopId || options.themeId,
|
||||
name: options.name,
|
||||
description: options.description || '',
|
||||
version: options.version || '1.0.0',
|
||||
version,
|
||||
apiVersion: options.apiVersion || '2.0.0',
|
||||
totalSize,
|
||||
totalChunks,
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
normalizeChrome,
|
||||
normalizeThemeTags,
|
||||
normalizeThemeV2,
|
||||
normalizeThemeVersion,
|
||||
themeAssetName,
|
||||
themeHasLaunchButton,
|
||||
type ThemeV2Asset,
|
||||
@@ -2923,6 +2924,12 @@
|
||||
notifyError('Add an author name so people know who made the theme.');
|
||||
return false;
|
||||
}
|
||||
const version = normalizeThemeVersion(definition.version);
|
||||
if (!version) {
|
||||
notifyError('The version must use semantic versioning, for example 1.2.3 or 1.2.3-beta.1.');
|
||||
return false;
|
||||
}
|
||||
if (definition.version !== version) definition.version = version;
|
||||
const folderName = savedFolder ?? toSafeName(definition.name);
|
||||
if (!folderName.replace(/_/g, '')) {
|
||||
notifyError('The theme name needs at least one letter or number.');
|
||||
@@ -3019,8 +3026,10 @@
|
||||
|
||||
if (!definition.name.trim()) error('The theme needs a name.');
|
||||
if (!definition.author.trim()) error('The theme needs an author.');
|
||||
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(definition.version.trim())) {
|
||||
warning('The version should use semantic versioning, for example 1.0.0.');
|
||||
if (!normalizeThemeVersion(definition.version)) {
|
||||
error(
|
||||
'The version must use semantic versioning, for example 1.0.0 or 1.0.0-beta.1, with no "v" prefix.'
|
||||
);
|
||||
}
|
||||
if (!themeHasLaunchButton(definition)) {
|
||||
error(
|
||||
@@ -3346,7 +3355,7 @@
|
||||
async function checkPublishVersion(): Promise<
|
||||
{ ok: true; version: string } | { ok: false; error: string }
|
||||
> {
|
||||
const proposed = semver.valid(definition.version.trim());
|
||||
const proposed = normalizeThemeVersion(definition.version);
|
||||
if (!proposed) {
|
||||
return {
|
||||
ok: false,
|
||||
@@ -3369,7 +3378,7 @@
|
||||
};
|
||||
}
|
||||
|
||||
const validRemote = semver.valid(remoteVersion);
|
||||
const validRemote = normalizeThemeVersion(remoteVersion);
|
||||
if (!validRemote) {
|
||||
return {
|
||||
ok: false,
|
||||
|
||||
@@ -748,6 +748,16 @@ export const normalizeThemeTags = (raw: unknown): string[] => {
|
||||
return tags;
|
||||
};
|
||||
|
||||
const THEME_VERSION_PATTERN =
|
||||
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(?:\+[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?$/;
|
||||
|
||||
/** Accept only strict SemVer 2.0.0, preserving prerelease and build metadata. */
|
||||
export const normalizeThemeVersion = (raw: unknown): string | undefined => {
|
||||
if (typeof raw !== 'string') return undefined;
|
||||
const version = raw.trim();
|
||||
return version.length > 0 && THEME_VERSION_PATTERN.test(version) ? version : undefined;
|
||||
};
|
||||
|
||||
const WORKSHOP_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
||||
|
||||
/** Keep a server-assigned workshop ID while preserving legacy slug IDs. */
|
||||
|
||||
Reference in new issue
Block a user