mirror of
https://github.com/unraid/api.git
synced 2025-12-31 05:29:48 -06: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:
223
.github/workflows/main.yml
vendored
223
.github/workflows/main.yml
vendored
@@ -12,19 +12,25 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
release-please:
|
||||
name: Release Please
|
||||
# Only run release-please on pushes to main
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
|
||||
- id: release
|
||||
uses: googleapis/release-please-action@v4
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
outputs:
|
||||
releases_created: ${{ steps.release.outputs.releases_created }}
|
||||
tag_name: ${{ steps.release.outputs.tag_name }}
|
||||
releases_created: ${{ steps.release.outputs.releases_created || 'false' }}
|
||||
tag_name: ${{ steps.release.outputs.tag_name || '' }}
|
||||
test-api:
|
||||
name: Test API
|
||||
defaults:
|
||||
run:
|
||||
working-directory: api
|
||||
@@ -73,7 +79,7 @@ jobs:
|
||||
run: pnpm run coverage
|
||||
|
||||
build-api:
|
||||
name: Build and Test API
|
||||
name: Build API
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
@@ -136,13 +142,16 @@ jobs:
|
||||
export API_VERSION
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build-and-pack
|
||||
run: |
|
||||
pnpm run build:release
|
||||
|
||||
tar -czf deploy/unraid-api.tgz -C deploy/pack/ .
|
||||
|
||||
- name: Upload tgz to Github artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unraid-api
|
||||
path: ${{ github.workspace }}/api/deploy/release/*.tgz
|
||||
path: ${{ github.workspace }}/api/deploy/unraid-api.tgz
|
||||
|
||||
build-unraid-ui-webcomponents:
|
||||
name: Build Unraid UI Library (Webcomponent Version)
|
||||
@@ -196,13 +205,11 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unraid-wc-ui
|
||||
path: unraid-ui/dist/
|
||||
path: unraid-ui/dist-wc/
|
||||
|
||||
build-web:
|
||||
# needs: [build-unraid-ui]
|
||||
name: Build Web App
|
||||
environment:
|
||||
name: production
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
@@ -214,10 +221,10 @@ jobs:
|
||||
- name: Create env file
|
||||
run: |
|
||||
touch .env
|
||||
echo VITE_ACCOUNT=${{ vars.VITE_ACCOUNT }} >> .env
|
||||
echo VITE_CONNECT=${{ vars.VITE_CONNECT }} >> .env
|
||||
echo VITE_UNRAID_NET=${{ vars.VITE_UNRAID_NET }} >> .env
|
||||
echo VITE_CALLBACK_KEY=${{ vars.VITE_CALLBACK_KEY }} >> .env
|
||||
echo VITE_ACCOUNT=${{ secrets.VITE_ACCOUNT }} >> .env
|
||||
echo VITE_CONNECT=${{ secrets.VITE_CONNECT }} >> .env
|
||||
echo VITE_UNRAID_NET=${{ secrets.VITE_UNRAID_NET }} >> .env
|
||||
echo VITE_CALLBACK_KEY=${{ secrets.VITE_CALLBACK_KEY }} >> .env
|
||||
cat .env
|
||||
|
||||
- name: Install Node
|
||||
@@ -273,9 +280,13 @@ jobs:
|
||||
path: web/.nuxt/nuxt-custom-elements/dist/unraid-components
|
||||
|
||||
build-plugin:
|
||||
needs: [build-api, build-web, build-unraid-ui-webcomponents]
|
||||
outputs:
|
||||
tag: ${{ steps.build-plugin.outputs.tag }}
|
||||
name: Build and Deploy Plugin
|
||||
needs:
|
||||
- release-please
|
||||
- build-api
|
||||
- build-web
|
||||
- build-unraid-ui-webcomponents
|
||||
- test-api
|
||||
defaults:
|
||||
run:
|
||||
working-directory: plugin
|
||||
@@ -298,7 +309,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
@@ -320,73 +330,92 @@ jobs:
|
||||
cd ${{ github.workspace }}
|
||||
pnpm install --frozen-lockfile --filter @unraid/connect-plugin
|
||||
|
||||
- name: Download Unraid UI Components
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: unraid-wc-ui
|
||||
path: ${{ github.workspace }}/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/uui
|
||||
merge-multiple: true
|
||||
- name: Download Unraid Web Components
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: unraid-wc-*
|
||||
path: ./plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components
|
||||
pattern: unraid-wc-rich
|
||||
path: ${{ github.workspace }}/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/nuxt
|
||||
merge-multiple: true
|
||||
- name: Download Unraid API
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: unraid-api
|
||||
path: /tmp/unraid-api/
|
||||
- name: Extract Unraid API and Build Plugin
|
||||
path: ${{ github.workspace }}/plugin/api/
|
||||
- name: Extract Unraid API
|
||||
run: |
|
||||
mkdir -p ${{ github.workspace }}/plugin/source/dynamix.unraid.net/usr/local/unraid-api
|
||||
tar -xzf ${{ github.workspace }}/plugin/api/unraid-api.tgz -C ${{ github.workspace }}/plugin/source/dynamix.unraid.net/usr/local/unraid-api
|
||||
- name: Build Plugin and TXZ Based on Event and Tag
|
||||
id: build-plugin
|
||||
run: |
|
||||
tar -xzf /tmp/unraid-api/unraid-api.tgz -C ${{ github.workspace }}/plugin/source/dynamix.unraid.net/usr/local/unraid-api
|
||||
cd ${{ github.workspace }}/plugin
|
||||
pnpm run build:txz
|
||||
|
||||
if [ -n "${{ github.event.pull_request.number }}" ]; then
|
||||
export TAG=PR${{ github.event.pull_request.number }}
|
||||
# Put tag into github env
|
||||
echo "TAG=${TAG}" >> $GITHUB_OUTPUT
|
||||
TAG="PR${{ github.event.pull_request.number }}"
|
||||
BUCKET_PATH="unraid-api/tag/${TAG}"
|
||||
else
|
||||
TAG=""
|
||||
BUCKET_PATH="unraid-api"
|
||||
fi
|
||||
|
||||
pnpm run build
|
||||
- name: Upload binary txz and plg to Github artifacts
|
||||
if [ "${{ needs.release-please.outputs.releases_created }}" == 'true' ]; then
|
||||
BASE_URL="https://stable.dl.unraid.net/unraid-api"
|
||||
else
|
||||
BASE_URL="https://preview.dl.unraid.net/unraid-api"
|
||||
fi
|
||||
|
||||
echo "BUCKET_PATH=${BUCKET_PATH}" >> $GITHUB_OUTPUT
|
||||
echo "TAG=${TAG}" >> $GITHUB_OUTPUT
|
||||
|
||||
pnpm run build:plugin --tag="${TAG}" --base-url="${BASE_URL}"
|
||||
|
||||
- name: Ensure Plugin Files Exist
|
||||
run: |
|
||||
if [ ! -f ./deploy/*.plg ]; then
|
||||
echo "Error: .plg file not found in plugin/deploy/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f ./deploy/*.txz ]; then
|
||||
echo "Error: .txz file not found in plugin/deploy/"
|
||||
exit 1
|
||||
fi
|
||||
- name: Upload to GHA
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: connect-files
|
||||
path: |
|
||||
plugin/deploy/release/plugins/
|
||||
plugin/deploy/release/archive/*.txz
|
||||
retention-days: 5
|
||||
if-no-files-found: error
|
||||
|
||||
release-pull-request:
|
||||
if: |
|
||||
github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test-api, build-plugin]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Make PR Release Folder
|
||||
run: mkdir pr-release/
|
||||
|
||||
- name: Download plugin binary tgz
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: connect-files
|
||||
|
||||
- name: Copy other release files to pr-release
|
||||
run: |
|
||||
cp archive/*.txz pr-release/
|
||||
cp plugins/pr/dynamix.unraid.net.plg pr-release/dynamix.unraid.net.plg
|
||||
|
||||
name: unraid-plugin
|
||||
path: plugin/deploy/
|
||||
- name: Upload to Cloudflare
|
||||
uses: jakejarvis/s3-sync-action@v0.5.1
|
||||
if: github.event_name == 'pull_request' || startsWith(github.ref, 'refs/heads/main')
|
||||
env:
|
||||
AWS_S3_ENDPOINT: ${{ secrets.CF_ENDPOINT }}
|
||||
AWS_S3_BUCKET: ${{ secrets.CF_BUCKET_PREVIEW }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: "auto"
|
||||
SOURCE_DIR: pr-release
|
||||
DEST_DIR: unraid-api/tag/${{ needs.build-plugin.outputs.tag }}
|
||||
AWS_DEFAULT_REGION: auto
|
||||
run: |
|
||||
# Sync the deploy directory to the Cloudflare bucket - CRC32 is required for the checksum-algorithm on cloudflare (ref. https://community.cloudflare.com/t/an-error-occurred-internalerror-when-calling-the-putobject-operation/764905/8)
|
||||
aws s3 sync deploy/ s3://${{ secrets.CF_BUCKET_PREVIEW }}/${{ steps.build-plugin.outputs.BUCKET_PATH }} --endpoint-url ${{ secrets.CF_ENDPOINT }} --checksum-algorithm CRC32
|
||||
|
||||
- name: Upload Release Assets
|
||||
if: needs.release-please.outputs.releases_created == 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
release_name=$(gh release list --repo ${{ github.repository }} --json name,isDraft --jq '.[] | select(.isDraft == true) | .name' | head -n 1)
|
||||
# For each file in release directory
|
||||
for file in deploy/*; do
|
||||
echo "Uploading $file to release..."
|
||||
gh release upload "${release_name}" "$file" --clobber
|
||||
done
|
||||
|
||||
- name: Comment URL
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: thollander/actions-comment-pull-request@v3
|
||||
with:
|
||||
comment-tag: prlink
|
||||
@@ -395,73 +424,5 @@ jobs:
|
||||
This plugin has been deployed to Cloudflare R2 and is available for testing.
|
||||
Download it at this URL:
|
||||
```
|
||||
https://preview.dl.unraid.net/unraid-api/tag/${{ needs.build-plugin.outputs.tag }}/dynamix.unraid.net.plg
|
||||
https://preview.dl.unraid.net/unraid-api/tag/${{ steps.build-plugin.outputs.tag }}/dynamix.unraid.net.plg
|
||||
```
|
||||
|
||||
release-staging:
|
||||
environment:
|
||||
name: staging
|
||||
# Only release if this is a push to the main branch
|
||||
if: startsWith(github.ref, 'refs/heads/main')
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test-api, build-plugin]
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Make Staging Release Folder
|
||||
run: mkdir staging-release/
|
||||
|
||||
- name: Download plugin binary tgz
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: connect-files
|
||||
|
||||
- name: Copy Files for Staging Release
|
||||
run: |
|
||||
cp archive/*.txz staging-release/
|
||||
cp plugins/staging/dynamix.unraid.net.plg staging-release/dynamix.unraid.net.plg
|
||||
ls -al staging-release
|
||||
|
||||
- name: Upload Staging Plugin to Cloudflare Bucket
|
||||
uses: jakejarvis/s3-sync-action@v0.5.1
|
||||
env:
|
||||
AWS_S3_ENDPOINT: ${{ secrets.CF_ENDPOINT }}
|
||||
AWS_S3_BUCKET: ${{ secrets.CF_BUCKET_PREVIEW }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: "auto"
|
||||
SOURCE_DIR: staging-release
|
||||
DEST_DIR: unraid-api
|
||||
|
||||
create-draft-release:
|
||||
# Only run if release-please created a release
|
||||
if: needs.release-please.outputs.releases_created == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [release-please, test-api, build-plugin]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download plugin binary tgz
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: connect-files
|
||||
|
||||
- name: Move Files to Release Folder
|
||||
run: |
|
||||
mkdir -p release/
|
||||
mv plugins/production/dynamix.unraid.net.plg release/
|
||||
mv archive/*.txz release/
|
||||
|
||||
- name: Upload Release Assets
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
release_name=$(gh release list --repo ${{ github.repository }} --json name,isDraft --jq '.[] | select(.isDraft == true) | .name' | head -n 1)
|
||||
# For each file in release directory
|
||||
for file in release/*; do
|
||||
echo "Uploading $file to release..."
|
||||
gh release upload "${release_name}" "$file" --clobber
|
||||
done
|
||||
|
||||
@@ -42,4 +42,4 @@ ENV NODE_ENV=production
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["pnpm", "run", "build-and-pack"]
|
||||
CMD ["pnpm", "run", "build:release"]
|
||||
@@ -20,8 +20,9 @@
|
||||
"// Build and Deploy": "",
|
||||
"build": "vite build --mode=production",
|
||||
"postbuild": "chmod +x dist/main.js && chmod +x dist/cli.js",
|
||||
"build:watch": "vite build --mode=production --watch",
|
||||
"build:docker": "./scripts/dc.sh run --rm builder",
|
||||
"build-and-pack": "tsx ./scripts/build.ts",
|
||||
"build:release": "tsx ./scripts/build.ts",
|
||||
"preunraid:deploy": "pnpm build",
|
||||
"unraid:deploy": "./scripts/deploy-dev.sh",
|
||||
"// GraphQL Codegen": "",
|
||||
|
||||
@@ -45,12 +45,6 @@ try {
|
||||
// chmod the cli
|
||||
await $`chmod +x ./dist/cli.js`;
|
||||
await $`chmod +x ./dist/main.js`;
|
||||
|
||||
// Create the tarball
|
||||
await $`tar -czf ../release/unraid-api.tgz ./`;
|
||||
|
||||
// Clean up
|
||||
cd('..');
|
||||
} catch (error) {
|
||||
// Error with a command
|
||||
if (Object.keys(error).includes('stderr')) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"version": "4.1.3",
|
||||
"scripts": {
|
||||
"build": "pnpm -r build",
|
||||
"build:watch": "pnpm -r build:watch",
|
||||
"dev": "pnpm -r dev",
|
||||
"unraid:deploy": "pnpm -r unraid:deploy",
|
||||
"test": "pnpm -r test",
|
||||
|
||||
@@ -5,4 +5,4 @@ deploy/*
|
||||
.github/*
|
||||
.vscode/*
|
||||
.DS_Store
|
||||
node_modules
|
||||
node_modules/*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22-bookworm-slim AS builder
|
||||
FROM node:22-bookworm-slim AS plugin-builder
|
||||
|
||||
# Install build tools and dependencies
|
||||
RUN apt-get update -y && apt-get install -y \
|
||||
@@ -18,8 +18,17 @@ WORKDIR /app
|
||||
|
||||
COPY package.json ./
|
||||
|
||||
RUN npm install --include=dev
|
||||
RUN corepack enable && pnpm install
|
||||
|
||||
COPY . .
|
||||
# Install a simple http server
|
||||
RUN npm install -g http-server
|
||||
|
||||
CMD ["npm", "run", "build"]
|
||||
# Expose port 8080
|
||||
EXPOSE 8080
|
||||
|
||||
COPY scripts/entrypoint.sh /start.sh
|
||||
RUN chmod +x /start.sh
|
||||
|
||||
ENTRYPOINT [ "/start.sh" ]
|
||||
|
||||
CMD [ "pnpm", "run", "build:watcher" ]
|
||||
|
||||
172
plugin/builder/__tests__/cli/setup-plugin-environment.spec.ts
Normal file
172
plugin/builder/__tests__/cli/setup-plugin-environment.spec.ts
Normal file
@@ -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;
|
||||
}
|
||||
});
|
||||
});
|
||||
47
plugin/builder/__tests__/cli/setup-txz-environment.spec.ts
Normal file
47
plugin/builder/__tests__/cli/setup-txz-environment.spec.ts
Normal file
@@ -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"
|
||||
);
|
||||
});
|
||||
});
|
||||
108
plugin/builder/build-plugin.ts
Normal file
108
plugin/builder/build-plugin.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { readFile, writeFile, mkdir, rename } from "fs/promises";
|
||||
import { $ } from "zx";
|
||||
import { escape as escapeHtml } from "html-sloppy-escaper";
|
||||
import { dirname, join } from "node:path";
|
||||
import { getTxzName, pluginName, startingDir } from "./utils/consts";
|
||||
import { getPluginUrl } from "./utils/bucket-urls";
|
||||
import { getMainTxzUrl } from "./utils/bucket-urls";
|
||||
import {
|
||||
deployDir,
|
||||
getDeployPluginPath,
|
||||
getRootPluginPath,
|
||||
} from "./utils/paths";
|
||||
import { PluginEnv, setupPluginEnv } from "./cli/setup-plugin-environment";
|
||||
import { cleanupPluginFiles } from "./utils/cleanup";
|
||||
|
||||
/**
|
||||
* Check if git is available
|
||||
*/
|
||||
const checkGit = async () => {
|
||||
try {
|
||||
await $`git log -1 --pretty=%B`;
|
||||
} catch (err) {
|
||||
console.error(`Error: git not available: ${err}`);
|
||||
throw new Error(`Git not available: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
const moveTxzFile = async (txzPath: string, pluginVersion: string) => {
|
||||
const txzName = getTxzName(pluginVersion);
|
||||
await rename(txzPath, join(deployDir, txzName));
|
||||
};
|
||||
|
||||
function updateEntityValue(
|
||||
xmlString: string,
|
||||
entityName: string,
|
||||
newValue: string
|
||||
) {
|
||||
console.log("Updating entity:", entityName, "with value:", newValue);
|
||||
const regex = new RegExp(`<!ENTITY ${entityName} "[^"]*">`);
|
||||
if (regex.test(xmlString)) {
|
||||
return xmlString.replace(regex, `<!ENTITY ${entityName} "${newValue}">`);
|
||||
}
|
||||
throw new Error(`Entity ${entityName} not found in XML`);
|
||||
}
|
||||
|
||||
const buildPlugin = async ({
|
||||
pluginVersion,
|
||||
baseUrl,
|
||||
tag,
|
||||
txzSha256,
|
||||
releaseNotes,
|
||||
}: PluginEnv) => {
|
||||
// Update plg file
|
||||
let plgContent = await readFile(getRootPluginPath({ startingDir }), "utf8");
|
||||
|
||||
// Update entity values
|
||||
const entities: Record<string, string> = {
|
||||
name: pluginName,
|
||||
version: pluginVersion,
|
||||
pluginURL: getPluginUrl({ baseUrl, tag }),
|
||||
MAIN_TXZ: getMainTxzUrl({ baseUrl, pluginVersion, tag }),
|
||||
TXZ_SHA256: txzSha256,
|
||||
...(tag ? { TAG: tag } : {}),
|
||||
};
|
||||
|
||||
console.log("Entities:", entities);
|
||||
// Iterate over entities and update them
|
||||
Object.entries(entities).forEach(([key, value]) => {
|
||||
if (!value) {
|
||||
throw new Error(`Entity ${key} not set in entities: ${JSON.stringify(entities)}`);
|
||||
}
|
||||
plgContent = updateEntityValue(plgContent, key, value);
|
||||
});
|
||||
|
||||
if (releaseNotes) {
|
||||
// Update the CHANGES section with release notes
|
||||
plgContent = plgContent.replace(
|
||||
/<CHANGES>.*?<\/CHANGES>/s,
|
||||
`<CHANGES>\n${escapeHtml(releaseNotes)}\n</CHANGES>`
|
||||
);
|
||||
}
|
||||
|
||||
await mkdir(dirname(getDeployPluginPath({ startingDir })), {
|
||||
recursive: true,
|
||||
});
|
||||
console.log("Writing plg file to:", getDeployPluginPath({ startingDir }));
|
||||
await writeFile(getDeployPluginPath({ startingDir }), plgContent);
|
||||
};
|
||||
|
||||
/**
|
||||
* Main build script
|
||||
*/
|
||||
|
||||
const main = async () => {
|
||||
try {
|
||||
const validatedEnv = await setupPluginEnv(process.argv);
|
||||
await checkGit();
|
||||
await cleanupPluginFiles();
|
||||
|
||||
await buildPlugin(validatedEnv);
|
||||
await moveTxzFile(validatedEnv.txzPath, validatedEnv.pluginVersion);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
await main();
|
||||
110
plugin/builder/build-txz.ts
Normal file
110
plugin/builder/build-txz.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { join } from "path";
|
||||
import { $, cd } from "zx";
|
||||
import { existsSync } from "node:fs";
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { getTxzName, pluginName, startingDir } from "./utils/consts";
|
||||
import { setupTxzEnv, TxzEnv } from "./cli/setup-txz-environment";
|
||||
import { cleanupTxzFiles } from "./utils/cleanup";
|
||||
|
||||
// Recursively search for manifest files
|
||||
const findManifestFiles = async (dir: string): Promise<string[]> => {
|
||||
const entries = await readdir(dir, { withFileTypes: true });
|
||||
const files: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...(await findManifestFiles(fullPath)));
|
||||
} else if (
|
||||
entry.isFile() &&
|
||||
(entry.name === "manifest.json" || entry.name === "ui.manifest.json")
|
||||
) {
|
||||
files.push(entry.name);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
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);
|
||||
throw new Error(
|
||||
`Webcomponents must contain both "ui.manifest.json" and "manifest.json" - be sure to have run pnpm build:wc in unraid-ui`
|
||||
);
|
||||
}
|
||||
|
||||
const apiDir = join(
|
||||
startingDir,
|
||||
"source",
|
||||
pluginName,
|
||||
"usr",
|
||||
"local",
|
||||
"unraid-api"
|
||||
);
|
||||
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);
|
||||
const txzPath = join(validatedEnv.txzOutputDir, getTxzName());
|
||||
|
||||
// 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;
|
||||
|
||||
await $`${join(startingDir, "scripts/makepkg")} --chown y --compress -${
|
||||
validatedEnv.compress
|
||||
} --linkadd y ${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();
|
||||
122
plugin/builder/cli/setup-plugin-environment.ts
Normal file
122
plugin/builder/cli/setup-plugin-environment.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { z } from "zod";
|
||||
import { access, constants, readFile } from "node:fs/promises";
|
||||
import { Command } from "commander";
|
||||
import { getStagingChangelogFromGit } from "../utils/changelog";
|
||||
import { createHash } from "node:crypto";
|
||||
import { getTxzPath } from "../utils/paths";
|
||||
|
||||
const safeParseEnvSchema = z.object({
|
||||
ci: z.boolean().optional(),
|
||||
baseUrl: z.string().url(),
|
||||
tag: z.string().optional().default(''),
|
||||
|
||||
txzPath: z.string().refine((val) => val.endsWith(".txz"), {
|
||||
message: "TXZ Path must end with .txz",
|
||||
}),
|
||||
pluginVersion: z.string().regex(/^\d{4}\.\d{2}\.\d{2}\.\d{4}$/, {
|
||||
message: "Plugin version must be in the format YYYY.MM.DD.HHMM",
|
||||
}),
|
||||
releaseNotesPath: z.string().optional(),
|
||||
});
|
||||
|
||||
const pluginEnvSchema = safeParseEnvSchema.extend({
|
||||
releaseNotes: z.string().nonempty("Release notes are required"),
|
||||
txzSha256: z.string().refine((val) => val.length === 64, {
|
||||
message: "TXZ SHA256 must be 64 characters long",
|
||||
}),
|
||||
});
|
||||
|
||||
export type PluginEnv = z.infer<typeof pluginEnvSchema>;
|
||||
|
||||
export const validatePluginEnv = async (
|
||||
envArgs: Record<string, any>
|
||||
): Promise<PluginEnv> => {
|
||||
const safeEnv = safeParseEnvSchema.parse(envArgs);
|
||||
if (safeEnv.releaseNotesPath) {
|
||||
await access(safeEnv.releaseNotesPath, constants.F_OK);
|
||||
const releaseNotes = await readFile(safeEnv.releaseNotesPath, "utf8");
|
||||
if (!releaseNotes || releaseNotes.length === 0) {
|
||||
throw new Error(
|
||||
`Release notes file is empty: ${safeEnv.releaseNotesPath}`
|
||||
);
|
||||
}
|
||||
envArgs.releaseNotes = releaseNotes;
|
||||
} else {
|
||||
envArgs.releaseNotes =
|
||||
process.env.TEST === "true"
|
||||
? "FAST_TEST_CHANGELOG"
|
||||
: await getStagingChangelogFromGit(safeEnv);
|
||||
}
|
||||
|
||||
if (safeEnv.txzPath) {
|
||||
await access(safeEnv.txzPath, constants.F_OK);
|
||||
console.log("Reading txz file from:", safeEnv.txzPath);
|
||||
const txzFile = await readFile(safeEnv.txzPath);
|
||||
if (!txzFile || txzFile.length === 0) {
|
||||
throw new Error(`TXZ Path is empty: ${safeEnv.txzPath}`);
|
||||
}
|
||||
envArgs.txzSha256 = getSha256(txzFile);
|
||||
}
|
||||
|
||||
const validatedEnv = pluginEnvSchema.parse(envArgs);
|
||||
|
||||
if (validatedEnv.tag) {
|
||||
console.warn("Tag is set, will generate a TAGGED build");
|
||||
}
|
||||
|
||||
return validatedEnv;
|
||||
};
|
||||
|
||||
export const getPluginVersion = () => {
|
||||
const now = new Date();
|
||||
|
||||
const formatUtcComponent = (component: number) => String(component).padStart(2, '0');
|
||||
|
||||
const year = now.getUTCFullYear();
|
||||
const month = formatUtcComponent(now.getUTCMonth() + 1);
|
||||
const day = formatUtcComponent(now.getUTCDate());
|
||||
const hour = formatUtcComponent(now.getUTCHours());
|
||||
const minute = formatUtcComponent(now.getUTCMinutes());
|
||||
|
||||
const version = `${year}.${month}.${day}.${hour}${minute}`;
|
||||
console.log("Plugin version:", version);
|
||||
return version;
|
||||
};
|
||||
|
||||
export const setupPluginEnv = async (argv: string[]): Promise<PluginEnv> => {
|
||||
// CLI setup for plugin environment
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.requiredOption(
|
||||
"--base-url <url>",
|
||||
"Base URL - will be used to determine the bucket, and combined with the tag (if set) to form the final URL",
|
||||
process.env.CI === "true"
|
||||
? "This is a CI build, please set the base URL manually"
|
||||
: `http://${process.env.HOST_LAN_IP}:8080`
|
||||
)
|
||||
.option(
|
||||
"--txz-path <path>",
|
||||
"Path to built package, will be used to generate the SHA256 and renamed with the plugin version",
|
||||
getTxzPath({ startingDir: process.cwd() })
|
||||
)
|
||||
.option(
|
||||
"--plugin-version <version>",
|
||||
"Plugin Version in the format YYYY.MM.DD.HHMM",
|
||||
getPluginVersion()
|
||||
)
|
||||
.option("--tag <tag>", "Tag (used for PR and staging builds)", process.env.TAG)
|
||||
.option("--release-notes-path <path>", "Path to release notes file")
|
||||
.option("--ci", "CI mode", process.env.CI === "true")
|
||||
.parse(argv);
|
||||
|
||||
const options = program.opts();
|
||||
console.log("Options:", options);
|
||||
const env = await validatePluginEnv(options);
|
||||
console.log("Plugin environment setup successfully:", env);
|
||||
return env;
|
||||
};
|
||||
|
||||
function getSha256(txzBlob: Buffer): string {
|
||||
return createHash("sha256").update(txzBlob).digest("hex");
|
||||
}
|
||||
48
plugin/builder/cli/setup-txz-environment.ts
Normal file
48
plugin/builder/cli/setup-txz-environment.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { join } from "path";
|
||||
import { z } from "zod";
|
||||
import { Command } from "commander";
|
||||
import { startingDir } from "../utils/consts";
|
||||
import { deployDir } from "../utils/paths";
|
||||
|
||||
const txzEnvSchema = z.object({
|
||||
ci: z.boolean().optional().default(false),
|
||||
skipValidation: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("false")
|
||||
.refine((v) => v === "true" || v === "false", "Must be true or false"),
|
||||
compress: z.string().optional().default("1"),
|
||||
txzOutputDir: z.string().optional().default(join(startingDir, deployDir)),
|
||||
});
|
||||
|
||||
export type TxzEnv = z.infer<typeof txzEnvSchema>;
|
||||
|
||||
export const validateTxzEnv = async (
|
||||
envArgs: Record<string, any>
|
||||
): Promise<TxzEnv> => {
|
||||
const validatedEnv = txzEnvSchema.parse(envArgs);
|
||||
|
||||
if ("skipValidation" in validatedEnv) {
|
||||
console.warn("skipValidation is true, skipping validation");
|
||||
}
|
||||
|
||||
return validatedEnv;
|
||||
};
|
||||
|
||||
export const setupTxzEnv = async (argv: string[]): Promise<TxzEnv> => {
|
||||
// CLI setup for TXZ environment
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.option("--skip-validation", "Skip validation", "false")
|
||||
.option("--ci", "CI mode", process.env.CI === "true")
|
||||
.option("--compress, -z", "Compress level", "1")
|
||||
|
||||
.parse(argv);
|
||||
|
||||
const options = program.opts();
|
||||
|
||||
const env = await validateTxzEnv(options);
|
||||
console.log("TXZ environment setup successfully:", env);
|
||||
return env;
|
||||
};
|
||||
49
plugin/builder/utils/bucket-urls.ts
Normal file
49
plugin/builder/utils/bucket-urls.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { getTxzName, LOCAL_BUILD_TAG, pluginNameWithExt } from "./consts";
|
||||
|
||||
// Define a common interface for URL parameters
|
||||
interface UrlParams {
|
||||
baseUrl: string;
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
interface TxzUrlParams extends UrlParams {
|
||||
pluginVersion: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bucket path for the given tag
|
||||
* ex. baseUrl = https://stable.dl.unraid.net/unraid-api
|
||||
* ex. tag = PR123
|
||||
* ex. returns = https://stable.dl.unraid.net/unraid-api/tag/PR123
|
||||
*/
|
||||
const getRootBucketPath = ({ baseUrl, tag }: UrlParams): URL => {
|
||||
// Append tag to the baseUrl if tag is set, otherwise return the baseUrl
|
||||
const url = new URL(baseUrl);
|
||||
if (tag && tag !== LOCAL_BUILD_TAG) {
|
||||
// Ensure the path ends with a trailing slash before adding the tag
|
||||
url.pathname = url.pathname.replace(/\/?$/, "/") + "tag/" + tag;
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the URL for the plugin file
|
||||
* ex. returns = BASE_URL/TAG/dynamix.unraid.net.plg
|
||||
*/
|
||||
export const getPluginUrl = (params: UrlParams): string => {
|
||||
const rootUrl = getRootBucketPath(params);
|
||||
// Ensure the path ends with a slash and join with the plugin name
|
||||
rootUrl.pathname = rootUrl.pathname.replace(/\/?$/, "/") + pluginNameWithExt;
|
||||
return rootUrl.toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the URL for the main TXZ file
|
||||
* ex. returns = BASE_URL/TAG/dynamix.unraid.net-4.1.3.txz
|
||||
*/
|
||||
export const getMainTxzUrl = (params: TxzUrlParams): string => {
|
||||
const rootUrl = getRootBucketPath(params);
|
||||
// Ensure the path ends with a slash and join with the txz name
|
||||
rootUrl.pathname = rootUrl.pathname.replace(/\/?$/, "/") + getTxzName(params.pluginVersion);
|
||||
return rootUrl.toString();
|
||||
};
|
||||
39
plugin/builder/utils/changelog.ts
Normal file
39
plugin/builder/utils/changelog.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import conventionalChangelog from "conventional-changelog";
|
||||
|
||||
import { PluginEnv } from "../cli/setup-plugin-environment";
|
||||
|
||||
export const getStagingChangelogFromGit = async ({
|
||||
pluginVersion,
|
||||
tag,
|
||||
}: Pick<PluginEnv, "pluginVersion" | "tag">): Promise<string> => {
|
||||
try {
|
||||
const changelogStream = conventionalChangelog(
|
||||
{
|
||||
preset: "conventionalcommits",
|
||||
},
|
||||
{
|
||||
version: pluginVersion,
|
||||
},
|
||||
tag
|
||||
? {
|
||||
from: "origin/main",
|
||||
to: "HEAD",
|
||||
}
|
||||
: {},
|
||||
undefined,
|
||||
tag
|
||||
? {
|
||||
headerPartial: `## [${tag}](https://github.com/unraid/api/${tag})\n\n`,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
let changelog = "";
|
||||
for await (const chunk of changelogStream) {
|
||||
changelog += chunk;
|
||||
}
|
||||
// Encode HTML entities using the 'he' library
|
||||
return changelog ?? "";
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to get changelog from git: ${err}`);
|
||||
}
|
||||
};
|
||||
18
plugin/builder/utils/cleanup.ts
Normal file
18
plugin/builder/utils/cleanup.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { deployDir } from "./paths";
|
||||
import { mkdir } from "node:fs/promises";
|
||||
import { startingDir } from "./consts";
|
||||
import { join } from "node:path";
|
||||
|
||||
export const cleanupTxzFiles = async () => {
|
||||
await mkdir(deployDir, { recursive: true });
|
||||
const txzFiles = join(startingDir, deployDir, "*.txz");
|
||||
await execSync(`rm -rf ${txzFiles}`);
|
||||
};
|
||||
|
||||
export const cleanupPluginFiles = async () => {
|
||||
await mkdir(deployDir, { recursive: true });
|
||||
const pluginFiles = join(startingDir, deployDir, "*.plg");
|
||||
await execSync(`rm -rf ${pluginFiles}`);
|
||||
};
|
||||
|
||||
13
plugin/builder/utils/consts.ts
Normal file
13
plugin/builder/utils/consts.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export const pluginName = "dynamix.unraid.net" as const;
|
||||
export const pluginNameWithExt = `${pluginName}.plg` as const;
|
||||
|
||||
export const getTxzName = (version?: string) =>
|
||||
version ? `${pluginName}-${version}.txz` : `${pluginName}.txz`;
|
||||
export const startingDir = process.cwd();
|
||||
|
||||
export const BASE_URLS = {
|
||||
STABLE: "https://stable.dl.unraid.net/unraid-api",
|
||||
PREVIEW: "https://preview.dl.unraid.net/unraid-api",
|
||||
} as const;
|
||||
|
||||
export const LOCAL_BUILD_TAG = "LOCAL_PLUGIN_BUILD" as const;
|
||||
43
plugin/builder/utils/paths.ts
Normal file
43
plugin/builder/utils/paths.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { join } from "path";
|
||||
import { getTxzName, pluginNameWithExt } from "./consts";
|
||||
|
||||
export interface PathConfig {
|
||||
startingDir: string;
|
||||
}
|
||||
|
||||
export interface TxzPathConfig extends PathConfig {
|
||||
pluginVersion?: string;
|
||||
}
|
||||
|
||||
export const deployDir = "deploy" as const;
|
||||
|
||||
/**
|
||||
* Get the path to the root plugin directory
|
||||
* @param startingDir - The starting directory
|
||||
* @returns The path to the root plugin directory
|
||||
*/
|
||||
export function getRootPluginPath({ startingDir }: PathConfig): string {
|
||||
return join(startingDir, "/plugins/", pluginNameWithExt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the deploy plugin directory
|
||||
* @param startingDir - The starting directory
|
||||
* @returns The path to the deploy plugin directory
|
||||
*/
|
||||
export function getDeployPluginPath({ startingDir }: PathConfig): string {
|
||||
return join(startingDir, deployDir, pluginNameWithExt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the TXZ file
|
||||
* @param startingDir - The starting directory
|
||||
* @param pluginVersion - The plugin version
|
||||
* @returns The path to the TXZ file
|
||||
*/
|
||||
export function getTxzPath({
|
||||
startingDir,
|
||||
pluginVersion,
|
||||
}: TxzPathConfig): string {
|
||||
return join(startingDir, deployDir, getTxzName(pluginVersion));
|
||||
}
|
||||
20
plugin/docker-compose.yml
Normal file
20
plugin/docker-compose.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
services:
|
||||
plugin-builder:
|
||||
ports:
|
||||
- 8080:8080
|
||||
build: .
|
||||
volumes:
|
||||
- ./:/app
|
||||
- /app/node_modules
|
||||
- ../.git:/app/.git
|
||||
- ./source:/app/source
|
||||
- ./scripts:/app/scripts
|
||||
- ../unraid-ui/dist-wc:/app/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/uui
|
||||
- ../web/.nuxt/nuxt-custom-elements/dist/unraid-components:/app/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/nuxt
|
||||
- ../api/deploy/pack/:/app/source/dynamix.unraid.net/usr/local/unraid-api
|
||||
stdin_open: true # equivalent to -i
|
||||
tty: true # equivalent to -t
|
||||
environment:
|
||||
- HOST_LAN_IP=${HOST_LAN_IP}
|
||||
- CI=${CI:-false}
|
||||
- TAG=${TAG}
|
||||
3398
plugin/package-lock.json
generated
Normal file
3398
plugin/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,11 +3,11 @@
|
||||
"version": "4.1.3",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"commander": "^13.1.0",
|
||||
"conventional-changelog": "^6.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"glob": "^11.0.1",
|
||||
"html-sloppy-escaper": "^0.1.0",
|
||||
"http-server": "^14.1.1",
|
||||
"semver": "^7.7.1",
|
||||
"tsx": "^4.19.2",
|
||||
"zod": "^3.24.1",
|
||||
@@ -17,29 +17,27 @@
|
||||
"license": "GPL-2.0-only",
|
||||
"scripts": {
|
||||
"// Build scripts": "",
|
||||
"build": "tsx scripts/build-plugin-and-txz.ts",
|
||||
"build": "pnpm run build:txz && pnpm run build:plugin",
|
||||
"build:txz": "tsx builder/build-txz.ts",
|
||||
"build:plugin": "tsx builder/build-plugin.ts",
|
||||
"build:validate": "npm run env:validate && npm run build",
|
||||
"build:watcher": "nodemon --watch 'source/**/*' --exec 'pnpm run build'",
|
||||
"// Docker commands": "",
|
||||
"docker:build": "docker build -t plugin-builder .",
|
||||
"docker:run": "docker run --env-file .env -v $(pwd)/:/app/ -v $(cd ../ && pwd)/.git:/app/.git -v $(pwd)/source:/app/source -v $(pwd)/scripts:/app/scripts plugin-builder",
|
||||
"docker:build-and-run": "npm run docker:build && npm run docker:run",
|
||||
"http-server": "http-server ./deploy/release/ -p 8080 --cors",
|
||||
"build:watch": "./scripts/dc.sh pnpm run build:watcher",
|
||||
"docker:build": "docker compose build",
|
||||
"docker:run": "./scripts/dc.sh /bin/bash",
|
||||
"docker:build-and-run": "pnpm run docker:build && pnpm run docker:run",
|
||||
"// Environment management": "",
|
||||
"env:init": "cp .env.example .env",
|
||||
"env:validate": "test -f .env || (echo 'Error: .env file missing. Run npm run env:init first' && exit 1)",
|
||||
"env:clean": "rm -f .env",
|
||||
"// Composite commands": "",
|
||||
"start": "npm run env:validate && npm run docker:build-and-run",
|
||||
"test": "npm run env:init && npm run start && npm run env:clean",
|
||||
"// Watchers for Other Changes": "",
|
||||
"wc:clean": "rm -r ./source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/*",
|
||||
"wc:watch": "cpx -w -v \"../web/.nuxt/nuxt-custom-elements/dist/unraid-components/**/*\" ./source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components",
|
||||
"api:watch": "cpx -w -C -v \"../api/deploy/pack/**/*\" ./source/dynamix.unraid.net/usr/local/unraid-api",
|
||||
"ui:watch": "cpx -w -v \"../unraid-ui/dist/**/*\" ./source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components",
|
||||
"watch:all": "npm run wc:clean && npm run wc:watch & npm run api:watch & npm run ui:watch"
|
||||
"// Testing": "",
|
||||
"test": "vitest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cpx2": "^8.0.0"
|
||||
"http-server": "^14.1.1",
|
||||
"nodemon": "^3.1.7",
|
||||
"vitest": "^3.0.7"
|
||||
},
|
||||
"packageManager": "pnpm@10.4.1"
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
<!ENTITY name "">
|
||||
<!ENTITY launch "Connect">
|
||||
<!ENTITY author "limetech">
|
||||
<!ENTITY env "">
|
||||
<!ENTITY version "">
|
||||
<!ENTITY pluginURL "">
|
||||
<!ENTITY source "/boot/config/plugins/dynamix.my.servers/&name;">
|
||||
<!ENTITY SHA256 "">
|
||||
<!ENTITY API_version "">
|
||||
<!ENTITY TXZ_SHA256 "">
|
||||
<!ENTITY NODEJS_VERSION "22.14.0">
|
||||
<!-- To get SHA256:
|
||||
wget https://nodejs.org/download/release/v22.14.0/node-v22.14.0-linux-x64.tar.xz
|
||||
@@ -32,9 +30,9 @@
|
||||
<!-- prevent prod plugin from installing when staging already installed, and vice versa -->
|
||||
<FILE Run="/bin/bash" Method="install">
|
||||
<INLINE>
|
||||
name="&name;" version="&version;" API_version="&API_version;" PLGTYPE="&env;" pluginURL="&pluginURL;"
|
||||
name="&name;" version="&version;" pluginURL="&pluginURL;"
|
||||
<![CDATA[
|
||||
echo "Installing ${name}.plg ${version} with Unraid API ${API_version}"
|
||||
echo "Installing ${name}.plg ${version}"
|
||||
if [ -f /boot/config/plugins/dynamix.unraid.net.staging.plg ]; then
|
||||
echo "ERROR: Cannot proceed with installation"
|
||||
echo "Reason: Staging Unraid Connect plugin detected at /boot/config/plugins/dynamix.unraid.net.staging.plg"
|
||||
@@ -149,7 +147,7 @@ exit 0
|
||||
<!-- download main txz -->
|
||||
<FILE Name="&source;.txz">
|
||||
<URL>&MAIN_TXZ;</URL>
|
||||
<SHA256>&SHA256;</SHA256>
|
||||
<SHA256>&TXZ_SHA256;</SHA256>
|
||||
</FILE>
|
||||
|
||||
<FILE Run="/bin/bash" Method="install">
|
||||
@@ -457,7 +455,7 @@ exit 0
|
||||
<!-- install all the things -->
|
||||
<FILE Run="/bin/bash" Method="install">
|
||||
<INLINE>
|
||||
TAG="&TAG;" PLGTYPE="&env;" MAINTXZ="&source;.txz"
|
||||
TAG="&TAG;" MAINTXZ="&source;.txz"
|
||||
<![CDATA[
|
||||
appendTextIfMissing() {
|
||||
FILE="$1" TEXT="$2"
|
||||
@@ -766,8 +764,6 @@ upgradepkg --install-new --reinstall "${MAINTXZ}"
|
||||
if [[ -n "$TAG" && "$TAG" != "" ]]; then
|
||||
printf -v sedcmd 's@^\*\*Unraid Connect\*\*@**Unraid Connect (%s)**@' "$TAG"
|
||||
sed -i "${sedcmd}" "/usr/local/emhttp/plugins/dynamix.unraid.net/README.md"
|
||||
elif [[ "$PLGTYPE" == "staging" ]]; then
|
||||
sed -i "s@^\*\*Unraid Connect\*\*@**Unraid Connect (staging)**@" "/usr/local/emhttp/plugins/dynamix.unraid.net/README.md"
|
||||
fi
|
||||
|
||||
echo
|
||||
@@ -775,9 +771,8 @@ echo "⚠️ Do not close this window yet"
|
||||
echo
|
||||
|
||||
# setup env
|
||||
if [ "${PLGTYPE}" = "production" ] || [ ! -f /boot/config/plugins/dynamix.my.servers/env ]; then
|
||||
echo "env=\"${PLGTYPE}\"">/boot/config/plugins/dynamix.my.servers/env
|
||||
fi
|
||||
echo "env=\"production\"">/boot/config/plugins/dynamix.my.servers/env
|
||||
|
||||
|
||||
# Use myservers.cfg values to help prevent conflicts when installing
|
||||
CFG=/boot/config/plugins/dynamix.my.servers/myservers.cfg
|
||||
@@ -810,14 +805,7 @@ if grep -q "SAMEORIGIN" "${FILE}"; then
|
||||
cp "$FILE" "$FILE-" OLD="add_header X-Frame-Options 'SAMEORIGIN';" NEW="add_header Content-Security-Policy \"frame-ancestors 'self' https://connect.myunraid.net/\";"
|
||||
sed -i "s#${OLD}#${NEW}#" "${FILE}"
|
||||
fi
|
||||
if [ "${PLGTYPE}" = "staging" ]; then
|
||||
# staging plugin allows an additional origin
|
||||
if ! grep -q "dev-my.myunraid.net:4000" "${FILE}"; then
|
||||
CHANGED=yes
|
||||
[[ ! -f "$FILE-" ]] && cp "$FILE" "$FILE-" OLD="add_header Content-Security-Policy \"frame-ancestors 'self' https://connect.myunraid.net/\";" NEW="add_header Content-Security-Policy \"frame-ancestors 'self' https://connect-staging.myunraid.net https://connect.myunraid.net/ https://dev-my.myunraid.net:4000/\";"
|
||||
sed -i "s#${OLD}#${NEW}#" "${FILE}"
|
||||
fi
|
||||
fi
|
||||
|
||||
FILE=/etc/rc.d/rc.nginx
|
||||
# brings older versions of Unraid in sync with 6.12.0
|
||||
if ! grep -q "#robots.txt any origin" "${FILE}"; then
|
||||
@@ -828,15 +816,6 @@ if ! grep -q "#robots.txt any origin" "${FILE}"; then
|
||||
sed -i "/${FIND}/a ${ADD}" "${FILE}"
|
||||
fi
|
||||
|
||||
# Prevent web component file downgrade if the webgui version is newer than the plugin version
|
||||
# Function to extract "ts" value from JSON file
|
||||
extract_ts() {
|
||||
local filepath="$1"
|
||||
local ts_value=null
|
||||
ts_value=$(jq -r '.ts' "$filepath" 2>/dev/null)
|
||||
echo "$ts_value"
|
||||
}
|
||||
|
||||
preventDowngradeAction() {
|
||||
local action="$1"
|
||||
local path="$2"
|
||||
@@ -861,8 +840,8 @@ preventDowngradeAction() {
|
||||
# Extract "ts" values from both files
|
||||
plgWebComponentPath="/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components"
|
||||
backupWebComponentPath="/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components-"
|
||||
plgManifestTs=$(extract_ts "$plgWebComponentPath/manifest.json")
|
||||
webguiManifestTs=$(extract_ts "$backupWebComponentPath/manifest.json")
|
||||
plgManifestTs=$(find "$plgWebComponentPath" -name manifest.json -exec jq -r '.ts' {} \; 2>/dev/null)
|
||||
webguiManifestTs=$(find "$backupWebComponentPath" -name manifest.json -exec jq -r '.ts' {} \; 2>/dev/null)
|
||||
|
||||
# Compare the "ts" values and return the file path of the higher value
|
||||
if [[ "$webguiManifestTs" -gt "$plgManifestTs" ]]; then
|
||||
|
||||
2170
plugin/pnpm-lock.yaml
generated
Normal file
2170
plugin/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,328 +0,0 @@
|
||||
import { execSync } from "child_process";
|
||||
import { cp, readFile, writeFile, mkdir, readdir } from "fs/promises";
|
||||
import { basename, join } from "path";
|
||||
import { createHash } from "node:crypto";
|
||||
import { $, cd } from "zx";
|
||||
import conventionalChangelog from "conventional-changelog";
|
||||
import { escape as escapeHtml } from "html-sloppy-escaper";
|
||||
import { existsSync } from "fs";
|
||||
import { format as formatDate } from "date-fns";
|
||||
import { setupEnvironment } from "./setup-environment";
|
||||
import { dirname } from "node:path";
|
||||
const pluginName = "dynamix.unraid.net" as const;
|
||||
const startingDir = process.cwd();
|
||||
|
||||
const validatedEnv = await setupEnvironment(startingDir);
|
||||
|
||||
const BASE_URLS = {
|
||||
STABLE: "https://stable.dl.unraid.net/unraid-api",
|
||||
PREVIEW: "https://preview.dl.unraid.net/unraid-api",
|
||||
...(validatedEnv.LOCAL_FILESERVER_URL
|
||||
? { LOCAL: validatedEnv.LOCAL_FILESERVER_URL }
|
||||
: {}),
|
||||
} as const;
|
||||
|
||||
// Setup environment variables
|
||||
// Ensure that git is available
|
||||
|
||||
try {
|
||||
await $`git log -1 --pretty=%B`;
|
||||
} catch (err) {
|
||||
console.error(`Error: git not available: ${err}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const createBuildDirectory = async () => {
|
||||
await execSync(`rm -rf deploy/pre-pack/*`);
|
||||
await execSync(`rm -rf deploy/release/*`);
|
||||
await execSync(`rm -rf deploy/test/*`);
|
||||
await mkdir("deploy/pre-pack", { recursive: true });
|
||||
await mkdir("deploy/release/plugins", { recursive: true });
|
||||
await mkdir("deploy/release/archive", { recursive: true });
|
||||
await mkdir("deploy/test", { recursive: true });
|
||||
};
|
||||
|
||||
function updateEntityValue(
|
||||
xmlString: string,
|
||||
entityName: string,
|
||||
newValue: string
|
||||
) {
|
||||
const regex = new RegExp(`<!ENTITY ${entityName} "[^"]*">`);
|
||||
if (regex.test(xmlString)) {
|
||||
return xmlString.replace(regex, `<!ENTITY ${entityName} "${newValue}">`);
|
||||
}
|
||||
throw new Error(`Entity ${entityName} not found in XML`);
|
||||
}
|
||||
|
||||
const validateSourceDir = async () => {
|
||||
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`);
|
||||
}
|
||||
// Validate that there are webcomponents
|
||||
const webcomponents = await readdir(webcomponentDir);
|
||||
if (webcomponents.length === 1 && webcomponents[0] === ".gitkeep") {
|
||||
throw new Error(`No webcomponents found in ${webcomponentDir}`);
|
||||
}
|
||||
// Check for the existence of "ui.manifest.json" as well as "manifest.json" in webcomponents
|
||||
if (
|
||||
!webcomponents.includes("ui.manifest.json") ||
|
||||
!webcomponents.includes("manifest.json")
|
||||
) {
|
||||
throw new Error(
|
||||
`Webcomponents must contain both "ui.manifest.json" and "manifest.json"`
|
||||
);
|
||||
}
|
||||
|
||||
const apiDir = join(
|
||||
startingDir,
|
||||
"source/dynamix.unraid.net/usr/local/unraid-api/package.json"
|
||||
);
|
||||
if (!existsSync(apiDir)) {
|
||||
throw new Error(`API directory ${apiDir} does not exist`);
|
||||
}
|
||||
};
|
||||
|
||||
const buildTxz = async (
|
||||
version: string
|
||||
): Promise<{
|
||||
txzName: string;
|
||||
txzSha256: string;
|
||||
}> => {
|
||||
if (
|
||||
validatedEnv.SKIP_VALIDATION !== "true" ||
|
||||
validatedEnv.LOCAL_FILESERVER_URL
|
||||
) {
|
||||
await validateSourceDir();
|
||||
}
|
||||
|
||||
const txzName = `${pluginName}-${version}.txz`;
|
||||
const txzPath = join(startingDir, "deploy/release/archive", txzName);
|
||||
const prePackDir = join(startingDir, "deploy/pre-pack");
|
||||
|
||||
// Copy all files from source to temp dir, excluding specific files
|
||||
await cp(join(startingDir, "source/dynamix.unraid.net"), prePackDir, {
|
||||
recursive: true,
|
||||
filter: (src) => {
|
||||
const filename = basename(src);
|
||||
return ![
|
||||
".DS_Store",
|
||||
"pkg_build.sh",
|
||||
"makepkg",
|
||||
"explodepkg",
|
||||
"sftp-config.json",
|
||||
".gitkeep",
|
||||
].includes(filename);
|
||||
},
|
||||
});
|
||||
|
||||
// Create package - must be run from within the pre-pack directory
|
||||
// Use cd option to run command from prePackDir
|
||||
await cd(prePackDir);
|
||||
$.verbose = true;
|
||||
|
||||
await $`${join(
|
||||
startingDir,
|
||||
"scripts/makepkg"
|
||||
)} -l y -c y --compress -1 "${txzPath}"`;
|
||||
$.verbose = false;
|
||||
await cd(startingDir);
|
||||
|
||||
// Calculate hashes
|
||||
const sha256 = createHash("sha256")
|
||||
.update(await readFile(txzPath))
|
||||
.digest("hex");
|
||||
console.log(`TXZ SHA256: ${sha256}`);
|
||||
|
||||
return { txzSha256: sha256, txzName };
|
||||
};
|
||||
|
||||
const getStagingChangelogFromGit = async (
|
||||
apiVersion: string,
|
||||
tag: string | null = null
|
||||
): Promise<string | null> => {
|
||||
console.debug("Getting changelog from git" + (tag ? " for TAG" : ""));
|
||||
try {
|
||||
const changelogStream = conventionalChangelog(
|
||||
{
|
||||
preset: "conventionalcommits",
|
||||
},
|
||||
{
|
||||
version: apiVersion,
|
||||
},
|
||||
tag
|
||||
? {
|
||||
from: "origin/main",
|
||||
to: "HEAD",
|
||||
}
|
||||
: {},
|
||||
undefined,
|
||||
tag
|
||||
? {
|
||||
headerPartial: `## [${tag}](https://github.com/unraid/api/${tag})\n\n`,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
let changelog = "";
|
||||
for await (const chunk of changelogStream) {
|
||||
changelog += chunk;
|
||||
}
|
||||
// Encode HTML entities using the 'he' library
|
||||
return escapeHtml(changelog) ?? null;
|
||||
} catch (err) {
|
||||
console.error(`Error: failed to get changelog from git: ${err}`);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
const buildPlugin = async ({
|
||||
type,
|
||||
txzSha256,
|
||||
txzName,
|
||||
version,
|
||||
tag = "",
|
||||
apiVersion,
|
||||
}: {
|
||||
type: "staging" | "pr" | "production" | "local";
|
||||
txzSha256: string;
|
||||
txzName: string;
|
||||
version: string;
|
||||
tag?: string;
|
||||
apiVersion: string;
|
||||
}) => {
|
||||
const rootPlgFile = join(startingDir, "/plugins/", `${pluginName}.plg`);
|
||||
// Set up paths
|
||||
const newPluginFile = join(
|
||||
startingDir,
|
||||
"/deploy/release/plugins/",
|
||||
type,
|
||||
`${pluginName}.plg`
|
||||
);
|
||||
|
||||
// Define URLs
|
||||
let PLUGIN_URL = "";
|
||||
let MAIN_TXZ = "";
|
||||
let RELEASE_NOTES: string | null = null;
|
||||
switch (type) {
|
||||
case "production":
|
||||
PLUGIN_URL = `${BASE_URLS.STABLE}/${pluginName}.plg`;
|
||||
MAIN_TXZ = `${BASE_URLS.STABLE}/${txzName}`;
|
||||
break;
|
||||
case "pr":
|
||||
PLUGIN_URL = `${BASE_URLS.PREVIEW}/tag/${tag}/${pluginName}.plg`;
|
||||
MAIN_TXZ = `${BASE_URLS.PREVIEW}/tag/${tag}/${txzName}`;
|
||||
RELEASE_NOTES = await getStagingChangelogFromGit(apiVersion, tag);
|
||||
break;
|
||||
case "staging":
|
||||
PLUGIN_URL = `${BASE_URLS.PREVIEW}/${pluginName}.plg`;
|
||||
MAIN_TXZ = `${BASE_URLS.PREVIEW}/${txzName}`;
|
||||
RELEASE_NOTES = await getStagingChangelogFromGit(apiVersion);
|
||||
break;
|
||||
case "local":
|
||||
PLUGIN_URL = `${BASE_URLS.LOCAL}/plugins/${type}/${pluginName}.plg`;
|
||||
MAIN_TXZ = `${BASE_URLS.LOCAL}/archive/${txzName}`;
|
||||
RELEASE_NOTES = await getStagingChangelogFromGit(apiVersion, tag);
|
||||
break;
|
||||
}
|
||||
|
||||
// Update plg file
|
||||
let plgContent = await readFile(rootPlgFile, "utf8");
|
||||
|
||||
// Update entity values
|
||||
const entities: Record<string, string> = {
|
||||
name: pluginName,
|
||||
env: type === "pr" ? "staging" : type,
|
||||
version: version,
|
||||
pluginURL: PLUGIN_URL,
|
||||
SHA256: txzSha256,
|
||||
MAIN_TXZ: MAIN_TXZ,
|
||||
TAG: tag,
|
||||
API_version: apiVersion,
|
||||
};
|
||||
|
||||
// Iterate over entities and update them
|
||||
Object.entries(entities).forEach(([key, value]) => {
|
||||
if (key !== "TAG" && !value) {
|
||||
throw new Error(`Entity ${key} not set in entities : ${value}`);
|
||||
}
|
||||
plgContent = updateEntityValue(plgContent, key, value);
|
||||
});
|
||||
|
||||
if (RELEASE_NOTES) {
|
||||
// Update the CHANGES section with release notes
|
||||
plgContent = plgContent.replace(
|
||||
/<CHANGES>.*?<\/CHANGES>/s,
|
||||
`<CHANGES>\n${RELEASE_NOTES}\n</CHANGES>`
|
||||
);
|
||||
}
|
||||
|
||||
await mkdir(dirname(newPluginFile), { recursive: true });
|
||||
await writeFile(newPluginFile, plgContent);
|
||||
console.log(`${type} plugin: ${newPluginFile}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Main build script
|
||||
*/
|
||||
|
||||
const main = async () => {
|
||||
await createBuildDirectory();
|
||||
|
||||
const version = formatDate(new Date(), "yyyy.MM.dd.HHmm");
|
||||
console.log(`Version: ${version}`);
|
||||
const { txzSha256, txzName } = await buildTxz(version);
|
||||
const { API_VERSION, TAG, LOCAL_FILESERVER_URL } = validatedEnv;
|
||||
|
||||
if (LOCAL_FILESERVER_URL) {
|
||||
await buildPlugin({
|
||||
type: "local",
|
||||
txzSha256,
|
||||
txzName,
|
||||
version,
|
||||
tag: TAG,
|
||||
apiVersion: API_VERSION,
|
||||
});
|
||||
} else if (TAG) {
|
||||
await buildPlugin({
|
||||
type: "pr",
|
||||
txzSha256,
|
||||
txzName,
|
||||
version,
|
||||
tag: TAG,
|
||||
apiVersion: API_VERSION,
|
||||
});
|
||||
}
|
||||
|
||||
await buildPlugin({
|
||||
type: "staging",
|
||||
txzSha256,
|
||||
txzName,
|
||||
version,
|
||||
apiVersion: API_VERSION,
|
||||
});
|
||||
await buildPlugin({
|
||||
type: "production",
|
||||
txzSha256,
|
||||
txzName,
|
||||
version,
|
||||
apiVersion: API_VERSION,
|
||||
});
|
||||
};
|
||||
|
||||
await main();
|
||||
20
plugin/scripts/dc.sh
Executable file
20
plugin/scripts/dc.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Get host IP based on platform
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS
|
||||
HOST_LAN_IP=$(ipconfig getifaddr en0 || ipconfig getifaddr en1 || echo "127.0.0.1")
|
||||
else
|
||||
# Linux and others
|
||||
HOST_LAN_IP=$(hostname -I | awk '{print $1}' || echo "127.0.0.1")
|
||||
fi
|
||||
|
||||
# Verify we have a valid IP
|
||||
if [[ ! $HOST_LAN_IP =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Error: Could not determine valid host IP address. Using localhost."
|
||||
HOST_LAN_IP="127.0.0.1"
|
||||
fi
|
||||
|
||||
CI=${CI:-false}
|
||||
TAG="LOCAL_PLUGIN_BUILD"
|
||||
docker compose run --service-ports --rm -e HOST_LAN_IP="$HOST_LAN_IP" -e CI="$CI" -e TAG="$TAG" plugin-builder "$@"
|
||||
16
plugin/scripts/entrypoint.sh
Normal file
16
plugin/scripts/entrypoint.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
mkdir -p /app/deploy/
|
||||
# Start http-server with common fileserver settings
|
||||
http-server /app/deploy/ \
|
||||
--port 8080 \
|
||||
--host 0.0.0.0 \
|
||||
--cors \
|
||||
--gzip \
|
||||
--brotli \
|
||||
--no-dotfiles \
|
||||
-c-1 \
|
||||
--silent &
|
||||
|
||||
# Execute whatever command was passed (or default CMD)
|
||||
exec "$@"
|
||||
@@ -381,8 +381,17 @@ fi
|
||||
if [ "$CHOWN" = "y" ]; then
|
||||
# Set strict mode and fail if commands fail
|
||||
set -e
|
||||
find . -type d -exec sudo chmod 755 {} + || exit 1
|
||||
find . -type d -exec sudo chown 0:0 {} + || exit 1
|
||||
echo "Setting permissions and ownerships"
|
||||
|
||||
# Use sudo if available, otherwise run directly
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
SUDO="sudo"
|
||||
else
|
||||
SUDO=""
|
||||
fi
|
||||
|
||||
$SUDO find . -type d -exec chmod 755 {} + || exit 1
|
||||
$SUDO find . -exec chown 0:0 {} + || exit 1
|
||||
set +e
|
||||
fi
|
||||
|
||||
@@ -416,7 +425,7 @@ rm -f ${TARGET_NAME}/${TAR_NAME}.${EXTENSION}
|
||||
# find ./ | sed '2,$s,^\./,,' | cpio --quiet -ovHustar > ${TARGET_NAME}/${TAR_NAME}.tar
|
||||
|
||||
# Create the package:
|
||||
find ./ | LC_COLLATE=C sort | sed '2,$s,^\./,,' | tar --no-recursion $ACLS $XATTRS $MTIME -T - -cvf - | $COMPRESSOR > ${TARGET_NAME}/${TAR_NAME}.${EXTENSION}
|
||||
find ./ | LC_COLLATE=C sort | sed '2,$s,^\./,,' | tar --no-recursion $ACLS $XATTRS $MTIME -T - -cf - | $COMPRESSOR > ${TARGET_NAME}/${TAR_NAME}.${EXTENSION}
|
||||
ERRCODE=$?
|
||||
if [ ! $ERRCODE = 0 ]; then
|
||||
echo "ERROR: $COMPRESSOR returned error code $ERRCODE -- makepkg failed."
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import { readFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import { z } from "zod";
|
||||
import { parse } from "semver";
|
||||
import { dotenv } from "zx";
|
||||
|
||||
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const envSchema = z.object({
|
||||
API_VERSION: z.string().refine((v) => {
|
||||
return parse(v) ?? false;
|
||||
}, "Must be a valid semver version"),
|
||||
TAG: z
|
||||
.string()
|
||||
.optional(),
|
||||
SKIP_VALIDATION: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("false")
|
||||
.refine((v) => v === "true" || v === "false", "Must be true or false"),
|
||||
LOCAL_FILESERVER_URL: z.string().url().optional(),
|
||||
});
|
||||
|
||||
type Env = z.infer<typeof envSchema>;
|
||||
|
||||
export const setupEnvironment = async (
|
||||
startingDir: string
|
||||
): Promise<Env> => {
|
||||
const getLocalEnvironmentVariablesFromApiFolder = async (): Promise<Partial<Env>> => {
|
||||
const apiDir = join(
|
||||
startingDir,
|
||||
"source/dynamix.unraid.net/usr/local/unraid-api"
|
||||
);
|
||||
const apiPackageJson = join(apiDir, "package.json");
|
||||
const apiPackageJsonContent = await readFile(apiPackageJson, "utf8");
|
||||
const apiPackageJsonObject = JSON.parse(apiPackageJsonContent);
|
||||
return {
|
||||
API_VERSION: apiPackageJsonObject.version,
|
||||
};
|
||||
};
|
||||
|
||||
const validatedEnv = envSchema.parse(
|
||||
{
|
||||
...process.env,
|
||||
...(await dotenv.config()),
|
||||
...(await getLocalEnvironmentVariablesFromApiFolder()),
|
||||
}
|
||||
);
|
||||
let shouldWait = false;
|
||||
|
||||
if (validatedEnv.SKIP_VALIDATION == "true") {
|
||||
console.warn("SKIP_VALIDATION is true, skipping validation");
|
||||
shouldWait = true;
|
||||
}
|
||||
|
||||
if (validatedEnv.TAG) {
|
||||
console.warn("TAG is set, will generate a TAGGED build");
|
||||
shouldWait = true;
|
||||
}
|
||||
|
||||
if (validatedEnv.LOCAL_FILESERVER_URL) {
|
||||
console.warn("LOCAL_FILESERVER_URL is set, will generate a local build");
|
||||
shouldWait = true;
|
||||
}
|
||||
|
||||
console.log("validatedEnv", validatedEnv);
|
||||
|
||||
if (shouldWait) {
|
||||
await wait(1000);
|
||||
}
|
||||
|
||||
return validatedEnv;
|
||||
};
|
||||
@@ -8,3 +8,105 @@ if [ "$1" = "remove" ]; then
|
||||
# Clean up node_modules before package removal
|
||||
rm -rf /usr/local/unraid-api/node_modules
|
||||
fi
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@apollo/protobufjs/bin/pbjs apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@apollo/protobufjs/bin/pbts apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf blessed )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../blessed/bin/tput.js blessed )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../esbuild/bin/esbuild esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf escodegen )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../escodegen/bin/escodegen.js escodegen )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esgenerate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../escodegen/bin/esgenerate.js esgenerate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esparse )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../esprima/bin/esparse.js esparse )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esvalidate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../esprima/bin/esvalidate.js esvalidate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf fxparser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../fast-xml-parser/src/cli/cli.js fxparser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf glob )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../glob/dist/esm/bin.mjs glob )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf js-yaml )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../js-yaml/bin/js-yaml.js js-yaml )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf jsesc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../jsesc/bin/jsesc jsesc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf loose-envify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../loose-envify/cli.js loose-envify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf mime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../mime/cli.js mime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf mkdirp )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../mkdirp/bin/cmd.js mkdirp )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf mustache )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../mustache/bin/mustache mustache )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf needle )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../needle/bin/needle needle )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf node-which )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../which/bin/node-which node-which )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@nuxtjs/opencollective/bin/opencollective.js opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf parser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@babel/parser/bin/babel-parser.js parser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pino )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pino/bin.js pino )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pino-pretty )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pino-pretty/bin.js pino-pretty )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2 )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2 pm2 )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2-dev )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2-dev pm2-dev )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2-docker )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2-docker pm2-docker )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2-runtime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2-runtime pm2-runtime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf prettier )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../prettier/bin/prettier.cjs prettier )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf relay-compiler )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@ardatan/relay-compiler/bin/relay-compiler relay-compiler )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf resolve )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../resolve/bin/resolve resolve )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf semver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../semver/bin/semver.js semver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sha.js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sha.js/bin.js sha.js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sshpk-conv )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sshpk/bin/sshpk-conv sshpk-conv )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sshpk-sign )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sshpk/bin/sshpk-sign sshpk-sign )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sshpk-verify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sshpk/bin/sshpk-verify sshpk-verify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf systeminformation )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../systeminformation/lib/cli.js systeminformation )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf tsc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../typescript/bin/tsc tsc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf tsserver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../typescript/bin/tsserver tsserver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf tsx )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../tsx/dist/cli.mjs tsx )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf ua-parser-js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../ua-parser-js/script/cli.js ua-parser-js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../uuid/dist/esm/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf xss )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../xss/bin/xss xss )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; rm -rf apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; ln -sf ../../bin/pbjs apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; rm -rf apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; ln -sf ../../bin/pbts apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/server/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/server/node_modules/.bin ; ln -sf ../uuid/dist/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/core/node_modules/.bin ; rm -rf opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/core/node_modules/.bin ; ln -sf ../../../../@nuxtjs/opencollective/bin/opencollective.js opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/graphql/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/graphql/node_modules/.bin ; ln -sf ../uuid/dist/esm/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/schedule/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/schedule/node_modules/.bin ; ln -sf ../uuid/dist/esm/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/agent/node_modules/.bin ; rm -rf semver )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/agent/node_modules/.bin ; ln -sf ../semver/bin/semver.js semver )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/io/node_modules/.bin ; rm -rf semver )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/io/node_modules/.bin ; ln -sf ../semver/bin/semver.js semver )
|
||||
( cd usr/local/unraid-api/node_modules/esbuild/node_modules/.bin ; rm -rf esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/esbuild/node_modules/.bin ; ln -sf ../../bin/esbuild esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/request/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/request/node_modules/.bin ; ln -sf ../uuid/bin/uuid uuid )
|
||||
|
||||
@@ -10,26 +10,47 @@ class WebComponentsExtractor
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
public function getAssetPath(string $asset): string
|
||||
private function findManifestFiles(string $manifestName): array
|
||||
{
|
||||
return self::PREFIXED_PATH . $asset;
|
||||
$basePath = '/usr/local/emhttp' . self::PREFIXED_PATH;
|
||||
$command = "find {$basePath} -name {$manifestName}";
|
||||
exec($command, $files);
|
||||
return $files;
|
||||
}
|
||||
|
||||
public function getManifestContents(string $pathFromComponents): array
|
||||
public function getAssetPath(string $asset, string $subfolder = ''): string
|
||||
{
|
||||
$filePath = '/usr/local/emhttp' . $this->getAssetPath($pathFromComponents);
|
||||
return json_decode(file_get_contents($filePath), true);
|
||||
return self::PREFIXED_PATH . ($subfolder ? $subfolder . '/' : '') . $asset;
|
||||
}
|
||||
|
||||
private function getRelativePath(string $fullPath): string
|
||||
{
|
||||
$basePath = '/usr/local/emhttp' . self::PREFIXED_PATH;
|
||||
$relative = str_replace($basePath, '', $fullPath);
|
||||
return dirname($relative);
|
||||
}
|
||||
|
||||
public function getManifestContents(string $manifestPath): array
|
||||
{
|
||||
$contents = @file_get_contents($manifestPath);
|
||||
return $contents ? json_decode($contents, true) : [];
|
||||
}
|
||||
|
||||
private function getRichComponentsFile(): string
|
||||
{
|
||||
$localManifest = $this->getManifestContents('manifest.json');
|
||||
|
||||
foreach ($localManifest as $key => $value) {
|
||||
if (strpos($key, self::RICH_COMPONENTS_ENTRY) !== false && isset($value["file"])) {
|
||||
return $value["file"];
|
||||
$manifestFiles = $this->findManifestFiles('manifest.json');
|
||||
|
||||
foreach ($manifestFiles as $manifestPath) {
|
||||
$manifest = $this->getManifestContents($manifestPath);
|
||||
$subfolder = $this->getRelativePath($manifestPath);
|
||||
|
||||
foreach ($manifest as $key => $value) {
|
||||
if (strpos($key, self::RICH_COMPONENTS_ENTRY) !== false && isset($value["file"])) {
|
||||
return ($subfolder ? $subfolder . '/' : '') . $value["file"];
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getRichComponentsScript(): string
|
||||
@@ -43,9 +64,25 @@ class WebComponentsExtractor
|
||||
|
||||
private function getUnraidUiScriptHtml(): string
|
||||
{
|
||||
$manifest = $this->getManifestContents('ui.manifest.json');
|
||||
$jsFile = $manifest[self::UI_ENTRY]['file'];
|
||||
$cssFile = $manifest[self::UI_STYLES_ENTRY]['file'];
|
||||
$manifestFiles = $this->findManifestFiles('ui.manifest.json');
|
||||
|
||||
if (empty($manifestFiles)) {
|
||||
error_log("No ui.manifest.json found");
|
||||
return '';
|
||||
}
|
||||
|
||||
$manifestPath = $manifestFiles[0]; // Use the first found manifest
|
||||
$manifest = $this->getManifestContents($manifestPath);
|
||||
$subfolder = $this->getRelativePath($manifestPath);
|
||||
|
||||
if (!isset($manifest[self::UI_ENTRY]) || !isset($manifest[self::UI_STYLES_ENTRY])) {
|
||||
error_log("Required entries not found in ui.manifest.json");
|
||||
return '';
|
||||
}
|
||||
|
||||
$jsFile = ($subfolder ? $subfolder . '/' : '') . $manifest[self::UI_ENTRY]['file'];
|
||||
$cssFile = ($subfolder ? $subfolder . '/' : '') . $manifest[self::UI_STYLES_ENTRY]['file'];
|
||||
|
||||
return '<script defer type="module">
|
||||
import { registerAllComponents } from "' . $this->getAssetPath($jsFile) . '";
|
||||
registerAllComponents({ pathToSharedCss: "' . $this->getAssetPath($cssFile) . '" });
|
||||
|
||||
7
plugin/vitest.config.ts
Normal file
7
plugin/vitest.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
},
|
||||
});
|
||||
249
pnpm-lock.yaml
generated
249
pnpm-lock.yaml
generated
@@ -444,6 +444,9 @@ importers:
|
||||
|
||||
plugin:
|
||||
dependencies:
|
||||
commander:
|
||||
specifier: ^13.1.0
|
||||
version: 13.1.0
|
||||
conventional-changelog:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0(conventional-commits-filter@5.0.0)
|
||||
@@ -456,9 +459,6 @@ importers:
|
||||
html-sloppy-escaper:
|
||||
specifier: ^0.1.0
|
||||
version: 0.1.0
|
||||
http-server:
|
||||
specifier: ^14.1.1
|
||||
version: 14.1.1
|
||||
semver:
|
||||
specifier: ^7.7.1
|
||||
version: 7.7.1
|
||||
@@ -472,9 +472,15 @@ importers:
|
||||
specifier: ^8.3.2
|
||||
version: 8.3.2
|
||||
devDependencies:
|
||||
cpx2:
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.0
|
||||
http-server:
|
||||
specifier: ^14.1.1
|
||||
version: 14.1.1
|
||||
nodemon:
|
||||
specifier: ^3.1.7
|
||||
version: 3.1.9
|
||||
vitest:
|
||||
specifier: ^3.0.7
|
||||
version: 3.0.7(@types/node@22.13.4)(happy-dom@17.1.4)(jiti@2.4.2)(jsdom@26.0.0)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)
|
||||
|
||||
unraid-ui:
|
||||
dependencies:
|
||||
@@ -3622,7 +3628,6 @@ packages:
|
||||
'@unraid/libvirt@1.1.3':
|
||||
resolution: {integrity: sha512-aZNHkwgQ/0e+5BE7i3Ru4GC3Ev8fEUlnU0wmTcuSbpN0r74rMpiGwzA/4cqIJU8X+Kj//I80pkUufzXzHmMWwQ==}
|
||||
engines: {node: '>=14'}
|
||||
cpu: [x64, arm64]
|
||||
os: [linux, darwin]
|
||||
|
||||
'@unraid/tailwind-rem-to-rem@1.1.0':
|
||||
@@ -3670,6 +3675,9 @@ packages:
|
||||
'@vitest/expect@3.0.6':
|
||||
resolution: {integrity: sha512-zBduHf/ja7/QRX4HdP1DSq5XrPgdN+jzLOwaTq/0qZjYfgETNFCKf9nOAp2j3hmom3oTbczuUzrzg9Hafh7hNg==}
|
||||
|
||||
'@vitest/expect@3.0.7':
|
||||
resolution: {integrity: sha512-QP25f+YJhzPfHrHfYHtvRn+uvkCFCqFtW9CktfBxmB+25QqWsx7VB2As6f4GmwllHLDhXNHvqedwhvMmSnNmjw==}
|
||||
|
||||
'@vitest/mocker@3.0.6':
|
||||
resolution: {integrity: sha512-KPztr4/tn7qDGZfqlSPQoF2VgJcKxnDNhmfR3VgZ6Fy1bO8T9Fc1stUiTXtqz0yG24VpD00pZP5f8EOFknjNuQ==}
|
||||
peerDependencies:
|
||||
@@ -3681,6 +3689,17 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/mocker@3.0.7':
|
||||
resolution: {integrity: sha512-qui+3BLz9Eonx4EAuR/i+QlCX6AUZ35taDQgwGkK/Tw6/WgwodSrjN1X2xf69IA/643ZX5zNKIn2svvtZDrs4w==}
|
||||
peerDependencies:
|
||||
msw: ^2.4.9
|
||||
vite: ^5.0.0 || ^6.0.0
|
||||
peerDependenciesMeta:
|
||||
msw:
|
||||
optional: true
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/pretty-format@2.0.5':
|
||||
resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==}
|
||||
|
||||
@@ -3690,18 +3709,30 @@ packages:
|
||||
'@vitest/pretty-format@3.0.6':
|
||||
resolution: {integrity: sha512-Zyctv3dbNL+67qtHfRnUE/k8qxduOamRfAL1BurEIQSyOEFffoMvx2pnDSSbKAAVxY0Ej2J/GH2dQKI0W2JyVg==}
|
||||
|
||||
'@vitest/pretty-format@3.0.7':
|
||||
resolution: {integrity: sha512-CiRY0BViD/V8uwuEzz9Yapyao+M9M008/9oMOSQydwbwb+CMokEq3XVaF3XK/VWaOK0Jm9z7ENhybg70Gtxsmg==}
|
||||
|
||||
'@vitest/runner@3.0.6':
|
||||
resolution: {integrity: sha512-JopP4m/jGoaG1+CBqubV/5VMbi7L+NQCJTu1J1Pf6YaUbk7bZtaq5CX7p+8sY64Sjn1UQ1XJparHfcvTTdu9cA==}
|
||||
|
||||
'@vitest/runner@3.0.7':
|
||||
resolution: {integrity: sha512-WeEl38Z0S2ZcuRTeyYqaZtm4e26tq6ZFqh5y8YD9YxfWuu0OFiGFUbnxNynwLjNRHPsXyee2M9tV7YxOTPZl2g==}
|
||||
|
||||
'@vitest/snapshot@3.0.6':
|
||||
resolution: {integrity: sha512-qKSmxNQwT60kNwwJHMVwavvZsMGXWmngD023OHSgn873pV0lylK7dwBTfYP7e4URy5NiBCHHiQGA9DHkYkqRqg==}
|
||||
|
||||
'@vitest/snapshot@3.0.7':
|
||||
resolution: {integrity: sha512-eqTUryJWQN0Rtf5yqCGTQWsCFOQe4eNz5Twsu21xYEcnFJtMU5XvmG0vgebhdLlrHQTSq5p8vWHJIeJQV8ovsA==}
|
||||
|
||||
'@vitest/spy@2.0.5':
|
||||
resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==}
|
||||
|
||||
'@vitest/spy@3.0.6':
|
||||
resolution: {integrity: sha512-HfOGx/bXtjy24fDlTOpgiAEJbRfFxoX3zIGagCqACkFKKZ/TTOE6gYMKXlqecvxEndKFuNHcHqP081ggZ2yM0Q==}
|
||||
|
||||
'@vitest/spy@3.0.7':
|
||||
resolution: {integrity: sha512-4T4WcsibB0B6hrKdAZTM37ekuyFZt2cGbEGd2+L0P8ov15J1/HUsUaqkXEQPNAWr4BtPPe1gI+FYfMHhEKfR8w==}
|
||||
|
||||
'@vitest/ui@3.0.6':
|
||||
resolution: {integrity: sha512-N4M2IUG2Q5LCeX4OWs48pQF4P3qsFejmDTc6QWGRFTLPrEe5EvM5HN0WSUnGAmuzQpSWv7ItfSsIJIWaEM2wpQ==}
|
||||
peerDependencies:
|
||||
@@ -3716,6 +3747,9 @@ packages:
|
||||
'@vitest/utils@3.0.6':
|
||||
resolution: {integrity: sha512-18ktZpf4GQFTbf9jK543uspU03Q2qya7ZGya5yiZ0Gx0nnnalBvd5ZBislbl2EhLjM8A8rt4OilqKG7QwcGkvQ==}
|
||||
|
||||
'@vitest/utils@3.0.7':
|
||||
resolution: {integrity: sha512-xePVpCRfooFX3rANQjwoditoXgWb1MaFbzmGuPP59MK6i13mrnDw/yEIyJudLeW6/38mCNcwCiJIGmpDPibAIg==}
|
||||
|
||||
'@volar/language-core@1.11.1':
|
||||
resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==}
|
||||
|
||||
@@ -4767,6 +4801,10 @@ packages:
|
||||
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
commander@13.1.0:
|
||||
resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
commander@2.15.1:
|
||||
resolution: {integrity: sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==}
|
||||
|
||||
@@ -5070,11 +5108,6 @@ packages:
|
||||
resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
cpx2@8.0.0:
|
||||
resolution: {integrity: sha512-RxD9jrSVNSOmfcbiPlr3XnKbUKH9K1w2HCv0skczUKhsZTueiDBecxuaSAKQkYSLQaGVA4ZQJZlTj5hVNNEvKg==}
|
||||
engines: {node: ^20.0.0 || >=22.0.0, npm: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
crc-32@1.2.2:
|
||||
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
|
||||
engines: {node: '>=0.8'}
|
||||
@@ -5274,10 +5307,6 @@ packages:
|
||||
debounce@1.2.1:
|
||||
resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
|
||||
|
||||
debounce@2.2.0:
|
||||
resolution: {integrity: sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
debug@2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
peerDependencies:
|
||||
@@ -6255,9 +6284,6 @@ packages:
|
||||
resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
find-index@0.1.1:
|
||||
resolution: {integrity: sha512-uJ5vWrfBKMcE6y2Z8834dwEZj9mNGxYa3t3I53OwFeuZ8D9oc2E5zcsrkuhX6h4iYrjhiv0T3szQmxlAV9uxDg==}
|
||||
|
||||
find-my-way@8.2.2:
|
||||
resolution: {integrity: sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -6521,10 +6547,6 @@ packages:
|
||||
glob-to-regexp@0.4.1:
|
||||
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
|
||||
|
||||
glob2base@0.0.12:
|
||||
resolution: {integrity: sha512-ZyqlgowMbfj2NPjxaZZ/EtsXlOch28FRXgMd64vqZWk1bT9+wvSRLYD1om9M7QfQru51zJPAT17qXm4/zd+9QA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
glob@10.4.5:
|
||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||
hasBin: true
|
||||
@@ -6913,10 +6935,6 @@ packages:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
ignore@6.0.2:
|
||||
resolution: {integrity: sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
ignore@7.0.3:
|
||||
resolution: {integrity: sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -8404,10 +8422,6 @@ packages:
|
||||
resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
p-map@7.0.3:
|
||||
resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
p-retry@6.2.1:
|
||||
resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==}
|
||||
engines: {node: '>=16.17'}
|
||||
@@ -9974,9 +9988,6 @@ packages:
|
||||
resolution: {integrity: sha512-yOI6G8WYfr0q8v8rRvE91wbxFU+rJPo760Va4MF6K0I6BZjO4r+xSynkvyPBP9tV1CIEUeRsiidjIs2rzb1CnQ==}
|
||||
hasBin: true
|
||||
|
||||
subarg@1.0.0:
|
||||
resolution: {integrity: sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==}
|
||||
|
||||
subscriptions-transport-ws@0.11.0:
|
||||
resolution: {integrity: sha512-8D4C6DIH5tGiAIpp5I0wD/xRlNiZAPGHygzCe7VzyzUoxHtawzjNAY9SUTXU05/EY2NMY9/9GF0ycizkXr1CWQ==}
|
||||
deprecated: The `subscriptions-transport-ws` package is no longer maintained. We recommend you use `graphql-ws` instead. For help migrating Apollo software to `graphql-ws`, see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws For general help using `graphql-ws`, see https://github.com/enisdenjo/graphql-ws/blob/master/README.md
|
||||
@@ -10702,6 +10713,11 @@ packages:
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
hasBin: true
|
||||
|
||||
vite-node@3.0.7:
|
||||
resolution: {integrity: sha512-2fX0QwX4GkkkpULXdT1Pf4q0tC1i1lFOyseKoonavXUNlQ77KpW2XqBGGNIm/J4Ows4KxgGJzDguYVPKwG/n5A==}
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
hasBin: true
|
||||
|
||||
vite-plugin-checker@0.8.0:
|
||||
resolution: {integrity: sha512-UA5uzOGm97UvZRTdZHiQVYFnd86AVn8EVaD4L3PoVzxH+IZSfaAw14WGFwX9QS23UW3lV/5bVKZn6l0w+q9P0g==}
|
||||
engines: {node: '>=14.16'}
|
||||
@@ -10865,6 +10881,34 @@ packages:
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
vitest@3.0.7:
|
||||
resolution: {integrity: sha512-IP7gPK3LS3Fvn44x30X1dM9vtawm0aesAa2yBIZ9vQf+qB69NXC5776+Qmcr7ohUXIQuLhk7xQR0aSUIDPqavg==}
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@types/debug': ^4.1.12
|
||||
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
|
||||
'@vitest/browser': 3.0.7
|
||||
'@vitest/ui': 3.0.7
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
peerDependenciesMeta:
|
||||
'@edge-runtime/vm':
|
||||
optional: true
|
||||
'@types/debug':
|
||||
optional: true
|
||||
'@types/node':
|
||||
optional: true
|
||||
'@vitest/browser':
|
||||
optional: true
|
||||
'@vitest/ui':
|
||||
optional: true
|
||||
happy-dom:
|
||||
optional: true
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
vizion@2.2.1:
|
||||
resolution: {integrity: sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==}
|
||||
engines: {node: '>=4.0'}
|
||||
@@ -15030,6 +15074,13 @@ snapshots:
|
||||
chai: 5.2.0
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/expect@3.0.7':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.0.7
|
||||
'@vitest/utils': 3.0.7
|
||||
chai: 5.2.0
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/mocker@3.0.6(vite@6.1.1(@types/node@20.17.19)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.0.6
|
||||
@@ -15046,6 +15097,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
vite: 6.1.1(@types/node@22.13.4)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)
|
||||
|
||||
'@vitest/mocker@3.0.7(vite@6.1.1(@types/node@22.13.4)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.0.7
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.17
|
||||
optionalDependencies:
|
||||
vite: 6.1.1(@types/node@22.13.4)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)
|
||||
|
||||
'@vitest/pretty-format@2.0.5':
|
||||
dependencies:
|
||||
tinyrainbow: 1.2.0
|
||||
@@ -15058,17 +15117,32 @@ snapshots:
|
||||
dependencies:
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/pretty-format@3.0.7':
|
||||
dependencies:
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/runner@3.0.6':
|
||||
dependencies:
|
||||
'@vitest/utils': 3.0.6
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/runner@3.0.7':
|
||||
dependencies:
|
||||
'@vitest/utils': 3.0.7
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/snapshot@3.0.6':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 3.0.6
|
||||
magic-string: 0.30.17
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/snapshot@3.0.7':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 3.0.7
|
||||
magic-string: 0.30.17
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/spy@2.0.5':
|
||||
dependencies:
|
||||
tinyspy: 3.0.2
|
||||
@@ -15077,6 +15151,10 @@ snapshots:
|
||||
dependencies:
|
||||
tinyspy: 3.0.2
|
||||
|
||||
'@vitest/spy@3.0.7':
|
||||
dependencies:
|
||||
tinyspy: 3.0.2
|
||||
|
||||
'@vitest/ui@3.0.6(vitest@3.0.6)':
|
||||
dependencies:
|
||||
'@vitest/utils': 3.0.6
|
||||
@@ -15107,6 +15185,12 @@ snapshots:
|
||||
loupe: 3.1.3
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/utils@3.0.7':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 3.0.7
|
||||
loupe: 3.1.3
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@volar/language-core@1.11.1':
|
||||
dependencies:
|
||||
'@volar/source-map': 1.11.1
|
||||
@@ -16362,6 +16446,8 @@ snapshots:
|
||||
|
||||
commander@12.1.0: {}
|
||||
|
||||
commander@13.1.0: {}
|
||||
|
||||
commander@2.15.1: {}
|
||||
|
||||
commander@2.20.3: {}
|
||||
@@ -16713,24 +16799,6 @@ snapshots:
|
||||
nan: 2.22.0
|
||||
optional: true
|
||||
|
||||
cpx2@8.0.0:
|
||||
dependencies:
|
||||
debounce: 2.2.0
|
||||
debug: 4.4.0(supports-color@9.4.0)
|
||||
duplexer: 0.1.2
|
||||
fs-extra: 11.3.0
|
||||
glob: 11.0.1
|
||||
glob2base: 0.0.12
|
||||
ignore: 6.0.2
|
||||
minimatch: 10.0.1
|
||||
p-map: 7.0.3
|
||||
resolve: 1.22.10
|
||||
safe-buffer: 5.2.1
|
||||
shell-quote: 1.8.2
|
||||
subarg: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
crc-32@1.2.2: {}
|
||||
|
||||
crc32-stream@6.0.0:
|
||||
@@ -16945,8 +17013,6 @@ snapshots:
|
||||
|
||||
debounce@1.2.1: {}
|
||||
|
||||
debounce@2.2.0: {}
|
||||
|
||||
debug@2.6.9:
|
||||
dependencies:
|
||||
ms: 2.0.0
|
||||
@@ -18164,8 +18230,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
find-index@0.1.1: {}
|
||||
|
||||
find-my-way@8.2.2:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
@@ -18458,10 +18522,6 @@ snapshots:
|
||||
|
||||
glob-to-regexp@0.4.1: {}
|
||||
|
||||
glob2base@0.0.12:
|
||||
dependencies:
|
||||
find-index: 0.1.1
|
||||
|
||||
glob@10.4.5:
|
||||
dependencies:
|
||||
foreground-child: 3.3.0
|
||||
@@ -18918,8 +18978,6 @@ snapshots:
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
ignore@6.0.2: {}
|
||||
|
||||
ignore@7.0.3: {}
|
||||
|
||||
image-meta@0.2.1: {}
|
||||
@@ -20629,8 +20687,6 @@ snapshots:
|
||||
dependencies:
|
||||
aggregate-error: 3.1.0
|
||||
|
||||
p-map@7.0.3: {}
|
||||
|
||||
p-retry@6.2.1:
|
||||
dependencies:
|
||||
'@types/retry': 0.12.2
|
||||
@@ -22375,10 +22431,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
subarg@1.0.0:
|
||||
dependencies:
|
||||
minimist: 1.2.8
|
||||
|
||||
subscriptions-transport-ws@0.11.0(graphql@16.10.0):
|
||||
dependencies:
|
||||
backo2: 1.0.2
|
||||
@@ -23166,6 +23218,27 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite-node@3.0.7(@types/node@22.13.4)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.0(supports-color@9.4.0)
|
||||
es-module-lexer: 1.6.0
|
||||
pathe: 2.0.3
|
||||
vite: 6.1.1(@types/node@22.13.4)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite-plugin-checker@0.8.0(eslint@9.21.0(jiti@2.4.2))(meow@9.0.0)(optionator@0.9.4)(typescript@5.7.3)(vite@6.1.1(@types/node@22.13.4)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vue-tsc@2.2.2(typescript@5.7.3)):
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.26.2
|
||||
@@ -23434,6 +23507,46 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vitest@3.0.7(@types/node@22.13.4)(happy-dom@17.1.4)(jiti@2.4.2)(jsdom@26.0.0)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0):
|
||||
dependencies:
|
||||
'@vitest/expect': 3.0.7
|
||||
'@vitest/mocker': 3.0.7(vite@6.1.1(@types/node@22.13.4)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))
|
||||
'@vitest/pretty-format': 3.0.7
|
||||
'@vitest/runner': 3.0.7
|
||||
'@vitest/snapshot': 3.0.7
|
||||
'@vitest/spy': 3.0.7
|
||||
'@vitest/utils': 3.0.7
|
||||
chai: 5.2.0
|
||||
debug: 4.4.0(supports-color@9.4.0)
|
||||
expect-type: 1.1.0
|
||||
magic-string: 0.30.17
|
||||
pathe: 2.0.3
|
||||
std-env: 3.8.0
|
||||
tinybench: 2.9.0
|
||||
tinyexec: 0.3.2
|
||||
tinypool: 1.0.2
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 6.1.1(@types/node@22.13.4)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)
|
||||
vite-node: 3.0.7(@types/node@22.13.4)(jiti@2.4.2)(stylus@0.57.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 22.13.4
|
||||
happy-dom: 17.1.4
|
||||
jsdom: 26.0.0
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- msw
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vizion@2.2.1:
|
||||
dependencies:
|
||||
async: 2.6.4
|
||||
|
||||
3
unraid-ui/.gitignore
vendored
3
unraid-ui/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
!.env.development
|
||||
!.env.development
|
||||
dist-wc/
|
||||
@@ -12,7 +12,7 @@ clean:
|
||||
rm -rf node_modules
|
||||
|
||||
build-wc:
|
||||
REM_PLUGIN=true vite build -c vite.web-component.ts --mode production
|
||||
vite build -c vite.web-component.ts --mode production
|
||||
|
||||
deploy server_name:
|
||||
rsync -avz -e ssh ./dist/ root@{{server_name}}:/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components
|
||||
@@ -22,6 +22,7 @@
|
||||
"// Build": "",
|
||||
"prebuild": "npm run clean",
|
||||
"build": "vite build",
|
||||
"build:watch": "vite build -c vite.web-component.ts --mode production --watch",
|
||||
"build:wc": "REM_PLUGIN=true vite build -c vite.web-component.ts --mode production",
|
||||
"clean": "rm -rf dist",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
|
||||
@@ -22,6 +22,7 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist-wc',
|
||||
manifest: 'ui.manifest.json',
|
||||
sourcemap: true,
|
||||
cssCodeSplit: false,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"build:dev": "nuxi build --dotenv .env.staging && pnpm run manifest-ts && pnpm run deploy-to-unraid:dev",
|
||||
"build:webgui": "pnpm run type-check && nuxi build --dotenv .env.production && pnpm run manifest-ts && pnpm run copy-to-webgui-repo",
|
||||
"build": "NODE_ENV=production nuxi build --dotenv .env.production && pnpm run manifest-ts",
|
||||
"build:watch": "nuxi build --dotenv .env.production --watch && pnpm run manifest-ts",
|
||||
"generate": "nuxt generate",
|
||||
"manifest-ts": "node ./scripts/add-timestamp-webcomponent-manifest.js",
|
||||
"// Deployment": "",
|
||||
|
||||
Reference in New Issue
Block a user