diff --git a/.github/workflows/Lark-notification.yml b/.github/workflows/Lark-notification.yml index 2c0914d..0b32311 100644 --- a/.github/workflows/Lark-notification.yml +++ b/.github/workflows/Lark-notification.yml @@ -21,7 +21,7 @@ jobs: env: HAS_LARK_WEBHOOK: ${{ secrets.LARK_CHATBOT_HOOK_URL != '' }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: denoland/setup-deno@v2 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9e3ce67..fcbc30d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,48 +13,20 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 if: ${{ env.VERCEL_TOKEN && env.VERCEL_ORG_ID && env.VERCEL_PROJECT_ID }} - - uses: actions/setup-node@v6 - if: ${{ env.VERCEL_TOKEN && env.VERCEL_ORG_ID && env.VERCEL_PROJECT_ID }} - with: - node-version: 24 - - name: Deploy to Vercel id: vercel-deployment if: ${{ env.VERCEL_TOKEN && env.VERCEL_ORG_ID && env.VERCEL_PROJECT_ID }} - shell: bash - run: | - set -euo pipefail - - npm install vercel -g - - if [[ "$GITHUB_REF" == 'refs/heads/main' ]]; then - DeployOutput=$(vercel -t "$VERCEL_TOKEN" --prod) - else - DeployOutput=$(vercel -t "$VERCEL_TOKEN") - fi - echo "$DeployOutput" - - ParsedURL=$(echo "$DeployOutput" | grep -Eo 'https://[^[:space:]]*\.vercel\.app' | tail -n 1) - - if [[ -z "$ParsedURL" ]]; then - echo "Failed to parse Vercel URL from deploy output" - exit 1 - fi - vercel inspect "$ParsedURL" -t "$VERCEL_TOKEN" -F json > vercel-inspect.json - - InspectURL=$(jq -r '.url // empty' vercel-inspect.json) - - if [[ -z "$InspectURL" ]]; then - echo "Failed to parse inspect url from vercel-inspect.json" - exit 1 - fi - if [[ "$InspectURL" != http* ]]; then - InspectURL="https://$InspectURL" - fi - echo "preview-url=$InspectURL" >> "$GITHUB_OUTPUT" + uses: amondnet/vercel-action@v42.3.0 + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} + vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} + vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} + working-directory: ./ + vercel-args: ${{ github.ref == 'refs/heads/main' && ' --prod' || '' }} - name: Lark notification uses: Open-Source-Bazaar/feishu-action@v3 @@ -68,13 +40,13 @@ jobs: header: title: tag: plain_text - content: Vercel 部署通知 + content: Vercel deployment template: blue body: elements: - tag: markdown content: | - **Git 仓库:** [${{ github.server_url }}/${{ github.repository }}](${{ github.server_url }}/${{ github.repository }}) - **代码分支:** [${{ github.ref }}](${{ github.server_url }}/${{ github.repository }}/tree/${{ github.ref_name }}) - **提交作者:** [${{ github.actor }}](${{ github.server_url }}/${{ github.actor }}) - **预览链接:** [${{ steps.vercel-deployment.outputs.preview-url }}](${{ steps.vercel-deployment.outputs.preview-url }}) + - **Git Repository**: [${{ github.server_url }}/${{ github.repository }}](${{ github.server_url }}/${{ github.repository }}) + - **Code Branch**: [${{ github.ref }}](${{ github.server_url }}/${{ github.repository }}/tree/${{ github.ref_name }}) + - **Commit Author**: [${{ github.actor }}](${{ github.server_url }}/${{ github.actor }}) + - **Preview Link**: [${{ steps.vercel-deployment.outputs.preview-url }}](${{ steps.vercel-deployment.outputs.preview-url }}) diff --git a/.github/workflows/self-scan.yml b/.github/workflows/self-scan.yml index bc67d9c..4538cac 100644 --- a/.github/workflows/self-scan.yml +++ b/.github/workflows/self-scan.yml @@ -9,7 +9,7 @@ jobs: self-scan: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: OWASP/cve-lite-cli@v1 with: diff --git a/Dockerfile b/Dockerfile index 579afa0..c209d7a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:24-slim AS base +FROM node:26-slim AS base RUN apt-get update && \ apt-get install ca-certificates curl libjemalloc-dev -y --no-install-recommends && \ rm -rf /var/lib/apt/lists/* @@ -9,17 +9,21 @@ ENV MALLOC_CONF=dirty_decay_ms:1000,narenas:2,background_thread:true ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN npm i pnpm@latest -g -COPY . /app WORKDIR /app FROM base AS build -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm i --frozen-lockfile +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./ +COPY patches ./patches +RUN pnpm fetch --frozen-lockfile --store-dir=/pnpm/store +COPY . . +RUN pnpm i --offline --frozen-lockfile --store-dir=/pnpm/store RUN pnpm build FROM base COPY --from=build /app/public ./public COPY --from=build /app/.next/static ./.next/static COPY --from=build /app/.next/standalone ./ +COPY --from=build /app/.env* ./ EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" diff --git a/eslint.config.ts b/eslint.config.ts index 5967bd1..821778e 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -1,116 +1,20 @@ -import cspellPlugin from '@cspell/eslint-plugin'; -import eslint from '@eslint/js'; -import nextPlugin from '@next/eslint-plugin-next'; -import stylistic from '@stylistic/eslint-plugin'; -import { defineConfig } from 'eslint/config'; -import eslintConfigPrettier from 'eslint-config-prettier'; -import react from 'eslint-plugin-react'; -import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort'; -import globals from 'globals'; -import tsEslint from 'typescript-eslint'; -import { fileURLToPath } from 'url'; +import nextJS from 'eslint-idea2app-next-js'; -/** - * @see{@link https://github.com/typescript-eslint/typescript-eslint/blob/main/eslint.config.mjs} - * @see{@link https://github.com/vercel/next.js/issues/71763#issuecomment-2476838298} - */ - -const tsconfigRootDir = fileURLToPath(new URL('.', import.meta.url)); - -export default defineConfig( - // register all of the plugins up-front - { - plugins: { - '@cspell': cspellPlugin, - '@stylistic': stylistic, - 'simple-import-sort': simpleImportSortPlugin, - '@typescript-eslint': tsEslint.plugin, - react, - '@next/next': nextPlugin, - }, - }, +export default [ + ...nextJS, { - // config with just ignores is the replacement for `.eslintignore` ignores: ['**/node_modules/**', '**/public/**', '**/.next/**', '.github/scripts/**'], }, - - // extends ... - eslint.configs.recommended, - ...tsEslint.configs.recommended, - - // base config { - languageOptions: { - globals: { ...globals.es2020, ...globals.browser, ...globals.node }, - parserOptions: { - projectService: true, - tsconfigRootDir, - warnOnUnsupportedTypeScriptVersion: false, - }, - }, rules: { - // spellchecker '@cspell/spellchecker': [ 'warn', { cspell: { - language: 'en', - dictionaries: ['typescript', 'node', 'html', 'css', 'bash', 'npm', 'pnpm'], - words: ['hackathon'], + words: ['aliyun', 'datav', 'Giscus', 'hackathon', 'Serwist'], }, }, ], - // stylistic - '@stylistic/padding-line-between-statements': [ - 'error', - { blankLine: 'always', prev: '*', next: 'return' }, - { blankLine: 'always', prev: 'directive', next: '*' }, - { blankLine: 'any', prev: 'directive', next: 'directive' }, - { - blankLine: 'always', - prev: '*', - next: ['enum', 'interface', 'type'], - }, - ], - 'arrow-body-style': ['error', 'as-needed'], - 'no-empty-pattern': 'warn', - 'no-console': ['error', { allow: ['warn', 'error', 'info'] }], - 'no-restricted-syntax': [ - 'error', - { - selector: "TSPropertySignature[key.name='children']", - message: 'Please use PropsWithChildren instead of defining children manually', - }, - ], - 'consistent-return': 'warn', - 'prefer-destructuring': ['error', { object: true, array: true }], - // simple-import-sort - 'simple-import-sort/exports': 'error', - 'simple-import-sort/imports': 'error', - // TypeScript - '@typescript-eslint/no-unused-vars': 'warn', - '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/no-empty-object-type': 'off', - '@typescript-eslint/no-unsafe-declaration-merging': 'warn', - '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], - // React - 'react/no-unescaped-entities': 'off', - 'react/self-closing-comp': ['error', { component: true, html: true }], - 'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }], - 'react/jsx-no-target-blank': 'warn', - 'react/jsx-sort-props': [ - 'error', - { - reservedFirst: true, - callbacksLast: true, - noSortAlphabetically: true, - }, - ], - // Next.js - ...nextPlugin.configs.recommended.rules, - ...nextPlugin.configs['core-web-vitals'].rules, - '@next/next/no-sync-scripts': 'warn', }, }, - eslintConfigPrettier, -); +]; diff --git a/next.config.ts b/next.config.ts index f07240c..954e858 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,6 +1,6 @@ import setMDX from '@next/mdx'; +import withSerwistInit from '@serwist/next'; import { NextConfig } from 'next'; -import setPWA from 'next-pwa'; // @ts-expect-error no official types import withLess from 'next-with-less'; import remarkFrontmatter from 'remark-frontmatter'; @@ -17,10 +17,9 @@ const withMDX = setMDX({ }, extension: /\.mdx?$/, }), - withPWA = setPWA({ - dest: 'public', - register: true, - skipWaiting: true, + withSerwist = withSerwistInit({ + swSrc: 'service-worker.ts', + swDest: 'public/sw.js', disable: isDev, }); @@ -51,7 +50,7 @@ const redirects: NextConfig['redirects'] = async () => }, ]); -export default withPWA( +export default withSerwist( withLess( withMDX({ pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'], diff --git a/package.json b/package.json index 5028fa0..05145b0 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "prepare": "husky", "install": "pnpx git-utility download https://github.com/Open-Source-Bazaar/key-vault main Open-Source-Bazaar.github.io || true", - "postinstall": "next typegen", + "postinstall": "next typegen --webpack", "dev": "next dev --webpack", "build": "cross-env CI=true next build --webpack", "start": "next start", @@ -14,75 +14,66 @@ }, "dependencies": { "@giscus/react": "^3.1.0", + "@khmyznikov/pwa-install": "^0.7.0", "@koa/router": "^15.7.0", "@mdx-js/loader": "^3.1.1", "@mdx-js/react": "^3.1.1", - "@next/mdx": "16.4.2", - "core-js": "^3.49.0", + "@next/mdx": "16.4.3", + "@serwist/next": "^9.5.13", + "core-js": "^3.51.0", "echarts-jsx": "^0.6.0", - "file-type": "^22.0.1", - "idea-react": "^2.2.2", + "file-type": "^22.2.0", + "idea-react": "^2.3.5", "jsonwebtoken": "^9.0.3", "koa": "^3.2.1", "koa-jwt": "^4.0.4", - "koajax": "^3.3.0", + "koajax": "^3.3.1", "license-filter": "^0.2.5", - "marked": "^15.0.12", - "mime": "^4.1.0", - "mobx": "^6.16.1", - "mobx-github": "^0.7.0", - "mobx-i18n": "^0.7.5", - "mobx-lark": "^2.10.0", - "mobx-react": "^9.2.2", - "mobx-react-helper": "^0.5.2", - "mobx-restful": "^2.1.4", - "mobx-restful-table": "^2.6.6", + "marked": "^18.1.2", + "mime": "^4.1.1", + "mobx": "^7.1.3", + "mobx-github": "^0.7.1", + "mobx-i18n": "^0.7.9", + "mobx-lark": "^2.10.2", + "mobx-react": "^10.0.3", + "mobx-react-helper": "^0.5.4", + "mobx-restful": "^2.1.5", + "mobx-restful-table": "^2.7.8", "mobx-strapi": "^0.8.1", - "next": "16.4.2", - "next-pwa": "^5.6.0", - "next-ssr-middleware": "^1.1.1", - "nodemailer": "^9.0.4", + "next": "16.4.3", + "next-ssr-middleware": "^1.1.5", + "nodemailer": "^10.2.1", "open-react-map": "^0.9.1", - "react": "^19.2.8", + "react": "^19.3.1", "react-bootstrap": "^2.10.10", - "react-dom": "^19.2.8", + "react-dom": "^19.3.1", "react-typed-component": "^1.0.6", "remark-frontmatter": "^5.0.0", - "remark-mdx-frontmatter": "^5.2.0", - "web-utility": "^4.7.2", - "yaml": "^2.9.0" + "remark-mdx-frontmatter": "^6.0.0", + "serwist": "^9.5.13", + "web-utility": "^4.8.1", + "yaml": "^2.9.1" }, "devDependencies": { - "@cspell/eslint-plugin": "^10.0.1", - "@eslint/js": "^10.0.1", - "@next/eslint-plugin-next": "16.4.2", "@softonus/prettier-plugin-duplicate-remover": "^1.1.2", - "@stylistic/eslint-plugin": "^5.10.0", - "@types/eslint-config-prettier": "^6.11.3", "@types/jsonwebtoken": "^9.0.10", "@types/koa": "^3.0.3", - "@types/next-pwa": "^5.6.9", - "@types/node": "^24.13.3", - "@types/nodemailer": "^8.0.1", - "@types/react": "^19.2.18", - "@types/react-dom": "^19.2.4", + "@types/node": "^26.6.5", + "@types/nodemailer": "^8.0.2", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", "cross-env": "^10.1.0", - "eslint": "^10.8.0", - "eslint-config-next": "16.4.2", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-react": "^7.37.5", - "eslint-plugin-simple-import-sort": "^14.0.0", - "globals": "^17.9.0", + "eslint": "^10.13.0", + "eslint-idea2app-next-js": "^0.1.1", "husky": "^9.1.7", "jiti": "^2.7.0", - "less": "^4.8.1", + "less": "^4.9.1", "less-loader": "^13.0.0", - "lint-staged": "^17.3.0", + "lint-staged": "^17.6.0", "next-with-less": "^3.0.1", - "prettier": "^3.9.6", + "prettier": "^3.9.10", "prettier-plugin-css-order": "^2.2.0", - "typescript": "~6.0.3", - "typescript-eslint": "^8.66.0" + "typescript": "~6.0.3" }, "resolutions": { "next": "$next" diff --git a/pages/_app.tsx b/pages/_app.tsx index 4390ea5..fc61c49 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,5 +1,7 @@ import '../styles/globals.css'; +import '@khmyznikov/pwa-install'; +import { SerwistProvider } from '@serwist/next/react'; import { HTTPError } from 'koajax'; import { configure } from 'mobx'; import { enableStaticRendering, observer } from 'mobx-react'; @@ -91,7 +93,7 @@ export default class CustomApp extends App { ], }; - return ( + const frame = ( @@ -99,6 +101,8 @@ export default class CustomApp extends App { {t('open_source_bazaar')} + + {isActivityPage ? ( ) : isOpenLibraryPath ? ( @@ -109,5 +113,7 @@ export default class CustomApp extends App {