mirror of
https://github.com/Koenkk/zigbee-OTA.git
synced 2026-06-24 09:48:42 +00:00
fix: Switch to biome & vitest (#726)
This commit is contained in:
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -27,10 +27,7 @@ jobs:
|
||||
run: pnpm run build
|
||||
|
||||
- name: Lint
|
||||
run: |
|
||||
pnpm run format:check
|
||||
pnpm run eslint
|
||||
run: pnpm run check
|
||||
|
||||
- name: Test
|
||||
# NOTE: see jest.config.ts `collectCoverageFrom`
|
||||
run: pnpm run coverage
|
||||
run: pnpm run test:coverage
|
||||
|
||||
2
.github/workflows/concat_cacerts.yml
vendored
2
.github/workflows/concat_cacerts.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
script: |
|
||||
const {concatCaCerts} = await import("${{ github.workspace }}/dist/ghw_concat_cacerts.js")
|
||||
|
||||
await concatCaCerts(github, core, context)
|
||||
concatCaCerts(github, core, context)
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -3,15 +3,15 @@ coverage/
|
||||
dist/
|
||||
temp/
|
||||
tmp/
|
||||
.jest-tmp/
|
||||
.test-tmp
|
||||
pr/
|
||||
tsconfig.tsbuildinfo
|
||||
|
||||
# used by tests
|
||||
images/jest-tmp
|
||||
images1/jest-tmp
|
||||
not-in-manifest-images/jest-tmp
|
||||
not-in-manifest-images1/jest-tmp
|
||||
images/test-tmp
|
||||
images1/test-tmp
|
||||
not-in-manifest-images/test-tmp
|
||||
not-in-manifest-images1/test-tmp
|
||||
|
||||
# MacOS indexing files
|
||||
.DS_Store
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
pnpm-lock.yaml
|
||||
.cache/
|
||||
images/
|
||||
images1/
|
||||
not-in-manifest-images/
|
||||
not-in-manifest-images1/
|
||||
index.json
|
||||
index1.json
|
||||
cacerts/
|
||||
cacerts.pem
|
||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["biomejs.biome", "vitest.explorer"]
|
||||
}
|
||||
61
biome.json
Normal file
61
biome.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
|
||||
"formatter": {
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 4,
|
||||
"lineWidth": 150,
|
||||
"bracketSpacing": false
|
||||
},
|
||||
"files": {
|
||||
"ignore": [
|
||||
"package.json",
|
||||
"./index.json",
|
||||
"./index1.json",
|
||||
"./.cache",
|
||||
"./cacerts",
|
||||
"./images",
|
||||
"./images1",
|
||||
"./not-in-manifest-images",
|
||||
"./not-in-manifest-images1"
|
||||
]
|
||||
},
|
||||
"linter": {
|
||||
"rules": {
|
||||
"suspicious": {
|
||||
"useAwait": "error",
|
||||
"noExportsInTest": "off"
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": "warn",
|
||||
"noParameterAssign": "warn",
|
||||
"useNamingConvention": {
|
||||
"level": "error",
|
||||
"options": {
|
||||
"strictCase": false,
|
||||
"conventions": [
|
||||
{
|
||||
"selector": {
|
||||
"kind": "objectLiteralProperty"
|
||||
},
|
||||
"formats": ["snake_case", "camelCase", "CONSTANT_CASE", "PascalCase"]
|
||||
},
|
||||
{
|
||||
"selector": {
|
||||
"kind": "typeProperty"
|
||||
},
|
||||
"formats": ["snake_case", "camelCase", "CONSTANT_CASE", "PascalCase"]
|
||||
},
|
||||
{
|
||||
"selector": {
|
||||
"kind": "const"
|
||||
},
|
||||
"formats": ["camelCase", "CONSTANT_CASE"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import eslint from '@eslint/js';
|
||||
import eslintConfigPrettier from 'eslint-config-prettier';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'script',
|
||||
parserOptions: {
|
||||
projectService: {
|
||||
allowDefaultProject: ['eslint.config.mjs', 'prettier.config.mjs'],
|
||||
},
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
'@typescript-eslint/ban-ts-comment': 'error',
|
||||
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'array-bracket-spacing': ['error', 'never'],
|
||||
'@typescript-eslint/return-await': ['error', 'always'],
|
||||
'object-curly-spacing': ['error', 'never'],
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ['tmp', 'dist', 'coverage'],
|
||||
},
|
||||
eslintConfigPrettier,
|
||||
);
|
||||
25
package.json
25
package.json
@@ -13,13 +13,12 @@
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "rm -rf dist coverage *.tsbuildinfo",
|
||||
"start": "node ./dist/index.js",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"eslint": "eslint . --max-warnings=0",
|
||||
"test": "jest test --config=./tests/jest.config.ts --silent --runInBand",
|
||||
"test:watch": "jest test --watch --config=./tests/jest.config.ts --silent --runInBand",
|
||||
"coverage": "jest test --config=./tests/jest.config.ts --silent --runInBand --coverage"
|
||||
"check": "biome check",
|
||||
"test": "vitest run --config ./tests/vitest.config.mts",
|
||||
"test:coverage": "vitest run --config ./tests/vitest.config.mts --coverage",
|
||||
"test:watch": "vitest watch --config ./tests/vitest.config.mts"
|
||||
},
|
||||
"keywords": [
|
||||
"zigbee",
|
||||
@@ -49,19 +48,11 @@
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.11.1",
|
||||
"@actions/github": "^6.0.0",
|
||||
"@eslint/core": "^0.12.0",
|
||||
"@eslint/js": "^9.23.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@octokit/rest": "^21.1.1",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.13.11",
|
||||
"eslint": "^9.23.0",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.5.3",
|
||||
"ts-jest": "^29.2.6",
|
||||
"ts-node": "^10.9.2",
|
||||
"@vitest/coverage-v8": "^3.0.9",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.27.0"
|
||||
"vitest": "^3.0.9"
|
||||
}
|
||||
}
|
||||
|
||||
3777
pnpm-lock.yaml
generated
3777
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,28 +0,0 @@
|
||||
import sortImports from '@ianvs/prettier-plugin-sort-imports';
|
||||
|
||||
export default {
|
||||
plugins: [sortImports],
|
||||
semi: true,
|
||||
trailingComma: 'all',
|
||||
singleQuote: true,
|
||||
printWidth: 150,
|
||||
bracketSpacing: false,
|
||||
endOfLine: 'lf',
|
||||
tabWidth: 4,
|
||||
importOrder: [
|
||||
'',
|
||||
'<TYPES>^(node:)',
|
||||
'',
|
||||
'<TYPES>',
|
||||
'',
|
||||
'<TYPES>^[.]',
|
||||
'',
|
||||
'<BUILTIN_MODULES>',
|
||||
'',
|
||||
'<THIRD_PARTY_MODULES>',
|
||||
'',
|
||||
'^zigbee',
|
||||
'',
|
||||
'^[.]',
|
||||
],
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
import {getJson, readCacheJson, writeCacheJson} from '../common.js';
|
||||
import {processFirmwareImage} from '../process_firmware_image.js';
|
||||
import {getJson, readCacheJson, writeCacheJson} from "../common.js";
|
||||
import {processFirmwareImage} from "../process_firmware_image.js";
|
||||
|
||||
type ImagesJsonBuildPart = {
|
||||
path: string; // .bin
|
||||
offset: number;
|
||||
type?: 'app' | 'storage';
|
||||
type?: "app" | "storage";
|
||||
ota?: string; // .ota
|
||||
};
|
||||
type ImagesJsonBuild = {
|
||||
@@ -21,11 +21,11 @@ type ImagesJson = {
|
||||
builds: ImagesJsonBuild[];
|
||||
};
|
||||
|
||||
const NAME = 'GammaTroniques';
|
||||
const NAME = "GammaTroniques";
|
||||
// const LOG_PREFIX = `[${NAME}]`;
|
||||
const BASE_URL = 'https://update.gammatroniques.fr/';
|
||||
const MANIFEST_URL_PATH = `/manifest.json`;
|
||||
const MODEL_IDS: [urlId: string, modelId: string][] = [['ticmeter', 'TICMeter']];
|
||||
const BASE_URL = "https://update.gammatroniques.fr/";
|
||||
const MANIFEST_URL_PATH = "/manifest.json";
|
||||
const MODEL_IDS: [urlId: string, modelId: string][] = [["ticmeter", "TICMeter"]];
|
||||
|
||||
function isDifferent(newData: ImagesJson, cachedData?: ImagesJson): boolean {
|
||||
return Boolean(process.env.IGNORE_CACHE) || !cachedData || cachedData.version !== newData.version;
|
||||
@@ -62,14 +62,14 @@ export async function download(): Promise<void> {
|
||||
|
||||
writeCacheJson(cacheFileName, page);
|
||||
|
||||
const appUrl: ImagesJsonBuildPart | undefined = page.builds[0].parts.find((part) => part.type === 'app');
|
||||
const appUrl: ImagesJsonBuildPart | undefined = page.builds[0].parts.find((part) => part.type === "app");
|
||||
|
||||
if (!appUrl || !appUrl.ota) {
|
||||
console.error(`${logPrefix} No image found.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const firmwareFileName = appUrl.ota.split('/').pop()!;
|
||||
const firmwareFileName = appUrl.ota.split("/").pop()!;
|
||||
|
||||
await processFirmwareImage(NAME, firmwareFileName, appUrl.ota, {modelId});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type {ExtraMetas} from '../types.js';
|
||||
import type {ExtraMetas} from "../types.js";
|
||||
|
||||
import {getJson, getLatestImage, readCacheJson, writeCacheJson} from '../common.js';
|
||||
import {processFirmwareImage} from '../process_firmware_image.js';
|
||||
import {getJson, getLatestImage, readCacheJson, writeCacheJson} from "../common.js";
|
||||
import {processFirmwareImage} from "../process_firmware_image.js";
|
||||
|
||||
type ReleaseAssetJson = {
|
||||
url: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {getJson, getLatestImage, readCacheJson, writeCacheJson} from '../common.js';
|
||||
import {processFirmwareImage} from '../process_firmware_image.js';
|
||||
import {getJson, getLatestImage, readCacheJson, writeCacheJson} from "../common.js";
|
||||
import {processFirmwareImage} from "../process_firmware_image.js";
|
||||
|
||||
type ImageJson = {
|
||||
createdAt: string;
|
||||
@@ -13,34 +13,34 @@ type ImageJson = {
|
||||
};
|
||||
type PageJson = {updates: ImageJson[]};
|
||||
|
||||
const NAME = 'Hue';
|
||||
const BASE_URL = 'https://firmware.meethue.com/v1/checkupdate?version=0&deviceTypeId=';
|
||||
const NAME = "Hue";
|
||||
const BASE_URL = "https://firmware.meethue.com/v1/checkupdate?version=0&deviceTypeId=";
|
||||
const DEVICE_TYPE_IDS: string[] = [
|
||||
'100b-111',
|
||||
'100b-112',
|
||||
"100b-111",
|
||||
"100b-112",
|
||||
// '100b-113',
|
||||
'100b-114',
|
||||
'100b-115',
|
||||
"100b-114",
|
||||
"100b-115",
|
||||
// '100b-116',
|
||||
'100b-117',
|
||||
'100b-118',
|
||||
"100b-117",
|
||||
"100b-118",
|
||||
// '100b-119',
|
||||
'100b-11a',
|
||||
"100b-11a",
|
||||
// '100b-11b',
|
||||
// '100b-11c',
|
||||
'100b-11d',
|
||||
'100b-11e',
|
||||
'100b-11f',
|
||||
'100b-120',
|
||||
"100b-11d",
|
||||
"100b-11e",
|
||||
"100b-11f",
|
||||
"100b-120",
|
||||
// '100b-121',
|
||||
// '100b-122',
|
||||
'100b-123',
|
||||
"100b-123",
|
||||
// '100b-124',
|
||||
'100b-125',
|
||||
"100b-125",
|
||||
// '100b-126',
|
||||
'100b-127',
|
||||
"100b-127",
|
||||
// '100b-128',
|
||||
'100b-129',
|
||||
"100b-129",
|
||||
// '100b-12a',
|
||||
// '100b-12b',
|
||||
// '100b-12c',
|
||||
@@ -98,7 +98,7 @@ export async function download(): Promise<void> {
|
||||
continue;
|
||||
}
|
||||
|
||||
const firmwareFileName = image.binaryUrl.split('/').pop()!;
|
||||
const firmwareFileName = image.binaryUrl.split("/").pop()!;
|
||||
|
||||
await processFirmwareImage(NAME, firmwareFileName, image.binaryUrl, {releaseNotes: image.releaseNotes || undefined});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {getJson, readCacheJson, writeCacheJson} from '../common.js';
|
||||
import {processFirmwareImage} from '../process_firmware_image.js';
|
||||
import {getJson, readCacheJson, writeCacheJson} from "../common.js";
|
||||
import {processFirmwareImage} from "../process_firmware_image.js";
|
||||
|
||||
type GatewayImageJson = {
|
||||
fw_binary_url: string;
|
||||
@@ -16,7 +16,9 @@ type GatewayImageJson = {
|
||||
};
|
||||
type DeviceImageJson = {
|
||||
fw_binary_url: string;
|
||||
// biome-ignore lint/style/useNamingConvention: <explanation>
|
||||
fw_file_version_LSB: number;
|
||||
// biome-ignore lint/style/useNamingConvention: <explanation>
|
||||
fw_file_version_MSB: number;
|
||||
fw_filesize: number;
|
||||
fw_image_type: number;
|
||||
@@ -25,16 +27,16 @@ type DeviceImageJson = {
|
||||
};
|
||||
type ImagesJson = (GatewayImageJson | DeviceImageJson)[];
|
||||
|
||||
const NAME = 'IKEA';
|
||||
const NAME = "IKEA";
|
||||
const LOG_PREFIX = `[${NAME}]`;
|
||||
const PRODUCTION_FIRMWARE_URL = 'http://fw.ota.homesmart.ikea.net/feed/version_info.json';
|
||||
const PRODUCTION_FIRMWARE_URL = "http://fw.ota.homesmart.ikea.net/feed/version_info.json";
|
||||
// const TEST_FIRMWARE_URL = 'http://fw.test.ota.homesmart.ikea.net/feed/version_info.json';
|
||||
export const RELEASE_NOTES_URL = 'https://ww8.ikea.com/ikeahomesmart/releasenotes/releasenotes.html';
|
||||
export const RELEASE_NOTES_URL = "https://ww8.ikea.com/ikeahomesmart/releasenotes/releasenotes.html";
|
||||
|
||||
function findInCache(image: DeviceImageJson, cachedData?: ImagesJson): DeviceImageJson | undefined {
|
||||
// `fw_type` compare ensures always `DeviceImagesJson`
|
||||
return cachedData?.find(
|
||||
(d) => d.fw_type == image.fw_type && d.fw_image_type == image.fw_image_type && d.fw_manufacturer_id == image.fw_manufacturer_id,
|
||||
(d) => d.fw_type === image.fw_type && d.fw_image_type === image.fw_image_type && d.fw_manufacturer_id === image.fw_manufacturer_id,
|
||||
) as DeviceImageJson | undefined;
|
||||
}
|
||||
|
||||
@@ -67,7 +69,7 @@ export async function download(): Promise<void> {
|
||||
continue;
|
||||
}
|
||||
|
||||
const firmwareFileName = image.fw_binary_url.split('/').pop()!;
|
||||
const firmwareFileName = image.fw_binary_url.split("/").pop()!;
|
||||
|
||||
if (!isDifferent(image, findInCache(image, cachedData))) {
|
||||
console.log(`[${NAME}:${firmwareFileName}] No change from last run.`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {getJson, readCacheJson, writeCacheJson} from '../common.js';
|
||||
import {processFirmwareImage} from '../process_firmware_image.js';
|
||||
import {RELEASE_NOTES_URL} from './ikea.js';
|
||||
import {getJson, readCacheJson, writeCacheJson} from "../common.js";
|
||||
import {processFirmwareImage} from "../process_firmware_image.js";
|
||||
import {RELEASE_NOTES_URL} from "./ikea.js";
|
||||
|
||||
type GatewayImageJson = {
|
||||
fw_type: 3;
|
||||
@@ -23,15 +23,15 @@ type DeviceImageJson = {
|
||||
type ImagesJson = (GatewayImageJson | DeviceImageJson)[];
|
||||
|
||||
// same name as `ikea.ts` to keep everything in same folder
|
||||
const NAME = 'IKEA';
|
||||
const NAME = "IKEA";
|
||||
const CACHE_FILENAME = `${NAME}_new`;
|
||||
const LOG_PREFIX = `[${NAME}_new]`;
|
||||
// requires cacerts/ikea_new.pem
|
||||
const FIRMWARE_URL = 'https://fw.ota.homesmart.ikea.com/check/update/prod';
|
||||
const FIRMWARE_URL = "https://fw.ota.homesmart.ikea.com/check/update/prod";
|
||||
|
||||
function findInCache(image: DeviceImageJson, cachedData?: ImagesJson): DeviceImageJson | undefined {
|
||||
// `fw_type` compare ensures always `DeviceImagesJson`
|
||||
return cachedData?.find((d) => d.fw_type == image.fw_type && d.fw_image_type == image.fw_image_type) as DeviceImageJson | undefined;
|
||||
return cachedData?.find((d) => d.fw_type === image.fw_type && d.fw_image_type === image.fw_image_type) as DeviceImageJson | undefined;
|
||||
}
|
||||
|
||||
function isDifferent(newData: DeviceImageJson, cachedData?: DeviceImageJson): boolean {
|
||||
@@ -58,7 +58,7 @@ export async function download(): Promise<void> {
|
||||
continue;
|
||||
}
|
||||
|
||||
const firmwareFileName = image.fw_binary_url.split('/').pop()!;
|
||||
const firmwareFileName = image.fw_binary_url.split("/").pop()!;
|
||||
|
||||
if (!isDifferent(image, findInCache(image, cachedData))) {
|
||||
console.log(`[${NAME}:${firmwareFileName}] No change from last run.`);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {getJson, getLatestImage, readCacheJson, writeCacheJson} from '../common.js';
|
||||
import {processFirmwareImage} from '../process_firmware_image.js';
|
||||
import {getJson, getLatestImage, readCacheJson, writeCacheJson} from "../common.js";
|
||||
import {processFirmwareImage} from "../process_firmware_image.js";
|
||||
|
||||
type DeviceImageJson = {
|
||||
version: string;
|
||||
channel: 'beta' | 'production';
|
||||
channel: "beta" | "production";
|
||||
firmware: string;
|
||||
manufacturer_id: number;
|
||||
image_type: number;
|
||||
@@ -12,15 +12,15 @@ type ModelsJson = {
|
||||
[k: string]: DeviceImageJson[];
|
||||
};
|
||||
|
||||
const NAME = 'Inovelli';
|
||||
const NAME = "Inovelli";
|
||||
const LOG_PREFIX = `[${NAME}]`;
|
||||
const FIRMWARE_URL = 'https://files.inovelli.com/firmware/firmware.json';
|
||||
const FIRMWARE_URL = "https://files.inovelli.com/firmware/firmware.json";
|
||||
|
||||
function sortByVersion(a: DeviceImageJson, b: DeviceImageJson): number {
|
||||
const aRadix = a.version.match(/[a-fA-F]/) ? 16 : 10;
|
||||
const bRadix = b.version.match(/[a-fA-F]/) ? 16 : 10;
|
||||
const aVersion = parseInt(a.version, aRadix);
|
||||
const bVersion = parseInt(b.version, bRadix);
|
||||
const aVersion = Number.parseInt(a.version, aRadix);
|
||||
const bVersion = Number.parseInt(b.version, bRadix);
|
||||
|
||||
return aVersion < bVersion ? -1 : aVersion > bVersion ? 1 : 0;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export async function download(): Promise<void> {
|
||||
const cachedData = readCacheJson<ModelsJson>(NAME);
|
||||
|
||||
for (const model in models) {
|
||||
if (model == '') {
|
||||
if (model === "") {
|
||||
// ignore empty key (bug)
|
||||
continue;
|
||||
}
|
||||
@@ -55,7 +55,7 @@ export async function download(): Promise<void> {
|
||||
continue;
|
||||
}
|
||||
|
||||
const firmwareFileName = image.firmware.split('/').pop()!;
|
||||
const firmwareFileName = image.firmware.split("/").pop()!;
|
||||
|
||||
if (cachedData && !isDifferent(image, getLatestImage(cachedData[model], sortByVersion))) {
|
||||
console.log(`[${NAME}:${firmwareFileName}] No change from last run.`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {getJson, readCacheJson, writeCacheJson} from '../common.js';
|
||||
import {processFirmwareImage} from '../process_firmware_image.js';
|
||||
import {getJson, readCacheJson, writeCacheJson} from "../common.js";
|
||||
import {processFirmwareImage} from "../process_firmware_image.js";
|
||||
|
||||
type ImageJson = {
|
||||
vendor: string;
|
||||
@@ -13,12 +13,12 @@ type ImageJson = {
|
||||
version: string;
|
||||
date: string;
|
||||
images: {
|
||||
'zigbee.ota': {
|
||||
"zigbee.ota": {
|
||||
url: string;
|
||||
hash: string;
|
||||
filesize: number;
|
||||
};
|
||||
'zigbee.bin': {
|
||||
"zigbee.bin": {
|
||||
url: string;
|
||||
hash: string;
|
||||
filesize: number;
|
||||
@@ -29,12 +29,12 @@ type ImageJson = {
|
||||
};
|
||||
};
|
||||
|
||||
const NAME = 'JetHome';
|
||||
const NAME = "JetHome";
|
||||
const LOG_PREFIX = `[${NAME}]`;
|
||||
const BASE_URL = 'https://fw.jethome.ru';
|
||||
const BASE_URL = "https://fw.jethome.ru";
|
||||
const DEVICE_URL = `${BASE_URL}/api/devices/`;
|
||||
|
||||
const MODEL_IDS = ['WS7'];
|
||||
const MODEL_IDS = ["WS7"];
|
||||
|
||||
function getCacheFileName(modelId: string): string {
|
||||
return `${NAME}_${modelId}`;
|
||||
@@ -62,14 +62,14 @@ export async function download(): Promise<void> {
|
||||
|
||||
// XXX: this is assumed to always be present even for devices that support OTA but without images yet available?
|
||||
if (image?.latest_firmware?.release?.images) {
|
||||
const firmware = image.latest_firmware.release.images['zigbee.ota'];
|
||||
const firmware = image.latest_firmware.release.images["zigbee.ota"];
|
||||
|
||||
if (!firmware) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const firmwareUrl = BASE_URL + firmware.url;
|
||||
const firmwareFileName = firmwareUrl.split('/').pop()!;
|
||||
const firmwareFileName = firmwareUrl.split("/").pop()!;
|
||||
const cacheFileName = getCacheFileName(modelId);
|
||||
|
||||
if (!isDifferent(image, readCacheJson(cacheFileName))) {
|
||||
@@ -85,7 +85,6 @@ export async function download(): Promise<void> {
|
||||
});
|
||||
} else {
|
||||
console.error(`${LOG_PREFIX} No image data for ${modelId}.`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {getJson, getLatestImage, readCacheJson, writeCacheJson} from '../common.js';
|
||||
import {processFirmwareImage, ProcessFirmwareImageStatus} from '../process_firmware_image.js';
|
||||
import {getJson, getLatestImage, readCacheJson, writeCacheJson} from "../common.js";
|
||||
import {ProcessFirmwareImageStatus, processFirmwareImage} from "../process_firmware_image.js";
|
||||
|
||||
type FirmwareJson = {
|
||||
blob: null;
|
||||
@@ -34,12 +34,12 @@ type ImagesJson = {
|
||||
};
|
||||
type GroupedImagesJson = Record<string, FirmwareJson[]>;
|
||||
|
||||
const NAME = 'LEDVANCE';
|
||||
const NAME = "LEDVANCE";
|
||||
const LOG_PREFIX = `[${NAME}]`;
|
||||
const FIRMWARE_URL = 'https://api.update.ledvance.com/v1/zigbee/firmwares/';
|
||||
const FIRMWARE_URL = "https://api.update.ledvance.com/v1/zigbee/firmwares/";
|
||||
// const UPDATE_CHECK_URL = 'https://api.update.ledvance.com/v1/zigbee/firmwares/newer';
|
||||
// const UPDATE_CHECK_PARAMS = `?company=${manufCode}&product=${imageType}&version=0.0.0`;
|
||||
const UPDATE_DOWNLOAD_URL = 'https://api.update.ledvance.com/v1/zigbee/firmwares/download';
|
||||
const UPDATE_DOWNLOAD_URL = "https://api.update.ledvance.com/v1/zigbee/firmwares/download";
|
||||
/** XXX: getting 429 after a few downloads, force more throttling. Seems to trigger after around ~20 requests. */
|
||||
const FETCH_FAILED_THROTTLE_MS = 60000;
|
||||
const FETCH_FAILED_RETRIES = 3;
|
||||
@@ -98,7 +98,7 @@ export async function download(): Promise<void> {
|
||||
|
||||
// const fileVersion = parseInt(fileVersionMatch[1], 16);
|
||||
const firmwareUrl = `${UPDATE_DOWNLOAD_URL}?company=${firmware.identity.company}&product=${firmware.identity.product}&version=${getVersionString(firmware)}`;
|
||||
const firmwareFileName = firmware.fullName.split('/').pop()!;
|
||||
const firmwareFileName = firmware.fullName.split("/").pop()!;
|
||||
|
||||
if (cachedDataByProduct && !isDifferent(firmware, getLatestImage(cachedDataByProduct[product], sortByReleased))) {
|
||||
console.log(`[${NAME}:${firmwareFileName}] No change from last run.`);
|
||||
@@ -112,7 +112,7 @@ export async function download(): Promise<void> {
|
||||
releaseNotes: firmware.releaseNotes,
|
||||
});
|
||||
|
||||
if (status === ProcessFirmwareImageStatus.REQUEST_FAILED) {
|
||||
if (status === ProcessFirmwareImageStatus.RequestFailed) {
|
||||
await new Promise((resolve) => setTimeout(resolve, FETCH_FAILED_THROTTLE_MS));
|
||||
} else {
|
||||
break;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as github from './github.js';
|
||||
import * as github from "./github.js";
|
||||
|
||||
const NAME = 'LiXee';
|
||||
const FIRMWARE_URL = 'https://api.github.com/repos/fairecasoimeme/Zlinky_TIC/releases';
|
||||
const NAME = "LiXee";
|
||||
const FIRMWARE_URL = "https://api.github.com/repos/fairecasoimeme/Zlinky_TIC/releases";
|
||||
/** @see https://github.com/fairecasoimeme/Zlinky_TIC?tab=readme-ov-file#route-or-limited-route-from-v7 */
|
||||
const FIRMWARE_EXT = '.ota';
|
||||
const FIRMWARE_EXT = ".ota";
|
||||
const FIRMWARE_LIMITED = `limited${FIRMWARE_EXT}`;
|
||||
|
||||
export async function writeCache(): Promise<void> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {getJson, readCacheJson, writeCacheJson} from '../common.js';
|
||||
import {processFirmwareImage} from '../process_firmware_image.js';
|
||||
import {getJson, readCacheJson, writeCacheJson} from "../common.js";
|
||||
import {processFirmwareImage} from "../process_firmware_image.js";
|
||||
|
||||
type ImageJson = {
|
||||
model: string;
|
||||
@@ -10,12 +10,12 @@ type ImagesJson = {
|
||||
versions: ImageJson[];
|
||||
};
|
||||
|
||||
const NAME = 'SalusControls';
|
||||
const NAME = "SalusControls";
|
||||
const LOG_PREFIX = `[${NAME}]`;
|
||||
const FIRMWARE_URL = 'https://eu.salusconnect.io/demo/default/status/firmware?timestamp=0';
|
||||
const FIRMWARE_URL = "https://eu.salusconnect.io/demo/default/status/firmware?timestamp=0";
|
||||
|
||||
function findInCache(image: ImageJson, cachedData?: ImagesJson): ImageJson | undefined {
|
||||
return cachedData?.versions?.find((d) => d.model == image.model);
|
||||
return cachedData?.versions?.find((d) => d.model === image.model);
|
||||
}
|
||||
|
||||
function isDifferent(newData: ImageJson, cachedData?: ImageJson): boolean {
|
||||
@@ -38,14 +38,14 @@ export async function download(): Promise<void> {
|
||||
|
||||
for (const image of images.versions) {
|
||||
const archiveUrl = image.url; //.replace(/^http:\/\//, 'https://');
|
||||
const archiveFileName = archiveUrl.split('/').pop()!;
|
||||
const archiveFileName = archiveUrl.split("/").pop()!;
|
||||
|
||||
if (!isDifferent(image, findInCache(image, cachedData))) {
|
||||
console.log(`[${NAME}:${archiveFileName}] No change from last run.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await processFirmwareImage(NAME, archiveFileName, archiveUrl, {manufacturerName: [NAME]}, true, (fileName) => fileName.endsWith('.ota'));
|
||||
await processFirmwareImage(NAME, archiveFileName, archiveUrl, {manufacturerName: [NAME]}, true, (fileName) => fileName.endsWith(".ota"));
|
||||
}
|
||||
|
||||
writeCacheJson(NAME, images);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import url from 'url';
|
||||
import url from "node:url";
|
||||
|
||||
import {getLatestImage, getText, readCacheJson, writeCacheJson} from '../common.js';
|
||||
import {processFirmwareImage} from '../process_firmware_image.js';
|
||||
import {getLatestImage, getText, readCacheJson, writeCacheJson} from "../common.js";
|
||||
import {processFirmwareImage} from "../process_firmware_image.js";
|
||||
|
||||
type Image = {
|
||||
fileName: string;
|
||||
@@ -14,14 +14,14 @@ type GroupedImages = {
|
||||
[k: string]: Image[];
|
||||
};
|
||||
|
||||
const NAME = 'Ubisys';
|
||||
const NAME = "Ubisys";
|
||||
const LOG_PREFIX = `[${NAME}]`;
|
||||
const FIRMWARE_HTML_URL = 'http://fwu.ubisys.de/smarthome/OTA/release/index';
|
||||
const FIRMWARE_HTML_URL = "http://fwu.ubisys.de/smarthome/OTA/release/index";
|
||||
|
||||
function groupByImageType(arr: Image[]): GroupedImages {
|
||||
return arr.reduce<GroupedImages>((acc, cur) => {
|
||||
acc[cur.imageType + (cur.hardwareVersionMax ? cur.hardwareVersionMax : '')] = [
|
||||
...(acc[cur.imageType + (cur.hardwareVersionMax ? cur.hardwareVersionMax : '')] || []),
|
||||
acc[cur.imageType + (cur.hardwareVersionMax ? cur.hardwareVersionMax : "")] = [
|
||||
...(acc[cur.imageType + (cur.hardwareVersionMax ? cur.hardwareVersionMax : "")] || []),
|
||||
cur,
|
||||
];
|
||||
return acc;
|
||||
@@ -37,7 +37,7 @@ function isDifferent(newData: Image, cachedData?: Image): boolean {
|
||||
}
|
||||
|
||||
function parseText(pageText: string): Image[] {
|
||||
const lines = pageText.split('\n');
|
||||
const lines = pageText.split("\n");
|
||||
const images: Image[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
@@ -48,9 +48,9 @@ function parseText(pageText: string): Image[] {
|
||||
images.push({
|
||||
fileName: imageMatch[0],
|
||||
imageType: imageMatch[1],
|
||||
hardwareVersionMin: parseInt(imageMatch[2], 16),
|
||||
hardwareVersionMax: parseInt(imageMatch[3], 16),
|
||||
fileVersion: parseInt(imageMatch[4], 16),
|
||||
hardwareVersionMin: Number.parseInt(imageMatch[2], 16),
|
||||
hardwareVersionMax: Number.parseInt(imageMatch[3], 16),
|
||||
fileVersion: Number.parseInt(imageMatch[4], 16),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import * as github from './github.js';
|
||||
import * as github from "./github.js";
|
||||
|
||||
const NAME = 'xyzroe';
|
||||
const FIRMWARE_URL = 'https://api.github.com/repos/xyzroe/ZigUSB_C6/releases';
|
||||
const FIRMWARE_EXT = '.ota';
|
||||
const NAME = "xyzroe";
|
||||
const FIRMWARE_URL = "https://api.github.com/repos/xyzroe/ZigUSB_C6/releases";
|
||||
const FIRMWARE_EXT = ".ota";
|
||||
|
||||
export async function writeCache(): Promise<void> {
|
||||
await github.writeCache(NAME, FIRMWARE_URL);
|
||||
}
|
||||
|
||||
export async function download(): Promise<void> {
|
||||
await github.download(NAME, FIRMWARE_URL, [(a): boolean => a.name.endsWith(FIRMWARE_EXT)], {modelId: 'ZigUSB_C6'});
|
||||
await github.download(NAME, FIRMWARE_URL, [(a): boolean => a.name.endsWith(FIRMWARE_EXT)], {modelId: "ZigUSB_C6"});
|
||||
}
|
||||
|
||||
154
src/common.ts
154
src/common.ts
@@ -1,28 +1,28 @@
|
||||
import type {ExtraMetas, ExtraMetasWithFileName, ImageHeader, RepoImageMeta} from './types';
|
||||
import type {ExtraMetas, ExtraMetasWithFileName, ImageHeader, RepoImageMeta} from "./types";
|
||||
|
||||
import assert from 'assert';
|
||||
import {exec} from 'child_process';
|
||||
import {createHash} from 'crypto';
|
||||
import {existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync} from 'fs';
|
||||
import path from 'path';
|
||||
import assert from "node:assert";
|
||||
import {exec} from "node:child_process";
|
||||
import {createHash} from "node:crypto";
|
||||
import {existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync} from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
export const UPGRADE_FILE_IDENTIFIER = Buffer.from([0x1e, 0xf1, 0xee, 0x0b]);
|
||||
export const BASE_REPO_URL = `https://raw.githubusercontent.com/Koenkk/zigbee-OTA/`;
|
||||
export const REPO_BRANCH = 'master';
|
||||
export const BASE_REPO_URL = "https://raw.githubusercontent.com/Koenkk/zigbee-OTA/";
|
||||
export const REPO_BRANCH = "master";
|
||||
/** Images used by OTA upgrade process */
|
||||
export const BASE_IMAGES_DIR = 'images';
|
||||
export const BASE_IMAGES_DIR = "images";
|
||||
/** Images used by OTA downgrade process */
|
||||
export const PREV_IMAGES_DIR = 'images1';
|
||||
export const PREV_IMAGES_DIR = "images1";
|
||||
/** Manifest used by OTA upgrade process */
|
||||
export const BASE_INDEX_MANIFEST_FILENAME = 'index.json';
|
||||
export const BASE_INDEX_MANIFEST_FILENAME = "index.json";
|
||||
/** Manifest used by OTA downgrade process */
|
||||
export const PREV_INDEX_MANIFEST_FILENAME = 'index1.json';
|
||||
export const CACHE_DIR = '.cache';
|
||||
export const TMP_DIR = 'tmp';
|
||||
export const PR_ARTIFACT_DIR = 'pr';
|
||||
export const PR_DIFF_FILENAME = 'PR_DIFF';
|
||||
export const PR_ERROR_FILENAME = 'PR_ERROR';
|
||||
export const PR_NUMBER_FILENAME = 'PR_NUMBER';
|
||||
export const PREV_INDEX_MANIFEST_FILENAME = "index1.json";
|
||||
export const CACHE_DIR = ".cache";
|
||||
export const TMP_DIR = "tmp";
|
||||
export const PR_ARTIFACT_DIR = "pr";
|
||||
export const PR_DIFF_FILENAME = "PR_DIFF";
|
||||
export const PR_ERROR_FILENAME = "PR_ERROR";
|
||||
export const PR_NUMBER_FILENAME = "PR_NUMBER";
|
||||
export const PR_ARTIFACT_DIFF_FILEPATH = path.join(PR_ARTIFACT_DIR, PR_DIFF_FILENAME);
|
||||
export const PR_ARTIFACT_ERROR_FILEPATH = path.join(PR_ARTIFACT_DIR, PR_ERROR_FILENAME);
|
||||
export const PR_ARTIFACT_NUMBER_FILEPATH = path.join(PR_ARTIFACT_DIR, PR_NUMBER_FILENAME);
|
||||
@@ -30,17 +30,17 @@ export const PR_ARTIFACT_NUMBER_FILEPATH = path.join(PR_ARTIFACT_DIR, PR_NUMBER_
|
||||
* 'ikea_new' first, to prioritize downloads from new URL
|
||||
*/
|
||||
export const ALL_AUTODL_MANUFACTURERS = [
|
||||
'gammatroniques',
|
||||
'hue',
|
||||
'ikea_new',
|
||||
'ikea',
|
||||
'inovelli',
|
||||
'jethome',
|
||||
'ledvance',
|
||||
'lixee',
|
||||
'salus',
|
||||
'ubisys',
|
||||
'xyzroe',
|
||||
"gammatroniques",
|
||||
"hue",
|
||||
"ikea_new",
|
||||
"ikea",
|
||||
"inovelli",
|
||||
"jethome",
|
||||
"ledvance",
|
||||
"lixee",
|
||||
"salus",
|
||||
"ubisys",
|
||||
"xyzroe",
|
||||
];
|
||||
|
||||
export async function execute(command: string): Promise<string> {
|
||||
@@ -60,11 +60,11 @@ export function primitivesArrayEquals(a: (string | number | boolean)[], b: (stri
|
||||
}
|
||||
|
||||
export function computeSHA512(buffer: Buffer): string {
|
||||
const hash = createHash('sha512');
|
||||
const hash = createHash("sha512");
|
||||
|
||||
hash.update(buffer);
|
||||
|
||||
return hash.digest('hex');
|
||||
return hash.digest("hex");
|
||||
}
|
||||
|
||||
export function getOutDir(folderName: string, basePath: string = BASE_IMAGES_DIR): string {
|
||||
@@ -82,21 +82,21 @@ export function getRepoFirmwareFileUrl(folderName: string, fileName: string, bas
|
||||
}
|
||||
|
||||
export function writeManifest(fileName: string, firmwareList: RepoImageMeta[]): void {
|
||||
writeFileSync(fileName, JSON.stringify(firmwareList, undefined, 2), 'utf8');
|
||||
writeFileSync(fileName, JSON.stringify(firmwareList, undefined, 2), "utf8");
|
||||
}
|
||||
|
||||
export function readManifest(fileName: string): RepoImageMeta[] {
|
||||
return JSON.parse(readFileSync(fileName, 'utf8'));
|
||||
return JSON.parse(readFileSync(fileName, "utf8"));
|
||||
}
|
||||
|
||||
export function writeCacheJson<T>(fileName: string, contents: T, basePath: string = CACHE_DIR): void {
|
||||
writeFileSync(path.join(basePath, `${fileName}.json`), JSON.stringify(contents), 'utf8');
|
||||
writeFileSync(path.join(basePath, `${fileName}.json`), JSON.stringify(contents), "utf8");
|
||||
}
|
||||
|
||||
export function readCacheJson<T>(fileName: string, basePath: string = CACHE_DIR): T | undefined {
|
||||
const filePath = path.join(basePath, `${fileName}.json`);
|
||||
|
||||
return existsSync(filePath) ? JSON.parse(readFileSync(filePath, 'utf8')) : undefined;
|
||||
return existsSync(filePath) ? JSON.parse(readFileSync(filePath, "utf8")) : undefined;
|
||||
}
|
||||
|
||||
export function parseImageHeader(buffer: Buffer): ImageHeader {
|
||||
@@ -110,7 +110,7 @@ export function parseImageHeader(buffer: Buffer): ImageHeader {
|
||||
imageType: buffer.readUInt16LE(12),
|
||||
fileVersion: buffer.readUInt32LE(14),
|
||||
zigbeeStackVersion: buffer.readUInt16LE(18),
|
||||
otaHeaderString: buffer.toString('utf8', 20, 52),
|
||||
otaHeaderString: buffer.toString("utf8", 20, 52),
|
||||
totalImageSize: buffer.readUInt32LE(52),
|
||||
};
|
||||
let headerPos = 56;
|
||||
@@ -132,7 +132,7 @@ export function parseImageHeader(buffer: Buffer): ImageHeader {
|
||||
headerPos += 2;
|
||||
}
|
||||
|
||||
assert(UPGRADE_FILE_IDENTIFIER.equals(header.otaUpgradeFileIdentifier), `Invalid upgrade file identifier`);
|
||||
assert(UPGRADE_FILE_IDENTIFIER.equals(header.otaUpgradeFileIdentifier), "Invalid upgrade file identifier");
|
||||
|
||||
return header;
|
||||
} catch (error) {
|
||||
@@ -199,25 +199,27 @@ export function getLatestImage<T>(list: T[] | undefined, compareFn: (a: T, b: T)
|
||||
return sortedList.slice(0, sortedList.length > 1 && process.env.PREV ? -1 : undefined).pop();
|
||||
}
|
||||
|
||||
export const enum ParsedImageStatus {
|
||||
NEW = 0,
|
||||
NEWER = 1,
|
||||
OLDER = 2,
|
||||
IDENTICAL = 3,
|
||||
export enum ParsedImageStatus {
|
||||
New = 0,
|
||||
Newer = 1,
|
||||
Older = 2,
|
||||
Identical = 3,
|
||||
}
|
||||
|
||||
export function getParsedImageStatus(parsedImage: ImageHeader, match?: RepoImageMeta): ParsedImageStatus {
|
||||
if (match) {
|
||||
if (match.fileVersion > parsedImage.fileVersion) {
|
||||
return ParsedImageStatus.OLDER;
|
||||
} else if (match.fileVersion < parsedImage.fileVersion) {
|
||||
return ParsedImageStatus.NEWER;
|
||||
} else {
|
||||
return ParsedImageStatus.IDENTICAL;
|
||||
return ParsedImageStatus.Older;
|
||||
}
|
||||
} else {
|
||||
return ParsedImageStatus.NEW;
|
||||
|
||||
if (match.fileVersion < parsedImage.fileVersion) {
|
||||
return ParsedImageStatus.Newer;
|
||||
}
|
||||
|
||||
return ParsedImageStatus.Identical;
|
||||
}
|
||||
|
||||
return ParsedImageStatus.New;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,8 +233,8 @@ export function getValidMetas(metas: Partial<ExtraMetas & ExtraMetasWithFileName
|
||||
const validMetas: ExtraMetasWithFileName = {};
|
||||
|
||||
if (!ignoreFileName) {
|
||||
if (metas.fileName != undefined) {
|
||||
if (typeof metas.fileName != 'string') {
|
||||
if (metas.fileName != null) {
|
||||
if (typeof metas.fileName !== "string") {
|
||||
throw new Error(`Invalid format for 'fileName', expected 'string' type.`);
|
||||
}
|
||||
|
||||
@@ -240,72 +242,76 @@ export function getValidMetas(metas: Partial<ExtraMetas & ExtraMetasWithFileName
|
||||
}
|
||||
}
|
||||
|
||||
if (metas.originalUrl != undefined) {
|
||||
if (typeof metas.originalUrl != 'string') {
|
||||
if (metas.originalUrl != null) {
|
||||
if (typeof metas.originalUrl !== "string") {
|
||||
throw new Error(`Invalid format for 'originalUrl', expected 'string' type.`);
|
||||
}
|
||||
|
||||
validMetas.originalUrl = metas.originalUrl;
|
||||
}
|
||||
|
||||
if (metas.force != undefined) {
|
||||
if (typeof metas.force != 'boolean') {
|
||||
if (metas.force != null) {
|
||||
if (typeof metas.force !== "boolean") {
|
||||
throw new Error(`Invalid format for 'force', expected 'boolean' type.`);
|
||||
}
|
||||
|
||||
validMetas.force = metas.force;
|
||||
}
|
||||
|
||||
if (metas.hardwareVersionMax != undefined) {
|
||||
if (typeof metas.hardwareVersionMax != 'number') {
|
||||
if (metas.hardwareVersionMax != null) {
|
||||
if (typeof metas.hardwareVersionMax !== "number") {
|
||||
throw new Error(`Invalid format for 'hardwareVersionMax', expected 'number' type.`);
|
||||
}
|
||||
|
||||
validMetas.hardwareVersionMax = metas.hardwareVersionMax;
|
||||
}
|
||||
|
||||
if (metas.hardwareVersionMin != undefined) {
|
||||
if (typeof metas.hardwareVersionMin != 'number') {
|
||||
if (metas.hardwareVersionMin != null) {
|
||||
if (typeof metas.hardwareVersionMin !== "number") {
|
||||
throw new Error(`Invalid format for 'hardwareVersionMin', expected 'number' type.`);
|
||||
}
|
||||
|
||||
validMetas.hardwareVersionMin = metas.hardwareVersionMin;
|
||||
}
|
||||
|
||||
if (metas.manufacturerName != undefined) {
|
||||
if (!Array.isArray(metas.manufacturerName) || metas.manufacturerName.length < 1 || metas.manufacturerName.some((m) => typeof m != 'string')) {
|
||||
if (metas.manufacturerName != null) {
|
||||
if (
|
||||
!Array.isArray(metas.manufacturerName) ||
|
||||
metas.manufacturerName.length < 1 ||
|
||||
metas.manufacturerName.some((m) => typeof m !== "string")
|
||||
) {
|
||||
throw new Error(`Invalid format for 'manufacturerName', expected 'array of string' type.`);
|
||||
}
|
||||
|
||||
validMetas.manufacturerName = metas.manufacturerName;
|
||||
}
|
||||
|
||||
if (metas.maxFileVersion != undefined) {
|
||||
if (typeof metas.maxFileVersion != 'number') {
|
||||
if (metas.maxFileVersion != null) {
|
||||
if (typeof metas.maxFileVersion !== "number") {
|
||||
throw new Error(`Invalid format for 'maxFileVersion', expected 'number' type.`);
|
||||
}
|
||||
|
||||
validMetas.maxFileVersion = metas.maxFileVersion;
|
||||
}
|
||||
|
||||
if (metas.minFileVersion != undefined) {
|
||||
if (typeof metas.minFileVersion != 'number') {
|
||||
if (metas.minFileVersion != null) {
|
||||
if (typeof metas.minFileVersion !== "number") {
|
||||
throw new Error(`Invalid format for 'minFileVersion', expected 'number' type.`);
|
||||
}
|
||||
|
||||
validMetas.minFileVersion = metas.minFileVersion;
|
||||
}
|
||||
|
||||
if (metas.modelId != undefined) {
|
||||
if (typeof metas.modelId != 'string') {
|
||||
if (metas.modelId != null) {
|
||||
if (typeof metas.modelId !== "string") {
|
||||
throw new Error(`Invalid format for 'modelId', expected 'string' type.`);
|
||||
}
|
||||
|
||||
validMetas.modelId = metas.modelId;
|
||||
}
|
||||
|
||||
if (metas.releaseNotes != undefined) {
|
||||
if (typeof metas.releaseNotes != 'string') {
|
||||
if (metas.releaseNotes != null) {
|
||||
if (typeof metas.releaseNotes !== "string") {
|
||||
throw new Error(`Invalid format for 'releaseNotes', expected 'string' type.`);
|
||||
}
|
||||
|
||||
@@ -337,7 +343,7 @@ export function addImageToPrev(
|
||||
prevManifest.splice(prevMatchIndex, 1);
|
||||
|
||||
// make sure fileName exists for migration from old system
|
||||
const prevFileName = prevMatch.fileName ? prevMatch.fileName : prevMatch.url.split('/').pop()!;
|
||||
const prevFileName = prevMatch.fileName ? prevMatch.fileName : prevMatch.url.split("/").pop()!;
|
||||
|
||||
rmSync(path.join(prevOutDir, prevFileName), {force: true});
|
||||
}
|
||||
@@ -380,23 +386,23 @@ export function addImageToBase(
|
||||
const [prevMatchIndex, prevMatch] = findMatchImage(parsedImage, prevManifest, extraMetas);
|
||||
const prevStatus = getParsedImageStatus(parsedImage, prevMatch);
|
||||
|
||||
if (prevStatus !== ParsedImageStatus.OLDER && prevStatus !== ParsedImageStatus.NEW) {
|
||||
if (prevStatus !== ParsedImageStatus.Older && prevStatus !== ParsedImageStatus.New) {
|
||||
console.warn(`${logPrefix} Base image is new/newer but prev image is not older/non-existing.`);
|
||||
}
|
||||
|
||||
if (prevStatus !== ParsedImageStatus.NEW) {
|
||||
if (prevStatus !== ParsedImageStatus.New) {
|
||||
console.log(`${logPrefix} Removing prev image.`);
|
||||
prevManifest.splice(prevMatchIndex, 1);
|
||||
|
||||
// make sure fileName exists for migration from old system
|
||||
const prevFileName = prevMatch!.fileName ? prevMatch!.fileName : prevMatch!.url.split('/').pop()!;
|
||||
const prevFileName = prevMatch!.fileName ? prevMatch!.fileName : prevMatch!.url.split("/").pop()!;
|
||||
|
||||
rmSync(path.join(prevOutDir, prevFileName), {force: true});
|
||||
}
|
||||
|
||||
// relocate base to prev
|
||||
// make sure fileName exists for migration from old system
|
||||
const baseFileName = baseMatch.fileName ? baseMatch.fileName : baseMatch.url.split('/').pop()!;
|
||||
const baseFileName = baseMatch.fileName ? baseMatch.fileName : baseMatch.url.split("/").pop()!;
|
||||
const baseFilePath = path.join(baseOutDir, baseFileName);
|
||||
|
||||
// if for some reason the file is no longer present (should not happen), don't add it to prev since link is broken
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
|
||||
import assert from 'assert';
|
||||
import {existsSync, mkdirSync, writeFileSync} from 'fs';
|
||||
import assert from "node:assert";
|
||||
import {existsSync, mkdirSync, writeFileSync} from "node:fs";
|
||||
|
||||
import {
|
||||
BASE_INDEX_MANIFEST_FILENAME,
|
||||
execute,
|
||||
PREV_INDEX_MANIFEST_FILENAME,
|
||||
PR_ARTIFACT_DIFF_FILEPATH,
|
||||
PR_ARTIFACT_DIR,
|
||||
PR_ARTIFACT_ERROR_FILEPATH,
|
||||
PR_ARTIFACT_NUMBER_FILEPATH,
|
||||
PREV_INDEX_MANIFEST_FILENAME,
|
||||
execute,
|
||||
readManifest,
|
||||
writeManifest,
|
||||
} from './common.js';
|
||||
import {getChangedOtaFiles} from './ghw_get_changed_ota_files.js';
|
||||
import {processOtaFiles} from './ghw_process_ota_files.js';
|
||||
} from "./common.js";
|
||||
import {getChangedOtaFiles} from "./ghw_get_changed_ota_files.js";
|
||||
import {processOtaFiles} from "./ghw_process_ota_files.js";
|
||||
|
||||
function throwError(comment: string): void {
|
||||
writeFileSync(PR_ARTIFACT_ERROR_FILEPATH, comment);
|
||||
@@ -26,14 +26,14 @@ function throwError(comment: string): void {
|
||||
}
|
||||
|
||||
export async function checkOtaPR(github: Octokit, core: typeof CoreApi, context: Context): Promise<void> {
|
||||
assert(context.payload.pull_request, 'Not a pull request');
|
||||
assert(!context.payload.pull_request.merged, 'Should not be executed on a merged pull request');
|
||||
assert(context.payload.pull_request, "Not a pull request");
|
||||
assert(!context.payload.pull_request.merged, "Should not be executed on a merged pull request");
|
||||
|
||||
if (!existsSync(PR_ARTIFACT_DIR)) {
|
||||
mkdirSync(PR_ARTIFACT_DIR, {recursive: true});
|
||||
}
|
||||
|
||||
writeFileSync(PR_ARTIFACT_NUMBER_FILEPATH, context.issue.number.toString(10), 'utf8');
|
||||
writeFileSync(PR_ARTIFACT_NUMBER_FILEPATH, context.issue.number.toString(10), "utf8");
|
||||
|
||||
const baseManifest = readManifest(BASE_INDEX_MANIFEST_FILENAME);
|
||||
const prevManifest = readManifest(PREV_INDEX_MANIFEST_FILENAME);
|
||||
@@ -58,9 +58,9 @@ export async function checkOtaPR(github: Octokit, core: typeof CoreApi, context:
|
||||
core.info(`Prev manifest has ${prevManifest.length} images.`);
|
||||
core.info(`Base manifest has ${baseManifest.length} images.`);
|
||||
|
||||
const diff = await execute(`git diff`);
|
||||
const diff = await execute("git diff");
|
||||
|
||||
core.startGroup('diff');
|
||||
core.startGroup("diff");
|
||||
core.info(diff);
|
||||
core.endGroup();
|
||||
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
|
||||
import {readdirSync, readFileSync, writeFileSync} from 'fs';
|
||||
import path from 'path';
|
||||
import {readFileSync, readdirSync, writeFileSync} from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
export const CACERTS_DIR = 'cacerts';
|
||||
export const CACERTS_CONCAT_FILEPATH = 'cacerts.pem';
|
||||
export const CACERTS_DIR = "cacerts";
|
||||
export const CACERTS_CONCAT_FILEPATH = "cacerts.pem";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export async function concatCaCerts(github: Octokit, core: typeof CoreApi, context: Context): Promise<void> {
|
||||
let pemContents: string = '';
|
||||
export function concatCaCerts(github: Octokit, core: typeof CoreApi, context: Context): void {
|
||||
let pemContents = "";
|
||||
|
||||
for (const pem of readdirSync(CACERTS_DIR)) {
|
||||
if (!pem.endsWith('.pem')) {
|
||||
if (!pem.endsWith(".pem")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
core.startGroup(pem);
|
||||
|
||||
pemContents += readFileSync(path.join(CACERTS_DIR, pem), 'utf8');
|
||||
pemContents += '\n';
|
||||
pemContents += readFileSync(path.join(CACERTS_DIR, pem), "utf8");
|
||||
pemContents += "\n";
|
||||
|
||||
core.endGroup();
|
||||
}
|
||||
|
||||
writeFileSync(CACERTS_CONCAT_FILEPATH, pemContents, 'utf8');
|
||||
writeFileSync(CACERTS_CONCAT_FILEPATH, pemContents, "utf8");
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
|
||||
import type {RepoImageMeta} from './types.js';
|
||||
import type {RepoImageMeta} from "./types.js";
|
||||
|
||||
import {BASE_IMAGES_DIR, BASE_INDEX_MANIFEST_FILENAME, execute, PREV_IMAGES_DIR, PREV_INDEX_MANIFEST_FILENAME, readManifest} from './common.js';
|
||||
import {BASE_IMAGES_DIR, BASE_INDEX_MANIFEST_FILENAME, PREV_IMAGES_DIR, PREV_INDEX_MANIFEST_FILENAME, execute, readManifest} from "./common.js";
|
||||
|
||||
// about 3 lines
|
||||
const MAX_RELEASE_NOTES_LENGTH = 380;
|
||||
@@ -19,7 +19,7 @@ function listItemWithReleaseNotes(imagePath: string, releaseNotes?: string): str
|
||||
let listItem = `* ${imagePath}`;
|
||||
|
||||
if (releaseNotes) {
|
||||
let notes = releaseNotes.replace(/[#*\r\n]+/g, '').replaceAll('-', '|');
|
||||
let notes = releaseNotes.replace(/[#*\r\n]+/g, "").replaceAll("-", "|");
|
||||
|
||||
if (notes.length > MAX_RELEASE_NOTES_LENGTH) {
|
||||
notes = `${notes.slice(0, MAX_RELEASE_NOTES_LENGTH)}...`;
|
||||
@@ -33,7 +33,7 @@ function listItemWithReleaseNotes(imagePath: string, releaseNotes?: string): str
|
||||
}
|
||||
|
||||
export async function createAutodlRelease(github: Octokit, core: typeof CoreApi, context: Context): Promise<void> {
|
||||
const tagName = new Date().toISOString().replace(/[:.]/g, '');
|
||||
const tagName = new Date().toISOString().replace(/[:.]/g, "");
|
||||
// --exclude-standard => Add the standard Git exclusions: .git/info/exclude, .gitignore in each directory, and the user’s global exclusion file.
|
||||
// --others => Show other (i.e. untracked) files in the output.
|
||||
// -z => \0 line termination on output and do not quote filenames.
|
||||
@@ -44,8 +44,8 @@ export async function createAutodlRelease(github: Octokit, core: typeof CoreApi,
|
||||
core.debug(`git ls-files for ${PREV_IMAGES_DIR}: ${downgradeImagesStr}`);
|
||||
|
||||
// -1 to remove empty string at end due to \0 termination
|
||||
const upgradeImages = upgradeImagesStr.split('\0').slice(0, -1);
|
||||
const downgradeImages = downgradeImagesStr.split('\0').slice(0, -1);
|
||||
const upgradeImages = upgradeImagesStr.split("\0").slice(0, -1);
|
||||
const downgradeImages = downgradeImagesStr.split("\0").slice(0, -1);
|
||||
|
||||
core.info(`Upgrade Images List: ${upgradeImages}`);
|
||||
core.info(`Downgrade Images List: ${downgradeImages}`);
|
||||
@@ -56,12 +56,12 @@ export async function createAutodlRelease(github: Octokit, core: typeof CoreApi,
|
||||
let body: string | undefined;
|
||||
|
||||
if (upgradeImages.length > 0 || downgradeImages.length > 0) {
|
||||
body = '';
|
||||
body = "";
|
||||
|
||||
if (upgradeImages.length > 0) {
|
||||
const listWithReleaseNotes = upgradeImages.map((v) => listItemWithReleaseNotes(v, findReleaseNotes(v, baseManifest)));
|
||||
body += `## New upgrade images from automatic download:
|
||||
${listWithReleaseNotes.join('\n')}
|
||||
${listWithReleaseNotes.join("\n")}
|
||||
|
||||
`;
|
||||
}
|
||||
@@ -69,7 +69,7 @@ ${listWithReleaseNotes.join('\n')}
|
||||
if (downgradeImages.length > 0) {
|
||||
const listWithReleaseNotes = downgradeImages.map((v) => listItemWithReleaseNotes(v, findReleaseNotes(v, prevManifest)));
|
||||
body += `## New downgrade images from automatic download:
|
||||
${listWithReleaseNotes.join('\n')}
|
||||
${listWithReleaseNotes.join("\n")}
|
||||
|
||||
`;
|
||||
}
|
||||
@@ -85,6 +85,6 @@ ${listWithReleaseNotes.join('\n')}
|
||||
prerelease: false,
|
||||
// get changes from PRs
|
||||
generate_release_notes: true,
|
||||
make_latest: 'true',
|
||||
make_latest: "true",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
|
||||
import assert from 'assert';
|
||||
import assert from "node:assert";
|
||||
|
||||
const IGNORE_OTA_WORKFLOW_LABEL = 'ignore-ota-workflow';
|
||||
const IGNORE_OTA_WORKFLOW_LABEL = "ignore-ota-workflow";
|
||||
|
||||
export async function createPRToDefault(
|
||||
github: Octokit,
|
||||
@@ -45,7 +45,7 @@ export async function createPRToDefault(
|
||||
ref: `heads/${fromBranchName}`,
|
||||
});
|
||||
|
||||
core.notice(`Nothing needed re-processing.`);
|
||||
core.notice("Nothing needed re-processing.");
|
||||
|
||||
// don't fail if no commits
|
||||
return;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
|
||||
import assert from 'assert';
|
||||
import assert from "node:assert";
|
||||
|
||||
import {BASE_IMAGES_DIR} from './common.js';
|
||||
import {BASE_IMAGES_DIR} from "./common.js";
|
||||
|
||||
export async function getChangedOtaFiles(
|
||||
github: Octokit,
|
||||
@@ -20,18 +20,18 @@ export async function getChangedOtaFiles(
|
||||
basehead,
|
||||
});
|
||||
|
||||
assert(compare.data.files && compare.data.files.length > 0, 'No file');
|
||||
assert(compare.data.files && compare.data.files.length > 0, "No file");
|
||||
|
||||
core.info(`Changed files: ${compare.data.files.map((f) => f.filename).join(', ')}`);
|
||||
core.info(`Changed files: ${compare.data.files.map((f) => f.filename).join(", ")}`);
|
||||
|
||||
const fileList = compare.data.files.filter((f) => f.filename.startsWith(`${BASE_IMAGES_DIR}/`));
|
||||
|
||||
if (throwIfFilesOutsideOfImages && fileList.length !== compare.data.files.length) {
|
||||
if (context.payload.pull_request) {
|
||||
throw new Error(`Detected changes in files outside of \`images\` directory. This is not allowed for a pull request with OTA files.`);
|
||||
} else {
|
||||
throw new Error(`Cannot run with files outside of \`images\` directory.`);
|
||||
throw new Error("Detected changes in files outside of `images` directory. This is not allowed for a pull request with OTA files.");
|
||||
}
|
||||
|
||||
throw new Error("Cannot run with files outside of `images` directory.");
|
||||
}
|
||||
|
||||
return fileList.map((f) => f.filename);
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
|
||||
import {existsSync, mkdirSync} from 'fs';
|
||||
import {existsSync, mkdirSync} from "node:fs";
|
||||
|
||||
import {ALL_AUTODL_MANUFACTURERS, CACHE_DIR} from './common.js';
|
||||
import {ALL_AUTODL_MANUFACTURERS, CACHE_DIR} from "./common.js";
|
||||
|
||||
export async function overwriteCache(github: Octokit, core: typeof CoreApi, context: Context, manufacturersCSV?: string): Promise<void> {
|
||||
if (!existsSync(CACHE_DIR)) {
|
||||
mkdirSync(CACHE_DIR, {recursive: true});
|
||||
}
|
||||
|
||||
const manufacturers = manufacturersCSV ? manufacturersCSV.trim().split(',') : ALL_AUTODL_MANUFACTURERS;
|
||||
const manufacturers = manufacturersCSV ? manufacturersCSV.trim().split(",") : ALL_AUTODL_MANUFACTURERS;
|
||||
|
||||
for (const manufacturer of manufacturers) {
|
||||
// ignore empty strings
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
|
||||
import type {ExtraMetas, GHExtraMetas, RepoImageMeta} from './types.js';
|
||||
import type {ExtraMetas, GHExtraMetas, RepoImageMeta} from "./types.js";
|
||||
|
||||
import assert from 'assert';
|
||||
import {readFileSync, renameSync} from 'fs';
|
||||
import path from 'path';
|
||||
import assert from "node:assert";
|
||||
import {readFileSync, renameSync} from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import {
|
||||
BASE_IMAGES_DIR,
|
||||
PREV_IMAGES_DIR,
|
||||
ParsedImageStatus,
|
||||
UPGRADE_FILE_IDENTIFIER,
|
||||
addImageToBase,
|
||||
addImageToPrev,
|
||||
BASE_IMAGES_DIR,
|
||||
findMatchImage,
|
||||
getOutDir,
|
||||
getParsedImageStatus,
|
||||
getValidMetas,
|
||||
ParsedImageStatus,
|
||||
parseImageHeader,
|
||||
PREV_IMAGES_DIR,
|
||||
UPGRADE_FILE_IDENTIFIER,
|
||||
} from './common.js';
|
||||
} from "./common.js";
|
||||
|
||||
const EXTRA_METAS_PR_BODY_START_TAG = '```json';
|
||||
const EXTRA_METAS_PR_BODY_END_TAG = '```';
|
||||
const EXTRA_METAS_PR_BODY_START_TAG = "```json";
|
||||
const EXTRA_METAS_PR_BODY_END_TAG = "```";
|
||||
|
||||
function getFileExtraMetas(extraMetas: GHExtraMetas, fileName: string): ExtraMetas {
|
||||
if (Array.isArray(extraMetas)) {
|
||||
const fileExtraMetas = extraMetas.find((m) => m.fileName === fileName) ?? {};
|
||||
/** @see getValidMetas */
|
||||
// biome-ignore lint/performance/noDelete: <explanation>
|
||||
delete fileExtraMetas.fileName;
|
||||
|
||||
return fileExtraMetas;
|
||||
@@ -39,16 +40,18 @@ function getFileExtraMetas(extraMetas: GHExtraMetas, fileName: string): ExtraMet
|
||||
}
|
||||
|
||||
async function getPRBody(github: Octokit, core: typeof CoreApi, context: Context): Promise<string | undefined> {
|
||||
assert(context.payload.pull_request || context.eventName === 'push');
|
||||
assert(context.payload.pull_request || context.eventName === "push");
|
||||
|
||||
if (context.payload.pull_request) {
|
||||
return context.payload.pull_request.body;
|
||||
} else if (context.eventName === 'push') {
|
||||
}
|
||||
|
||||
if (context.eventName === "push") {
|
||||
const pushMsg = context.payload.head_commit.message as string;
|
||||
const prMatch = pushMsg.match(/\(#(\d+)\)/);
|
||||
|
||||
if (prMatch) {
|
||||
const prNumber = parseInt(prMatch[1], 10);
|
||||
const prNumber = Number.parseInt(prMatch[1], 10);
|
||||
|
||||
try {
|
||||
const pr = await github.rest.pulls.get({
|
||||
@@ -78,15 +81,15 @@ async function parsePRBodyExtraMetas(github: Octokit, core: typeof CoreApi, cont
|
||||
if (metasStart !== -1 && metasEnd > metasStart) {
|
||||
const metas = JSON.parse(prBody.slice(metasStart + EXTRA_METAS_PR_BODY_START_TAG.length, metasEnd)) as GHExtraMetas;
|
||||
|
||||
core.info(`Extra metas from PR body:`);
|
||||
core.info("Extra metas from PR body:");
|
||||
core.info(JSON.stringify(metas, undefined, 2));
|
||||
|
||||
if (Array.isArray(metas)) {
|
||||
extraMetas = [];
|
||||
|
||||
for (const meta of metas) {
|
||||
if (!meta.fileName || typeof meta.fileName != 'string') {
|
||||
core.info(`Ignoring meta in array with missing/invalid fileName:`);
|
||||
if (!meta.fileName || typeof meta.fileName !== "string") {
|
||||
core.info("Ignoring meta in array with missing/invalid fileName:");
|
||||
core.info(JSON.stringify(meta, undefined, 2));
|
||||
continue;
|
||||
}
|
||||
@@ -119,14 +122,14 @@ export async function processOtaFiles(
|
||||
core.startGroup(filePath);
|
||||
|
||||
const logPrefix = `[${filePath}]`;
|
||||
let failureComment: string = '';
|
||||
let failureComment = "";
|
||||
|
||||
try {
|
||||
const firmwareFileName = path.basename(filePath);
|
||||
const manufacturer = filePath.replace(BASE_IMAGES_DIR, '').replace(firmwareFileName, '').replaceAll('/', '').trim();
|
||||
const manufacturer = filePath.replace(BASE_IMAGES_DIR, "").replace(firmwareFileName, "").replaceAll("/", "").trim();
|
||||
|
||||
if (!manufacturer) {
|
||||
throw new Error(`File should be in its associated manufacturer subfolder`);
|
||||
throw new Error("File should be in its associated manufacturer subfolder");
|
||||
}
|
||||
|
||||
const firmwareBuffer = Buffer.from(readFileSync(filePath));
|
||||
@@ -146,14 +149,14 @@ export async function processOtaFiles(
|
||||
const statusToBase = getParsedImageStatus(parsedImage, baseMatch);
|
||||
|
||||
switch (statusToBase) {
|
||||
case ParsedImageStatus.OLDER: {
|
||||
case ParsedImageStatus.Older: {
|
||||
// if prev doesn't have a match, move to prev
|
||||
const [prevMatchIndex, prevMatch] = findMatchImage(parsedImage, prevManifest, fileExtraMetas);
|
||||
const statusToPrev = getParsedImageStatus(parsedImage, prevMatch);
|
||||
|
||||
switch (statusToPrev) {
|
||||
case ParsedImageStatus.OLDER:
|
||||
case ParsedImageStatus.IDENTICAL: {
|
||||
case ParsedImageStatus.Older:
|
||||
case ParsedImageStatus.Identical: {
|
||||
failureComment = `Base manifest has higher version:
|
||||
\`\`\`json
|
||||
${JSON.stringify(baseMatch, undefined, 2)}
|
||||
@@ -169,11 +172,11 @@ ${JSON.stringify(parsedImage, undefined, 2)}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParsedImageStatus.NEWER:
|
||||
case ParsedImageStatus.NEW: {
|
||||
case ParsedImageStatus.Newer:
|
||||
case ParsedImageStatus.New: {
|
||||
addImageToPrev(
|
||||
logPrefix,
|
||||
statusToPrev === ParsedImageStatus.NEWER,
|
||||
statusToPrev === ParsedImageStatus.Newer,
|
||||
prevManifest,
|
||||
prevMatchIndex,
|
||||
prevMatch!,
|
||||
@@ -197,7 +200,7 @@ ${JSON.stringify(parsedImage, undefined, 2)}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParsedImageStatus.IDENTICAL: {
|
||||
case ParsedImageStatus.Identical: {
|
||||
failureComment = `Conflict with image at index \`${baseMatchIndex}\`:
|
||||
\`\`\`json
|
||||
${JSON.stringify(baseMatch, undefined, 2)}
|
||||
@@ -209,11 +212,11 @@ ${JSON.stringify(parsedImage, undefined, 2)}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParsedImageStatus.NEWER:
|
||||
case ParsedImageStatus.NEW: {
|
||||
case ParsedImageStatus.Newer:
|
||||
case ParsedImageStatus.New: {
|
||||
addImageToBase(
|
||||
logPrefix,
|
||||
statusToBase === ParsedImageStatus.NEWER,
|
||||
statusToBase === ParsedImageStatus.Newer,
|
||||
prevManifest,
|
||||
prevOutDir,
|
||||
baseManifest,
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
|
||||
import assert from 'assert';
|
||||
import {existsSync, readFileSync, writeFileSync} from 'fs';
|
||||
import assert from "node:assert";
|
||||
import {existsSync, readFileSync, writeFileSync} from "node:fs";
|
||||
|
||||
import {execute, PR_ARTIFACT_DIR, PR_DIFF_FILENAME, PR_ERROR_FILENAME, PR_NUMBER_FILENAME} from './common.js';
|
||||
import {PR_ARTIFACT_DIR, PR_DIFF_FILENAME, PR_ERROR_FILENAME, PR_NUMBER_FILENAME, execute} from "./common.js";
|
||||
|
||||
export async function reportOtaPR(github: Octokit, core: typeof CoreApi, context: Context): Promise<void> {
|
||||
assert(context.payload.workflow_run, 'Not a workflow run');
|
||||
assert(context.payload.workflow_run, "Not a workflow run");
|
||||
|
||||
// XXX: context.payload.workflow_run is not typed...
|
||||
const workflow_run = context.payload.workflow_run as Awaited<ReturnType<typeof github.rest.actions.getWorkflowRun>>['data'];
|
||||
const workflowRun = context.payload.workflow_run as Awaited<ReturnType<typeof github.rest.actions.getWorkflowRun>>["data"];
|
||||
|
||||
// workflow_run.conclusion: action_required, cancelled, failure, neutral, skipped, stale, success, timed_out, startup_failure, null
|
||||
if (workflow_run.conclusion !== 'success' && workflow_run.conclusion !== 'failure') {
|
||||
core.info(`Ignoring workflow run ${workflow_run.html_url} with conclusion ${workflow_run.conclusion}.`);
|
||||
if (workflowRun.conclusion !== "success" && workflowRun.conclusion !== "failure") {
|
||||
core.info(`Ignoring workflow run ${workflowRun.html_url} with conclusion ${workflowRun.conclusion}.`);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -23,17 +23,17 @@ export async function reportOtaPR(github: Octokit, core: typeof CoreApi, context
|
||||
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: workflow_run.id,
|
||||
run_id: workflowRun.id,
|
||||
});
|
||||
const matchArtifact = artifacts.data.artifacts.find((artifact) => artifact.name == PR_ARTIFACT_DIR);
|
||||
const matchArtifact = artifacts.data.artifacts.find((artifact) => artifact.name === PR_ARTIFACT_DIR);
|
||||
|
||||
assert(matchArtifact, `No artifact found for ${workflow_run.url}`);
|
||||
assert(matchArtifact, `No artifact found for ${workflowRun.url}`);
|
||||
|
||||
const download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
archive_format: "zip",
|
||||
});
|
||||
const artifactZipFileName = `${PR_ARTIFACT_DIR}.zip`;
|
||||
|
||||
@@ -43,16 +43,16 @@ export async function reportOtaPR(github: Octokit, core: typeof CoreApi, context
|
||||
|
||||
core.info(unzipOutput);
|
||||
|
||||
assert(existsSync(PR_NUMBER_FILENAME), `Invalid artifact for ${workflow_run.html_url}`);
|
||||
assert(existsSync(PR_NUMBER_FILENAME), `Invalid artifact for ${workflowRun.html_url}`);
|
||||
|
||||
const prNumber = parseInt(readFileSync(PR_NUMBER_FILENAME, 'utf8'), 10);
|
||||
const prNumber = Number.parseInt(readFileSync(PR_NUMBER_FILENAME, "utf8"), 10);
|
||||
|
||||
core.info(`Running for pr#${prNumber} for ${workflow_run.html_url}`);
|
||||
core.info(`Running for pr#${prNumber} for ${workflowRun.html_url}`);
|
||||
|
||||
if (workflow_run.conclusion === 'failure') {
|
||||
assert(existsSync(PR_ERROR_FILENAME), `Workflow failed but could not find ${PR_ERROR_FILENAME} for ${workflow_run.html_url}`);
|
||||
if (workflowRun.conclusion === "failure") {
|
||||
assert(existsSync(PR_ERROR_FILENAME), `Workflow failed but could not find ${PR_ERROR_FILENAME} for ${workflowRun.html_url}`);
|
||||
|
||||
const prError = readFileSync(PR_ERROR_FILENAME, 'utf8');
|
||||
const prError = readFileSync(PR_ERROR_FILENAME, "utf8");
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
@@ -62,10 +62,12 @@ export async function reportOtaPR(github: Octokit, core: typeof CoreApi, context
|
||||
});
|
||||
|
||||
throw new Error(prError);
|
||||
} else if (workflow_run.conclusion === 'success') {
|
||||
assert(existsSync(PR_DIFF_FILENAME), `Workflow succeeded but could not find ${PR_DIFF_FILENAME} for ${workflow_run.html_url}`);
|
||||
}
|
||||
|
||||
const prDiff = readFileSync(PR_DIFF_FILENAME, 'utf8');
|
||||
if (workflowRun.conclusion === "success") {
|
||||
assert(existsSync(PR_DIFF_FILENAME), `Workflow succeeded but could not find ${PR_DIFF_FILENAME} for ${workflowRun.html_url}`);
|
||||
|
||||
const prDiff = readFileSync(PR_DIFF_FILENAME, "utf8");
|
||||
|
||||
core.info(prDiff);
|
||||
await github.rest.issues.createComment({
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
|
||||
import type {RepoImageMeta} from './types';
|
||||
import type {RepoImageMeta} from "./types";
|
||||
|
||||
import {existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync} from 'fs';
|
||||
import path from 'path';
|
||||
import {existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync} from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import {
|
||||
addImageToBase,
|
||||
addImageToPrev,
|
||||
BASE_IMAGES_DIR,
|
||||
BASE_INDEX_MANIFEST_FILENAME,
|
||||
BASE_REPO_URL,
|
||||
PREV_IMAGES_DIR,
|
||||
PREV_INDEX_MANIFEST_FILENAME,
|
||||
ParsedImageStatus,
|
||||
REPO_BRANCH,
|
||||
UPGRADE_FILE_IDENTIFIER,
|
||||
addImageToBase,
|
||||
addImageToPrev,
|
||||
computeSHA512,
|
||||
findMatchImage,
|
||||
getOutDir,
|
||||
getParsedImageStatus,
|
||||
getRepoFirmwareFileUrl,
|
||||
getValidMetas,
|
||||
ParsedImageStatus,
|
||||
parseImageHeader,
|
||||
PREV_IMAGES_DIR,
|
||||
PREV_INDEX_MANIFEST_FILENAME,
|
||||
readManifest,
|
||||
REPO_BRANCH,
|
||||
UPGRADE_FILE_IDENTIFIER,
|
||||
writeManifest,
|
||||
} from './common.js';
|
||||
} from "./common.js";
|
||||
|
||||
/** These are now handled by autodl */
|
||||
const IGNORE_3RD_PARTIES = ['https://github.com/fairecasoimeme/', 'https://github.com/xyzroe/'];
|
||||
const IGNORE_3RD_PARTIES = ["https://github.com/fairecasoimeme/", "https://github.com/xyzroe/"];
|
||||
|
||||
const DIR_3RD_PARTIES = {
|
||||
'https://otau.meethue.com/': 'Hue',
|
||||
'https://images.tuyaeu.com/': 'Tuya',
|
||||
'https://tr-zha.s3.amazonaws.com/': 'ThirdReality',
|
||||
"https://otau.meethue.com/": "Hue",
|
||||
"https://images.tuyaeu.com/": "Tuya",
|
||||
"https://tr-zha.s3.amazonaws.com/": "ThirdReality",
|
||||
// NOTE: no longer valid / unable to access via script
|
||||
// 'https://www.elektroimportoren.no/docs/lib/4512772-Firmware-35.ota': 'Namron',
|
||||
// 'https://deconz.dresden-elektronik.de/': 'DresdenElektronik',
|
||||
};
|
||||
|
||||
export const NOT_IN_BASE_MANIFEST_IMAGES_DIR = 'not-in-manifest-images';
|
||||
export const NOT_IN_PREV_MANIFEST_IMAGES_DIR = 'not-in-manifest-images1';
|
||||
export const NOT_IN_MANIFEST_FILENAME = 'not-in-manifest.json';
|
||||
export const NOT_IN_BASE_MANIFEST_IMAGES_DIR = "not-in-manifest-images";
|
||||
export const NOT_IN_PREV_MANIFEST_IMAGES_DIR = "not-in-manifest-images1";
|
||||
export const NOT_IN_MANIFEST_FILENAME = "not-in-manifest.json";
|
||||
|
||||
function ignore3rdParty(meta: RepoImageMeta): boolean {
|
||||
for (const ignore of IGNORE_3RD_PARTIES) {
|
||||
@@ -67,11 +67,10 @@ async function download3rdParties(
|
||||
github: Octokit,
|
||||
core: typeof CoreApi,
|
||||
context: Context,
|
||||
/* istanbul ignore next */
|
||||
outDirFinder = get3rdPartyDir,
|
||||
/* v8 ignore next */ outDirFinder = get3rdPartyDir,
|
||||
): Promise<void> {
|
||||
if (!process.env.NODE_EXTRA_CA_CERTS) {
|
||||
throw new Error(`Download 3rd Parties requires \`NODE_EXTRA_CA_CERTS=cacerts.pem\`.`);
|
||||
throw new Error("Download 3rd Parties requires `NODE_EXTRA_CA_CERTS=cacerts.pem`.");
|
||||
}
|
||||
|
||||
const baseManifest = readManifest(BASE_INDEX_MANIFEST_FILENAME);
|
||||
@@ -101,7 +100,7 @@ async function download3rdParties(
|
||||
continue;
|
||||
}
|
||||
|
||||
const fileName = decodeURIComponent(meta.url.split('/').pop()!);
|
||||
const fileName = decodeURIComponent(meta.url.split("/").pop()!);
|
||||
const outDirName = outDirFinder(meta);
|
||||
|
||||
if (outDirName) {
|
||||
@@ -130,7 +129,7 @@ async function download3rdParties(
|
||||
const statusToBase = getParsedImageStatus(parsedImage, baseMatch);
|
||||
|
||||
switch (statusToBase) {
|
||||
case ParsedImageStatus.OLDER: {
|
||||
case ParsedImageStatus.Older: {
|
||||
addImageToPrev(
|
||||
`[${fileName}]`,
|
||||
false, // no prev existed before
|
||||
@@ -158,16 +157,16 @@ async function download3rdParties(
|
||||
break;
|
||||
}
|
||||
|
||||
case ParsedImageStatus.IDENTICAL: {
|
||||
case ParsedImageStatus.Identical: {
|
||||
core.warning(`Conflict with image at index \`${baseMatchIndex}\`: ${JSON.stringify(baseMatch)}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
case ParsedImageStatus.NEWER:
|
||||
case ParsedImageStatus.NEW: {
|
||||
case ParsedImageStatus.Newer:
|
||||
case ParsedImageStatus.New: {
|
||||
addImageToBase(
|
||||
`[${fileName}]`,
|
||||
statusToBase === ParsedImageStatus.NEWER,
|
||||
statusToBase === ParsedImageStatus.Newer,
|
||||
prevManifest,
|
||||
prevOutDir,
|
||||
baseManifest,
|
||||
@@ -196,12 +195,11 @@ async function download3rdParties(
|
||||
} catch (error) {
|
||||
core.error(`Ignoring ${fileName}: ${error}`);
|
||||
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore start */
|
||||
if (firmwareFilePath) {
|
||||
rmSync(firmwareFilePath, {force: true});
|
||||
}
|
||||
|
||||
continue;
|
||||
/* v8 ignore stop */
|
||||
}
|
||||
} else {
|
||||
core.warning(`Ignoring '${fileName}' with no out dir specified.`);
|
||||
@@ -230,12 +228,13 @@ function checkImagesAgainstManifests(github: Octokit, core: typeof CoreApi, cont
|
||||
core.info(`Checking ${manifestName} (currently ${manifest.length} images)...`);
|
||||
|
||||
for (const subfolderName of readdirSync(imagesDir)) {
|
||||
// skip removal of anything not desired while running jest tests
|
||||
// skip removal of anything not desired while running tests
|
||||
// compare should match data.test.ts > IMAGES_TEST_DIR
|
||||
/* istanbul ignore if */
|
||||
if (process.env.JEST_WORKER_ID && subfolderName !== 'jest-tmp') {
|
||||
/* v8 ignore start */
|
||||
if (process.env.VITEST_WORKER_ID && subfolderName !== "test-tmp") {
|
||||
continue;
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
const subfolderPath = path.join(imagesDir, subfolderName);
|
||||
|
||||
@@ -365,25 +364,29 @@ export async function reProcessAllImages(
|
||||
throw new Error(`${NOT_IN_PREV_MANIFEST_IMAGES_DIR} is not empty. Cannot run.`);
|
||||
}
|
||||
|
||||
/* istanbul ignore if */
|
||||
/* v8 ignore start */
|
||||
if (!existsSync(BASE_IMAGES_DIR)) {
|
||||
mkdirSync(BASE_IMAGES_DIR, {recursive: true});
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
/* istanbul ignore if */
|
||||
/* v8 ignore start */
|
||||
if (!existsSync(PREV_IMAGES_DIR)) {
|
||||
mkdirSync(PREV_IMAGES_DIR, {recursive: true});
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
/* istanbul ignore if */
|
||||
/* v8 ignore start */
|
||||
if (!existsSync(BASE_INDEX_MANIFEST_FILENAME)) {
|
||||
writeManifest(BASE_INDEX_MANIFEST_FILENAME, []);
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
/* istanbul ignore if */
|
||||
/* v8 ignore start */
|
||||
if (!existsSync(PREV_INDEX_MANIFEST_FILENAME)) {
|
||||
writeManifest(PREV_INDEX_MANIFEST_FILENAME, []);
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
if (!skipDownload3rdParties) {
|
||||
await download3rdParties(github, core, context, downloadOutDirFinder);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
|
||||
import {existsSync, mkdirSync, rmSync} from 'fs';
|
||||
import {existsSync, mkdirSync, rmSync} from "node:fs";
|
||||
|
||||
import {ALL_AUTODL_MANUFACTURERS, BASE_INDEX_MANIFEST_FILENAME, CACHE_DIR, PREV_INDEX_MANIFEST_FILENAME, TMP_DIR, writeManifest} from './common.js';
|
||||
import {ALL_AUTODL_MANUFACTURERS, BASE_INDEX_MANIFEST_FILENAME, CACHE_DIR, PREV_INDEX_MANIFEST_FILENAME, TMP_DIR, writeManifest} from "./common.js";
|
||||
|
||||
export async function runAutodl(github: Octokit, core: typeof CoreApi, context: Context, manufacturersCSV?: string): Promise<void> {
|
||||
const manufacturers = manufacturersCSV ? manufacturersCSV.trim().split(',') : ALL_AUTODL_MANUFACTURERS;
|
||||
const manufacturers = manufacturersCSV ? manufacturersCSV.trim().split(",") : ALL_AUTODL_MANUFACTURERS;
|
||||
|
||||
core.info(`Setup...`);
|
||||
core.info("Setup...");
|
||||
|
||||
if (!existsSync(CACHE_DIR)) {
|
||||
mkdirSync(CACHE_DIR, {recursive: true});
|
||||
@@ -52,7 +52,7 @@ export async function runAutodl(github: Octokit, core: typeof CoreApi, context:
|
||||
core.endGroup();
|
||||
}
|
||||
|
||||
core.info(`Teardown...`);
|
||||
core.info("Teardown...");
|
||||
|
||||
rmSync(TMP_DIR, {recursive: true, force: true});
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
|
||||
import assert from 'assert';
|
||||
import assert from "node:assert";
|
||||
|
||||
import {BASE_INDEX_MANIFEST_FILENAME, PREV_INDEX_MANIFEST_FILENAME, readManifest, writeManifest} from './common.js';
|
||||
import {getChangedOtaFiles} from './ghw_get_changed_ota_files.js';
|
||||
import {processOtaFiles} from './ghw_process_ota_files.js';
|
||||
import {BASE_INDEX_MANIFEST_FILENAME, PREV_INDEX_MANIFEST_FILENAME, readManifest, writeManifest} from "./common.js";
|
||||
import {getChangedOtaFiles} from "./ghw_get_changed_ota_files.js";
|
||||
import {processOtaFiles} from "./ghw_process_ota_files.js";
|
||||
|
||||
export async function updateManifests(github: Octokit, core: typeof CoreApi, context: Context): Promise<void> {
|
||||
assert(context.eventName === 'push', 'Not a push');
|
||||
assert(context.eventName === "push", "Not a push");
|
||||
|
||||
const filePaths = await getChangedOtaFiles(github, core, context, `${context.payload.before}...${context.payload.after}`, true);
|
||||
const baseManifest = readManifest(BASE_INDEX_MANIFEST_FILENAME);
|
||||
|
||||
26
src/index.ts
26
src/index.ts
@@ -1,13 +1,13 @@
|
||||
export * as common from './common.js';
|
||||
export {checkOtaPR} from './ghw_check_ota_pr.js';
|
||||
export {concatCaCerts} from './ghw_concat_cacerts.js';
|
||||
export {createAutodlRelease} from './ghw_create_autodl_release.js';
|
||||
export {createPRToDefault} from './ghw_create_pr_to_default.js';
|
||||
export {getChangedOtaFiles} from './ghw_get_changed_ota_files.js';
|
||||
export {overwriteCache} from './ghw_overwrite_cache.js';
|
||||
export {processOtaFiles} from './ghw_process_ota_files.js';
|
||||
export {reportOtaPR} from './ghw_report_ota_pr.js';
|
||||
export {reProcessAllImages} from './ghw_reprocess_all_images.js';
|
||||
export {runAutodl} from './ghw_run_autodl.js';
|
||||
export {updateManifests} from './ghw_update_manifests.js';
|
||||
export {processFirmwareImage} from './process_firmware_image.js';
|
||||
export * as common from "./common.js";
|
||||
export {checkOtaPR} from "./ghw_check_ota_pr.js";
|
||||
export {concatCaCerts} from "./ghw_concat_cacerts.js";
|
||||
export {createAutodlRelease} from "./ghw_create_autodl_release.js";
|
||||
export {createPRToDefault} from "./ghw_create_pr_to_default.js";
|
||||
export {getChangedOtaFiles} from "./ghw_get_changed_ota_files.js";
|
||||
export {overwriteCache} from "./ghw_overwrite_cache.js";
|
||||
export {processOtaFiles} from "./ghw_process_ota_files.js";
|
||||
export {reportOtaPR} from "./ghw_report_ota_pr.js";
|
||||
export {reProcessAllImages} from "./ghw_reprocess_all_images.js";
|
||||
export {runAutodl} from "./ghw_run_autodl.js";
|
||||
export {updateManifests} from "./ghw_update_manifests.js";
|
||||
export {processFirmwareImage} from "./process_firmware_image.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {readFileSync} from 'fs';
|
||||
import {readFileSync} from "node:fs";
|
||||
|
||||
import {parseImageHeader} from './common.js';
|
||||
import {parseImageHeader} from "./common.js";
|
||||
|
||||
console.log(parseImageHeader(readFileSync(process.argv[2])));
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import type {ExtraMetas} from './types';
|
||||
import type {ExtraMetas} from "./types";
|
||||
|
||||
import assert from 'assert';
|
||||
import {readdirSync, readFileSync, renameSync, rmSync, writeFileSync} from 'fs';
|
||||
import path from 'path';
|
||||
import assert from "node:assert";
|
||||
import {readFileSync, readdirSync, renameSync, rmSync, writeFileSync} from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import {extract} from 'tar';
|
||||
import {extract} from "tar";
|
||||
|
||||
import {
|
||||
addImageToBase,
|
||||
addImageToPrev,
|
||||
BASE_IMAGES_DIR,
|
||||
BASE_INDEX_MANIFEST_FILENAME,
|
||||
PREV_IMAGES_DIR,
|
||||
PREV_INDEX_MANIFEST_FILENAME,
|
||||
ParsedImageStatus,
|
||||
TMP_DIR,
|
||||
UPGRADE_FILE_IDENTIFIER,
|
||||
addImageToBase,
|
||||
addImageToPrev,
|
||||
findMatchImage,
|
||||
getOutDir,
|
||||
getParsedImageStatus,
|
||||
ParsedImageStatus,
|
||||
parseImageHeader,
|
||||
PREV_IMAGES_DIR,
|
||||
PREV_INDEX_MANIFEST_FILENAME,
|
||||
readManifest,
|
||||
TMP_DIR,
|
||||
UPGRADE_FILE_IDENTIFIER,
|
||||
writeManifest,
|
||||
} from './common.js';
|
||||
} from "./common.js";
|
||||
|
||||
export const enum ProcessFirmwareImageStatus {
|
||||
ERROR = -1,
|
||||
SUCCESS = 0,
|
||||
REQUEST_FAILED = 1,
|
||||
TAR_NO_IMAGE = 2,
|
||||
export enum ProcessFirmwareImageStatus {
|
||||
Error = -1,
|
||||
Success = 0,
|
||||
RequestFailed = 1,
|
||||
TarNoImage = 2,
|
||||
}
|
||||
|
||||
async function tarExtract(filePath: string, outDir: string, tarImageFinder: (fileName: string) => boolean): Promise<string> {
|
||||
@@ -71,7 +71,7 @@ export async function processFirmwareImage(
|
||||
firmwareFileName: string,
|
||||
firmwareFileUrl: string,
|
||||
extraMetas: ExtraMetas = {},
|
||||
tar: boolean = false,
|
||||
tar = false,
|
||||
tarImageFinder?: (fileName: string) => boolean,
|
||||
): Promise<ProcessFirmwareImageStatus> {
|
||||
// throttle requests (this is done at the top to ensure always executed)
|
||||
@@ -80,9 +80,9 @@ export async function processFirmwareImage(
|
||||
let firmwareFilePath: string | undefined;
|
||||
const logPrefix = `[${manufacturer}:${firmwareFileName}]`;
|
||||
|
||||
if (tar && !firmwareFileName.endsWith('.tar.gz')) {
|
||||
if (tar && !firmwareFileName.endsWith(".tar.gz")) {
|
||||
// ignore non-archive
|
||||
return ProcessFirmwareImageStatus.TAR_NO_IMAGE;
|
||||
return ProcessFirmwareImageStatus.TarNoImage;
|
||||
}
|
||||
|
||||
const prevManifest = readManifest(PREV_INDEX_MANIFEST_FILENAME);
|
||||
@@ -95,11 +95,11 @@ export async function processFirmwareImage(
|
||||
|
||||
if (!firmwareFile.ok || !firmwareFile.body) {
|
||||
console.error(`${logPrefix} Invalid response from ${firmwareFileUrl} status=${firmwareFile.status}.`);
|
||||
return ProcessFirmwareImageStatus.REQUEST_FAILED;
|
||||
return ProcessFirmwareImageStatus.RequestFailed;
|
||||
}
|
||||
|
||||
if (tar) {
|
||||
assert(tarImageFinder, `No image finder function supplied for tar.`);
|
||||
assert(tarImageFinder, "No image finder function supplied for tar.");
|
||||
|
||||
const archiveBuffer = Buffer.from(await firmwareFile.arrayBuffer());
|
||||
const archiveFilePath = path.join(baseOutDir, firmwareFileName);
|
||||
@@ -110,7 +110,7 @@ export async function processFirmwareImage(
|
||||
firmwareFileName = await tarExtract(archiveFilePath, baseOutDir, tarImageFinder);
|
||||
} catch {
|
||||
console.error(`${logPrefix} No image found for ${firmwareFileUrl}.`);
|
||||
return ProcessFirmwareImageStatus.TAR_NO_IMAGE;
|
||||
return ProcessFirmwareImageStatus.TarNoImage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,14 +121,14 @@ export async function processFirmwareImage(
|
||||
const statusToBase = getParsedImageStatus(parsedImage, baseMatch);
|
||||
|
||||
switch (statusToBase) {
|
||||
case ParsedImageStatus.OLDER: {
|
||||
case ParsedImageStatus.Older: {
|
||||
// if prev doesn't have a match, move to prev
|
||||
const [prevMatchIndex, prevMatch] = findMatchImage(parsedImage, prevManifest, extraMetas);
|
||||
const statusToPrev = getParsedImageStatus(parsedImage, prevMatch);
|
||||
|
||||
switch (statusToPrev) {
|
||||
case ParsedImageStatus.OLDER:
|
||||
case ParsedImageStatus.IDENTICAL: {
|
||||
case ParsedImageStatus.Older:
|
||||
case ParsedImageStatus.Identical: {
|
||||
console.log(
|
||||
`${logPrefix} Base manifest has higher version and an equal or better match is already present in prev manifest. Ignoring.`,
|
||||
);
|
||||
@@ -136,11 +136,11 @@ export async function processFirmwareImage(
|
||||
break;
|
||||
}
|
||||
|
||||
case ParsedImageStatus.NEWER:
|
||||
case ParsedImageStatus.NEW: {
|
||||
case ParsedImageStatus.Newer:
|
||||
case ParsedImageStatus.New: {
|
||||
addImageToPrev(
|
||||
logPrefix,
|
||||
statusToPrev === ParsedImageStatus.NEWER,
|
||||
statusToPrev === ParsedImageStatus.Newer,
|
||||
prevManifest,
|
||||
prevMatchIndex,
|
||||
prevMatch!,
|
||||
@@ -166,17 +166,17 @@ export async function processFirmwareImage(
|
||||
break;
|
||||
}
|
||||
|
||||
case ParsedImageStatus.IDENTICAL: {
|
||||
case ParsedImageStatus.Identical: {
|
||||
console.log(`${logPrefix} Base manifest already has version ${parsedImage.fileVersion}. Ignoring.`);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ParsedImageStatus.NEWER:
|
||||
case ParsedImageStatus.NEW: {
|
||||
case ParsedImageStatus.Newer:
|
||||
case ParsedImageStatus.New: {
|
||||
addImageToBase(
|
||||
logPrefix,
|
||||
statusToBase === ParsedImageStatus.NEWER,
|
||||
statusToBase === ParsedImageStatus.Newer,
|
||||
prevManifest,
|
||||
prevOutDir,
|
||||
baseManifest,
|
||||
@@ -203,12 +203,13 @@ export async function processFirmwareImage(
|
||||
} catch (error) {
|
||||
console.error(`${logPrefix} Failed to save firmware file ${firmwareFileName}: ${(error as Error).stack!}.`);
|
||||
|
||||
/* istanbul ignore if */
|
||||
/* v8 ignore start */
|
||||
if (firmwareFilePath) {
|
||||
rmSync(firmwareFilePath, {force: true});
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
return ProcessFirmwareImageStatus.ERROR;
|
||||
return ProcessFirmwareImageStatus.Error;
|
||||
}
|
||||
|
||||
writeManifest(PREV_INDEX_MANIFEST_FILENAME, prevManifest);
|
||||
@@ -217,5 +218,5 @@ export async function processFirmwareImage(
|
||||
console.log(`Prev manifest has ${prevManifest.length} images.`);
|
||||
console.log(`Base manifest has ${baseManifest.length} images.`);
|
||||
|
||||
return ProcessFirmwareImageStatus.SUCCESS;
|
||||
return ProcessFirmwareImageStatus.Success;
|
||||
}
|
||||
|
||||
@@ -65,10 +65,10 @@ export interface RepoImageMeta extends ImageInfo, ImageMeta {
|
||||
|
||||
export type ExtraMetas = Omit<
|
||||
RepoImageMeta,
|
||||
'fileName' | 'fileVersion' | 'fileSize' | 'url' | 'imageType' | 'manufacturerCode' | 'sha512' | 'otaHeaderString'
|
||||
"fileName" | "fileVersion" | "fileSize" | "url" | "imageType" | "manufacturerCode" | "sha512" | "otaHeaderString"
|
||||
>;
|
||||
export type ExtraMetasWithFileName = Omit<
|
||||
RepoImageMeta,
|
||||
'fileName' | 'fileVersion' | 'fileSize' | 'url' | 'imageType' | 'manufacturerCode' | 'sha512' | 'otaHeaderString'
|
||||
"fileName" | "fileVersion" | "fileSize" | "url" | "imageType" | "manufacturerCode" | "sha512" | "otaHeaderString"
|
||||
> & {fileName?: string};
|
||||
export type GHExtraMetas = ExtraMetas | ExtraMetasWithFileName[];
|
||||
|
||||
@@ -5,23 +5,24 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import type {ExtraMetas, RepoImageMeta} from '../src/types';
|
||||
import type {ExtraMetas, RepoImageMeta} from "../src/types";
|
||||
|
||||
import {copyFileSync, existsSync, mkdirSync} from 'fs';
|
||||
import path from 'path';
|
||||
import {copyFileSync, existsSync, mkdirSync} from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import * as common from '../src/common';
|
||||
import {it} from "vitest";
|
||||
import * as common from "../src/common";
|
||||
|
||||
export const IMAGE_V14_1 = 'ZLinky_router_v14.ota';
|
||||
export const IMAGE_V14_2 = 'ZLinky_router_v14_limited.ota';
|
||||
export const IMAGE_V13_1 = 'ZLinky_router_v13.ota';
|
||||
export const IMAGE_V13_2 = 'ZLinky_router_v13_limited.ota';
|
||||
export const IMAGE_V12_1 = 'ZLinky_router_v12.ota';
|
||||
export const IMAGE_V12_2 = 'ZLinky_router_v12_limited.ota';
|
||||
export const IMAGE_INVALID = 'not-a-valid-file.ota';
|
||||
export const IMAGE_TAR = '45856_00000006.tar.gz';
|
||||
export const IMAGE_TAR_OTA = 'Jasco_5_0_1_OnOff_45856_v6.ota';
|
||||
export const IMAGES_TEST_DIR = 'jest-tmp';
|
||||
export const IMAGE_V14_1 = "ZLinky_router_v14.ota";
|
||||
export const IMAGE_V14_2 = "ZLinky_router_v14_limited.ota";
|
||||
export const IMAGE_V13_1 = "ZLinky_router_v13.ota";
|
||||
export const IMAGE_V13_2 = "ZLinky_router_v13_limited.ota";
|
||||
export const IMAGE_V12_1 = "ZLinky_router_v12.ota";
|
||||
export const IMAGE_V12_2 = "ZLinky_router_v12_limited.ota";
|
||||
export const IMAGE_INVALID = "not-a-valid-file.ota";
|
||||
export const IMAGE_TAR = "45856_00000006.tar.gz";
|
||||
export const IMAGE_TAR_OTA = "Jasco_5_0_1_OnOff_45856_v6.ota";
|
||||
export const IMAGES_TEST_DIR = "test-tmp";
|
||||
export const BASE_IMAGES_TEST_DIR_PATH = path.join(common.BASE_IMAGES_DIR, IMAGES_TEST_DIR);
|
||||
export const PREV_IMAGES_TEST_DIR_PATH = path.join(common.PREV_IMAGES_DIR, IMAGES_TEST_DIR);
|
||||
/**
|
||||
@@ -44,8 +45,8 @@ export const IMAGE_V14_1_METAS = {
|
||||
url: `${common.BASE_REPO_URL}${common.REPO_BRANCH}/images/${IMAGES_TEST_DIR}/${IMAGE_V14_1}`,
|
||||
imageType: 1,
|
||||
manufacturerCode: 4151,
|
||||
sha512: 'cc69b0745c72daf8deda935ba47aa7abd34dfcaaa4bc35bfa0605cd7937b0ecd8582ba0c08110df4f620c8aa87798d201f407d3d7e17198cfef1a4aa13c5013d',
|
||||
otaHeaderString: 'OM15081-RTR-JN5189-0000000000000',
|
||||
sha512: "cc69b0745c72daf8deda935ba47aa7abd34dfcaaa4bc35bfa0605cd7937b0ecd8582ba0c08110df4f620c8aa87798d201f407d3d7e17198cfef1a4aa13c5013d",
|
||||
otaHeaderString: "OM15081-RTR-JN5189-0000000000000",
|
||||
};
|
||||
/**
|
||||
* - otaUpgradeFileIdentifier: <Buffer 1e f1 ee 0b>,
|
||||
@@ -67,8 +68,8 @@ export const IMAGE_V14_2_METAS = {
|
||||
url: `${common.BASE_REPO_URL}${common.REPO_BRANCH}/images/${IMAGES_TEST_DIR}/${IMAGE_V14_2}`,
|
||||
imageType: 2,
|
||||
manufacturerCode: 4151,
|
||||
sha512: 'f851cbff7297ba6223a969ba8da5182f9ef199cf9c8459c8408432e48485c1a8f018f6e1703a42f40143cccd3bf460c0acd92117d899e507a36845f24e970595',
|
||||
otaHeaderString: 'OM15081-RTR-LIMITED-JN5189-00000',
|
||||
sha512: "f851cbff7297ba6223a969ba8da5182f9ef199cf9c8459c8408432e48485c1a8f018f6e1703a42f40143cccd3bf460c0acd92117d899e507a36845f24e970595",
|
||||
otaHeaderString: "OM15081-RTR-LIMITED-JN5189-00000",
|
||||
};
|
||||
/**
|
||||
* - otaUpgradeFileIdentifier: <Buffer 1e f1 ee 0b>,
|
||||
@@ -90,8 +91,8 @@ export const IMAGE_V13_1_METAS = {
|
||||
url: `${common.BASE_REPO_URL}${common.REPO_BRANCH}/images1/${IMAGES_TEST_DIR}/${IMAGE_V13_1}`,
|
||||
imageType: 1,
|
||||
manufacturerCode: 4151,
|
||||
sha512: '4d7ab47dcb24e478e0abb35e691222b7691e77ed5a56de3f9c82e8682730649b1a154110b7207d4391c32eae53a869e20878e880fc153dbe046690b870be8486',
|
||||
otaHeaderString: 'OM15081-RTR-JN5189-0000000000000',
|
||||
sha512: "4d7ab47dcb24e478e0abb35e691222b7691e77ed5a56de3f9c82e8682730649b1a154110b7207d4391c32eae53a869e20878e880fc153dbe046690b870be8486",
|
||||
otaHeaderString: "OM15081-RTR-JN5189-0000000000000",
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -121,8 +122,8 @@ export const IMAGE_V13_2_METAS = {
|
||||
url: `${common.BASE_REPO_URL}${common.REPO_BRANCH}/images1/${IMAGES_TEST_DIR}/${IMAGE_V13_2}`,
|
||||
imageType: 2,
|
||||
manufacturerCode: 4151,
|
||||
sha512: 'dd77b28a3b4664e7ad944fcffaa9eca9f3adb0bbe598e12bdd6eece8070a8cdda6792bed378d173dd5b4532b4cdb88cebda0ef0c432c4c4d6581aa9f2bbba54d',
|
||||
otaHeaderString: 'OM15081-RTR-LIMITED-JN5189-00000',
|
||||
sha512: "dd77b28a3b4664e7ad944fcffaa9eca9f3adb0bbe598e12bdd6eece8070a8cdda6792bed378d173dd5b4532b4cdb88cebda0ef0c432c4c4d6581aa9f2bbba54d",
|
||||
otaHeaderString: "OM15081-RTR-LIMITED-JN5189-00000",
|
||||
};
|
||||
/**
|
||||
* - otaUpgradeFileIdentifier: <Buffer 1e f1 ee 0b>,
|
||||
@@ -144,8 +145,8 @@ export const IMAGE_V12_1_METAS = {
|
||||
url: `${common.BASE_REPO_URL}${common.REPO_BRANCH}/images1/${IMAGES_TEST_DIR}/${IMAGE_V12_1}`,
|
||||
imageType: 1,
|
||||
manufacturerCode: 4151,
|
||||
sha512: '5d7e0a20141b78b85b4b046e623bc2bba24b28563464fe70227e79d0acdd5c0bde2adbd9d2557bd6cdfef2036d964c35c9e1746a8f1356af3325dd96f7a80e56',
|
||||
otaHeaderString: 'OM15081-RTR-JN5189-0000000000000',
|
||||
sha512: "5d7e0a20141b78b85b4b046e623bc2bba24b28563464fe70227e79d0acdd5c0bde2adbd9d2557bd6cdfef2036d964c35c9e1746a8f1356af3325dd96f7a80e56",
|
||||
otaHeaderString: "OM15081-RTR-JN5189-0000000000000",
|
||||
};
|
||||
/**
|
||||
* - otaUpgradeFileIdentifier: <Buffer 1e f1 ee 0b>,
|
||||
@@ -167,8 +168,8 @@ export const IMAGE_V12_2_METAS = {
|
||||
url: `${common.BASE_REPO_URL}${common.REPO_BRANCH}/images1/${IMAGES_TEST_DIR}/${IMAGE_V12_2}`,
|
||||
imageType: 2,
|
||||
manufacturerCode: 4151,
|
||||
sha512: '4e178e56c1559e11734c07abbb95110675df7738f3ca3e5dbc99393325295ff6c66bd63ba55c0ef6043a80608dbec2be7a1e845f31ffd94f1cb63f32f0d48c6e',
|
||||
otaHeaderString: 'OM15081-RTR-LIMITED-JN5189-00000',
|
||||
sha512: "4e178e56c1559e11734c07abbb95110675df7738f3ca3e5dbc99393325295ff6c66bd63ba55c0ef6043a80608dbec2be7a1e845f31ffd94f1cb63f32f0d48c6e",
|
||||
otaHeaderString: "OM15081-RTR-LIMITED-JN5189-00000",
|
||||
};
|
||||
/** obviously bogus, just for mocking */
|
||||
export const IMAGE_INVALID_METAS = {
|
||||
@@ -179,8 +180,8 @@ export const IMAGE_INVALID_METAS = {
|
||||
url: `${common.BASE_REPO_URL}${common.REPO_BRANCH}/images/${IMAGES_TEST_DIR}/${IMAGE_INVALID}`,
|
||||
imageType: 1,
|
||||
manufacturerCode: 65535,
|
||||
sha512: 'abcd',
|
||||
otaHeaderString: 'nothing',
|
||||
sha512: "abcd",
|
||||
otaHeaderString: "nothing",
|
||||
};
|
||||
/**
|
||||
* - otaUpgradeFileIdentifier: <Buffer 1e f1 ee 0b>,
|
||||
@@ -202,12 +203,13 @@ export const IMAGE_TAR_METAS = {
|
||||
url: `${common.BASE_REPO_URL}${common.REPO_BRANCH}/images/${IMAGES_TEST_DIR}/${IMAGE_TAR_OTA}`,
|
||||
imageType: 2,
|
||||
manufacturerCode: 4388,
|
||||
sha512: '3306332e001eab9d71c9360089d450ea21e2c08bac957b523643c042707887e85db0c510f3480bdbcfcfe2398eeaad88d455f346f1e07841e1d690d8c16dc211',
|
||||
otaHeaderString: 'Jasco 45856 image\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
sha512: "3306332e001eab9d71c9360089d450ea21e2c08bac957b523643c042707887e85db0c510f3480bdbcfcfe2398eeaad88d455f346f1e07841e1d690d8c16dc211",
|
||||
otaHeaderString: "Jasco 45856 image\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
};
|
||||
|
||||
export const getImageOriginalDirPath = (imageName: string): string => {
|
||||
return path.join('tests', common.BASE_IMAGES_DIR, imageName);
|
||||
// allow running in vitest explorer
|
||||
return path.join(path.resolve().endsWith("tests") ? "." : "tests", common.BASE_IMAGES_DIR, imageName);
|
||||
};
|
||||
|
||||
export const useImage = (imageName: string, outDir: string = BASE_IMAGES_TEST_DIR_PATH): {filename: string} => {
|
||||
@@ -220,7 +222,7 @@ export const useImage = (imageName: string, outDir: string = BASE_IMAGES_TEST_DI
|
||||
copyFileSync(getImageOriginalDirPath(imageName), realPath);
|
||||
|
||||
// return as posix for github match
|
||||
return {filename: path.posix.join(outDir.replaceAll('\\', '/'), imageName)};
|
||||
return {filename: path.posix.join(outDir.replaceAll("\\", "/"), imageName)};
|
||||
};
|
||||
|
||||
export const withExtraMetas = (meta: RepoImageMeta, extraMetas: ExtraMetas): RepoImageMeta => {
|
||||
@@ -234,7 +236,9 @@ export const getAdjustedContent = (fileName: string, content: RepoImageMeta[]):
|
||||
// @ts-expect-error override
|
||||
url: `${common.BASE_REPO_URL}${common.REPO_BRANCH}/${common.BASE_IMAGES_DIR}/${IMAGES_TEST_DIR}/${c.fileName}`,
|
||||
});
|
||||
} else if (fileName === common.PREV_INDEX_MANIFEST_FILENAME && c.url.includes(`${common.BASE_IMAGES_DIR}`)) {
|
||||
}
|
||||
|
||||
if (fileName === common.PREV_INDEX_MANIFEST_FILENAME && c.url.includes(`${common.BASE_IMAGES_DIR}`)) {
|
||||
return withExtraMetas(c, {
|
||||
// @ts-expect-error override
|
||||
url: `${common.BASE_REPO_URL}${common.REPO_BRANCH}/${common.PREV_IMAGES_DIR}/${IMAGES_TEST_DIR}/${c.fileName}`,
|
||||
@@ -246,4 +250,4 @@ export const getAdjustedContent = (fileName: string, content: RepoImageMeta[]):
|
||||
};
|
||||
|
||||
// required to consider as a 'test suite'
|
||||
it('passes', () => {});
|
||||
it("passes", () => {});
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
|
||||
import type {RepoImageMeta} from '../src/types';
|
||||
import type {RepoImageMeta} from "../src/types";
|
||||
|
||||
import {existsSync, readFileSync, rmSync} from 'fs';
|
||||
import path from 'path';
|
||||
import {existsSync, readFileSync, rmSync} from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import * as common from '../src/common';
|
||||
import {checkOtaPR} from '../src/ghw_check_ota_pr';
|
||||
import {type MockInstance, afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
|
||||
import * as common from "../src/common";
|
||||
import {checkOtaPR} from "../src/ghw_check_ota_pr";
|
||||
import {
|
||||
BASE_IMAGES_TEST_DIR_PATH,
|
||||
getAdjustedContent,
|
||||
IMAGES_TEST_DIR,
|
||||
IMAGE_INVALID,
|
||||
IMAGE_V12_1,
|
||||
IMAGE_V12_1_METAS,
|
||||
@@ -22,19 +23,20 @@ import {
|
||||
IMAGE_V14_1_METAS,
|
||||
IMAGE_V14_2,
|
||||
IMAGE_V14_2_METAS,
|
||||
IMAGES_TEST_DIR,
|
||||
PREV_IMAGES_TEST_DIR_PATH,
|
||||
getAdjustedContent,
|
||||
useImage,
|
||||
withExtraMetas,
|
||||
} from './data.test';
|
||||
} from "./data.test";
|
||||
|
||||
const github = {
|
||||
rest: {
|
||||
repos: {
|
||||
compareCommitsWithBasehead: jest.fn<
|
||||
ReturnType<Octokit['rest']['repos']['compareCommitsWithBasehead']>,
|
||||
Parameters<Octokit['rest']['repos']['compareCommitsWithBasehead']>,
|
||||
unknown
|
||||
compareCommitsWithBasehead:
|
||||
vi.fn<
|
||||
(
|
||||
...args: Parameters<Octokit["rest"]["repos"]["compareCommitsWithBasehead"]>
|
||||
) => ReturnType<Octokit["rest"]["repos"]["compareCommitsWithBasehead"]>
|
||||
>(),
|
||||
},
|
||||
},
|
||||
@@ -45,49 +47,51 @@ const core: Partial<typeof CoreApi> = {
|
||||
warning: console.warn,
|
||||
error: console.error,
|
||||
notice: console.log,
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
startGroup: vi.fn(),
|
||||
endGroup: vi.fn(),
|
||||
};
|
||||
const context: Partial<Context> = {
|
||||
payload: {
|
||||
pull_request: {
|
||||
number: 1,
|
||||
head: {
|
||||
sha: 'abcd',
|
||||
sha: "abcd",
|
||||
},
|
||||
base: {
|
||||
sha: 'zyxw',
|
||||
sha: "zyxw",
|
||||
},
|
||||
},
|
||||
},
|
||||
issue: {
|
||||
owner: 'Koenkk',
|
||||
repo: 'zigbee-OTA',
|
||||
owner: "Koenkk",
|
||||
repo: "zigbee-OTA",
|
||||
number: 1,
|
||||
},
|
||||
repo: {
|
||||
owner: 'Koenkk',
|
||||
repo: 'zigbee-OTA',
|
||||
owner: "Koenkk",
|
||||
repo: "zigbee-OTA",
|
||||
},
|
||||
};
|
||||
|
||||
describe('Github Workflow: Check OTA PR', () => {
|
||||
describe("Github Workflow: Check OTA PR", () => {
|
||||
let baseManifest: RepoImageMeta[];
|
||||
let prevManifest: RepoImageMeta[];
|
||||
let readManifestSpy: jest.SpyInstance;
|
||||
let writeManifestSpy: jest.SpyInstance;
|
||||
let addImageToBaseSpy: jest.SpyInstance;
|
||||
let addImageToPrevSpy: jest.SpyInstance;
|
||||
let readManifestSpy: MockInstance;
|
||||
let writeManifestSpy: MockInstance;
|
||||
let addImageToBaseSpy: MockInstance;
|
||||
let addImageToPrevSpy: MockInstance;
|
||||
let filePaths: ReturnType<typeof useImage>[] = [];
|
||||
|
||||
const getManifest = (fileName: string): RepoImageMeta[] => {
|
||||
if (fileName === common.BASE_INDEX_MANIFEST_FILENAME) {
|
||||
return baseManifest;
|
||||
} else if (fileName === common.PREV_INDEX_MANIFEST_FILENAME) {
|
||||
return prevManifest;
|
||||
} else {
|
||||
throw new Error(`${fileName} not supported`);
|
||||
}
|
||||
|
||||
if (fileName === common.PREV_INDEX_MANIFEST_FILENAME) {
|
||||
return prevManifest;
|
||||
}
|
||||
|
||||
throw new Error(`${fileName} not supported`);
|
||||
};
|
||||
|
||||
const setManifest = (fileName: string, content: RepoImageMeta[]): void => {
|
||||
@@ -115,7 +119,7 @@ describe('Github Workflow: Check OTA PR', () => {
|
||||
return newContext;
|
||||
};
|
||||
|
||||
const expectNoChanges = (noReadManifest: boolean = false): void => {
|
||||
const expectNoChanges = (noReadManifest = false): void => {
|
||||
if (noReadManifest) {
|
||||
expect(readManifestSpy).toHaveBeenCalledTimes(0);
|
||||
} else {
|
||||
@@ -136,16 +140,17 @@ describe('Github Workflow: Check OTA PR', () => {
|
||||
addImageToPrevSpy.mockRestore();
|
||||
rmSync(BASE_IMAGES_TEST_DIR_PATH, {recursive: true, force: true});
|
||||
rmSync(PREV_IMAGES_TEST_DIR_PATH, {recursive: true, force: true});
|
||||
rmSync(IMAGES_TEST_DIR, {recursive: true, force: true});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
resetManifests();
|
||||
|
||||
filePaths = [];
|
||||
readManifestSpy = jest.spyOn(common, 'readManifest').mockImplementation(getManifest);
|
||||
writeManifestSpy = jest.spyOn(common, 'writeManifest').mockImplementation(setManifest);
|
||||
addImageToBaseSpy = jest.spyOn(common, 'addImageToBase');
|
||||
addImageToPrevSpy = jest.spyOn(common, 'addImageToPrev');
|
||||
readManifestSpy = vi.spyOn(common, "readManifest").mockImplementation(getManifest);
|
||||
writeManifestSpy = vi.spyOn(common, "writeManifest").mockImplementation(setManifest);
|
||||
addImageToBaseSpy = vi.spyOn(common, "addImageToBase");
|
||||
addImageToPrevSpy = vi.spyOn(common, "addImageToPrev");
|
||||
github.rest.repos.compareCommitsWithBasehead.mockImplementation(
|
||||
// @ts-expect-error mock
|
||||
() => ({data: {files: filePaths}}),
|
||||
@@ -167,92 +172,92 @@ describe('Github Workflow: Check OTA PR', () => {
|
||||
// console.log(`SHA512: ${common.computeSHA512(firmwareBuffer)}`);
|
||||
// })
|
||||
|
||||
it('hard failure from outside PR context', async () => {
|
||||
it("hard failure from outside PR context", async () => {
|
||||
filePaths = [useImage(IMAGE_V14_1)];
|
||||
|
||||
await expect(async () => {
|
||||
// @ts-expect-error mock
|
||||
await checkOtaPR(github, core, {payload: {}});
|
||||
}).rejects.toThrow(`Not a pull request`);
|
||||
}).rejects.toThrow("Not a pull request");
|
||||
|
||||
expectNoChanges(true);
|
||||
});
|
||||
|
||||
it('hard failure from merged PR context', async () => {
|
||||
it("hard failure from merged PR context", async () => {
|
||||
filePaths = [useImage(IMAGE_V14_1)];
|
||||
|
||||
await expect(async () => {
|
||||
// @ts-expect-error mock
|
||||
await checkOtaPR(github, core, {payload: {pull_request: {merged: true}}});
|
||||
}).rejects.toThrow(`Should not be executed on a merged pull request`);
|
||||
}).rejects.toThrow("Should not be executed on a merged pull request");
|
||||
|
||||
expectNoChanges(true);
|
||||
});
|
||||
|
||||
it('hard failure with no file changed', async () => {
|
||||
it("hard failure with no file changed", async () => {
|
||||
filePaths = [];
|
||||
|
||||
await expect(async () => {
|
||||
// @ts-expect-error mock
|
||||
await checkOtaPR(github, core, context);
|
||||
}).rejects.toThrow(`No file`);
|
||||
}).rejects.toThrow("No file");
|
||||
|
||||
expectNoChanges(false);
|
||||
expect(existsSync(common.PR_ARTIFACT_NUMBER_FILEPATH)).toStrictEqual(true);
|
||||
expect(readFileSync(common.PR_ARTIFACT_NUMBER_FILEPATH, 'utf8')).toStrictEqual(`${context.payload?.pull_request?.number}`);
|
||||
expect(readFileSync(common.PR_ARTIFACT_NUMBER_FILEPATH, "utf8")).toStrictEqual(`${context.payload?.pull_request?.number}`);
|
||||
expect(existsSync(common.PR_ARTIFACT_DIFF_FILEPATH)).toStrictEqual(false);
|
||||
expect(existsSync(common.PR_ARTIFACT_ERROR_FILEPATH)).toStrictEqual(true);
|
||||
expect(readFileSync(common.PR_ARTIFACT_ERROR_FILEPATH, 'utf8')).toStrictEqual(`No file`);
|
||||
expect(readFileSync(common.PR_ARTIFACT_ERROR_FILEPATH, "utf8")).toStrictEqual("No file");
|
||||
});
|
||||
|
||||
it('failure with file outside of images directory', async () => {
|
||||
it("failure with file outside of images directory", async () => {
|
||||
filePaths = [useImage(IMAGE_V13_1, PREV_IMAGES_TEST_DIR_PATH), useImage(IMAGE_V14_1)];
|
||||
|
||||
await expect(async () => {
|
||||
// @ts-expect-error mock
|
||||
await checkOtaPR(github, core, context);
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining(`Detected changes in files outside`)}));
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining("Detected changes in files outside")}));
|
||||
|
||||
expectNoChanges(false);
|
||||
});
|
||||
|
||||
it('failure when no manufacturer subfolder', async () => {
|
||||
it("failure when no manufacturer subfolder", async () => {
|
||||
filePaths = [useImage(IMAGE_V14_1, common.BASE_IMAGES_DIR)];
|
||||
|
||||
await expect(async () => {
|
||||
// @ts-expect-error mock
|
||||
await checkOtaPR(github, core, context);
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining(`File should be in its associated manufacturer subfolder`)}));
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining("File should be in its associated manufacturer subfolder")}));
|
||||
|
||||
expectNoChanges(false);
|
||||
|
||||
rmSync(path.join(common.BASE_IMAGES_DIR, IMAGE_V14_1), {force: true});
|
||||
});
|
||||
|
||||
it('failure with invalid OTA file', async () => {
|
||||
it("failure with invalid OTA file", async () => {
|
||||
filePaths = [useImage(IMAGE_INVALID)];
|
||||
|
||||
await expect(async () => {
|
||||
// @ts-expect-error mock
|
||||
await checkOtaPR(github, core, context);
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining(`Not a valid OTA file`)}));
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining("Not a valid OTA file")}));
|
||||
|
||||
expectNoChanges(false);
|
||||
});
|
||||
|
||||
it('failure with identical OTA file', async () => {
|
||||
it("failure with identical OTA file", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V14_1_METAS]);
|
||||
filePaths = [useImage(IMAGE_V14_1)];
|
||||
|
||||
await expect(async () => {
|
||||
// @ts-expect-error mock
|
||||
await checkOtaPR(github, core, context);
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining(`Conflict with image at index \`0\``)}));
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining("Conflict with image at index `0`")}));
|
||||
|
||||
expectNoChanges(false);
|
||||
});
|
||||
|
||||
it('failure with older OTA file that has identical in prev', async () => {
|
||||
it("failure with older OTA file that has identical in prev", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V14_1_METAS]);
|
||||
setManifest(common.PREV_INDEX_MANIFEST_FILENAME, [IMAGE_V13_1_METAS]);
|
||||
filePaths = [useImage(IMAGE_V13_1)];
|
||||
@@ -261,13 +266,13 @@ describe('Github Workflow: Check OTA PR', () => {
|
||||
// @ts-expect-error mock
|
||||
await checkOtaPR(github, core, context);
|
||||
}).rejects.toThrow(
|
||||
expect.objectContaining({message: expect.stringContaining(`an equal or better match is already present in prev manifest`)}),
|
||||
expect.objectContaining({message: expect.stringContaining("an equal or better match is already present in prev manifest")}),
|
||||
);
|
||||
|
||||
expectNoChanges(false);
|
||||
});
|
||||
|
||||
it('failure with older OTA file that has newer in prev', async () => {
|
||||
it("failure with older OTA file that has newer in prev", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V14_1_METAS]);
|
||||
setManifest(common.PREV_INDEX_MANIFEST_FILENAME, [IMAGE_V13_1_METAS]);
|
||||
filePaths = [useImage(IMAGE_V12_1)];
|
||||
@@ -276,13 +281,13 @@ describe('Github Workflow: Check OTA PR', () => {
|
||||
// @ts-expect-error mock
|
||||
await checkOtaPR(github, core, context);
|
||||
}).rejects.toThrow(
|
||||
expect.objectContaining({message: expect.stringContaining(`an equal or better match is already present in prev manifest`)}),
|
||||
expect.objectContaining({message: expect.stringContaining("an equal or better match is already present in prev manifest")}),
|
||||
);
|
||||
|
||||
expectNoChanges(false);
|
||||
});
|
||||
|
||||
it('success into base', async () => {
|
||||
it("success into base", async () => {
|
||||
filePaths = [useImage(IMAGE_V14_1)];
|
||||
|
||||
// @ts-expect-error mock
|
||||
@@ -295,12 +300,12 @@ describe('Github Workflow: Check OTA PR', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V14_1_METAS]);
|
||||
expect(existsSync(common.PR_ARTIFACT_NUMBER_FILEPATH)).toStrictEqual(true);
|
||||
expect(readFileSync(common.PR_ARTIFACT_NUMBER_FILEPATH, 'utf8')).toStrictEqual(`${context.payload?.pull_request?.number}`);
|
||||
expect(readFileSync(common.PR_ARTIFACT_NUMBER_FILEPATH, "utf8")).toStrictEqual(`${context.payload?.pull_request?.number}`);
|
||||
expect(existsSync(common.PR_ARTIFACT_DIFF_FILEPATH)).toStrictEqual(true);
|
||||
expect(existsSync(common.PR_ARTIFACT_ERROR_FILEPATH)).toStrictEqual(false);
|
||||
});
|
||||
|
||||
it('success into prev', async () => {
|
||||
it("success into prev", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V14_1_METAS]);
|
||||
|
||||
filePaths = [useImage(IMAGE_V13_1)];
|
||||
@@ -316,7 +321,7 @@ describe('Github Workflow: Check OTA PR', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, [IMAGE_V13_1_METAS]);
|
||||
});
|
||||
|
||||
it('success with newer than current without existing prev', async () => {
|
||||
it("success with newer than current without existing prev", async () => {
|
||||
filePaths = [useImage(IMAGE_V13_1), useImage(IMAGE_V14_1)];
|
||||
|
||||
// @ts-expect-error mock
|
||||
@@ -330,7 +335,7 @@ describe('Github Workflow: Check OTA PR', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, [IMAGE_V13_1_METAS]);
|
||||
});
|
||||
|
||||
it('success with newer than current with existing prev', async () => {
|
||||
it("success with newer than current with existing prev", async () => {
|
||||
filePaths = [useImage(IMAGE_V12_1), useImage(IMAGE_V13_1), useImage(IMAGE_V14_1)];
|
||||
|
||||
// @ts-expect-error mock
|
||||
@@ -344,7 +349,7 @@ describe('Github Workflow: Check OTA PR', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, [IMAGE_V13_1_METAS]);
|
||||
});
|
||||
|
||||
it('success with older that is newer than prev', async () => {
|
||||
it("success with older that is newer than prev", async () => {
|
||||
setManifest(common.PREV_INDEX_MANIFEST_FILENAME, [IMAGE_V12_1_METAS]);
|
||||
filePaths = [useImage(IMAGE_V14_1), useImage(IMAGE_V13_1)];
|
||||
|
||||
@@ -359,7 +364,7 @@ describe('Github Workflow: Check OTA PR', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, [IMAGE_V13_1_METAS]);
|
||||
});
|
||||
|
||||
it('success with newer with missing file', async () => {
|
||||
it("success with newer with missing file", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V13_1_METAS]);
|
||||
filePaths = [useImage(IMAGE_V14_1)];
|
||||
|
||||
@@ -374,7 +379,7 @@ describe('Github Workflow: Check OTA PR', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, []);
|
||||
});
|
||||
|
||||
it('success with multiple different files', async () => {
|
||||
it("success with multiple different files", async () => {
|
||||
filePaths = [useImage(IMAGE_V14_2), useImage(IMAGE_V14_1)];
|
||||
|
||||
// @ts-expect-error mock
|
||||
@@ -388,7 +393,7 @@ describe('Github Workflow: Check OTA PR', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, []);
|
||||
});
|
||||
|
||||
it('success with extra metas', async () => {
|
||||
it("success with extra metas", async () => {
|
||||
filePaths = [useImage(IMAGE_V14_1)];
|
||||
const newContext = withBody(`Text before start tag \`\`\`json {"manufacturerName": ["lixee"]} \`\`\` Text after end tag`);
|
||||
|
||||
@@ -401,11 +406,11 @@ describe('Github Workflow: Check OTA PR', () => {
|
||||
expect(addImageToPrevSpy).toHaveBeenCalledTimes(0);
|
||||
expect(writeManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.BASE_INDEX_MANIFEST_FILENAME, [
|
||||
withExtraMetas(IMAGE_V14_1_METAS, {manufacturerName: ['lixee']}),
|
||||
withExtraMetas(IMAGE_V14_1_METAS, {manufacturerName: ["lixee"]}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('success with all extra metas', async () => {
|
||||
it("success with all extra metas", async () => {
|
||||
filePaths = [useImage(IMAGE_V14_1)];
|
||||
const newContext = withBody(`Text before start tag
|
||||
\`\`\`json
|
||||
@@ -435,16 +440,16 @@ Text after end tag`);
|
||||
force: false,
|
||||
hardwareVersionMax: 2,
|
||||
hardwareVersionMin: 1,
|
||||
manufacturerName: ['lixee'],
|
||||
manufacturerName: ["lixee"],
|
||||
maxFileVersion: 5,
|
||||
minFileVersion: 3,
|
||||
modelId: 'bogus',
|
||||
releaseNotes: 'bugfixes',
|
||||
modelId: "bogus",
|
||||
releaseNotes: "bugfixes",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('success with newer than current but minFileVersion keeps both', async () => {
|
||||
it("success with newer than current but minFileVersion keeps both", async () => {
|
||||
filePaths = [useImage(IMAGE_V13_1), useImage(IMAGE_V14_1)];
|
||||
const newContext = withBody(
|
||||
`Text before start tag \`\`\`json [{"fileName":"ZLinky_router_v14.ota", "minFileVersion": 16783874}] \`\`\` Text after end tag`,
|
||||
@@ -467,7 +472,7 @@ Text after end tag`);
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, []);
|
||||
});
|
||||
|
||||
it('success with newer than current but maxFileVersion keeps both', async () => {
|
||||
it("success with newer than current but maxFileVersion keeps both", async () => {
|
||||
filePaths = [useImage(IMAGE_V13_1), useImage(IMAGE_V14_1)];
|
||||
const newContext = withBody(
|
||||
`Text before start tag \`\`\`json [{"fileName":"ZLinky_router_v13.ota", "maxFileVersion": 16783873}] \`\`\` Text after end tag`,
|
||||
@@ -491,7 +496,7 @@ Text after end tag`);
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, []);
|
||||
});
|
||||
|
||||
it('success with newer than current but maxFileVersion/minFileVersion keeps both', async () => {
|
||||
it("success with newer than current but maxFileVersion/minFileVersion keeps both", async () => {
|
||||
filePaths = [useImage(IMAGE_V13_1), useImage(IMAGE_V14_1)];
|
||||
const newContext = withBody(
|
||||
`Text before start tag \`\`\`json [{"fileName":"ZLinky_router_v13.ota", "maxFileVersion": 16783873},{"fileName":"ZLinky_router_v14.ota", "minFileVersion": 16783874}] \`\`\` Text after end tag`,
|
||||
@@ -515,7 +520,7 @@ Text after end tag`);
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, []);
|
||||
});
|
||||
|
||||
it('failure with invalid extra metas', async () => {
|
||||
it("failure with invalid extra metas", async () => {
|
||||
filePaths = [useImage(IMAGE_V14_1)];
|
||||
const newContext = withBody(`Text before start tag \`\`\`json {"manufacturerName": "myManuf"} \`\`\` Text after end tag`);
|
||||
|
||||
@@ -530,17 +535,17 @@ Text after end tag`);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['fileName'],
|
||||
['originalUrl'],
|
||||
['force'],
|
||||
['hardwareVersionMax'],
|
||||
['hardwareVersionMin'],
|
||||
['manufacturerName'],
|
||||
['maxFileVersion'],
|
||||
['minFileVersion'],
|
||||
['modelId'],
|
||||
['releaseNotes'],
|
||||
])('failure with invalid type for extra meta %s', async (metaName) => {
|
||||
["fileName"],
|
||||
["originalUrl"],
|
||||
["force"],
|
||||
["hardwareVersionMax"],
|
||||
["hardwareVersionMin"],
|
||||
["manufacturerName"],
|
||||
["maxFileVersion"],
|
||||
["minFileVersion"],
|
||||
["modelId"],
|
||||
["releaseNotes"],
|
||||
])("failure with invalid type for extra meta %s", async (metaName) => {
|
||||
filePaths = [useImage(IMAGE_V14_1)];
|
||||
// use object since no value type is ever expected to be object
|
||||
const newContext = withBody(`Text before start tag \`\`\`json {"${metaName}": {}} \`\`\` Text after end tag`);
|
||||
@@ -553,7 +558,7 @@ Text after end tag`);
|
||||
expectNoChanges(false);
|
||||
});
|
||||
|
||||
it('success with multiple files and specific extra metas', async () => {
|
||||
it("success with multiple files and specific extra metas", async () => {
|
||||
filePaths = [useImage(IMAGE_V13_1), useImage(IMAGE_V14_1), useImage(IMAGE_V12_1)];
|
||||
const newContext = withBody(`Text before start tag
|
||||
\`\`\`json
|
||||
@@ -574,15 +579,15 @@ Text after end tag`);
|
||||
expect(addImageToPrevSpy).toHaveBeenCalledTimes(1);
|
||||
expect(writeManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.BASE_INDEX_MANIFEST_FILENAME, [
|
||||
withExtraMetas(IMAGE_V13_1_METAS_MAIN, {manufacturerName: ['lixee']}),
|
||||
withExtraMetas(IMAGE_V14_1_METAS, {manufacturerName: ['lixee'], hardwareVersionMin: 2}),
|
||||
withExtraMetas(IMAGE_V13_1_METAS_MAIN, {manufacturerName: ["lixee"]}),
|
||||
withExtraMetas(IMAGE_V14_1_METAS, {manufacturerName: ["lixee"], hardwareVersionMin: 2}),
|
||||
]);
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, [
|
||||
withExtraMetas(IMAGE_V12_1_METAS, {manufacturerName: ['lixee']}),
|
||||
withExtraMetas(IMAGE_V12_1_METAS, {manufacturerName: ["lixee"]}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('success with multiple files and specific extra metas, ignore without fileName', async () => {
|
||||
it("success with multiple files and specific extra metas, ignore without fileName", async () => {
|
||||
filePaths = [useImage(IMAGE_V12_1), useImage(IMAGE_V13_1), useImage(IMAGE_V14_1)];
|
||||
const newContext = withBody(`Text before start tag
|
||||
\`\`\`json
|
||||
@@ -603,7 +608,7 @@ Text after end tag`);
|
||||
expect(writeManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.BASE_INDEX_MANIFEST_FILENAME, [
|
||||
IMAGE_V13_1_METAS_MAIN,
|
||||
withExtraMetas(IMAGE_V14_1_METAS, {manufacturerName: ['lixee'], hardwareVersionMin: 2}),
|
||||
withExtraMetas(IMAGE_V14_1_METAS, {manufacturerName: ["lixee"], hardwareVersionMin: 2}),
|
||||
]);
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, [IMAGE_V12_1_METAS]);
|
||||
});
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
// import type CoreApi from '@actions/core';
|
||||
// import type {Context} from '@actions/github/lib/context';
|
||||
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
import {describe, it, vi} from "vitest";
|
||||
|
||||
const github = {
|
||||
rest: {
|
||||
issues: {
|
||||
createComment: jest.fn<
|
||||
ReturnType<Octokit['rest']['issues']['createComment']>,
|
||||
Parameters<Octokit['rest']['issues']['createComment']>,
|
||||
unknown
|
||||
>(),
|
||||
createComment:
|
||||
vi.fn<(...args: Parameters<Octokit["rest"]["issues"]["createComment"]>) => ReturnType<Octokit["rest"]["issues"]["createComment"]>>(),
|
||||
},
|
||||
pulls: {
|
||||
createReviewComment: jest.fn<
|
||||
ReturnType<Octokit['rest']['pulls']['createReviewComment']>,
|
||||
Parameters<Octokit['rest']['pulls']['createReviewComment']>,
|
||||
unknown
|
||||
createReviewComment:
|
||||
vi.fn<
|
||||
(
|
||||
...args: Parameters<Octokit["rest"]["pulls"]["createReviewComment"]>
|
||||
) => ReturnType<Octokit["rest"]["pulls"]["createReviewComment"]>
|
||||
>(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('Github Workflow: Report OTA PR', () => {
|
||||
it('passes', async () => {
|
||||
describe("Github Workflow: Report OTA PR", () => {
|
||||
it("passes", () => {
|
||||
console.log(github);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
|
||||
import type {RepoImageMeta} from '../src/types';
|
||||
import type {RepoImageMeta} from "../src/types";
|
||||
|
||||
import {copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync} from 'fs';
|
||||
import path from 'path';
|
||||
import {copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync} from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import * as common from '../src/common';
|
||||
import {type MockInstance, afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
|
||||
import * as common from "../src/common";
|
||||
import {
|
||||
NOT_IN_BASE_MANIFEST_IMAGES_DIR,
|
||||
NOT_IN_MANIFEST_FILENAME,
|
||||
NOT_IN_PREV_MANIFEST_IMAGES_DIR,
|
||||
reProcessAllImages,
|
||||
} from '../src/ghw_reprocess_all_images';
|
||||
} from "../src/ghw_reprocess_all_images";
|
||||
import {
|
||||
BASE_IMAGES_TEST_DIR_PATH,
|
||||
getAdjustedContent,
|
||||
getImageOriginalDirPath,
|
||||
IMAGES_TEST_DIR,
|
||||
IMAGE_INVALID,
|
||||
IMAGE_INVALID_METAS,
|
||||
IMAGE_V12_1,
|
||||
@@ -24,11 +24,12 @@ import {
|
||||
IMAGE_V13_1_METAS,
|
||||
IMAGE_V14_1,
|
||||
IMAGE_V14_1_METAS,
|
||||
IMAGES_TEST_DIR,
|
||||
PREV_IMAGES_TEST_DIR_PATH,
|
||||
getAdjustedContent,
|
||||
getImageOriginalDirPath,
|
||||
useImage,
|
||||
withExtraMetas,
|
||||
} from './data.test';
|
||||
} from "./data.test";
|
||||
|
||||
/** not used */
|
||||
const github = {};
|
||||
@@ -38,14 +39,14 @@ const core: Partial<typeof CoreApi> = {
|
||||
warning: console.warn,
|
||||
error: console.error,
|
||||
notice: console.log,
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
startGroup: vi.fn(),
|
||||
endGroup: vi.fn(),
|
||||
};
|
||||
const context: Partial<Context> = {
|
||||
payload: {},
|
||||
repo: {
|
||||
owner: 'Koenkk',
|
||||
repo: 'zigbee-OTA',
|
||||
owner: "Koenkk",
|
||||
repo: "zigbee-OTA",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -56,7 +57,7 @@ const OLD_META_3RD_PARTY_1_METAS = {
|
||||
fileSize: 258104,
|
||||
manufacturerCode: 4107,
|
||||
imageType: 256,
|
||||
sha512: 'c63a1eb02ac030f3a76d9e81a4d48695796457d263bb1dae483688134e550d9846c37a3fd0eab2d4670f12f11b79691a5cf2789af0dbd90d703512496190a0a5',
|
||||
sha512: "c63a1eb02ac030f3a76d9e81a4d48695796457d263bb1dae483688134e550d9846c37a3fd0eab2d4670f12f11b79691a5cf2789af0dbd90d703512496190a0a5",
|
||||
// mock fileName to trigger mocked fetch properly
|
||||
url: `https://otau.meethue.com/storage/ZGB_100B_0100/2dcfe6e6-0177-4c81-a1d9-4d2bd2ea1fb7/${OLD_META_3RD_PARTY_1_REAL_IMAGE}`,
|
||||
};
|
||||
@@ -67,8 +68,8 @@ const OLD_META_3RD_PARTY_2_METAS = {
|
||||
fileSize: 307682,
|
||||
manufacturerCode: 4417,
|
||||
imageType: 54179,
|
||||
modelId: 'TS011F',
|
||||
sha512: '01939ca4fc790432d2c233e19b2440c1e0248d2ce85c9299e0b88928cb2341de675350ac7b78187a25f06a2768f93db0a17c4ba950b60c82c072e0c0833cfcfb',
|
||||
modelId: "TS011F",
|
||||
sha512: "01939ca4fc790432d2c233e19b2440c1e0248d2ce85c9299e0b88928cb2341de675350ac7b78187a25f06a2768f93db0a17c4ba950b60c82c072e0c0833cfcfb",
|
||||
// mock fileName to trigger mocked fetch properly
|
||||
url: `https://images.tuyaeu.com/smart/firmware/upgrade/20220907/${OLD_META_3RD_PARTY_2_REAL_IMAGE}`,
|
||||
};
|
||||
@@ -77,8 +78,8 @@ const OLD_META_3RD_PARTY_IGNORED_METAS = {
|
||||
fileSize: 693230,
|
||||
manufacturerCode: 13379,
|
||||
imageType: 4113,
|
||||
sha512: '66040fb2b2787bf8ebfc75bc3c7356c7d8b966b4c82282bd7393783b8dc453ec2c8dcb4d7c9fe7c0a83d87739bd3677f205d79edddfa4fa2749305ca987887b1',
|
||||
url: 'https://github.com/xyzroe/ZigUSB_C6/releases/download/317/ZigUSB_C6.ota',
|
||||
sha512: "66040fb2b2787bf8ebfc75bc3c7356c7d8b966b4c82282bd7393783b8dc453ec2c8dcb4d7c9fe7c0a83d87739bd3677f205d79edddfa4fa2749305ca987887b1",
|
||||
url: "https://github.com/xyzroe/ZigUSB_C6/releases/download/317/ZigUSB_C6.ota",
|
||||
};
|
||||
const NOT_IN_BASE_MANIFEST_IMAGE_DIR_PATH = path.join(NOT_IN_BASE_MANIFEST_IMAGES_DIR, IMAGES_TEST_DIR);
|
||||
const NOT_IN_PREV_MANIFEST_IMAGE_DIR_PATH = path.join(NOT_IN_PREV_MANIFEST_IMAGES_DIR, IMAGES_TEST_DIR);
|
||||
@@ -88,30 +89,36 @@ const NOT_IN_PREV_MANIFEST_FILEPATH = path.join(NOT_IN_PREV_MANIFEST_IMAGES_DIR,
|
||||
const NOT_IN_PREV_MANIFEST_IMAGES_DIR_TMP = `${NOT_IN_PREV_MANIFEST_IMAGES_DIR}-moved-by-jest`;
|
||||
const NOT_IN_BASE_MANIFEST_IMAGES_DIR_TMP = `${NOT_IN_BASE_MANIFEST_IMAGES_DIR}-moved-by-jest`;
|
||||
|
||||
describe('Github Workflow: Re-Process All Images', () => {
|
||||
describe("Github Workflow: Re-Process All Images", () => {
|
||||
let baseManifest: RepoImageMeta[];
|
||||
let prevManifest: RepoImageMeta[];
|
||||
let notInBaseManifest: RepoImageMeta[];
|
||||
let notInPrevManifest: RepoImageMeta[];
|
||||
let readManifestSpy: jest.SpyInstance;
|
||||
let writeManifestSpy: jest.SpyInstance;
|
||||
let addImageToBaseSpy: jest.SpyInstance;
|
||||
let addImageToPrevSpy: jest.SpyInstance;
|
||||
let coreWarningSpy: jest.SpyInstance;
|
||||
let coreErrorSpy: jest.SpyInstance;
|
||||
let readManifestSpy: MockInstance;
|
||||
let writeManifestSpy: MockInstance;
|
||||
let addImageToBaseSpy: MockInstance;
|
||||
let addImageToPrevSpy: MockInstance;
|
||||
let coreWarningSpy: MockInstance;
|
||||
let coreErrorSpy: MockInstance;
|
||||
|
||||
const getManifest = (fileName: string): RepoImageMeta[] => {
|
||||
if (fileName === common.BASE_INDEX_MANIFEST_FILENAME) {
|
||||
return baseManifest;
|
||||
} else if (fileName === common.PREV_INDEX_MANIFEST_FILENAME) {
|
||||
return prevManifest;
|
||||
} else if (fileName === path.join(NOT_IN_BASE_MANIFEST_IMAGES_DIR, NOT_IN_MANIFEST_FILENAME)) {
|
||||
return notInBaseManifest;
|
||||
} else if (fileName === path.join(NOT_IN_PREV_MANIFEST_IMAGES_DIR, NOT_IN_MANIFEST_FILENAME)) {
|
||||
return notInPrevManifest;
|
||||
} else {
|
||||
throw new Error(`${fileName} not supported`);
|
||||
}
|
||||
|
||||
if (fileName === common.PREV_INDEX_MANIFEST_FILENAME) {
|
||||
return prevManifest;
|
||||
}
|
||||
|
||||
if (fileName === path.join(NOT_IN_BASE_MANIFEST_IMAGES_DIR, NOT_IN_MANIFEST_FILENAME)) {
|
||||
return notInBaseManifest;
|
||||
}
|
||||
|
||||
if (fileName === path.join(NOT_IN_PREV_MANIFEST_IMAGES_DIR, NOT_IN_MANIFEST_FILENAME)) {
|
||||
return notInPrevManifest;
|
||||
}
|
||||
|
||||
throw new Error(`${fileName} not supported`);
|
||||
};
|
||||
|
||||
const setManifest = (fileName: string, content: RepoImageMeta[]): void => {
|
||||
@@ -137,8 +144,10 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
|
||||
const withOldMetas = (metas: RepoImageMeta): RepoImageMeta => {
|
||||
const oldMetas = structuredClone(metas);
|
||||
// biome-ignore lint/performance/noDelete: <explanation>
|
||||
delete oldMetas.originalUrl;
|
||||
// @ts-expect-error mock
|
||||
// biome-ignore lint/performance/noDelete: <explanation>
|
||||
delete oldMetas.sha512;
|
||||
|
||||
return oldMetas;
|
||||
@@ -179,17 +188,19 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
rmSync(NOT_IN_BASE_MANIFEST_IMAGES_DIR, {recursive: true, force: true});
|
||||
renameSync(NOT_IN_BASE_MANIFEST_IMAGES_DIR_TMP, NOT_IN_BASE_MANIFEST_IMAGES_DIR);
|
||||
}
|
||||
|
||||
rmSync(IMAGES_TEST_DIR, {recursive: true, force: true});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
resetManifests();
|
||||
|
||||
readManifestSpy = jest.spyOn(common, 'readManifest').mockImplementation(getManifest);
|
||||
writeManifestSpy = jest.spyOn(common, 'writeManifest').mockImplementation(setManifest);
|
||||
addImageToBaseSpy = jest.spyOn(common, 'addImageToBase');
|
||||
addImageToPrevSpy = jest.spyOn(common, 'addImageToPrev');
|
||||
coreWarningSpy = jest.spyOn(core, 'warning');
|
||||
coreErrorSpy = jest.spyOn(core, 'error');
|
||||
readManifestSpy = vi.spyOn(common, "readManifest").mockImplementation(getManifest);
|
||||
writeManifestSpy = vi.spyOn(common, "writeManifest").mockImplementation(setManifest);
|
||||
addImageToBaseSpy = vi.spyOn(common, "addImageToBase");
|
||||
addImageToPrevSpy = vi.spyOn(common, "addImageToPrev");
|
||||
coreWarningSpy = vi.spyOn(core, "warning");
|
||||
coreErrorSpy = vi.spyOn(core, "error");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -199,27 +210,27 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
rmSync(NOT_IN_PREV_MANIFEST_IMAGE_DIR_PATH, {recursive: true, force: true});
|
||||
});
|
||||
|
||||
it('failure when moving not in manifest if base out directory is not empty', async () => {
|
||||
it("failure when moving not in manifest if base out directory is not empty", async () => {
|
||||
mkdirSync(NOT_IN_BASE_MANIFEST_IMAGE_DIR_PATH, {recursive: true});
|
||||
copyFileSync(getImageOriginalDirPath(IMAGE_V12_1), path.join(NOT_IN_BASE_MANIFEST_IMAGE_DIR_PATH, IMAGE_V12_1));
|
||||
|
||||
await expect(async () => {
|
||||
// @ts-expect-error mocked as needed
|
||||
await reProcessAllImages(github, core, context, false, true);
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining('is not empty')}));
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining("is not empty")}));
|
||||
});
|
||||
|
||||
it('failure when moving not in manifest if prev out directory is not empty', async () => {
|
||||
it("failure when moving not in manifest if prev out directory is not empty", async () => {
|
||||
mkdirSync(NOT_IN_PREV_MANIFEST_IMAGE_DIR_PATH, {recursive: true});
|
||||
copyFileSync(getImageOriginalDirPath(IMAGE_V12_1), path.join(NOT_IN_PREV_MANIFEST_IMAGE_DIR_PATH, IMAGE_V12_1));
|
||||
|
||||
await expect(async () => {
|
||||
// @ts-expect-error mocked as needed
|
||||
await reProcessAllImages(github, core, context, false, true);
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining('is not empty')}));
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining("is not empty")}));
|
||||
});
|
||||
|
||||
it('failure when image not in subdirectory', async () => {
|
||||
it("failure when image not in subdirectory", async () => {
|
||||
// this is renaming the image to the same as the test dir name for simplicity in code exclusion
|
||||
const outPath = path.join(common.PREV_IMAGES_DIR, IMAGES_TEST_DIR);
|
||||
|
||||
@@ -239,7 +250,7 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
rmSync(outPath, {force: true});
|
||||
});
|
||||
|
||||
it('removes image not in manifest', async () => {
|
||||
it("removes image not in manifest", async () => {
|
||||
const imagePath = useImage(IMAGE_V12_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
|
||||
// @ts-expect-error mocked as needed
|
||||
@@ -250,10 +261,10 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expectWriteNoChange(1, common.PREV_INDEX_MANIFEST_FILENAME);
|
||||
expectWriteNoChange(2, common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith(expect.stringContaining(`Not found in base manifest:`));
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith(expect.stringContaining("Not found in base manifest:"));
|
||||
});
|
||||
|
||||
it('removes multiple images not in manifest', async () => {
|
||||
it("removes multiple images not in manifest", async () => {
|
||||
const image1Path = useImage(IMAGE_V13_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
const image2Path = useImage(IMAGE_V12_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
const image3Path = useImage(IMAGE_V12_1, PREV_IMAGES_TEST_DIR_PATH);
|
||||
@@ -269,12 +280,12 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expectWriteNoChange(1, common.PREV_INDEX_MANIFEST_FILENAME);
|
||||
expectWriteNoChange(2, common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
// prev first, then alphabetical
|
||||
expect(coreWarningSpy).toHaveBeenNthCalledWith(1, expect.stringContaining(`Not found in base manifest:`));
|
||||
expect(coreWarningSpy).toHaveBeenNthCalledWith(2, expect.stringContaining(`Not found in base manifest:`));
|
||||
expect(coreWarningSpy).toHaveBeenNthCalledWith(3, expect.stringContaining(`Not found in base manifest:`));
|
||||
expect(coreWarningSpy).toHaveBeenNthCalledWith(1, expect.stringContaining("Not found in base manifest:"));
|
||||
expect(coreWarningSpy).toHaveBeenNthCalledWith(2, expect.stringContaining("Not found in base manifest:"));
|
||||
expect(coreWarningSpy).toHaveBeenNthCalledWith(3, expect.stringContaining("Not found in base manifest:"));
|
||||
});
|
||||
|
||||
it('moves image not in manifest', async () => {
|
||||
it("moves image not in manifest", async () => {
|
||||
const oldPath = useImage(IMAGE_V12_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
|
||||
// @ts-expect-error mocked as needed
|
||||
@@ -290,7 +301,7 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expectWriteNoChange(3, common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
});
|
||||
|
||||
it('moves multiple images not in manifest', async () => {
|
||||
it("moves multiple images not in manifest", async () => {
|
||||
const oldPath1 = useImage(IMAGE_V13_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
const oldPath2 = useImage(IMAGE_V12_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
const oldPath3 = useImage(IMAGE_V12_1, PREV_IMAGES_TEST_DIR_PATH);
|
||||
@@ -315,7 +326,7 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expectWriteNoChange(4, common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
});
|
||||
|
||||
it('removes invalid not in manifest even if remove disabled', async () => {
|
||||
it("removes invalid not in manifest even if remove disabled", async () => {
|
||||
const oldPath = useImage(IMAGE_INVALID, BASE_IMAGES_TEST_DIR_PATH);
|
||||
|
||||
// @ts-expect-error mocked as needed
|
||||
@@ -326,12 +337,12 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(1, common.PREV_INDEX_MANIFEST_FILENAME, []);
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(2, common.BASE_INDEX_MANIFEST_FILENAME, []);
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining(`Removing`));
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining(`Not a valid OTA file`));
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith(expect.stringContaining(`Not found in base manifest`));
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Removing"));
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Not a valid OTA file"));
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith(expect.stringContaining("Not found in base manifest"));
|
||||
});
|
||||
|
||||
it('removes invalid in manifest', async () => {
|
||||
it("removes invalid in manifest", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_INVALID_METAS]);
|
||||
const oldPath = useImage(IMAGE_INVALID, BASE_IMAGES_TEST_DIR_PATH);
|
||||
|
||||
@@ -343,11 +354,11 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(1, common.PREV_INDEX_MANIFEST_FILENAME, []);
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(2, common.BASE_INDEX_MANIFEST_FILENAME, []);
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining(`Removing`));
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining(`Not a valid OTA file`));
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Removing"));
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Not a valid OTA file"));
|
||||
});
|
||||
|
||||
it('keeps image and rewrites manifest', async () => {
|
||||
it("keeps image and rewrites manifest", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [withOldMetas(IMAGE_V14_1_METAS)]);
|
||||
const imagePath = useImage(IMAGE_V14_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
|
||||
@@ -361,11 +372,11 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(2, common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V14_1_METAS]);
|
||||
});
|
||||
|
||||
it('keeps image with escaped url and rewrites manifest', async () => {
|
||||
it("keeps image with escaped url and rewrites manifest", async () => {
|
||||
const oldMetas = withOldMetas(IMAGE_V14_1_METAS);
|
||||
const fileName = oldMetas.url.split('/').pop()!;
|
||||
const newName = fileName.replace('.ota', `(%1).ota`);
|
||||
const baseUrl = oldMetas.url.replace(fileName, '');
|
||||
const fileName = oldMetas.url.split("/").pop()!;
|
||||
const newName = fileName.replace(".ota", "(%1).ota");
|
||||
const baseUrl = oldMetas.url.replace(fileName, "");
|
||||
oldMetas.url = baseUrl + encodeURIComponent(newName);
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [oldMetas]);
|
||||
const imagePath = useImage(IMAGE_V14_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
@@ -385,11 +396,12 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
// @ts-expect-error override
|
||||
{fileName: newName, url: `${baseUrl}${encodeURIComponent(newName)}`},
|
||||
);
|
||||
// biome-ignore lint/performance/noDelete: <explanation>
|
||||
delete outManifestMetas.originalUrl;
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(2, common.BASE_INDEX_MANIFEST_FILENAME, [outManifestMetas]);
|
||||
});
|
||||
|
||||
it('ignores when same images referenced multiple times in manifest', async () => {
|
||||
it("ignores when same images referenced multiple times in manifest", async () => {
|
||||
const oldMetas1 = withOldMetas(IMAGE_V14_1_METAS);
|
||||
const oldMetas2 = withOldMetas(IMAGE_V14_1_METAS);
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [oldMetas1, oldMetas2]);
|
||||
@@ -408,9 +420,9 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps same images referenced multiple times in manifest with different extra metas', async () => {
|
||||
const oldMetas1 = withExtraMetas(withOldMetas(IMAGE_V14_1_METAS), {modelId: 'test1'});
|
||||
const oldMetas2 = withExtraMetas(withOldMetas(IMAGE_V14_1_METAS), {modelId: 'test2'});
|
||||
it("keeps same images referenced multiple times in manifest with different extra metas", async () => {
|
||||
const oldMetas1 = withExtraMetas(withOldMetas(IMAGE_V14_1_METAS), {modelId: "test1"});
|
||||
const oldMetas2 = withExtraMetas(withOldMetas(IMAGE_V14_1_METAS), {modelId: "test2"});
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [oldMetas1, oldMetas2]);
|
||||
const image1Path = useImage(IMAGE_V14_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
|
||||
@@ -422,28 +434,30 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(1, common.PREV_INDEX_MANIFEST_FILENAME, []);
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(2, common.BASE_INDEX_MANIFEST_FILENAME, [
|
||||
withExtraMetas(IMAGE_V14_1_METAS, {modelId: 'test1'}),
|
||||
withExtraMetas(IMAGE_V14_1_METAS, {modelId: 'test2'}),
|
||||
withExtraMetas(IMAGE_V14_1_METAS, {modelId: "test1"}),
|
||||
withExtraMetas(IMAGE_V14_1_METAS, {modelId: "test2"}),
|
||||
]);
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`found multiple times in ${common.BASE_INDEX_MANIFEST_FILENAME} manifest`),
|
||||
);
|
||||
});
|
||||
|
||||
describe('downloads', () => {
|
||||
let fetchSpy: jest.SpyInstance;
|
||||
let setTimeoutSpy: jest.SpyInstance;
|
||||
describe("downloads", () => {
|
||||
let fetchSpy: MockInstance;
|
||||
let setTimeoutSpy: MockInstance;
|
||||
let fetchReturnedStatus: {ok: boolean; status: number; body?: object} = {ok: true, status: 200, body: {}};
|
||||
const get3rdPartyDir = jest.fn().mockReturnValue(IMAGES_TEST_DIR);
|
||||
const get3rdPartyDir = vi.fn().mockReturnValue(IMAGES_TEST_DIR);
|
||||
|
||||
const adaptUrl = (originalUrl: string, manifestName: string): string => {
|
||||
if (manifestName === common.BASE_INDEX_MANIFEST_FILENAME) {
|
||||
return originalUrl.replace(`/${common.PREV_IMAGES_DIR}/`, `/${common.BASE_IMAGES_DIR}/`);
|
||||
} else if (manifestName === common.PREV_INDEX_MANIFEST_FILENAME) {
|
||||
return originalUrl.replace(`/${common.BASE_IMAGES_DIR}/`, `/${common.PREV_IMAGES_DIR}/`);
|
||||
} else {
|
||||
throw new Error(`Not supported: ${manifestName}`);
|
||||
}
|
||||
|
||||
if (manifestName === common.PREV_INDEX_MANIFEST_FILENAME) {
|
||||
return originalUrl.replace(`/${common.BASE_IMAGES_DIR}/`, `/${common.PREV_IMAGES_DIR}/`);
|
||||
}
|
||||
|
||||
throw new Error(`Not supported: ${manifestName}`);
|
||||
};
|
||||
|
||||
afterAll(() => {
|
||||
@@ -452,9 +466,12 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.NODE_EXTRA_CA_CERTS = 'cacerts.pem';
|
||||
process.env.NODE_EXTRA_CA_CERTS = "cacerts.pem";
|
||||
|
||||
get3rdPartyDir.mockClear();
|
||||
|
||||
fetchReturnedStatus = {ok: true, status: 200, body: {}};
|
||||
fetchSpy = jest.spyOn(global, 'fetch').mockImplementation(
|
||||
fetchSpy = vi.spyOn(global, "fetch").mockImplementation(
|
||||
// @ts-expect-error mocked as needed
|
||||
(input) => {
|
||||
return {
|
||||
@@ -462,11 +479,11 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
status: fetchReturnedStatus.status,
|
||||
body: fetchReturnedStatus.body,
|
||||
// @ts-expect-error Buffer <> ArrayBuffer (props not used)
|
||||
arrayBuffer: (): ArrayBuffer => readFileSync(getImageOriginalDirPath((input as string).split('/').pop()!)),
|
||||
arrayBuffer: (): ArrayBuffer => readFileSync(getImageOriginalDirPath((input as string).split("/").pop()!)),
|
||||
};
|
||||
},
|
||||
);
|
||||
setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(
|
||||
setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation(
|
||||
// @ts-expect-error mock
|
||||
(fn) => {
|
||||
fn();
|
||||
@@ -474,18 +491,18 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('failure without CA Certificates ENV', async () => {
|
||||
process.env.NODE_EXTRA_CA_CERTS = '';
|
||||
it("failure without CA Certificates ENV", async () => {
|
||||
process.env.NODE_EXTRA_CA_CERTS = "";
|
||||
|
||||
await expect(async () => {
|
||||
// @ts-expect-error mocked as needed
|
||||
await reProcessAllImages(github, core, context, true, false, get3rdPartyDir);
|
||||
}).rejects.toThrow(
|
||||
expect.objectContaining({message: expect.stringContaining(`Download 3rd Parties requires \`NODE_EXTRA_CA_CERTS=cacerts.pem\``)}),
|
||||
expect.objectContaining({message: expect.stringContaining("Download 3rd Parties requires `NODE_EXTRA_CA_CERTS=cacerts.pem`")}),
|
||||
);
|
||||
});
|
||||
|
||||
it('failure with malformed metas', async () => {
|
||||
it("failure with malformed metas", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [
|
||||
// @ts-expect-error old metas
|
||||
{
|
||||
@@ -493,9 +510,9 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
fileSize: 307682,
|
||||
manufacturerCode: 4417,
|
||||
imageType: 54179,
|
||||
modelId: 'TS011F',
|
||||
sha512: '01939ca4fc790432d2c233e19b2440c1e0248d2ce85c9299e0b88928cb2341de675350ac7b78187a25f06a2768f93db0a17c4ba950b60c82c072e0c0833cfcfb',
|
||||
url: '', // not undefined to pass setManifest
|
||||
modelId: "TS011F",
|
||||
sha512: "01939ca4fc790432d2c233e19b2440c1e0248d2ce85c9299e0b88928cb2341de675350ac7b78187a25f06a2768f93db0a17c4ba950b60c82c072e0c0833cfcfb",
|
||||
url: "", // not undefined to pass setManifest
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -509,10 +526,10 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expectWriteNoChange(2, common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
expectWriteNoChange(3, common.PREV_INDEX_MANIFEST_FILENAME);
|
||||
expectWriteNoChange(4, common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining(`Ignoring malformed`));
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Ignoring malformed"));
|
||||
});
|
||||
|
||||
it('failure from fetch ok', async () => {
|
||||
it("failure from fetch ok", async () => {
|
||||
setManifest(
|
||||
common.BASE_INDEX_MANIFEST_FILENAME,
|
||||
// @ts-expect-error old metas
|
||||
@@ -535,7 +552,7 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores urls from this repo', async () => {
|
||||
it("ignores urls from this repo", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V14_1_METAS]);
|
||||
// prevent trigger removal because of missing file
|
||||
useImage(IMAGE_V14_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
@@ -552,7 +569,7 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expectWriteNoChange(4, common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
});
|
||||
|
||||
it('ignores urls with no out dir specified', async () => {
|
||||
it("ignores urls with no out dir specified", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [
|
||||
// @ts-expect-error old metas
|
||||
{
|
||||
@@ -560,9 +577,9 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
fileSize: 307682,
|
||||
manufacturerCode: 4417,
|
||||
imageType: 54179,
|
||||
modelId: 'TS011F',
|
||||
sha512: '01939ca4fc790432d2c233e19b2440c1e0248d2ce85c9299e0b88928cb2341de675350ac7b78187a25f06a2768f93db0a17c4ba950b60c82c072e0c0833cfcfb',
|
||||
url: 'https://www.elektroimportoren.no/docs/lib/4512772-Firmware-35.ota',
|
||||
modelId: "TS011F",
|
||||
sha512: "01939ca4fc790432d2c233e19b2440c1e0248d2ce85c9299e0b88928cb2341de675350ac7b78187a25f06a2768f93db0a17c4ba950b60c82c072e0c0833cfcfb",
|
||||
url: "https://www.elektroimportoren.no/docs/lib/4512772-Firmware-35.ota",
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -576,10 +593,10 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expectWriteNoChange(2, common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
expectWriteNoChange(3, common.PREV_INDEX_MANIFEST_FILENAME);
|
||||
expectWriteNoChange(4, common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith(expect.stringContaining(`no out dir specified`));
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith(expect.stringContaining("no out dir specified"));
|
||||
});
|
||||
|
||||
it('ignores invalid OTA file', async () => {
|
||||
it("ignores invalid OTA file", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [
|
||||
Object.assign({}, withOldMetas(IMAGE_INVALID_METAS), {
|
||||
url: `https://images.tuyaeu.com/smart/firmware/upgrade/20220907/${IMAGES_TEST_DIR}/${IMAGE_INVALID}`,
|
||||
@@ -596,11 +613,11 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expectWriteNoChange(2, common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
expectWriteNoChange(3, common.PREV_INDEX_MANIFEST_FILENAME);
|
||||
expectWriteNoChange(4, common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining(`Ignoring`));
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining(`Not a valid OTA file`));
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Ignoring"));
|
||||
expect(coreErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Not a valid OTA file"));
|
||||
});
|
||||
|
||||
it('ignores identical image', async () => {
|
||||
it("ignores identical image", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [
|
||||
IMAGE_V14_1_METAS,
|
||||
Object.assign({}, withOldMetas(IMAGE_V14_1_METAS), {
|
||||
@@ -618,10 +635,10 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expect(addImageToPrevSpy).toHaveBeenCalledTimes(0);
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(1, common.PREV_INDEX_MANIFEST_FILENAME, []);
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(2, common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V14_1_METAS]);
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith(expect.stringContaining(`Conflict with image at index \`0\``));
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith(expect.stringContaining("Conflict with image at index `0`"));
|
||||
});
|
||||
|
||||
it('success without mocked get3rdPartyDir', async () => {
|
||||
it("success without mocked get3rdPartyDir", async () => {
|
||||
// NOTE: this is using a name (ZLinky_router_v13.ota) and out dir (Hue) that is unlikely to ever be in conflict with actual Hue images
|
||||
setManifest(
|
||||
common.BASE_INDEX_MANIFEST_FILENAME,
|
||||
@@ -641,14 +658,14 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
withExtraMetas(OLD_META_3RD_PARTY_1_REAL_METAS, {
|
||||
originalUrl: OLD_META_3RD_PARTY_1_METAS.url,
|
||||
// @ts-expect-error override
|
||||
url: adaptUrl(OLD_META_3RD_PARTY_1_REAL_METAS.url, common.BASE_INDEX_MANIFEST_FILENAME).replace(IMAGES_TEST_DIR, 'Hue'),
|
||||
url: adaptUrl(OLD_META_3RD_PARTY_1_REAL_METAS.url, common.BASE_INDEX_MANIFEST_FILENAME).replace(IMAGES_TEST_DIR, "Hue"),
|
||||
}),
|
||||
]);
|
||||
|
||||
rmSync(path.join(common.BASE_IMAGES_DIR, 'Hue', OLD_META_3RD_PARTY_1_REAL_IMAGE));
|
||||
rmSync(path.join(common.BASE_IMAGES_DIR, "Hue", OLD_META_3RD_PARTY_1_REAL_IMAGE));
|
||||
});
|
||||
|
||||
it('success with add different metas and ignored', async () => {
|
||||
it("success with add different metas and ignored", async () => {
|
||||
setManifest(
|
||||
common.BASE_INDEX_MANIFEST_FILENAME,
|
||||
// @ts-expect-error old metas
|
||||
@@ -683,7 +700,7 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith(expect.stringContaining(`Removing ignored '${OLD_META_3RD_PARTY_IGNORED_METAS.url}'`));
|
||||
});
|
||||
|
||||
it('success with add+move same and ignored', async () => {
|
||||
it("success with add+move same and ignored", async () => {
|
||||
setManifest(
|
||||
common.BASE_INDEX_MANIFEST_FILENAME,
|
||||
// @ts-expect-error old metas
|
||||
@@ -715,7 +732,7 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expect(coreWarningSpy).toHaveBeenCalledWith(expect.stringContaining(`Removing ignored '${OLD_META_3RD_PARTY_IGNORED_METAS.url}'`));
|
||||
});
|
||||
|
||||
it('success with add to prev', async () => {
|
||||
it("success with add to prev", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [
|
||||
IMAGE_V14_1_METAS,
|
||||
// @ts-expect-error old metas
|
||||
@@ -746,16 +763,16 @@ describe('Github Workflow: Re-Process All Images', () => {
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(4, common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V14_1_METAS]);
|
||||
});
|
||||
|
||||
it('success with escaped', async () => {
|
||||
it("success with escaped", async () => {
|
||||
const oldMetas = structuredClone(OLD_META_3RD_PARTY_1_METAS);
|
||||
const fileName = oldMetas.url.split('/').pop()!;
|
||||
const newName = fileName.replace('.ota', `(%1).ota`);
|
||||
const baseUrl = oldMetas.url.replace(fileName, '');
|
||||
const fileName = oldMetas.url.split("/").pop()!;
|
||||
const newName = fileName.replace(".ota", "(%1).ota");
|
||||
const baseUrl = oldMetas.url.replace(fileName, "");
|
||||
oldMetas.url = baseUrl + encodeURIComponent(newName);
|
||||
// @ts-expect-error old metas
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [oldMetas]);
|
||||
// link back to existing image from fetch
|
||||
fetchSpy = jest.spyOn(global, 'fetch').mockImplementationOnce(
|
||||
fetchSpy = vi.spyOn(global, "fetch").mockImplementationOnce(
|
||||
// @ts-expect-error mocked as needed
|
||||
() => {
|
||||
return {
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
import type CoreApi from '@actions/core';
|
||||
import type {Context} from '@actions/github/lib/context';
|
||||
import type {Octokit} from '@octokit/rest';
|
||||
import type CoreApi from "@actions/core";
|
||||
import type {Context} from "@actions/github/lib/context";
|
||||
import type {Octokit} from "@octokit/rest";
|
||||
|
||||
import type {RepoImageMeta} from '../src/types';
|
||||
import type {RepoImageMeta} from "../src/types";
|
||||
|
||||
import {rmSync} from 'fs';
|
||||
import {rmSync} from "node:fs";
|
||||
|
||||
import * as common from '../src/common';
|
||||
import {updateManifests} from '../src/ghw_update_manifests';
|
||||
import {type MockInstance, afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
|
||||
import * as common from "../src/common";
|
||||
import {updateManifests} from "../src/ghw_update_manifests";
|
||||
import {
|
||||
BASE_IMAGES_TEST_DIR_PATH,
|
||||
getAdjustedContent,
|
||||
IMAGE_V13_1,
|
||||
IMAGE_V14_1,
|
||||
IMAGE_V14_1_METAS,
|
||||
PREV_IMAGES_TEST_DIR_PATH,
|
||||
getAdjustedContent,
|
||||
useImage,
|
||||
withExtraMetas,
|
||||
} from './data.test';
|
||||
} from "./data.test";
|
||||
|
||||
const github = {
|
||||
rest: {
|
||||
pulls: {
|
||||
get: jest.fn<ReturnType<Octokit['rest']['pulls']['get']>, Parameters<Octokit['rest']['pulls']['get']>, unknown>(),
|
||||
get: vi.fn<(...args: Parameters<Octokit["rest"]["pulls"]["get"]>) => ReturnType<Octokit["rest"]["pulls"]["get"]>>(),
|
||||
},
|
||||
repos: {
|
||||
compareCommitsWithBasehead: jest.fn<
|
||||
ReturnType<Octokit['rest']['repos']['compareCommitsWithBasehead']>,
|
||||
Parameters<Octokit['rest']['repos']['compareCommitsWithBasehead']>,
|
||||
unknown
|
||||
compareCommitsWithBasehead:
|
||||
vi.fn<
|
||||
(
|
||||
...args: Parameters<Octokit["rest"]["repos"]["compareCommitsWithBasehead"]>
|
||||
) => ReturnType<Octokit["rest"]["repos"]["compareCommitsWithBasehead"]>
|
||||
>(),
|
||||
},
|
||||
},
|
||||
@@ -39,40 +41,42 @@ const core: Partial<typeof CoreApi> = {
|
||||
warning: console.warn,
|
||||
error: console.error,
|
||||
notice: console.log,
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
startGroup: vi.fn(),
|
||||
endGroup: vi.fn(),
|
||||
};
|
||||
const context: Partial<Context> = {
|
||||
eventName: 'push',
|
||||
eventName: "push",
|
||||
payload: {
|
||||
head_commit: {
|
||||
message: 'push from pr (#213)',
|
||||
message: "push from pr (#213)",
|
||||
},
|
||||
},
|
||||
repo: {
|
||||
owner: 'Koenkk',
|
||||
repo: 'zigbee-OTA',
|
||||
owner: "Koenkk",
|
||||
repo: "zigbee-OTA",
|
||||
},
|
||||
};
|
||||
|
||||
describe('Github Workflow: Update manifests', () => {
|
||||
describe("Github Workflow: Update manifests", () => {
|
||||
let baseManifest: RepoImageMeta[];
|
||||
let prevManifest: RepoImageMeta[];
|
||||
let readManifestSpy: jest.SpyInstance;
|
||||
let writeManifestSpy: jest.SpyInstance;
|
||||
let addImageToBaseSpy: jest.SpyInstance;
|
||||
let addImageToPrevSpy: jest.SpyInstance;
|
||||
let readManifestSpy: MockInstance;
|
||||
let writeManifestSpy: MockInstance;
|
||||
let addImageToBaseSpy: MockInstance;
|
||||
let addImageToPrevSpy: MockInstance;
|
||||
let filePaths: ReturnType<typeof useImage>[] = [];
|
||||
let prBody: string | undefined;
|
||||
|
||||
const getManifest = (fileName: string): RepoImageMeta[] => {
|
||||
if (fileName === common.BASE_INDEX_MANIFEST_FILENAME) {
|
||||
return baseManifest;
|
||||
} else if (fileName === common.PREV_INDEX_MANIFEST_FILENAME) {
|
||||
return prevManifest;
|
||||
} else {
|
||||
throw new Error(`${fileName} not supported`);
|
||||
}
|
||||
|
||||
if (fileName === common.PREV_INDEX_MANIFEST_FILENAME) {
|
||||
return prevManifest;
|
||||
}
|
||||
|
||||
throw new Error(`${fileName} not supported`);
|
||||
};
|
||||
|
||||
const setManifest = (fileName: string, content: RepoImageMeta[]): void => {
|
||||
@@ -92,7 +96,7 @@ describe('Github Workflow: Update manifests', () => {
|
||||
prevManifest = [];
|
||||
};
|
||||
|
||||
const expectNoChanges = (noReadManifest: boolean = false): void => {
|
||||
const expectNoChanges = (noReadManifest = false): void => {
|
||||
if (noReadManifest) {
|
||||
expect(readManifestSpy).toHaveBeenCalledTimes(0);
|
||||
} else {
|
||||
@@ -119,10 +123,10 @@ describe('Github Workflow: Update manifests', () => {
|
||||
resetManifests();
|
||||
|
||||
filePaths = [];
|
||||
readManifestSpy = jest.spyOn(common, 'readManifest').mockImplementation(getManifest);
|
||||
writeManifestSpy = jest.spyOn(common, 'writeManifest').mockImplementation(setManifest);
|
||||
addImageToBaseSpy = jest.spyOn(common, 'addImageToBase');
|
||||
addImageToPrevSpy = jest.spyOn(common, 'addImageToPrev');
|
||||
readManifestSpy = vi.spyOn(common, "readManifest").mockImplementation(getManifest);
|
||||
writeManifestSpy = vi.spyOn(common, "writeManifest").mockImplementation(setManifest);
|
||||
addImageToBaseSpy = vi.spyOn(common, "addImageToBase");
|
||||
addImageToPrevSpy = vi.spyOn(common, "addImageToPrev");
|
||||
github.rest.pulls.get.mockImplementation(
|
||||
// @ts-expect-error mock
|
||||
() => ({data: {body: prBody}}),
|
||||
@@ -139,29 +143,29 @@ describe('Github Workflow: Update manifests', () => {
|
||||
rmSync(common.PR_ARTIFACT_DIR, {recursive: true, force: true});
|
||||
});
|
||||
|
||||
it('hard failure from outside push context', async () => {
|
||||
it("hard failure from outside push context", async () => {
|
||||
filePaths = [useImage(IMAGE_V14_1)];
|
||||
|
||||
await expect(async () => {
|
||||
// @ts-expect-error mock
|
||||
await updateManifests(github, core, {payload: {}});
|
||||
}).rejects.toThrow(`Not a push`);
|
||||
}).rejects.toThrow("Not a push");
|
||||
|
||||
expectNoChanges(true);
|
||||
});
|
||||
|
||||
it('failure with file outside of images directory', async () => {
|
||||
it("failure with file outside of images directory", async () => {
|
||||
filePaths = [useImage(IMAGE_V13_1, PREV_IMAGES_TEST_DIR_PATH), useImage(IMAGE_V14_1)];
|
||||
|
||||
await expect(async () => {
|
||||
// @ts-expect-error mock
|
||||
await updateManifests(github, core, context);
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining(`Cannot run with files outside`)}));
|
||||
}).rejects.toThrow(expect.objectContaining({message: expect.stringContaining("Cannot run with files outside")}));
|
||||
|
||||
expectNoChanges(true);
|
||||
});
|
||||
|
||||
it('success into base', async () => {
|
||||
it("success into base", async () => {
|
||||
filePaths = [useImage(IMAGE_V14_1)];
|
||||
|
||||
// @ts-expect-error mock
|
||||
@@ -175,7 +179,7 @@ describe('Github Workflow: Update manifests', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V14_1_METAS]);
|
||||
});
|
||||
|
||||
it('success with extra metas', async () => {
|
||||
it("success with extra metas", async () => {
|
||||
filePaths = [useImage(IMAGE_V14_1)];
|
||||
prBody = `Text before start tag \`\`\`json {"manufacturerName": ["lixee"]} \`\`\` Text after end tag`;
|
||||
|
||||
@@ -188,18 +192,18 @@ describe('Github Workflow: Update manifests', () => {
|
||||
expect(addImageToPrevSpy).toHaveBeenCalledTimes(0);
|
||||
expect(writeManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.BASE_INDEX_MANIFEST_FILENAME, [
|
||||
withExtraMetas(IMAGE_V14_1_METAS, {manufacturerName: ['lixee']}),
|
||||
withExtraMetas(IMAGE_V14_1_METAS, {manufacturerName: ["lixee"]}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('fails to get PR for extra metas', async () => {
|
||||
it("fails to get PR for extra metas", async () => {
|
||||
filePaths = [useImage(IMAGE_V14_1)];
|
||||
github.rest.pulls.get.mockRejectedValueOnce('403');
|
||||
github.rest.pulls.get.mockRejectedValueOnce("403");
|
||||
|
||||
await expect(async () => {
|
||||
// @ts-expect-error mock
|
||||
await updateManifests(github, core, context);
|
||||
}).rejects.toThrow(expect.objectContaining({message: `Failed to get PR#213 for extra metas: 403`}));
|
||||
}).rejects.toThrow(expect.objectContaining({message: "Failed to get PR#213 for extra metas: 403"}));
|
||||
|
||||
expectNoChanges(false);
|
||||
});
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
/**
|
||||
* For a detailed explanation regarding each configuration property, visit:
|
||||
* https://jestjs.io/docs/configuration
|
||||
*/
|
||||
|
||||
// import type {Config} from 'jest';
|
||||
|
||||
import {createDefaultEsmPreset, JestConfigWithTsJest} from 'ts-jest';
|
||||
|
||||
const defaultEsmPreset = createDefaultEsmPreset();
|
||||
|
||||
const config: JestConfigWithTsJest = {
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
// automock: false,
|
||||
|
||||
// Stop running tests after `n` failures
|
||||
// bail: 0,
|
||||
|
||||
// The directory where Jest should store its cached dependency information
|
||||
cacheDirectory: '.jest-tmp',
|
||||
|
||||
// Automatically clear mock calls, instances, contexts and results before every test
|
||||
clearMocks: true,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
collectCoverage: false,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
collectCoverageFrom: [
|
||||
'src/ghw_check_ota_pr.ts',
|
||||
'src/ghw_get_changed_ota_files.ts',
|
||||
'src/ghw_process_ota_files.ts',
|
||||
'src/process_firmware_image.ts',
|
||||
'src/ghw_reprocess_all_images.ts',
|
||||
],
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
coverageDirectory: 'coverage',
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
// "\\\\node_modules\\\\"
|
||||
// ],
|
||||
|
||||
// Indicates which provider should be used to instrument code for coverage
|
||||
coverageProvider: 'babel',
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
coverageReporters: [
|
||||
// "json",
|
||||
// "text",
|
||||
'lcov',
|
||||
// "clover"
|
||||
],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 100,
|
||||
functions: 100,
|
||||
lines: 100,
|
||||
statements: 100,
|
||||
},
|
||||
},
|
||||
|
||||
// A path to a custom dependency extractor
|
||||
// dependencyExtractor: undefined,
|
||||
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// The default configuration for fake timers
|
||||
// fakeTimers: {
|
||||
// "enableGlobally": false
|
||||
// },
|
||||
|
||||
// Force coverage collection from ignored files using an array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
// A path to a module which exports an async function that is triggered once before all test suites
|
||||
// globalSetup: undefined,
|
||||
|
||||
// A path to a module which exports an async function that is triggered once after all test suites
|
||||
// globalTeardown: undefined,
|
||||
|
||||
// A set of global variables that need to be available in all test environments
|
||||
// globals: {},
|
||||
|
||||
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||
maxWorkers: '50%',
|
||||
|
||||
// An array of directory names to be searched recursively up from the requiring module's location
|
||||
// moduleDirectories: [
|
||||
// "node_modules"
|
||||
// ],
|
||||
|
||||
// An array of file extensions your modules use
|
||||
moduleFileExtensions: [
|
||||
// commonly used first
|
||||
'ts',
|
||||
'json',
|
||||
'js',
|
||||
'mjs',
|
||||
'cjs',
|
||||
'jsx',
|
||||
'tsx',
|
||||
'node',
|
||||
],
|
||||
|
||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||
// moduleNameMapper: {},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
// modulePathIgnorePatterns: [],
|
||||
|
||||
// Activates notifications for test results
|
||||
// notify: false,
|
||||
|
||||
// An enum that specifies notification mode. Requires { notify: true }
|
||||
// notifyMode: "failure-change",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
// preset: undefined,
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: undefined,
|
||||
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state before every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
// resolver: undefined,
|
||||
|
||||
// Automatically restore mock state and implementation before every test
|
||||
// restoreMocks: false,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
rootDir: '..',
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
// roots: [
|
||||
// "<rootDir>"
|
||||
// ],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
// setupFiles: [],
|
||||
|
||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||
// setupFilesAfterEnv: [],
|
||||
|
||||
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
||||
// slowTestThreshold: 5,
|
||||
|
||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
// testEnvironment: "jest-environment-node",
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
// Adds a location field to test results
|
||||
// testLocationInResults: false,
|
||||
|
||||
// The glob patterns Jest uses to detect test files
|
||||
// testMatch: [
|
||||
// "**/__tests__/**/*.[jt]s?(x)",
|
||||
// "**/?(*.)+(spec|test).[tj]s?(x)"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "\\\\node_modules\\\\"
|
||||
// ],
|
||||
|
||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||
// testRegex: [],
|
||||
|
||||
// This option allows the use of a custom results processor
|
||||
// testResultsProcessor: undefined,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jest-circus/runner",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
// transform: undefined,
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
// transformIgnorePatterns: [
|
||||
// "\\\\node_modules\\\\",
|
||||
// "\\.pnp\\.[^\\\\]+$"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
// Indicates whether each individual test should be reported during the run
|
||||
// verbose: undefined,
|
||||
|
||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||
// watchPathIgnorePatterns: [],
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
|
||||
...defaultEsmPreset,
|
||||
moduleNameMapper: {
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,13 +1,13 @@
|
||||
import type {RepoImageMeta} from '../src/types';
|
||||
import type {RepoImageMeta} from "../src/types";
|
||||
|
||||
import {existsSync, mkdirSync, readFileSync, rmSync} from 'fs';
|
||||
import {existsSync, mkdirSync, readFileSync, rmSync} from "node:fs";
|
||||
|
||||
import * as common from '../src/common';
|
||||
import {processFirmwareImage, ProcessFirmwareImageStatus} from '../src/process_firmware_image';
|
||||
import {type MockInstance, afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
|
||||
import * as common from "../src/common";
|
||||
import {ProcessFirmwareImageStatus, processFirmwareImage} from "../src/process_firmware_image";
|
||||
import {
|
||||
BASE_IMAGES_TEST_DIR_PATH,
|
||||
getAdjustedContent,
|
||||
getImageOriginalDirPath,
|
||||
IMAGES_TEST_DIR,
|
||||
IMAGE_INVALID,
|
||||
IMAGE_TAR,
|
||||
IMAGE_TAR_METAS,
|
||||
@@ -17,33 +17,36 @@ import {
|
||||
IMAGE_V13_1_METAS,
|
||||
IMAGE_V14_1,
|
||||
IMAGE_V14_1_METAS,
|
||||
IMAGES_TEST_DIR,
|
||||
PREV_IMAGES_TEST_DIR_PATH,
|
||||
getAdjustedContent,
|
||||
getImageOriginalDirPath,
|
||||
useImage,
|
||||
withExtraMetas,
|
||||
} from './data.test';
|
||||
} from "./data.test";
|
||||
|
||||
describe('Process Firmware Image', () => {
|
||||
describe("Process Firmware Image", () => {
|
||||
let baseManifest: RepoImageMeta[];
|
||||
let prevManifest: RepoImageMeta[];
|
||||
let consoleErrorSpy: jest.SpyInstance;
|
||||
let consoleLogSpy: jest.SpyInstance;
|
||||
let readManifestSpy: jest.SpyInstance;
|
||||
let writeManifestSpy: jest.SpyInstance;
|
||||
let addImageToBaseSpy: jest.SpyInstance;
|
||||
let addImageToPrevSpy: jest.SpyInstance;
|
||||
let fetchSpy: jest.SpyInstance;
|
||||
let setTimeoutSpy: jest.SpyInstance;
|
||||
let consoleErrorSpy: MockInstance;
|
||||
let consoleLogSpy: MockInstance;
|
||||
let readManifestSpy: MockInstance;
|
||||
let writeManifestSpy: MockInstance;
|
||||
let addImageToBaseSpy: MockInstance;
|
||||
let addImageToPrevSpy: MockInstance;
|
||||
let fetchSpy: MockInstance;
|
||||
let setTimeoutSpy: MockInstance;
|
||||
let fetchReturnedStatus: {ok: boolean; status: number; body?: object} = {ok: true, status: 200, body: {}};
|
||||
|
||||
const getManifest = (fileName: string): RepoImageMeta[] => {
|
||||
if (fileName === common.BASE_INDEX_MANIFEST_FILENAME) {
|
||||
return baseManifest;
|
||||
} else if (fileName === common.PREV_INDEX_MANIFEST_FILENAME) {
|
||||
return prevManifest;
|
||||
} else {
|
||||
throw new Error(`${fileName} not supported`);
|
||||
}
|
||||
|
||||
if (fileName === common.PREV_INDEX_MANIFEST_FILENAME) {
|
||||
return prevManifest;
|
||||
}
|
||||
|
||||
throw new Error(`${fileName} not supported`);
|
||||
};
|
||||
|
||||
const setManifest = (fileName: string, content: RepoImageMeta[]): void => {
|
||||
@@ -71,7 +74,7 @@ describe('Process Firmware Image', () => {
|
||||
return newMeta;
|
||||
};
|
||||
|
||||
const expectNoChanges = (noReadManifest: boolean = false): void => {
|
||||
const expectNoChanges = (noReadManifest = false): void => {
|
||||
if (noReadManifest) {
|
||||
expect(readManifestSpy).toHaveBeenCalledTimes(0);
|
||||
} else {
|
||||
@@ -84,7 +87,7 @@ describe('Process Firmware Image', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledTimes(0);
|
||||
};
|
||||
|
||||
const expectWriteNoChanges = (inBase: boolean = true, inPrev: boolean = true): void => {
|
||||
const expectWriteNoChanges = (inBase = true, inPrev = true): void => {
|
||||
if (inBase) {
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
@@ -115,19 +118,20 @@ describe('Process Firmware Image', () => {
|
||||
setTimeoutSpy.mockRestore();
|
||||
rmSync(BASE_IMAGES_TEST_DIR_PATH, {recursive: true, force: true});
|
||||
rmSync(PREV_IMAGES_TEST_DIR_PATH, {recursive: true, force: true});
|
||||
rmSync(IMAGES_TEST_DIR, {recursive: true, force: true});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
resetManifests();
|
||||
|
||||
fetchReturnedStatus = {ok: true, status: 200, body: {}};
|
||||
consoleErrorSpy = jest.spyOn(console, 'error');
|
||||
consoleLogSpy = jest.spyOn(console, 'log');
|
||||
readManifestSpy = jest.spyOn(common, 'readManifest').mockImplementation(getManifest);
|
||||
writeManifestSpy = jest.spyOn(common, 'writeManifest').mockImplementation(setManifest);
|
||||
addImageToBaseSpy = jest.spyOn(common, 'addImageToBase');
|
||||
addImageToPrevSpy = jest.spyOn(common, 'addImageToPrev');
|
||||
fetchSpy = jest.spyOn(global, 'fetch').mockImplementation(
|
||||
consoleErrorSpy = vi.spyOn(console, "error");
|
||||
consoleLogSpy = vi.spyOn(console, "log");
|
||||
readManifestSpy = vi.spyOn(common, "readManifest").mockImplementation(getManifest);
|
||||
writeManifestSpy = vi.spyOn(common, "writeManifest").mockImplementation(setManifest);
|
||||
addImageToBaseSpy = vi.spyOn(common, "addImageToBase");
|
||||
addImageToPrevSpy = vi.spyOn(common, "addImageToPrev");
|
||||
fetchSpy = vi.spyOn(global, "fetch").mockImplementation(
|
||||
// @ts-expect-error mocked as needed
|
||||
(input) => {
|
||||
return {
|
||||
@@ -139,7 +143,7 @@ describe('Process Firmware Image', () => {
|
||||
};
|
||||
},
|
||||
);
|
||||
setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(
|
||||
setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation(
|
||||
// @ts-expect-error mock
|
||||
(fn) => {
|
||||
fn();
|
||||
@@ -152,72 +156,72 @@ describe('Process Firmware Image', () => {
|
||||
rmSync(PREV_IMAGES_TEST_DIR_PATH, {recursive: true, force: true});
|
||||
});
|
||||
|
||||
it('failure with fetch ok', async () => {
|
||||
it("failure with fetch ok", async () => {
|
||||
fetchReturnedStatus.ok = false;
|
||||
fetchReturnedStatus.status = 429;
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V14_1, IMAGE_V14_1);
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.REQUEST_FAILED);
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.RequestFailed);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`Invalid response from ${IMAGE_V14_1} status=${fetchReturnedStatus.status}.`),
|
||||
);
|
||||
expectNoChanges(false);
|
||||
});
|
||||
|
||||
it('failure with fetch body', async () => {
|
||||
it("failure with fetch body", async () => {
|
||||
fetchReturnedStatus.body = undefined;
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V14_1, IMAGE_V14_1);
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.REQUEST_FAILED);
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.RequestFailed);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`Invalid response from ${IMAGE_V14_1} status=${fetchReturnedStatus.status}.`),
|
||||
);
|
||||
expectNoChanges(false);
|
||||
});
|
||||
|
||||
it('failure with invalid OTA file', async () => {
|
||||
it("failure with invalid OTA file", async () => {
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_INVALID, IMAGE_INVALID);
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.ERROR);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(`Not a valid OTA fil`));
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.Error);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Not a valid OTA fil"));
|
||||
expectNoChanges(false);
|
||||
});
|
||||
|
||||
it('failure with identical OTA file', async () => {
|
||||
it("failure with identical OTA file", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V14_1_METAS]);
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V14_1, IMAGE_V14_1);
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.SUCCESS);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining(`Base manifest already has version`));
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.Success);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Base manifest already has version"));
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(1, common.PREV_INDEX_MANIFEST_FILENAME, getManifest(common.PREV_INDEX_MANIFEST_FILENAME));
|
||||
expect(writeManifestSpy).toHaveBeenNthCalledWith(2, common.BASE_INDEX_MANIFEST_FILENAME, getManifest(common.BASE_INDEX_MANIFEST_FILENAME));
|
||||
expectWriteNoChanges();
|
||||
});
|
||||
|
||||
it('failure with older OTA file that has identical in prev', async () => {
|
||||
it("failure with older OTA file that has identical in prev", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V14_1_METAS]);
|
||||
setManifest(common.PREV_INDEX_MANIFEST_FILENAME, [IMAGE_V13_1_METAS]);
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V13_1, IMAGE_V13_1);
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.SUCCESS);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining(`an equal or better match is already present in prev manifest`));
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.Success);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("an equal or better match is already present in prev manifest"));
|
||||
expectWriteNoChanges();
|
||||
});
|
||||
|
||||
it('failure with older OTA file that has newer in prev', async () => {
|
||||
it("failure with older OTA file that has newer in prev", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [IMAGE_V14_1_METAS]);
|
||||
setManifest(common.PREV_INDEX_MANIFEST_FILENAME, [IMAGE_V13_1_METAS]);
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V12_1, IMAGE_V12_1);
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.SUCCESS);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining(`an equal or better match is already present in prev manifest`));
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.Success);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("an equal or better match is already present in prev manifest"));
|
||||
expectWriteNoChanges();
|
||||
});
|
||||
|
||||
it('success into base', async () => {
|
||||
it("success into base", async () => {
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V14_1, IMAGE_V14_1);
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.SUCCESS);
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.Success);
|
||||
expect(readManifestSpy).toHaveBeenCalledWith(common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
expect(readManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME);
|
||||
expect(addImageToBaseSpy).toHaveBeenCalledTimes(1);
|
||||
@@ -226,12 +230,12 @@ describe('Process Firmware Image', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.BASE_INDEX_MANIFEST_FILENAME, [withOriginalUrl(IMAGE_V14_1, IMAGE_V14_1_METAS)]);
|
||||
});
|
||||
|
||||
it('success into prev', async () => {
|
||||
it("success into prev", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [withOriginalUrl(IMAGE_V14_1, IMAGE_V14_1_METAS)]);
|
||||
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V13_1, IMAGE_V13_1);
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.SUCCESS);
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.Success);
|
||||
expect(readManifestSpy).toHaveBeenCalledWith(common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
expect(readManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME);
|
||||
expect(addImageToBaseSpy).toHaveBeenCalledTimes(0);
|
||||
@@ -241,13 +245,13 @@ describe('Process Firmware Image', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, [withOriginalUrl(IMAGE_V13_1, IMAGE_V13_1_METAS)]);
|
||||
});
|
||||
|
||||
it('success with newer than current without existing prev', async () => {
|
||||
it("success with newer than current without existing prev", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [withOriginalUrl(IMAGE_V13_1, IMAGE_V13_1_METAS)]);
|
||||
useImage(IMAGE_V13_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V14_1, IMAGE_V14_1);
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.SUCCESS);
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.Success);
|
||||
expect(readManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(addImageToBaseSpy).toHaveBeenCalledTimes(1);
|
||||
expect(addImageToPrevSpy).toHaveBeenCalledTimes(0);
|
||||
@@ -256,7 +260,7 @@ describe('Process Firmware Image', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, [withOriginalUrl(IMAGE_V13_1, IMAGE_V13_1_METAS)]);
|
||||
});
|
||||
|
||||
it('success with newer than current with existing prev', async () => {
|
||||
it("success with newer than current with existing prev", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [withOriginalUrl(IMAGE_V13_1, IMAGE_V13_1_METAS)]);
|
||||
setManifest(common.PREV_INDEX_MANIFEST_FILENAME, [withOriginalUrl(IMAGE_V12_1, IMAGE_V12_1_METAS)]);
|
||||
useImage(IMAGE_V13_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
@@ -264,7 +268,7 @@ describe('Process Firmware Image', () => {
|
||||
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V14_1, IMAGE_V14_1);
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.SUCCESS);
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.Success);
|
||||
expect(readManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(addImageToBaseSpy).toHaveBeenCalledTimes(1);
|
||||
expect(addImageToPrevSpy).toHaveBeenCalledTimes(0);
|
||||
@@ -273,7 +277,7 @@ describe('Process Firmware Image', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, [withOriginalUrl(IMAGE_V13_1, IMAGE_V13_1_METAS)]);
|
||||
});
|
||||
|
||||
it('success with older that is newer than prev', async () => {
|
||||
it("success with older that is newer than prev", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [withOriginalUrl(IMAGE_V14_1, IMAGE_V14_1_METAS)]);
|
||||
setManifest(common.PREV_INDEX_MANIFEST_FILENAME, [withOriginalUrl(IMAGE_V12_1, IMAGE_V12_1_METAS)]);
|
||||
useImage(IMAGE_V14_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
@@ -281,7 +285,7 @@ describe('Process Firmware Image', () => {
|
||||
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V13_1, IMAGE_V13_1);
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.SUCCESS);
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.Success);
|
||||
expect(readManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(addImageToBaseSpy).toHaveBeenCalledTimes(0);
|
||||
expect(addImageToPrevSpy).toHaveBeenCalledTimes(1);
|
||||
@@ -290,13 +294,13 @@ describe('Process Firmware Image', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, [withOriginalUrl(IMAGE_V13_1, IMAGE_V13_1_METAS)]);
|
||||
});
|
||||
|
||||
it('success with newer with missing file', async () => {
|
||||
it("success with newer with missing file", async () => {
|
||||
setManifest(common.BASE_INDEX_MANIFEST_FILENAME, [withOriginalUrl(IMAGE_V13_1, IMAGE_V13_1_METAS)]);
|
||||
// useImage(IMAGE_V13_1, BASE_IMAGES_TEST_DIR_PATH);
|
||||
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V14_1, IMAGE_V14_1);
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.SUCCESS);
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.Success);
|
||||
expect(readManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(addImageToBaseSpy).toHaveBeenCalledTimes(1);
|
||||
expect(addImageToPrevSpy).toHaveBeenCalledTimes(0);
|
||||
@@ -305,34 +309,34 @@ describe('Process Firmware Image', () => {
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, []);
|
||||
});
|
||||
|
||||
it('success with extra metas', async () => {
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V14_1, IMAGE_V14_1, {manufacturerName: ['lixee']});
|
||||
it("success with extra metas", async () => {
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V14_1, IMAGE_V14_1, {manufacturerName: ["lixee"]});
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.SUCCESS);
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.Success);
|
||||
expect(readManifestSpy).toHaveBeenCalledWith(common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
expect(readManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME);
|
||||
expect(addImageToBaseSpy).toHaveBeenCalledTimes(1);
|
||||
expect(addImageToPrevSpy).toHaveBeenCalledTimes(0);
|
||||
expect(writeManifestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(writeManifestSpy).toHaveBeenCalledWith(common.BASE_INDEX_MANIFEST_FILENAME, [
|
||||
withOriginalUrl(IMAGE_V14_1, withExtraMetas(IMAGE_V14_1_METAS, {manufacturerName: ['lixee']})),
|
||||
withOriginalUrl(IMAGE_V14_1, withExtraMetas(IMAGE_V14_1_METAS, {manufacturerName: ["lixee"]})),
|
||||
]);
|
||||
});
|
||||
|
||||
it('success with all extra metas', async () => {
|
||||
it("success with all extra metas", async () => {
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_V14_1, IMAGE_V14_1, {
|
||||
originalUrl: `https://example.com/${IMAGE_V14_1}`,
|
||||
force: false,
|
||||
hardwareVersionMax: 2,
|
||||
hardwareVersionMin: 1,
|
||||
manufacturerName: ['lixee'],
|
||||
manufacturerName: ["lixee"],
|
||||
maxFileVersion: 5,
|
||||
minFileVersion: 3,
|
||||
modelId: 'bogus',
|
||||
releaseNotes: 'bugfixes',
|
||||
modelId: "bogus",
|
||||
releaseNotes: "bugfixes",
|
||||
});
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.SUCCESS);
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.Success);
|
||||
expect(readManifestSpy).toHaveBeenCalledWith(common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
expect(readManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME);
|
||||
expect(addImageToBaseSpy).toHaveBeenCalledTimes(1);
|
||||
@@ -345,24 +349,24 @@ describe('Process Firmware Image', () => {
|
||||
force: false,
|
||||
hardwareVersionMax: 2,
|
||||
hardwareVersionMin: 1,
|
||||
manufacturerName: ['lixee'],
|
||||
manufacturerName: ["lixee"],
|
||||
maxFileVersion: 5,
|
||||
minFileVersion: 3,
|
||||
modelId: 'bogus',
|
||||
releaseNotes: 'bugfixes',
|
||||
modelId: "bogus",
|
||||
releaseNotes: "bugfixes",
|
||||
}),
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
it('success with tar', async () => {
|
||||
it("success with tar", async () => {
|
||||
if (!existsSync(common.TMP_DIR)) {
|
||||
mkdirSync(common.TMP_DIR, {recursive: true});
|
||||
}
|
||||
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_TAR, IMAGE_TAR, {}, true, (f) => f.endsWith('.ota'));
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_TAR, IMAGE_TAR, {}, true, (f) => f.endsWith(".ota"));
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.SUCCESS);
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.Success);
|
||||
expect(readManifestSpy).toHaveBeenCalledWith(common.BASE_INDEX_MANIFEST_FILENAME);
|
||||
expect(readManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME);
|
||||
expect(addImageToBaseSpy).toHaveBeenCalledTimes(1);
|
||||
@@ -373,28 +377,28 @@ describe('Process Firmware Image', () => {
|
||||
rmSync(common.TMP_DIR, {recursive: true, force: true});
|
||||
});
|
||||
|
||||
it('failure with invalid tar', async () => {
|
||||
it("failure with invalid tar", async () => {
|
||||
if (!existsSync(common.TMP_DIR)) {
|
||||
mkdirSync(common.TMP_DIR, {recursive: true});
|
||||
}
|
||||
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_INVALID, IMAGE_INVALID, {}, true, (f) => f.endsWith('.ota'));
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_INVALID, IMAGE_INVALID, {}, true, (f) => f.endsWith(".ota"));
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.TAR_NO_IMAGE);
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.TarNoImage);
|
||||
expectNoChanges(true);
|
||||
|
||||
rmSync(common.TMP_DIR, {recursive: true, force: true});
|
||||
});
|
||||
|
||||
it('failure with extract tar (missing dir)', async () => {
|
||||
it("failure with extract tar (missing dir)", async () => {
|
||||
// if (!existsSync(common.TMP_DIR)) {
|
||||
// mkdirSync(common.TMP_DIR, {recursive: true});
|
||||
// }
|
||||
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_TAR, IMAGE_TAR, {}, true, (f) => f.endsWith('.ota'));
|
||||
const status = await processFirmwareImage(IMAGES_TEST_DIR, IMAGE_TAR, IMAGE_TAR, {}, true, (f) => f.endsWith(".ota"));
|
||||
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.TAR_NO_IMAGE);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.objectContaining({syscall: 'chdir', code: 'ENOENT'}));
|
||||
expect(status).toStrictEqual(ProcessFirmwareImageStatus.TarNoImage);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.objectContaining({syscall: "chdir", code: "ENOENT"}));
|
||||
expectNoChanges(false);
|
||||
|
||||
rmSync(common.TMP_DIR, {recursive: true, force: true});
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig",
|
||||
"include": ["./**/*", "jest.config.ts"],
|
||||
"include": ["./**/*", "vitest.config.mts"],
|
||||
"compilerOptions": {
|
||||
"types": ["jest"],
|
||||
"rootDir": "..",
|
||||
"noEmit": true
|
||||
},
|
||||
"references": [{"path": ".."}]
|
||||
"references": [{ "path": ".." }]
|
||||
}
|
||||
|
||||
32
tests/vitest.config.mts
Normal file
32
tests/vitest.config.mts
Normal file
@@ -0,0 +1,32 @@
|
||||
import {defineConfig} from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
onConsoleLog() {
|
||||
return true;
|
||||
},
|
||||
coverage: {
|
||||
enabled: false,
|
||||
provider: "v8",
|
||||
include: [
|
||||
"src/ghw_check_ota_pr.ts",
|
||||
"src/ghw_get_changed_ota_files.ts",
|
||||
"src/ghw_process_ota_files.ts",
|
||||
"src/process_firmware_image.ts",
|
||||
"src/ghw_reprocess_all_images.ts",
|
||||
],
|
||||
extension: [".ts"],
|
||||
// exclude: [],
|
||||
clean: true,
|
||||
cleanOnRerun: true,
|
||||
reportsDirectory: "coverage",
|
||||
reporter: ["text", "html"],
|
||||
reportOnFailure: false,
|
||||
thresholds: {
|
||||
100: true,
|
||||
},
|
||||
},
|
||||
clearMocks: true,
|
||||
fileParallelism: false,
|
||||
},
|
||||
});
|
||||
@@ -14,8 +14,5 @@
|
||||
"composite": true,
|
||||
"checkJs": true
|
||||
},
|
||||
"include": ["./src/**/*"],
|
||||
"ts-node": {
|
||||
"esm": true
|
||||
}
|
||||
"include": ["./src/**/*"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user