diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cdc5f94 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI +'on': + push: + branches: [master] + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: .tool-versions + cache: yarn + + - name: Install Packages + run: yarn --immutable + + - name: Run linter + run: yarn lint diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..695dfec --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 24 diff --git a/package.json b/package.json index 3a5727e..20710a4 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "lint:fix": "yarn eslint --max-warnings 0 --cache --fix && yarn prettier --write .", "dev": "next dev", "build": "next build", - "start": "next start" + "start": "next start", + "prepare": "husky" }, "dependencies": { "@chakra-ui/react": "^3.31.0", @@ -44,9 +45,17 @@ "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", "globals": "^16.5.0", + "husky": "^9.1.7", + "lint-staged": "^16.2.7", "prettier": "^3.8.0", "typescript": "^5.9.3", "typescript-eslint": "^8.53.0" }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "yarn eslint --cache --fix --max-warnings 0 --no-warn-ignored", + "yarn prettier --write" + ] + }, "packageManager": "yarn@4.11.0" } diff --git a/src/app/debug/page.tsx b/src/app/debug/page.tsx index a0514fb..aa0bab2 100644 --- a/src/app/debug/page.tsx +++ b/src/app/debug/page.tsx @@ -26,7 +26,6 @@ import OtaPartition, { OtaPartitionDetails } from '@/esp/OtaPartition'; import HexSpan from '@/components/HexSpan'; import HexViewer from '@/components/HexViewer'; import { downloadData } from '@/utils/download'; -import FileUpload, { FileUploadHandle } from '@/components/FileUpload'; import { FirmwareInfo } from '@/utils/firmwareIdentifier'; function OtadataDebug({ otaPartition }: { otaPartition: OtaPartition }) { @@ -220,6 +219,7 @@ function FirmwareIdentificationDebug({ case 'crosspoint': return 'blue'; case 'unknown': + default: return 'orange'; } }; @@ -272,9 +272,8 @@ function FirmwareIdentificationDebug({ } export default function Debug() { - const { actions, debugActions, stepData, isRunning } = useEspOperations(); + const { debugActions, stepData, isRunning } = useEspOperations(); const [debugOutputNode, setDebugOutputNode] = useState(null); - const appPartitionFileInput = React.useRef(null); return ( @@ -284,8 +283,8 @@ export default function Debug() {

These are few tools to help debugging / administering your Xtink - device. They're designed to be used by those who are - intentionally messing around with their device. + device. They’re designed to be used by those who are intentionally + messing around with their device.

Read otadata partition will read the raw data out of the{' '} @@ -303,8 +302,8 @@ export default function Debug() { otadata to switch the boot partition.

- Identify firmware in both partitions will read both app0 and - app1 partitions and automatically identify which firmware is + Identify firmware in both partitions will read both app0 + and app1 partitions and automatically identify which firmware is installed on each (Official English, Official Chinese, CrossPoint Community, or Custom).

@@ -414,4 +413,4 @@ export default function Debug() { ) : null}
); -} \ No newline at end of file +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 11a459a..bedc09c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -48,10 +48,10 @@ export default function Home() {

- I've tried to make this foolproof and while the likelihood - of unrecoverable things going wrong is extremely low, it's - never zero. So proceed with care and make sure to grab a backup - using Save full flash before flashing your device. + I’ve tried to make this foolproof and while the likelihood of + unrecoverable things going wrong is extremely low, it’s never + zero. So proceed with care and make sure to grab a backup using{' '} + Save full flash before flashing your device.

Once you start Write flash from file or{' '} @@ -75,10 +75,10 @@ export default function Home() { goes wrong.

- Save full flash will read your device's flash and save - it as flash.bin. This will take around 25 minutes to - complete. You can use that file (or someone else's) with{' '} - Write full flash from file to overwrite your device's + Save full flash will read your device’s flash and save it + as flash.bin. This will take around 25 minutes to + complete. You can use that file (or someone else’s) with{' '} + Write full flash from file to overwrite your device’s entire flash.

@@ -116,8 +116,8 @@ export default function Home() { OTA fast flash controls

- Before using this, I'd strongly recommend taking a backup of - your device using Save full flash above. + Before using this, I’d strongly recommend taking a backup of your + device using Save full flash above.

Flash English/Chinese firmware will download the firmware, @@ -208,9 +208,9 @@ export default function Home() { Change device language Before starting the process, it is recommended to change the device - language to English. To do this, select "Settings" icon, then - click "OK / Confirm" button and "OK / Confirm" again until English is shown. - Otherwise, the language will still be Chinese after flashing + language to English. To do this, select “Settings” icon, then click + “OK / Confirm” button and “OK / Confirm” again until English is + shown. Otherwise, the language will still be Chinese after flashing and you may not notice changes. @@ -221,9 +221,9 @@ export default function Home() { Device restart instructions Once you complete a write operation, you will need to restart your - device by pressing and releasing the small "Reset" button near the bottom - right, followed quickly by pressing and holding of the main power - button for about 3 seconds. + device by pressing and releasing the small “Reset” button near the + bottom right, followed quickly by pressing and holding of the main + power button for about 3 seconds. diff --git a/src/esp/EspController.ts b/src/esp/EspController.ts index 855fdf4..27db8b4 100644 --- a/src/esp/EspController.ts +++ b/src/esp/EspController.ts @@ -104,7 +104,7 @@ export default class EspController { return this.espLoader.readFlash(offset, 0x640000, onPacketReceived); } - async readAppPartitionForIdentification( + async readAppPartitionForIdentification( partitionLabel: 'app0' | 'app1', { readSize = 0x6400, // Default to 25KB (0x6400) for fast identification @@ -123,7 +123,7 @@ export default class EspController { // Optimized read for firmware identification with flexible read size and offset: // - Default (25KB / 0x6400): Fast path, covers 99% of cases // - Additional chunks: Specify offset multiples of 25KB until identification succeeds - // In testing, most firmwares are identified within the first 25KB read, so reading the entire + // In testing, most firmwares are identified within the first 25KB read, so reading the entire // partition is unnecessary in the majority of cases. const baseOffset = partitionLabel === 'app0' ? 0x10000 : 0x650000; @@ -183,4 +183,4 @@ export default class EspController { reportProgress, }); } -} \ No newline at end of file +} diff --git a/src/esp/useEspOperations.ts b/src/esp/useEspOperations.ts index a568824..eda15bf 100644 --- a/src/esp/useEspOperations.ts +++ b/src/esp/useEspOperations.ts @@ -23,10 +23,10 @@ export function useEspOperations() { const wrapWithRunning = (fn: (...a: Args) => Promise) => - async (...a: Args) => { - setIsRunning(true); - return fn(...a).finally(() => setIsRunning(false)); - }; + async (...a: Args) => { + setIsRunning(true); + return fn(...a).finally(() => setIsRunning(false)); + }; const flashRemoteFirmware = async ( getFirmware: () => Promise, @@ -416,6 +416,7 @@ export function useEspOperations() { let info: FirmwareInfo | undefined; for (let offset = 0; offset < maxReadSize; offset += chunkSize) { + // eslint-disable-next-line no-await-in-loop const chunk = await espController.readAppPartitionForIdentification( partitionLabel, { @@ -441,7 +442,13 @@ export function useEspOperations() { } } - return info ?? { type: 'unknown', version: 'unknown', displayName: 'Custom/Unknown Firmware' }; // Return the last identification result if not found + return ( + info ?? { + type: 'unknown', + version: 'unknown', + displayName: 'Custom/Unknown Firmware', + } + ); // Return the last identification result if not found }; const app0Info = await runStep('Read app0 partition', () => @@ -486,4 +493,4 @@ export function useEspOperations() { readAndIdentifyAllFirmware: wrapWithRunning(readAndIdentifyAllFirmware), }, }; -} \ No newline at end of file +} diff --git a/src/utils/firmwareIdentifier.ts b/src/utils/firmwareIdentifier.ts index 909148b..8edd0d6 100644 --- a/src/utils/firmwareIdentifier.ts +++ b/src/utils/firmwareIdentifier.ts @@ -19,9 +19,9 @@ function findString( return -1; } - for (let i = startOffset; i <= data.length - searchBytes.length; i++) { + for (let i = startOffset; i <= data.length - searchBytes.length; i += 1) { let match = true; - for (let j = 0; j < searchBytes.length; j++) { + for (let j = 0; j < searchBytes.length; j += 1) { if (data[i + j] !== searchBytes[j]) { match = false; break; @@ -73,7 +73,7 @@ function extractVersion(data: Uint8Array, searchLimit = 25000): string { // Try to find V-pattern versions (official firmware) // Pattern: [any byte]