Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
lts/iron
lts/krypton
121 changes: 83 additions & 38 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,71 +1,116 @@
FROM node:20-alpine AS base
# ============================================
# Stage 1: Dependencies Installation Stage
# ============================================

# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
# IMPORTANT: Node.js Version Maintenance
# This Dockerfile uses Node.js 24.13.0-slim, which was the latest LTS version at the time of writing.
# To ensure security and compatibility, regularly update the NODE_VERSION ARG to the latest LTS version.
ARG NODE_VERSION=24.13.0-slim

FROM node:${NODE_VERSION} AS dependencies

# Set working directory
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* .yarnrc.yml package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then corepack enable&&yarn --immutable; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
# Copy package-related files first to leverage Docker's caching mechanism
COPY package.json yarn.lock* .yarnrc.yml package-lock.json* pnpm-lock.yaml* .npmrc* ./

# Install project dependencies with frozen lockfile for reproducible builds
RUN --mount=type=cache,target=/root/.npm \
--mount=type=cache,target=/usr/local/share/.cache/yarn \
--mount=type=cache,target=/root/.local/share/pnpm/store \
if [ -f package-lock.json ]; then \
npm ci --no-audit --no-fund; \
elif [ -f yarn.lock ]; then \
corepack enable yarn && yarn install --frozen-lockfile --production=false; \
elif [ -f pnpm-lock.yaml ]; then \
corepack enable pnpm && pnpm install --frozen-lockfile; \
else \
echo "No lockfile found." && exit 1; \
Comment on lines +16 to +29
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# 查看 Dockerfile 全部内容
find . -name "Dockerfile" -type f | head -5

Repository: FurryConsChina/website

Length of output: 78


🏁 Script executed:

# 查找 Dockerfile 位置和内容
git ls-files | grep -i dockerfile | head -10

Repository: FurryConsChina/website

Length of output: 76


🏁 Script executed:

# 读取 Dockerfile 完整内容
cat -n Dockerfile

Repository: FurryConsChina/website

Length of output: 5052


🏁 Script executed:

# 检查是否存在 .npmrc 文件
find . -name ".npmrc*" -type f 2>/dev/null

Repository: FurryConsChina/website

Length of output: 48


🏁 Script executed:

# 检查 .dockerignore 文件
if [ -f .dockerignore ]; then cat -n .dockerignore; else echo ".dockerignore 不存在"; fi

Repository: FurryConsChina/website

Length of output: 175


🏁 Script executed:

# 检查项目根目录所有隐藏文件
ls -la | grep -E '^\.' | head -20

Repository: FurryConsChina/website

Length of output: 48


🏁 Script executed:

# 读取 .dockerignore 文件内容
cat -n .dockerignore

Repository: FurryConsChina/website

Length of output: 175


🏁 Script executed:

# 直接读取 .dockerignore
cat .dockerignore

Repository: FurryConsChina/website

Length of output: 133


不要直接复制 .npmrc 到构建层。

Line 16 在 COPY 命令中包含 .npmrc* 的通配符模式。如果 .npmrc 包含 npm registry 凭据,当前做法会将其作为普通文件复制到镜像层,导致凭据暴露在构建缓存中。Docker 官方推荐使用 BuildKit secret mount 处理敏感凭据,使其仅在安装命令期间临时可见。

建议修改
-COPY package.json yarn.lock* .yarnrc.yml package-lock.json* pnpm-lock.yaml* .npmrc* ./
+COPY package.json yarn.lock* .yarnrc.yml package-lock.json* pnpm-lock.yaml* ./

 RUN --mount=type=cache,target=/root/.npm \
     --mount=type=cache,target=/usr/local/share/.cache/yarn \
     --mount=type=cache,target=/root/.local/share/pnpm/store \
+    --mount=type=secret,id=npmrc,target=/app/.npmrc,required=false \
   if [ -f package-lock.json ]; then \

