feat: support standalone Docker runtime and internal CMS API
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
.git
|
||||
node_modules
|
||||
.next
|
||||
.env
|
||||
.env.*
|
||||
*.log
|
||||
*.tsbuildinfo
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM node:24-alpine AS build
|
||||
WORKDIR /app
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
ARG NEXT_PUBLIC_API_URL=http://localhost:18000
|
||||
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||
RUN node --test tests/*.test.cjs && npm run build
|
||||
|
||||
FROM node:24-alpine AS runtime
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production \
|
||||
NEXT_TELEMETRY_DISABLED=1 \
|
||||
HOSTNAME=0.0.0.0 \
|
||||
PORT=3000
|
||||
COPY --from=build --chown=node:node /app/.next/standalone ./
|
||||
COPY --from=build --chown=node:node /app/.next/static ./.next/static
|
||||
COPY --from=build --chown=node:node /app/public ./public
|
||||
USER node
|
||||
EXPOSE 3000
|
||||
CMD ["node", "server.js"]
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
images: {
|
||||
// Wagtail 后端媒体地址(开发环境 + 生产环境国内 OSS/CDN 域名)。
|
||||
// 详见 documents/设计方案分析与完善版.md §2.3。
|
||||
|
||||
Generated
+49
-3
@@ -281,10 +281,33 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz",
|
||||
"integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.3",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
|
||||
"integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
|
||||
"integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -2243,6 +2266,29 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.1",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
* 详见 documents/设计方案分析与完善版.md §2.7 Headless API 设计规范。
|
||||
*/
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000";
|
||||
const PUBLIC_API_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000";
|
||||
const API_BASE_URL = typeof window === "undefined"
|
||||
? process.env.API_INTERNAL_URL || PUBLIC_API_URL
|
||||
: PUBLIC_API_URL;
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
|
||||
@@ -137,6 +137,21 @@ test("page fetches share the publish cache tag without changing snippet tags", a
|
||||
assert.equal(calls[1].next.tags.join(","), "site-settings");
|
||||
});
|
||||
|
||||
test("API client separates container internal and browser addresses", async () => {
|
||||
const env = { NEXT_PUBLIC_API_URL: "http://localhost:18000", API_INTERNAL_URL: "http://backend:8000" };
|
||||
for (const browser of [false, true]) {
|
||||
let requestedURL;
|
||||
const globals = {
|
||||
process: { env },
|
||||
fetch: async (url) => { requestedURL = url; return { ok: true, json: async () => ({}) }; },
|
||||
...(browser ? { window: {} } : {}),
|
||||
};
|
||||
const { fetchAPI } = loadModule("services/api-client.ts", {}, globals);
|
||||
await fetchAPI("/api/v2/pages/");
|
||||
assert.equal(requestedURL, `${browser ? env.NEXT_PUBLIC_API_URL : env.API_INTERNAL_URL}/api/v2/pages/`);
|
||||
}
|
||||
});
|
||||
|
||||
test("authenticated webhook expires shared cache immediately and rejects unauthorized requests", async () => {
|
||||
const calls = [];
|
||||
const { POST } = loadModule("app/api/revalidate/route.ts", {
|
||||
|
||||
Reference in New Issue
Block a user