Files
fleet/frontend/utilities/file/fileUtils.tsx
T
Carlo fb0e817bd0 Show .py script-only packages as available for install on macOS hosts (#49457)
**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 -->
2026-07-20 15:13:50 -04:00

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;
}