参考:Docker BuildKit Secrets Documentation

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 16 - 29, The Dockerfile COPY currently includes a
glob ".npmrc*" which will bake any .npmrc (and credentials) into the image
layer; remove ".npmrc*" from the COPY step and instead supply npm credentials as
a BuildKit secret during the RUN dependency-install step (the RUN that checks
package-lock.json / yarn.lock / pnpm-lock.yaml) by using
--mount=type=secret,id=npmrc (or a similar secret id) so the installer can read
the credentials only at build time; update the dependency-install RUN
invocations (the RUN that contains npm ci / corepack enable yarn && yarn install
/ corepack enable pnpm && pnpm install) to mount the secret and point the
installer to it (e.g., via NPM_CONFIG_USERCONFIG or appropriate flag) rather
than copying .npmrc into the image.

fi

# ============================================
# Stage 2: Build Next.js application in standalone mode
# ============================================

# Rebuild the source code only when needed
FROM base AS builder
FROM node:${NODE_VERSION} AS builder
RUN apk add --no-cache git
Comment thread
PaiJi marked this conversation as resolved.
Outdated

# Set working directory
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules

# Copy project dependencies from dependencies stage
COPY --from=dependencies /app/node_modules ./node_modules

# Copy application source code
COPY . .

ENV NODE_ENV=production

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
# ENV NEXT_TELEMETRY_DISABLED=1

# Build Next.js application
# If you want to speed up Docker rebuilds, you can cache the build artifacts
# by adding: --mount=type=cache,target=/app/.next/cache
# This caches the .next/cache directory across builds, but it also prevents
# .next/cache/fetch-cache from being included in the final image, meaning
# cached fetch responses from the build won't be available at runtime.
RUN if [ -f package-lock.json ]; then \
npm run build; \
elif [ -f yarn.lock ]; then \
corepack enable yarn && yarn build; \
elif [ -f pnpm-lock.yaml ]; then \
corepack enable pnpm && pnpm build; \
else \
echo "No lockfile found." && exit 1; \
fi

RUN corepack enable&&yarn build
# ============================================
# Stage 3: Run Next.js application
# ============================================

# If using npm comment out above and use below instead
# RUN npm run build
FROM node:${NODE_VERSION} AS runner

# Production image, copy all the files and run next
FROM base AS runner
# Set working directory
WORKDIR /app

ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
# Set production environment variables
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the run time.
# ENV NEXT_TELEMETRY_DISABLED=1

COPY --from=builder /app/public ./public
# Copy production assets
COPY --from=builder --chown=node:node /app/public ./public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
RUN chown node:node .next

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=node:node /app/.next/standalone ./
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
#No idea why next-i18n want this
COPY --from=builder --chown=nextjs:nodejs /app/next.config.js ./next.config.js
COPY --from=builder --chown=nextjs:nodejs /app/next-i18next.config.js ./next-i18next.config.js

USER nextjs
# If you want to persist the fetch cache generated during the build so that
# cached responses are available immediately on startup, uncomment this line:
# COPY --from=builder --chown=node:node /app/.next/cache ./.next/cache

EXPOSE 3000
# Switch to non-root user for security best practices
USER node
Comment thread
PaiJi marked this conversation as resolved.

ENV PORT 3000
# set hostname to localhost
ENV HOSTNAME "0.0.0.0"
# Expose port 3000 to allow HTTP traffic
EXPOSE 3000

# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD ["node", "server.js"]
# Start Next.js standalone server
CMD ["node", "server.js"]
53 changes: 53 additions & 0 deletions additional.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,56 @@
declare const VERSION: string;
declare const COMMITHASH: string;
declare const BRANCH: string;

type TrackEventValue = Record<string, unknown>;
type TMapLatLngInstance = object;

interface AnalyticsTracker {
track: (eventName: string, eventValue?: TrackEventValue) => void;
}

interface TMapMapInstance {
on: (eventName: string, handler: () => void) => void;
}

