Files
api/plugin/builder/build-txz.ts
Eli Bosley 4f63b4cf3b feat: native slackware package (#1381)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added detailed versioning for plugin packages incorporating
architecture and build identifiers.
- Simplified and improved install/uninstall scripts with backup and
dynamic package detection.
- Introduced comprehensive setup, verification, patching, and cleanup
scripts for the Unraid API environment.
- Enhanced service control with explicit start, stop, restart, and
status commands.
- Added robust dependency management scripts for restoring and archiving
Node.js modules.
- Implemented vendor archive metadata storage and dynamic handling
during build and runtime.
- Added new CLI options and environment schemas for consistent build
configuration.
- Introduced new shutdown scripts to gracefully stop flash-backup and
unraid-api services.
- Added utility scripts for API version detection and vendor archive
configuration.
- Added a new package description file detailing Unraid API features and
homepage link.

- **Bug Fixes**
- Improved validation and error reporting for missing manifests,
dependencies, and configuration files.
  - Enhanced fallback logic for locating and creating vendor archives.
- Fixed iframe compatibility in UI by updating HTML and Firefox
preference files.

- **Chores**
- Updated .gitignore with generated file patterns for Node.js binaries
and archives.
  - Removed obsolete internal documentation and legacy cleanup scripts.
- Refined Docker Compose and CI workflows to pass precise API versioning
and manage build artifacts.
- Centralized common environment validation and CLI option definitions
across build tools.
- Cleaned up plugin manifest by removing Node.js and PNPM-related
entities and legacy logic.
- Improved logging and error handling in build and installation scripts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-08 22:54:10 -04:00

203 lines
6.6 KiB
TypeScript

import { join } from "path";
import { $, cd } from "zx";
import { existsSync } from "node:fs";
import { readdir, writeFile } from "node:fs/promises";
import { getTxzName, pluginName, startingDir } from "./utils/consts";
import { ensureNodeJs } from "./utils/nodejs-helper";
import { setupTxzEnv, TxzEnv } from "./cli/setup-txz-environment";
import { cleanupTxzFiles } from "./utils/cleanup";
import { apiDir } from "./utils/paths";
import { getVendorBundleName, getVendorFullPath } from "./build-vendor-store";
import { getAssetUrl } from "./utils/bucket-urls";
// Recursively search for manifest files
const findManifestFiles = async (dir: string): Promise<string[]> => {
try {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
try {
files.push(...(await findManifestFiles(fullPath)));
} catch (error) {
// Log and continue if a subdirectory can't be read
console.warn(`Warning: Could not read directory ${fullPath}: ${error.message}`);
}
} else if (
entry.isFile() &&
(entry.name === "manifest.json" || entry.name === "ui.manifest.json")
) {
files.push(entry.name);
}
}
return files;
} catch (error) {
if (error.code === 'ENOENT') {
console.warn(`Directory does not exist: ${dir}`);
return [];
}
throw error; // Re-throw other errors
}
};
// Function to store vendor archive information in a recoverable location
const storeVendorArchiveInfo = async (version: string, vendorUrl: string, vendorFilename: string) => {
try {
if (!version || !vendorUrl || !vendorFilename) {
throw new Error("Cannot store vendor archive info: Missing required parameters");
}
// Create a config directory in the source tree
const configDir = join(
startingDir,
"source",
"dynamix.unraid.net",
"usr",
"local",
"share",
"dynamix.unraid.net",
"config"
);
// Ensure directory exists
await $`mkdir -p ${configDir}`;
// Get the full path for vendor archive
const vendorFullPath = getVendorFullPath(version);
// Create a JSON config file with vendor information
const configData = {
vendor_store_url: vendorUrl,
vendor_store_path: vendorFullPath,
api_version: version
};
// Validate all fields are present
Object.entries(configData).forEach(([key, value]) => {
if (!value) {
throw new Error(`Cannot store vendor archive info: Missing value for ${key}`);
}
});
const configPath = join(configDir, "vendor_archive.json");
await writeFile(configPath, JSON.stringify(configData, null, 2));
console.log(`Vendor archive information stored in ${configPath}`);
console.log(`API Version: ${version}`);
console.log(`Vendor URL: ${vendorUrl}`);
console.log(`Vendor Full Path: ${vendorFullPath}`);
return true;
} catch (error) {
console.error(`Failed to store vendor archive information: ${error.message}`);
throw error; // Re-throw to prevent build from succeeding with invalid vendor info
}
};
const validateSourceDir = async (validatedEnv: TxzEnv) => {
if (!validatedEnv.ci) {
console.log("Validating TXZ source directory");
}
const sourceDir = join(startingDir, "source");
if (!existsSync(sourceDir)) {
throw new Error(`Source directory ${sourceDir} does not exist`);
}
// Validate existence of webcomponent files:
// source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components
const webcomponentDir = join(
sourceDir,
"dynamix.unraid.net",
"usr",
"local",
"emhttp",
"plugins",
"dynamix.my.servers",
"unraid-components"
);
if (!existsSync(webcomponentDir)) {
throw new Error(`Webcomponent directory ${webcomponentDir} does not exist`);
}
const manifestFiles = await findManifestFiles(webcomponentDir);
const hasManifest = manifestFiles.includes("manifest.json");
const hasUiManifest = manifestFiles.includes("ui.manifest.json");
if (!hasManifest || !hasUiManifest) {
console.log("Existing Manifest Files:", manifestFiles);
const missingFiles: string[] = [];
if (!hasManifest) missingFiles.push("manifest.json");
if (!hasUiManifest) missingFiles.push("ui.manifest.json");
throw new Error(
`Webcomponents missing required file(s): ${missingFiles.join(", ")} - ` +
`${!hasUiManifest ? "run 'pnpm build:wc' in unraid-ui for ui.manifest.json" : ""}` +
`${!hasManifest && !hasUiManifest ? " and " : ""}` +
`${!hasManifest ? "run 'pnpm build' in web for manifest.json" : ""}`
);
}
if (!existsSync(apiDir)) {
throw new Error(`API directory ${apiDir} does not exist`);
}
const packageJson = join(apiDir, "package.json");
if (!existsSync(packageJson)) {
throw new Error(`API package.json file ${packageJson} does not exist`);
}
// Now CHMOD the api/dist directory files to allow execution
await $`chmod +x ${apiDir}/dist/*.js`;
};
const buildTxz = async (validatedEnv: TxzEnv) => {
await validateSourceDir(validatedEnv);
// Use version from validated environment
const version = validatedEnv.apiVersion;
// Always use version when getting txz name
const txzName = getTxzName(version);
console.log(`Package name: ${txzName}`);
const txzPath = join(validatedEnv.txzOutputDir, txzName);
// Use the getVendorBundleName function for consistent naming
const vendorFilename = getVendorBundleName(version);
// Use the baseUrl and tag from validatedEnv, consistent with build-plugin.ts
const vendorUrl = getAssetUrl({
baseUrl: validatedEnv.baseUrl,
tag: validatedEnv.tag
}, vendorFilename);
console.log(`Storing vendor archive information: ${vendorUrl} -> ${vendorFilename}`);
await storeVendorArchiveInfo(version, vendorUrl, vendorFilename);
await ensureNodeJs();
// Create package - must be run from within the pre-pack directory
// Use cd option to run command from prePackDir
await cd(join(startingDir, "source", pluginName));
$.verbose = true;
// Create the package using the default package name
await $`${join(startingDir, "scripts/makepkg")} --chown y --compress -${
validatedEnv.compress
} --linkadd n ${txzPath}`;
$.verbose = false;
await cd(startingDir);
};
const main = async () => {
try {
const validatedEnv = await setupTxzEnv(process.argv);
await cleanupTxzFiles();
await buildTxz(validatedEnv);
} catch (error) {
console.error(error);
process.exit(1);
}
};
await main();