mirror of
https://github.com/unraid/api.git
synced 2026-05-05 14:41:54 -05:00
feat: split plugin builds
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced containerized plugin deployment support with updated Docker Compose configurations. - Added continuous build watch modes for API, web, and UI components for smoother development iterations. - Added a new job for API testing in the CI/CD workflow. - Added a new shell script to determine the local host's IP address for Docker configurations. - Introduced a new entry point and HTTP server setup in the plugin's Docker environment. - Added new scripts for building and watching plugin changes in real-time. - Added a new script for building the project in watch mode for the API and UI components. - **Improvements** - Streamlined the plugin installation process and refined release workflows for a more reliable user experience. - Enhanced overall CI/CD pipelines to ensure efficient, production-ready deployments. - Updated artifact upload paths and job definitions for clarity and efficiency. - Implemented new utility functions for better URL management and changelog generation. - Modified the `.dockerignore` file to ignore all contents within the `node_modules` directory. - Added new constants and functions for managing plugin paths and configurations. - Updated the build process in the Dockerfile to focus on release operations. - **Tests** - Expanded automated testing to validate environment setups and build stability, ensuring higher reliability during updates. - Introduced new test suites for validating plugin environment setups and configurations. - Added tests for validating environment variables and handling of manifest files. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Michael Datelle <mdatelle@icloud.com> Co-authored-by: mdatelle <mike@datelle.net> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Pujit Mehrotra <pujit@lime-technology.com>
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import {
|
||||
validatePluginEnv,
|
||||
setupPluginEnv,
|
||||
} from "../../cli/setup-plugin-environment";
|
||||
import { access, readFile } from "node:fs/promises";
|
||||
|
||||
// Mock fs/promises
|
||||
vi.mock("node:fs/promises", () => ({
|
||||
access: vi.fn(),
|
||||
readFile: vi.fn(),
|
||||
constants: {
|
||||
F_OK: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(readFile).mockImplementation((path, encoding) => {
|
||||
console.log("Mock readFile called with:", path, encoding);
|
||||
|
||||
// If called with encoding parameter (for release notes)
|
||||
if (encoding === "utf8") {
|
||||
if (path.toString().includes("valid-release-notes.txt")) {
|
||||
return Promise.resolve("Release notes content");
|
||||
}
|
||||
}
|
||||
// If called without encoding (for txz file)
|
||||
if (path.toString().includes("test.txz")) {
|
||||
return Promise.resolve(Buffer.from("test content"));
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(`File not found: ${path}`));
|
||||
});
|
||||
vi.mocked(access).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
describe("validatePluginEnv", () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("validates required fields", async () => {
|
||||
const validEnv = {
|
||||
baseUrl: "https://example.com",
|
||||
txzPath: "./test.txz",
|
||||
pluginVersion: "2024.05.05.1232",
|
||||
};
|
||||
|
||||
const result = await validatePluginEnv(validEnv);
|
||||
expect(result).toMatchObject(validEnv);
|
||||
});
|
||||
|
||||
it("throws on invalid URL", async () => {
|
||||
const invalidEnv = {
|
||||
baseUrl: "not-a-url",
|
||||
txzPath: "./test.txz",
|
||||
pluginVersion: "2024.05.05.1232",
|
||||
};
|
||||
|
||||
await expect(validatePluginEnv(invalidEnv)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("handles tag option in non-CI mode", async () => {
|
||||
const envWithTag = {
|
||||
baseUrl: "https://example.com",
|
||||
txzPath: "./test.txz",
|
||||
pluginVersion: "2024.05.05.1232",
|
||||
tag: "v1.0.0",
|
||||
};
|
||||
|
||||
const result = await validatePluginEnv(envWithTag);
|
||||
|
||||
expect(result.releaseNotes).toBe("FAST_TEST_CHANGELOG");
|
||||
expect(result.tag).toBe("v1.0.0");
|
||||
});
|
||||
|
||||
it("reads release notes when release-notes-path is provided", async () => {
|
||||
const envWithNotes = {
|
||||
baseUrl: "https://example.com",
|
||||
txzPath: "./test.txz",
|
||||
pluginVersion: "2024.05.05.1232",
|
||||
releaseNotesPath: "valid-release-notes.txt",
|
||||
};
|
||||
|
||||
const result = await validatePluginEnv(envWithNotes);
|
||||
|
||||
expect(access).toHaveBeenCalledWith("valid-release-notes.txt", 0);
|
||||
expect(readFile).toHaveBeenCalledWith("valid-release-notes.txt", "utf8");
|
||||
expect(result.releaseNotes).toBe("Release notes content");
|
||||
});
|
||||
|
||||
it("throws when release notes file is empty", async () => {
|
||||
// Instead of overwriting the entire mock, just mock this specific case
|
||||
vi.mocked(readFile).mockImplementationOnce((path, encoding) => {
|
||||
if (path === "/path/to/notes.md" && encoding === "utf8") {
|
||||
return Promise.resolve("");
|
||||
}
|
||||
return Promise.reject(new Error("Unexpected mock call"));
|
||||
});
|
||||
|
||||
const envWithEmptyNotes = {
|
||||
baseUrl: "https://example.com",
|
||||
txzPath: "./test.txz",
|
||||
pluginVersion: "2024.05.05.1232",
|
||||
releaseNotesPath: "/path/to/notes.md",
|
||||
};
|
||||
|
||||
await expect(validatePluginEnv(envWithEmptyNotes)).rejects.toThrow(
|
||||
"Release notes file is empty: /path/to/notes.md"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setupPluginEnv", () => {
|
||||
it("sets up environment from CLI arguments", async () => {
|
||||
const argv = [
|
||||
"node",
|
||||
"script.js",
|
||||
"--plugin-version",
|
||||
"2024.05.05.1232",
|
||||
"--txz-path",
|
||||
"./test.txz",
|
||||
"--base-url",
|
||||
"https://example.com",
|
||||
];
|
||||
|
||||
const result = await setupPluginEnv(argv);
|
||||
expect(result).toMatchObject({
|
||||
pluginVersion: "2024.05.05.1232",
|
||||
txzPath: "./test.txz",
|
||||
baseUrl: "https://example.com",
|
||||
});
|
||||
});
|
||||
|
||||
it("throws when required options are missing", async () => {
|
||||
const argv = ["node", "script.js"]; // Missing required options
|
||||
await expect(setupPluginEnv(argv)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("handles optional CLI arguments", async () => {
|
||||
const argv = [
|
||||
"node",
|
||||
"script.js",
|
||||
"--txz-path",
|
||||
"./test.txz",
|
||||
"--base-url",
|
||||
"https://example.com",
|
||||
"--tag",
|
||||
"PR1203",
|
||||
"--ci",
|
||||
"--plugin-version",
|
||||
"2024.05.05.1232",
|
||||
];
|
||||
|
||||
try {
|
||||
const result = await setupPluginEnv(argv);
|
||||
expect(result).toMatchObject({
|
||||
pluginVersion: "2024.05.05.1232",
|
||||
txzPath: "./test.txz",
|
||||
txzSha256:
|
||||
"6ae8a75555209fd6c44157c0aed8016e763ff435a19cf186f76863140143ff72",
|
||||
baseUrl: "https://example.com",
|
||||
tag: "PR1203",
|
||||
ci: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { join } from "path";
|
||||
import { validateTxzEnv, TxzEnv } from "../../cli/setup-txz-environment";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { startingDir } from "../../utils/consts";
|
||||
import { deployDir } from "../../utils/paths";
|
||||
|
||||
describe("setupTxzEnvironment", () => {
|
||||
it("should return default values when no arguments are provided", async () => {
|
||||
const envArgs = {};
|
||||
const expected: TxzEnv = { ci: false, skipValidation: "false", compress: "1", txzOutputDir: join(startingDir, deployDir) };
|
||||
|
||||
const result = await validateTxzEnv(envArgs);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should parse and return provided environment arguments", async () => {
|
||||
const envArgs = { ci: true, skipValidation: "true", txzOutputDir: join(startingDir, "deploy/release/test"), compress: '8' };
|
||||
const expected: TxzEnv = { ci: true, skipValidation: "true", compress: "8", txzOutputDir: join(startingDir, "deploy/release/test") };
|
||||
|
||||
const result = await validateTxzEnv(envArgs);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should warn and skip validation when skipValidation is true", async () => {
|
||||
const envArgs = { skipValidation: "true" };
|
||||
const consoleWarnSpy = vi
|
||||
.spyOn(console, "warn")
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await validateTxzEnv(envArgs);
|
||||
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
"skipValidation is true, skipping validation"
|
||||
);
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("should throw an error for invalid SKIP_VALIDATION value", async () => {
|
||||
const envArgs = { skipValidation: "invalid" };
|
||||
|
||||
await expect(validateTxzEnv(envArgs)).rejects.toThrow(
|
||||
"Must be true or false"
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user