interface TMapNamespace {
LatLng: new (lat: number | undefined, lng: number | undefined) => TMapLatLngInstance;
Map: new (
container: HTMLElement | null,
options: {
center: TMapLatLngInstance;
zoom: number;
pitch: number;
rotation: number;
}
) => TMapMapInstance;
MultiMarker: new (options: {
id: string;
map: TMapMapInstance;
styles: {
marker: unknown;
};
geometries: Array<{
id: string;
styleId: string;
position: TMapLatLngInstance;
properties: {
title: string;
};
}>;
}) => unknown;
MarkerStyle: new (options: {
width: number;
height: number;
anchor: {
x: number;
y: number;
};
}) => unknown;
}

interface Window {
gtag?: (command: "event", eventName: string, eventValue?: TrackEventValue) => void;
TMap?: TMapNamespace;
umami?: AnalyticsTracker;
}
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@headlessui/react": "^2.2.10",
"@next/third-parties": "^16.2.3",
"@sentry/nextjs": "^10.48.0",
"autoprefixer": "^10.4.27",
"autoprefixer": "^10.5.0",
"axios": "^1.15.0",
"clsx": "^2.1.1",
"dayjs": "^1.11.20",
Expand All @@ -25,17 +25,17 @@
"eslint": "9.39.4",
"eslint-config-next": "16.2.3",
"git-revision-webpack-plugin": "^5.0.0",
"i18next": "^25.10.10",
"i18next": "^26.0.4",
"next": "16.2.3",
"next-i18next": "^15.4.3",
"next-i18next": "^16.0.5",
"postcss": "^8.5.9",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-nested": "^7.0.2",
"postcss-preset-env": "^11.2.0",
"postcss-preset-env": "^11.2.1",
"react": "19.2.5",
"react-dom": "19.2.5",
"react-hot-toast": "^2.6.0",
"react-i18next": "^16.6.6",
"react-i18next": "^17.0.3",
"react-icons": "^5.6.0",
"tailwindcss": "^3.4.19",
"typescript": "5.9.3",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/en/common.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"header.title": "FurConsCalendar",
"header.slogan": "Happy New Year! 🎉",
"header.slogan": "Waiting for vacation? 🐕",
"header.nav.homepage": "Home",
"header.nav.city": "Cities",
"header.nav.organization": "Organizers",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/zh-Hans/common.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"header.title": "兽展日历",
"header.slogan": "新年快乐🎉",
"header.slogan": "五一你去哪?🐕",
"header.nav.homepage": "首页",
"header.nav.city": "城市",
"header.nav.organization": "展商",
Expand Down
2 changes: 1 addition & 1 deletion src/components/OrganizationLinkButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FaPaw } from "react-icons/fa";
import { HiOutlineHome, HiOutlineMail } from "react-icons/hi";
import { FaQq, FaTwitter, FaWeibo } from "react-icons/fa";
import { SiBilibili, SiXiaohongshu } from "react-icons/si";
import { TFunction } from "next-i18next";
import type { TFunction } from "next-i18next/pages";
import { FaFacebook } from "react-icons/fa6";
import { ReactNode } from "react";

Expand Down
2 changes: 1 addition & 1 deletion src/components/SimpleEventCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import dayjs from "dayjs";
import "dayjs/locale/zh-cn";
import "dayjs/locale/zh-tw";
import { SimpleEventItem } from "@/types/event";
import { useTranslation } from "next-i18next";
import { useTranslation } from "next-i18next/pages";
import { getDayjsLocale } from "@/utils/locale";

function SimpleEventCard({ event }: { event: SimpleEventItem }) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/event/EventSourceButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { sendTrack } from "@/utils/track";
import { useTranslation } from "next-i18next";
import { useTranslation } from "next-i18next/pages";

