**Related issue:** Resolves #49455 Offer `.py` script-only packages on macOS hosts, matching `.sh`. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Python (`.py`) installer packages are now treated as compatible alongside shell (`.sh`) installers on macOS and Linux. * Python installers can now appear in software availability, self-service installation, and setup experience selections. * Windows behavior remains unchanged (Unix-script installers are excluded). * **Bug Fixes** * Improved cross-platform compatibility matching for Unix-like hosts when choosing the first eligible installer package. * **Tests** * Added and expanded unit/integration coverage for `.py` installer compatibility across platforms and flows. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
105 lines
2.7 KiB
TypeScript
105 lines
2.7 KiB
TypeScript
import React from "react";
|
|
import { PackageType } from "interfaces/package_type";
|
|
import TooltipWrapper from "components/TooltipWrapper";
|
|
|
|
type IPlatformDisplayName =
|
|
| "macOS"
|
|
| "Windows"
|
|
| "Linux"
|
|
| "iOS/iPadOS"
|
|
| "macOS & Linux";
|
|
|
|
export const FILE_EXTENSIONS_TO_PLATFORM_DISPLAY_NAME: Record<
|
|
string,
|
|
IPlatformDisplayName
|
|
> = {
|
|
json: "macOS",
|
|
pkg: "macOS",
|
|
mobileconfig: "macOS",
|
|
exe: "Windows",
|
|
msi: "Windows",
|
|
xml: "Windows",
|
|
deb: "Linux",
|
|
rpm: "Linux",
|
|
"tar.gz": "Linux",
|
|
sh: "macOS & Linux",
|
|
ps1: "Windows",
|
|
py: "macOS & Linux",
|
|
ipa: "iOS/iPadOS",
|
|
};
|
|
|
|
/** Currently only using tar.gz, but keeping the others for future use
|
|
* and to avoid breaking changes. */
|
|
const compoundExtensions = ["tar.gz", "tar.xz", "tar.bz2", "tar.zst"];
|
|
|
|
/** Currently only using tgz, but keeping the others for future use
|
|
* and to avoid breaking changes. */
|
|
const extensionAliases: Record<string, string> = {
|
|
tgz: "tar.gz",
|
|
tbz2: "tar.bz2",
|
|
tzst: "tar.zst",
|
|
txz: "tar.xz",
|
|
};
|
|
|
|
/** Extract the extension, considering compound extensions like .tar.gz;
|
|
* Aliases like .tgz will be converted to compound extensions like .tar.gz
|
|
*/
|
|
export const getExtensionFromFileName = (fileName: string) => {
|
|
const lower = fileName.toLowerCase();
|
|
const parts = lower.split(".");
|
|
|
|
// Find compound extension
|
|
const compound = compoundExtensions.find((ext) => {
|
|
const extParts = ext.split(".");
|
|
return parts.slice(-extParts.length).join(".") === ext;
|
|
});
|
|
|
|
// Choose extension: compound or simple
|
|
let ext: string | undefined;
|
|
if (compound) {
|
|
ext = compound;
|
|
} else if (parts.length > 1) {
|
|
ext = parts.pop();
|
|
}
|
|
|
|
// Map aliases if needed
|
|
if (ext && extensionAliases[ext]) {
|
|
ext = extensionAliases[ext];
|
|
}
|
|
|
|
return ext as PackageType | undefined;
|
|
};
|
|
|
|
/** This gets the platform display name from the file.
|
|
* Script packages (.sh, .py) map to "macOS & Linux" since they run on both;
|
|
* .ipa maps to iOS/iPadOS with a tooltip noting it covers both.
|
|
*/
|
|
export const getPlatformDisplayName = (file: File) => {
|
|
const fileExt = getExtensionFromFileName(file.name);
|
|
if (!fileExt) {
|
|
return undefined;
|
|
}
|
|
if (fileExt === "ipa") {
|
|
return (
|
|
<TooltipWrapper tipContent="Software will be added for both platforms.">
|
|
{FILE_EXTENSIONS_TO_PLATFORM_DISPLAY_NAME[fileExt]}
|
|
</TooltipWrapper>
|
|
);
|
|
}
|
|
|
|
return FILE_EXTENSIONS_TO_PLATFORM_DISPLAY_NAME[fileExt];
|
|
};
|
|
|
|
/** This gets the file details from the file. */
|
|
export const getFileDetails = (file: File) => {
|
|
return {
|
|
name: file.name,
|
|
description: getPlatformDisplayName(file),
|
|
};
|
|
};
|
|
|
|
export interface IFileDetails {
|
|
name: string;
|
|
description?: React.ReactNode;
|
|
}
|