mirror of
https://github.com/nimbold/Firelink.git
synced 2026-07-31 06:32:18 +00:00
fix(release): harden packaging and filename localization
This commit is contained in:
@@ -11,7 +11,7 @@ let receivedSignal;
|
||||
export const APPIMAGE_CONFIG = JSON.stringify({
|
||||
bundle: {
|
||||
resources: {
|
||||
'engine-dist/': null,
|
||||
'../THIRD_PARTY_NOTICES.md': 'THIRD_PARTY_NOTICES.md',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,11 +2,11 @@ import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { APPIMAGE_CONFIG, appImageBundleArguments } from './build-linux-appimage.js';
|
||||
|
||||
test('AppImage config removes only engine-dist from Tauri resources', () => {
|
||||
test('AppImage config keeps notices while excluding the staged engine payload', () => {
|
||||
assert.deepEqual(JSON.parse(APPIMAGE_CONFIG), {
|
||||
bundle: {
|
||||
resources: {
|
||||
'engine-dist/': null,
|
||||
'../THIRD_PARTY_NOTICES.md': 'THIRD_PARTY_NOTICES.md',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -58,6 +58,14 @@ describe('date/time formatting', () => {
|
||||
expect(formatted).toContain('123');
|
||||
});
|
||||
|
||||
it('uses the default Persian date when only locale or timezone options are supplied', () => {
|
||||
expect(formatDateTime(instant, {
|
||||
locale: 'fa',
|
||||
calendar: 'persian',
|
||||
options: { timeZone: 'UTC' }
|
||||
})).toBe('۱۴۰۵/۰۱/۰۱');
|
||||
});
|
||||
|
||||
it('returns a safe placeholder for malformed timestamps and rejects unknown preferences', () => {
|
||||
expect(formatDateTime('not-a-timestamp', { locale: 'en' })).toBe('-');
|
||||
expect(isCalendarPreference('gregorian')).toBe(true);
|
||||
|
||||
@@ -79,7 +79,10 @@ const formatPersianDateTime = (
|
||||
const hasDateOptions = Object.keys(options).some(key => DATE_OPTION_KEYS.has(key));
|
||||
const hasTimeOptions = options.timeStyle !== undefined ||
|
||||
TIME_OPTION_KEYS.some(key => (options as unknown as Record<string, unknown>)[key] !== undefined);
|
||||
const includeDate = Object.keys(options).length === 0 || hasDateOptions;
|
||||
// Locale and timezone options do not select a date or time field by
|
||||
// themselves. Match Intl's default date behavior for those option-only
|
||||
// calls instead of returning an empty string.
|
||||
const includeDate = hasDateOptions || !hasTimeOptions;
|
||||
const parts: string[] = [];
|
||||
|
||||
if (includeDate) {
|
||||
|
||||
@@ -66,6 +66,13 @@ describe('download connection resolution', () => {
|
||||
});
|
||||
|
||||
describe('download filename matching', () => {
|
||||
it('matches frontend filenames to the backend Windows device-name canonicalization', () => {
|
||||
expect(canonicalizeDownloadFileName('CON.txt')).toBe('CON-.txt');
|
||||
expect(canonicalizeDownloadFileName('com1.archive.zip')).toBe('com1.archive-.zip');
|
||||
expect(downloadFileNamesMatch('CON-.txt', 'CON.txt')).toBe(true);
|
||||
expect(downloadFileNamesMatch('console.txt', 'CON.txt')).toBe(false);
|
||||
});
|
||||
|
||||
it('truncates long names by UTF-8 bytes while preserving the extension', () => {
|
||||
const filename = canonicalizeDownloadFileName(`${'title '.repeat(100)}.mp4`);
|
||||
|
||||
|
||||
+19
-1
@@ -48,6 +48,17 @@ export const DOWNLOAD_CONNECTIONS_MAX = 16;
|
||||
// bound is also conservative for Windows filename components.
|
||||
export const MAX_DOWNLOAD_FILENAME_BYTES = 255;
|
||||
const FILENAME_TRUNCATION_MARKER = '…';
|
||||
const WINDOWS_RESERVED_FILENAME_STEMS = new Set([
|
||||
'CON',
|
||||
'PRN',
|
||||
'AUX',
|
||||
'NUL',
|
||||
'CLOCK$',
|
||||
'CONIN$',
|
||||
'CONOUT$',
|
||||
...Array.from({ length: 9 }, (_, index) => `COM${index + 1}`),
|
||||
...Array.from({ length: 9 }, (_, index) => `LPT${index + 1}`)
|
||||
]);
|
||||
|
||||
const utf8ByteLength = (value: string): number => new TextEncoder().encode(value).length;
|
||||
|
||||
@@ -150,7 +161,14 @@ export const canonicalizeDownloadFileName = (fileName: string): string => {
|
||||
.replace(/[\u0000-\u001f\u007f-\u009f<>:"/\\|?*]/g, '-')
|
||||
.trim()
|
||||
.replace(/[. ]+$/g, '');
|
||||
const canonical = sanitized && sanitized !== '.' && sanitized !== '..' ? sanitized : 'download';
|
||||
let canonical = sanitized && sanitized !== '.' && sanitized !== '..' ? sanitized : 'download';
|
||||
const reservedStem = canonical.split('.')[0]?.toUpperCase();
|
||||
if (reservedStem && WINDOWS_RESERVED_FILENAME_STEMS.has(reservedStem)) {
|
||||
const extensionStart = canonical.lastIndexOf('.');
|
||||
const base = extensionStart > 0 ? canonical.slice(0, extensionStart) : canonical;
|
||||
const extension = extensionStart > 0 ? canonical.slice(extensionStart) : '';
|
||||
canonical = `${base}-${extension}`;
|
||||
}
|
||||
if (utf8ByteLength(canonical) <= MAX_DOWNLOAD_FILENAME_BYTES) return canonical;
|
||||
|
||||
const extensionStart = canonical.lastIndexOf('.');
|
||||
|
||||
Reference in New Issue
Block a user