export default function EventSourceButton({
sources,
Expand Down
2 changes: 1 addition & 1 deletion src/components/eventCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "dayjs/locale/zh-cn";
import "dayjs/locale/zh-tw";
import Link from "next/link";
import { useEffect, useMemo } from "react";
import { useTranslation } from "next-i18next";
import { useTranslation } from "next-i18next/pages";
import Image from "@/components/image";
import { getEventCoverImgPath } from "@/utils/imageLoader";
import { sendTrack } from "@/utils/track";
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/footer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AboutUsLinks, FriendSiteLinks } from "@/constants/staticConfig";
import { sendTrack } from "@/utils/track";
import Link from "next/link";
import { useTranslation } from "next-i18next";
import { useTranslation } from "next-i18next/pages";
import Image from "@/components/image";

export default function Footer({ isCNRegion }: { isCNRegion: boolean }) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useRouter } from "next/router";
import { useEffect, useMemo, useRef, useState } from "react";
import { HiMenu } from "react-icons/hi";
import { IoMdClose } from "react-icons/io";
import { useTranslation } from "next-i18next";
import { useTranslation } from "next-i18next/pages";
import Image from "@/components/image";
import { TbMenuDeep } from "react-icons/tb";
import { useCurrentPath } from "@/utils/path";
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import AnnouncementSlider from "@/components/announcementSlider";
import { useRouter } from "next/router";
import { Toaster } from "react-hot-toast";
import Sidebar from "@/components/Sidebar";
import { useTranslation } from "next-i18next";
import { useTranslation } from "next-i18next/pages";
import { IS_CN_REGION, PUBLIC_URL } from "@/utils/env";

const getCanonicalUrl = (locale: currentSupportLocale | undefined, path: string) => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/localeMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { FaLanguage } from "react-icons/fa6";
import { useTranslation } from "next-i18next";
import { useTranslation } from "next-i18next/pages";

export default function LocaleMenu() {
const { t } = useTranslation();
Expand Down
2 changes: 1 addition & 1 deletion src/components/organizationStatus/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import clsx from "clsx";
import { GrStatusGoodSmall } from "react-icons/gr";
import { useTranslation } from "next-i18next";
import { useTranslation } from "next-i18next/pages";
import type { TFunction } from "i18next";

export default function OrganizationStatus(props: { status: string }) {
Expand Down
4 changes: 2 additions & 2 deletions src/pages/404.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { sendTrack } from "@/utils/track";
import { GetStaticPropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { serverSideTranslations } from "next-i18next/pages/serverSideTranslations";
import Link from "next/link";
import { useEffect } from "react";
import { useTranslation } from "next-i18next";
import { useTranslation } from "next-i18next/pages";

export default function NotFoundPage() {
const { t } = useTranslation();
Expand Down
4 changes: 2 additions & 2 deletions src/pages/500.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { sendTrack } from "@/utils/track";
import { GetStaticPropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { serverSideTranslations } from "next-i18next/pages/serverSideTranslations";
import Link from "next/link";
import { useEffect } from "react";
import { useTranslation } from "next-i18next";
import { useTranslation } from "next-i18next/pages";

export default function Custom500() {
const { t } = useTranslation();
Expand Down
4 changes: 2 additions & 2 deletions src/pages/[organization]/[slug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import { sendTrack } from "@/utils/track";
import { getEventDetailUrl } from "@/utils/url";
import clsx from "clsx";
import { GetServerSidePropsContext } from "next";
import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useTranslation } from "next-i18next/pages";
import { serverSideTranslations } from "next-i18next/pages/serverSideTranslations";
import Link from "next/link";
import Script from "next/script";
import { useState } from "react";
Expand Down
4 changes: 2 additions & 2 deletions src/pages/[organization]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import "dayjs/locale/zh-cn";
import "dayjs/locale/zh-tw";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/pages/serverSideTranslations";
import { useTranslation } from "next-i18next/pages";
import { OrganizationsAPI } from "@/api/organizations";
import * as z from "zod/v4";
import { EventCardItem } from "@/types/event";
Expand Down
2 changes: 1 addition & 1 deletion src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { appWithTranslation } from "next-i18next";
import { appWithTranslation } from "next-i18next/pages";

import { Noto_Sans_SC, Rubik } from "next/font/google";
import { GoogleAnalytics } from "@next/third-parties/google";
Expand Down
Loading
Loading