From 734469169f705b9c9e1a55020dbf5614f8ab6ba2 Mon Sep 17 00:00:00 2001 From: Astrian Zheng Date: Thu, 22 May 2025 11:36:44 +1000 Subject: [PATCH] chore: update CI workflow and dependencies; enhance linter rules and add husky for pre-commit checks --- .gitea/workflows/workflow.yaml | 11 ++++++--- .github/workflows/workflow.yaml | 18 +++++++------- biome.json | 17 ++++++++++++- package-lock.json | 17 +++++++++++++ package.json | 8 +++++- src/main.ts | 44 +++++++++++++++++++-------------- 6 files changed, 82 insertions(+), 33 deletions(-) diff --git a/.gitea/workflows/workflow.yaml b/.gitea/workflows/workflow.yaml index 78c48e5..79427d6 100644 --- a/.gitea/workflows/workflow.yaml +++ b/.gitea/workflows/workflow.yaml @@ -4,21 +4,26 @@ on: push: branches: - '*' + pull_request: + branches: + - '*' jobs: quality: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Node.js - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: node-version: '22' - name: Install dependencies run: npm ci - - name: Run Biome + - name: Run Biome checks run: npm run quality-check + - name: Run husky tests + run: npm test if: always() outputs: status: ${{ job.status }} diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index f0ec398..6a92a6d 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -1,7 +1,7 @@ -name: Quality Check +name: CI Pipeline on: - push: + pull_request: branches: - '*' @@ -10,15 +10,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Node.js - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: node-version: '22' + cache: 'npm' - name: Install dependencies run: npm ci - - name: Run Biome - run: npm run quality-check - if: always() - outputs: - status: ${{ job.status }} \ No newline at end of file + - name: Run Biome checks + run: npx @biomejs/biome check --apply . + - name: Run tests + run: npm test diff --git a/biome.json b/biome.json index c7f4c63..55fad2f 100644 --- a/biome.json +++ b/biome.json @@ -19,7 +19,22 @@ "linter": { "enabled": true, "rules": { - "recommended": true + "recommended": true, + "correctness": { + "noUnusedVariables": "error", + "noUndeclaredVariables": "error", + "noUnreachable": "error", + "noUnsafeFinally": "error" + }, + "security": { + "noDangerouslySetInnerHtml": "error", + "noGlobalEval": "error" + }, + "style": { + "noVar": "error", + "useBlockStatements": "error", + "useConst": "error" + } } }, "javascript": { diff --git a/package-lock.json b/package-lock.json index 8386ce9..3ac5499 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^12.1.2", "dts-bundle-generator": "^9.5.1", + "husky": "^9.1.7", "i": "^0.3.7", "npm": "^11.4.0", "rimraf": "^6.0.1", @@ -1040,6 +1041,22 @@ "node": ">= 0.4" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/i": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", diff --git a/package.json b/package.json index 181a8f6..a8ae9c8 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^12.1.2", "dts-bundle-generator": "^9.5.1", + "husky": "^9.1.7", "i": "^0.3.7", "npm": "^11.4.0", "rimraf": "^6.0.1", @@ -33,5 +34,10 @@ "tslib": "^2.8.1", "typescript": "^5.8.3" }, - "type": "module" + "type": "module", + "husky": { + "hooks": { + "pre-commit": "npm run quality-check && npm test" + } + } } diff --git a/src/main.ts b/src/main.ts index f85f1e2..57a5408 100644 --- a/src/main.ts +++ b/src/main.ts @@ -83,8 +83,8 @@ export default (options: ComponentOptions) => { conditionalElements: this._conditionalElements, evaluateIfCondition: this._evaluateIfCondition.bind(this), }, - options.states, - options.statesListeners, + states, + statesListeners, ) // initialize shadow dom @@ -126,7 +126,9 @@ export default (options: ComponentOptions) => { private _scheduleUpdate(elements: Set) { requestAnimationFrame(() => { - for (const element of elements) this._updateElement(element) + for (const element of elements) { + this._updateElement(element) + } }) } @@ -142,8 +144,9 @@ export default (options: ComponentOptions) => { const result = renderFunction() // Update DOM - if (typeof result === 'string') element.innerHTML = result - else if (result instanceof Node) { + if (typeof result === 'string') { + element.innerHTML = result + } else if (result instanceof Node) { element.innerHTML = '' element.appendChild(result) } @@ -155,21 +158,21 @@ export default (options: ComponentOptions) => { private _evaluateIfCondition(element: Element, condition: string) { const info = this._conditionalElements.get(element) - if (!info) return + if (!info) { return } // Evaluate the condition const result = this._evaluateExpression(condition) const shouldShow = Boolean(result) if (shouldShow !== info.isPresent) { - if (shouldShow) + if (shouldShow) { // Insert the element back into the DOM info.placeholder.parentNode?.insertBefore( element, info.placeholder.nextSibling, ) - // Remove the element from the DOM - else element.parentNode?.removeChild(element) + // Remove the element from the DOM + } else { element.parentNode?.removeChild(element) } // Update the state info.isPresent = shouldShow @@ -180,8 +183,9 @@ export default (options: ComponentOptions) => { private _evaluateExpression(expression: string): unknown { try { // get the state keys and values - if (this._states[expression] !== undefined) + if (this._states[expression] !== undefined) { return this._states[expression] + } // execute the expression const stateKeys = Object.keys(this._states) @@ -189,10 +193,11 @@ export default (options: ComponentOptions) => { const func = new Function(...stateKeys, `return ${expression}`) const execRes = func(...stateValues) - if (typeof execRes !== 'boolean') + if (typeof execRes !== 'boolean') { throw new Error( `The expression "${expression}" must return a boolean value.`, ) + } return execRes } catch (error) { console.error(`Error evaluating expression: ${expression}`, error) @@ -232,15 +237,16 @@ export default (options: ComponentOptions) => { // (name) => { for (const name of Object.getOwnPropertyNames( Object.getPrototypeOf(this), - )) + )) { if ( typeof (this as Record)[name] === 'function' && name !== 'constructor' - ) + ) { context[name] = ( this as unknown as Record unknown> )[name].bind(this) - + } + } return context } @@ -360,7 +366,7 @@ export default (options: ComponentOptions) => { let result = this._states for (const part of parts) { - if (result === undefined || result === null) return undefined + if (result === undefined || result === null) { return undefined } result = (result as { [key: string]: Record })[part] } @@ -368,11 +374,11 @@ export default (options: ComponentOptions) => { } connectedCallback() { - if (onMount) onMount.call(this) + if (onMount) { onMount.call(this) } } disconnectedCallback() { - if (onUnmount) onUnmount.call(this) + if (onUnmount) { onUnmount.call(this) } } static get observedAttributes() { @@ -384,7 +390,7 @@ export default (options: ComponentOptions) => { oldValue: string, newValue: string, ) { - if (onAttributeChanged) onAttributeChanged(attrName, oldValue, newValue) + if (onAttributeChanged) { onAttributeChanged(attrName, oldValue, newValue) } } // state manager @@ -396,7 +402,7 @@ export default (options: ComponentOptions) => { const parts = keyPath.split('.') let result = this._states for (const part of parts) { - if (result === undefined || result === null) return undefined + if (result === undefined || result === null) { return undefined } result = (result as { [key: string]: Record })[part] } return result