diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml index 7016bbca..8ff3dbbe 100644 --- a/.github/workflows/quality-gate.yml +++ b/.github/workflows/quality-gate.yml @@ -58,12 +58,20 @@ jobs: - name: Install dependencies working-directory: ./web - run: npm install + run: npm ci - name: Run Linter working-directory: ./web run: npm run lint + # Typecheck gate. Currently non-blocking (continue-on-error) because the + # existing codebase still has pre-existing type errors. Once the count + # reaches zero, remove `continue-on-error` to make this a hard merge block. + - name: Typecheck + working-directory: ./web + continue-on-error: true + run: npm run typecheck + - name: Run Tests working-directory: ./web run: npm run test @@ -85,6 +93,48 @@ jobs: name: web-dist path: web/dist/ + # ======================================== + # BACKEND - INDEXER SERVICE (lint/smoke/audit) + # No build step exists (plain ESM node service); we lockfile-enforce install, + # syntax-check the entry point, and audit. The audit is currently non-blocking + # because the express@5 + sqlite3(node-gyp) trees carry transitive advisories + # that need a broader dependency upgrade; flip continue-on-error off once the + # tree is clean. + # ======================================== + backend: + name: Backend Indexer + runs-on: pwap-runner + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Cache npm dependencies + uses: actions/cache@v4 + with: + path: backend/node_modules + key: ${{ runner.os }}-backend-${{ hashFiles('backend/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-backend- + + - name: Install dependencies + working-directory: ./backend + run: npm ci + + - name: Syntax check (entry point) + working-directory: ./backend + run: node --check src/index.js + + - name: npm audit (high + critical, production deps) + working-directory: ./backend + continue-on-error: true + run: npm audit --audit-level=high --omit=dev + # ======================================== # BUILD & PUSH DOCKER IMAGE TO GHCR # Immutable artifact for audit + rollback (vs ephemeral GHA artifact). @@ -631,7 +681,7 @@ jobs: - name: Web — npm audit (high + critical, production deps only) working-directory: ./web run: | - npm install + npm ci # Audit only production dependencies. Build tooling (vite, esbuild, # vite-plugin-node-polyfills → elliptic, etc.) ships to no user, and # advisories on those dev deps kept blocking production deploys. diff --git a/backend/package-lock.json b/backend/package-lock.json index 7131404a..d8722758 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -2994,9 +2994,9 @@ } }, "node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -3215,9 +3215,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/backend/package.json b/backend/package.json index c7a7048b..c6ed1b6c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,7 +9,8 @@ "dev": "node --watch src/index.js" }, "overrides": { - "tar": "^7.4.3", + "tar": "^7.5.22", + "ws": "^8.21.1", "@pezkuwi/api": "^16.5.23", "@pezkuwi/api-augment": "^16.5.23", "@pezkuwi/api-base": "^16.5.23", diff --git a/package.json b/package.json index f4f61775..d5dbff90 100644 --- a/package.json +++ b/package.json @@ -6,16 +6,16 @@ "scripts": { "build": "npm run build:web && npm run build:sdk-ui && npm run build:mobile", "build:web": "cd web && npm run build", - "build:sdk-ui": "cd /home/mamostehp/pezkuwi-sdk-ui && yarn build", + "build:sdk-ui": "cd \"${SDK_UI_DIR:-../pezkuwi-sdk-ui}\" 2>/dev/null && yarn build || echo 'sdk-ui: skipped (set SDK_UI_DIR to the pezkuwi-sdk-ui checkout to enable)'", "build:mobile": "cd mobile && npx expo export --platform web", "build:parallel": "concurrently \"npm run build:web\" \"npm run build:sdk-ui\"", "dev": "concurrently \"npm run dev:web\" \"npm run dev:mobile\"", "dev:web": "cd web && npm run dev", - "dev:sdk-ui": "cd /home/mamostehp/pezkuwi-sdk-ui && yarn start", + "dev:sdk-ui": "cd \"${SDK_UI_DIR:-../pezkuwi-sdk-ui}\" && yarn start", "dev:mobile": "cd mobile && npm run dev", "install:all": "npm run install:web && npm run install:sdk-ui && npm run install:mobile && npm run install:backend", "install:web": "cd web && npm install", - "install:sdk-ui": "cd /home/mamostehp/pezkuwi-sdk-ui && yarn install", + "install:sdk-ui": "cd \"${SDK_UI_DIR:-../pezkuwi-sdk-ui}\" 2>/dev/null && yarn install || echo 'sdk-ui: skipped (set SDK_UI_DIR to the pezkuwi-sdk-ui checkout to enable)'", "install:mobile": "cd mobile && npm install", "install:backend": "cd backend && npm install", "lint": "npm run lint:web && npm run lint:mobile", diff --git a/web/Dockerfile b/web/Dockerfile index 6b5b51b9..facd1c91 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -49,5 +49,5 @@ WORKDIR /dist COPY --from=builder /build/web/dist /dist LABEL org.opencontainers.image.source="https://github.com/pezkuwichain/pwap" LABEL org.opencontainers.image.description="pwap/web static SPA — Pezkuwi wallet/exchange frontend" -LABEL org.opencontainers.image.licenses="proprietary" +LABEL org.opencontainers.image.licenses="MIT" CMD ["sh", "-c", "echo 'pwap-web image — extract /dist via: docker create + docker cp'; sleep infinity"] diff --git a/web/generate-docs-structure.cjs b/web/generate-docs-structure.cjs index b74fa275..40e29bb6 100644 --- a/web/generate-docs-structure.cjs +++ b/web/generate-docs-structure.cjs @@ -17,8 +17,23 @@ const rustdocDestPath = path.join(publicPath, 'sdk_docs'); // Destination for BU const structureOutputPath = path.join(publicPath, 'docs-structure.json'); const rustdocBuildOutputPath = path.join(pezkuwiSdkRoot, 'target', 'doc'); // Output of cargo doc -// Absolute path to rustup (used to build rustdoc) -const rustupPath = '/home/mamostehp/.cargo/bin/rustup'; +// Path to rustup (used to build rustdoc). Resolve dynamically instead of +// hardcoding a per-machine absolute path: prefer $RUSTUP_PATH, then whatever +// is on PATH, then the conventional ~/.cargo/bin/rustup, then bare 'rustup'. +function resolveRustupPath() { + if (process.env.RUSTUP_PATH) return process.env.RUSTUP_PATH; + try { + const which = spawnSync('bash', ['-lc', 'command -v rustup'], { encoding: 'utf8' }); + if (which.status === 0 && which.stdout && which.stdout.trim()) { + return which.stdout.trim(); + } + } catch { /* fall through */ } + const home = process.env.HOME || require('os').homedir(); + const cargoRustup = path.join(home, '.cargo', 'bin', 'rustup'); + if (fs.existsSync(cargoRustup)) return cargoRustup; + return 'rustup'; +} +const rustupPath = resolveRustupPath(); // Path to the rebranding script (now .cjs) const rebrandScriptPath = path.join(__dirname, 'rebrand-rustdoc.cjs'); diff --git a/web/package-lock.json b/web/package-lock.json index 72ab23f1..7073b55b 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -58,7 +58,7 @@ "clsx": "^2.1.1", "cmdk": "^1.0.0", "date-fns": "^3.6.0", - "dompurify": "^3.3.1", + "dompurify": "^3.4.12", "embla-carousel-react": "^8.3.0", "highlight.js": "^11.9.0", "i18next": "^23.7.6", @@ -103,7 +103,7 @@ "globals": "^15.15.0", "husky": "^9.1.7", "jsdom": "^27.2.0", - "postcss": "^8.4.47", + "postcss": "^8.5.23", "tailwindcss": "^3.4.11", "typescript": "^5.5.3", "typescript-eslint": "^8.0.1", @@ -7651,9 +7651,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.10.tgz", - "integrity": "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==", + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -10054,9 +10054,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "funding": [ { "type": "github", @@ -10800,9 +10800,9 @@ } }, "node_modules/postcss": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", - "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", "funding": [ { "type": "opencollective", @@ -10819,7 +10819,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, diff --git a/web/package.json b/web/package.json index 17307b1c..b247cadd 100644 --- a/web/package.json +++ b/web/package.json @@ -8,6 +8,7 @@ "dev": "cp .env.development .env && vite", "prebuild": "node generate-docs-structure.cjs", "build": "vite build", + "typecheck": "tsc --noEmit -p tsconfig.app.json", "build:telegram": "vite build --config vite.config.telegram.ts", "build:dev": "cp .env.development .env && vite build", "build:alfa": "cp .env.alfa .env && vite build", @@ -70,7 +71,7 @@ "clsx": "^2.1.1", "cmdk": "^1.0.0", "date-fns": "^3.6.0", - "dompurify": "^3.3.1", + "dompurify": "^3.4.12", "embla-carousel-react": "^8.3.0", "highlight.js": "^11.9.0", "i18next": "^23.7.6", @@ -142,7 +143,7 @@ "globals": "^15.15.0", "husky": "^9.1.7", "jsdom": "^27.2.0", - "postcss": "^8.4.47", + "postcss": "^8.5.23", "tailwindcss": "^3.4.11", "typescript": "^5.5.3", "typescript-eslint": "^8.0.1", diff --git a/web/src/components/notifications/NotificationBell.tsx b/web/src/components/notifications/NotificationBell.tsx index 1dae87de..daed1af2 100644 --- a/web/src/components/notifications/NotificationBell.tsx +++ b/web/src/components/notifications/NotificationBell.tsx @@ -29,11 +29,14 @@ export default function NotificationBell() { const [open, setOpen] = useState(false); useEffect(() => { - if (user) { - loadNotifications(); + if (!user) return; - subscribeToNotifications(); - } + loadNotifications(); + // Return the unsubscribe callback so the realtime channel is torn down + // on unmount / when the user changes — otherwise channels leak and + // setState fires after unmount. + const unsubscribe = subscribeToNotifications(); + return unsubscribe; // eslint-disable-next-line react-hooks/exhaustive-deps }, [user]); @@ -55,7 +58,7 @@ export default function NotificationBell() { const subscribeToNotifications = () => { const channel = supabase - .channel('notifications') + .channel(`notifications-${user?.id}`) .on( 'postgres_changes', { diff --git a/web/src/main.tsx b/web/src/main.tsx index 9825ce79..6899153e 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -26,6 +26,10 @@ if (typeof window !== 'undefined' && import.meta.env.PROD) { }; } +// Augment @pezkuwi/api types so api.query/tx/rpc return typed values +// instead of the generic `Codec`. Must be imported before any api usage. +import '@pezkuwi/api-augment'; + import { createRoot } from 'react-dom/client' import App from './App.tsx' import './index.css' diff --git a/web/src/pages/ProfileSettings.tsx b/web/src/pages/ProfileSettings.tsx index 09a4be93..9faa07fe 100644 --- a/web/src/pages/ProfileSettings.tsx +++ b/web/src/pages/ProfileSettings.tsx @@ -398,8 +398,8 @@ export default function ProfileSettings() { English Türkçe العربية - Kurdî - کوردی + Kurdî (Kurmancî) + کوردی (سۆرانی) فارسی diff --git a/web/vite.config.ts b/web/vite.config.ts index 6d8b7e36..61eae8f3 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -66,6 +66,9 @@ export default defineConfig(({ command }) => ({ optimizeDeps: { include: ['@pezkuwi/util-crypto', '@pezkuwi/util', '@pezkuwi/api', '@pezkuwi/extension-dapp', '@pezkuwi/keyring', 'buffer'], }, + // Strip all console.* and debugger statements from production bundles so the + // 600+ dev-time console calls never ship. Dev keeps full logging. + esbuild: command === 'build' ? { drop: ['console', 'debugger'] } : {}, build: { rollupOptions: { external: [],