36 changed files with 779 additions and 346 deletions
@ -1 +1,4 @@ |
|||||
DATABASE_URL=postgresql://postgres:xxxxxx@localhost:6666/postgres |
DATABASE_URL=file:./db.sqlite |
||||
|
STATIC_DIR=static |
||||
|
UPLOAD_SUBDIR=upload |
||||
|
NITRO_PORT=3399 |
||||
@ -0,0 +1,117 @@ |
|||||
|
/** |
||||
|
* 部署后首次启动前执行:若库中尚无 admin,则用环境变量创建首个管理员。 |
||||
|
* 逻辑对齐 `packages/drizzle-pkg/seed.ts`,使用 SQLite(与 `migrate-sqlite.js` 相同)。 |
||||
|
* |
||||
|
* 环境变量(与 seed.ts 一致): |
||||
|
* - DATABASE_URL:SQLite 路径,可为 `file:/path/to/db.sqlite` 或裸路径 |
||||
|
* - BOOTSTRAP_ADMIN_USERNAME / BOOTSTRAP_ADMIN_PASSWORD:可选;未设置或校验失败则跳过 |
||||
|
*/ |
||||
|
import Database from 'better-sqlite3' |
||||
|
import { hash } from 'bcryptjs' |
||||
|
import { mkdirSync } from 'node:fs' |
||||
|
import path from 'node:path' |
||||
|
import { fileURLToPath } from 'node:url' |
||||
|
|
||||
|
/** 与 `server/service/auth/index.ts` 一致 */ |
||||
|
const USERNAME_REGEX = /^[a-zA-Z0-9_]{3,20}$/ |
||||
|
const MIN_PASSWORD_LENGTH = 6 |
||||
|
|
||||
|
/** 与 seed.ts 一致:仅当小写用户名本身符合 slug 规则时写入 public_slug */ |
||||
|
const PUBLIC_SLUG_REGEX = /^[a-z0-9]{3,20}$/ |
||||
|
|
||||
|
function derivePublicSlug(username) { |
||||
|
const lower = username.toLowerCase() |
||||
|
return PUBLIC_SLUG_REGEX.test(lower) ? lower : null |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 与 `migrate-sqlite.js` 一致:相对路径相对 `process.cwd()`(例如 `.output/run.sh` 下与迁移写入同一文件)。 |
||||
|
* Nitro 内 `resolveSqliteDatabaseUrl` 在无法锚定 drizzle-pkg 时也会回退到 cwd,避免「迁移/seed 成功、服务 CANTOPEN」。 |
||||
|
*/ |
||||
|
function resolveSqliteFilePath(dbUrl) { |
||||
|
const stripped = dbUrl.startsWith('file:') ? dbUrl.slice('file:'.length) : dbUrl |
||||
|
if (!stripped) { |
||||
|
throw new Error('DATABASE_URL 未设置,且未提供有效的 SQLite 文件路径') |
||||
|
} |
||||
|
if (path.isAbsolute(stripped)) { |
||||
|
return stripped |
||||
|
} |
||||
|
return path.resolve(process.cwd(), stripped) |
||||
|
} |
||||
|
|
||||
|
function openSqlite() { |
||||
|
const dbUrl = process.env.DATABASE_URL || '' |
||||
|
const sqlitePath = resolveSqliteFilePath(dbUrl) |
||||
|
mkdirSync(path.dirname(sqlitePath), { recursive: true }) |
||||
|
return new Database(sqlitePath) |
||||
|
} |
||||
|
|
||||
|
async function main() { |
||||
|
const db = openSqlite() |
||||
|
try { |
||||
|
const existingAdmin = db |
||||
|
.prepare(`SELECT id FROM users WHERE role = ? LIMIT 1`) |
||||
|
.get('admin') |
||||
|
if (existingAdmin) { |
||||
|
console.log('Bootstrap skipped: admin exists') |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
const username = process.env.BOOTSTRAP_ADMIN_USERNAME |
||||
|
const password = process.env.BOOTSTRAP_ADMIN_PASSWORD |
||||
|
|
||||
|
if (!username || !password) { |
||||
|
console.warn( |
||||
|
'Bootstrap skipped: set BOOTSTRAP_ADMIN_USERNAME and BOOTSTRAP_ADMIN_PASSWORD', |
||||
|
) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if (!USERNAME_REGEX.test(username) || password.length < MIN_PASSWORD_LENGTH) { |
||||
|
console.warn( |
||||
|
'Bootstrap skipped: invalid BOOTSTRAP_ADMIN_USERNAME or BOOTSTRAP_ADMIN_PASSWORD', |
||||
|
) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
const passwordHash = await hash(password, 10) |
||||
|
const { maxId } = db.prepare(`SELECT COALESCE(MAX(id), 0) AS maxId FROM users`).get() |
||||
|
const userId = (maxId ?? 0) + 1 |
||||
|
const publicSlug = derivePublicSlug(username) |
||||
|
|
||||
|
try { |
||||
|
db.prepare( |
||||
|
` |
||||
|
INSERT INTO users (id, username, password, role, status, public_slug) |
||||
|
VALUES (@id, @username, @password, @role, @status, @public_slug) |
||||
|
`,
|
||||
|
).run({ |
||||
|
id: userId, |
||||
|
username, |
||||
|
password: passwordHash, |
||||
|
role: 'admin', |
||||
|
status: 'active', |
||||
|
public_slug: publicSlug, |
||||
|
}) |
||||
|
console.log('Bootstrap complete: admin user created') |
||||
|
} catch (err) { |
||||
|
console.warn( |
||||
|
'Bootstrap skipped: could not insert admin (unique conflict or DB error)', |
||||
|
err, |
||||
|
) |
||||
|
} |
||||
|
} finally { |
||||
|
db.close() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const isMain = |
||||
|
process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url) |
||||
|
|
||||
|
if (isMain) { |
||||
|
// 成功时自然退出(退出码 0);仅未捕获错误时 process.exit(1)。
|
||||
|
main().catch((e) => { |
||||
|
console.error(e) |
||||
|
process.exit(1) |
||||
|
}) |
||||
|
} |
||||
@ -1,11 +0,0 @@ |
|||||
|
|
||||
import { drizzle } from "drizzle-orm/node-postgres"; |
|
||||
|
|
||||
if (process.env.NODE_ENV === 'production') { |
|
||||
// 打包时需要保证migrator被引入
|
|
||||
import('drizzle-orm/node-postgres/migrator') |
|
||||
} |
|
||||
|
|
||||
const _db = drizzle(process.env.DATABASE_URL!); |
|
||||
|
|
||||
export { _db as dbGlobal } |
|
||||
@ -1,16 +0,0 @@ |
|||||
import { sql } from "drizzle-orm"; |
|
||||
import { integer, pgTable, timestamp, varchar } from "drizzle-orm/pg-core"; |
|
||||
|
|
||||
export const users = pgTable("users", { |
|
||||
id: integer().primaryKey(), |
|
||||
username: varchar().notNull().unique(), |
|
||||
email: varchar(), |
|
||||
nickname: varchar(), |
|
||||
password: varchar().notNull(), |
|
||||
avatar: varchar(), |
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(), |
|
||||
updatedAt: timestamp('updated_at') |
|
||||
.defaultNow() |
|
||||
.$onUpdate(() => sql`CURRENT_TIMESTAMP`) |
|
||||
.notNull(), |
|
||||
}); |
|
||||
@ -1,10 +0,0 @@ |
|||||
import { drizzle } from 'drizzle-orm/libsql'; |
|
||||
|
|
||||
if (process.env.NODE_ENV === 'production') { |
|
||||
// 打包时需要保证migrator被引入
|
|
||||
import('drizzle-orm/better-sqlite3/migrator') |
|
||||
} |
|
||||
|
|
||||
const _db = drizzle(process.env.DATABASE_URL!); |
|
||||
|
|
||||
export { _db as dbGlobal } |
|
||||
@ -1 +1,23 @@ |
|||||
export { dbGlobal } from '../database/pg/db' |
import { drizzle } from "drizzle-orm/better-sqlite3"; |
||||
|
import { resolveSqliteDatabaseUrl } from "./resolve-sqlite-url"; |
||||
|
|
||||
|
if (process.env.NODE_ENV === "production") { |
||||
|
// 打包时需要保证migrator被引入
|
||||
|
import("drizzle-orm/better-sqlite3/migrator"); |
||||
|
} |
||||
|
|
||||
|
const rawUrl = process.env.DATABASE_URL; |
||||
|
if (!rawUrl) { |
||||
|
throw new Error("DATABASE_URL 未设置"); |
||||
|
} |
||||
|
const resolvedUrl = resolveSqliteDatabaseUrl(rawUrl); |
||||
|
process.env.DATABASE_URL = resolvedUrl; |
||||
|
|
||||
|
// better-sqlite3 需要裸文件路径;`file:` 前缀仍保留在 DATABASE_URL 供 drizzle-kit 等使用
|
||||
|
const sqlitePath = resolvedUrl.startsWith("file:") |
||||
|
? resolvedUrl.slice("file:".length) |
||||
|
: resolvedUrl; |
||||
|
|
||||
|
const _db = drizzle(sqlitePath); |
||||
|
|
||||
|
export { _db as dbGlobal }; |
||||
|
|||||
@ -0,0 +1,33 @@ |
|||||
|
import { existsSync, readFileSync } from "node:fs"; |
||||
|
import path from "node:path"; |
||||
|
import { fileURLToPath } from "node:url"; |
||||
|
|
||||
|
function isDrizzlePkgRoot(dir: string): boolean { |
||||
|
const pkg = path.join(dir, "package.json"); |
||||
|
if (!existsSync(pkg)) { |
||||
|
return false; |
||||
|
} |
||||
|
try { |
||||
|
const { name } = JSON.parse(readFileSync(pkg, "utf8")) as { name?: string }; |
||||
|
return name === "drizzle-pkg"; |
||||
|
} catch { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* `drizzle-pkg` 包根目录(与 `package.json`、`db.sqlite` 同级)。 |
||||
|
* - 开发:用 `import.meta` 锚定,避免 cwd 变化把 `file:db.sqlite` 指到错误文件(只读 / DBMOVED)。 |
||||
|
* - 生产:打包后 chunk 路径不可靠,回退到 `cwd/packages/drizzle-pkg`;再不行则回退 `process.cwd()`(与 `.output` 下 migrate/seed 的相对 `DATABASE_URL` 一致)。 |
||||
|
*/ |
||||
|
export function getDrizzlePkgRoot(): string { |
||||
|
const fromMeta = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); |
||||
|
if (isDrizzlePkgRoot(fromMeta)) { |
||||
|
return fromMeta; |
||||
|
} |
||||
|
const fromCwd = path.resolve(process.cwd(), "packages/drizzle-pkg"); |
||||
|
if (isDrizzlePkgRoot(fromCwd)) { |
||||
|
return fromCwd; |
||||
|
} |
||||
|
return process.cwd(); |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
import path from "node:path"; |
||||
|
import { getDrizzlePkgRoot } from "./paths"; |
||||
|
|
||||
|
/** 将 `file:` 相对路径解析为绝对路径(相对 drizzle-pkg 根目录) */ |
||||
|
export function resolveSqliteDatabaseUrl(url: string): string { |
||||
|
if (!url.startsWith("file:")) { |
||||
|
return url; |
||||
|
} |
||||
|
let filePath = url.slice("file:".length); |
||||
|
if (path.isAbsolute(filePath)) { |
||||
|
return `file:${filePath}`; |
||||
|
} |
||||
|
const root = getDrizzlePkgRoot(); |
||||
|
return `file:${path.resolve(root, filePath)}`; |
||||
|
} |
||||
@ -1 +1,25 @@ |
|||||
export { users } from '../../database/pg/schema/auth' |
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; |
||||
|
|
||||
|
export const users = sqliteTable("users", { |
||||
|
id: integer().primaryKey(), |
||||
|
username: text().notNull().unique(), |
||||
|
email: text(), |
||||
|
nickname: text(), |
||||
|
password: text().notNull(), |
||||
|
avatar: text(), |
||||
|
role: text().notNull().default("user"), |
||||
|
status: text().notNull().default("active"), |
||||
|
publicSlug: text("public_slug").unique(), |
||||
|
bioMarkdown: text("bio_markdown"), |
||||
|
bioVisibility: text("bio_visibility").notNull().default("private"), |
||||
|
socialLinksJson: text("social_links_json").notNull().default("[]"), |
||||
|
avatarVisibility: text("avatar_visibility").notNull().default("private"), |
||||
|
discoverVisible: integer("discover_visible", { mode: "boolean" }).notNull().default(true), |
||||
|
discoverLocation: text("discover_location"), |
||||
|
discoverShowLocation: integer("discover_show_location", { mode: "boolean" }).notNull().default(false), |
||||
|
createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), |
||||
|
updatedAt: integer("updated_at", { mode: "timestamp_ms" }) |
||||
|
.defaultNow() |
||||
|
.$onUpdate(() => new Date()) |
||||
|
.notNull(), |
||||
|
}); |
||||
|
|||||
@ -0,0 +1,50 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
|
||||
|
# 配置区(只改这里) |
||||
|
GIT_REPO_URL="ssh://root@git.xieyaxin.top:8892/topuser/nuxt4-demo.git" |
||||
|
PROD_BRANCH="deploy" # 你要存放产物的分支名(会自动创建) |
||||
|
BUILD_FOLDER=".output" # 打包产物目录 |
||||
|
ARCHIVE_NAME="build-output.tar.gz" |
||||
|
ARCHIVE_PATH=".tmp_${ARCHIVE_NAME}" |
||||
|
COMMIT_MSG="deploy: build at $(date +'%Y-%m-%d %H:%M:%S')" |
||||
|
|
||||
|
# 1. 先打包 |
||||
|
echo "📦 构建项目..." |
||||
|
bun run build |
||||
|
|
||||
|
# 2. 克隆远程产物分支到临时目录 |
||||
|
echo "⬇️ 拉取产物分支..." |
||||
|
git clone --single-branch --branch $PROD_BRANCH $GIT_REPO_URL .tmp_dist || { |
||||
|
echo "🆕 分支不存在,创建新分支..." |
||||
|
mkdir .tmp_dist |
||||
|
cd .tmp_dist |
||||
|
git init |
||||
|
git checkout -b $PROD_BRANCH |
||||
|
git remote add origin $GIT_REPO_URL |
||||
|
cd .. |
||||
|
} |
||||
|
|
||||
|
# 3. 压缩构建产物 |
||||
|
echo "🗜️ 压缩构建目录..." |
||||
|
tar -czf "$ARCHIVE_PATH" -C "$BUILD_FOLDER" . |
||||
|
|
||||
|
# 4. 删除旧产物,复制新产物 |
||||
|
echo "♻️ 更新产物文件..." |
||||
|
rm -rf .tmp_dist/* |
||||
|
cp -r $BUILD_FOLDER/.drone.yml .tmp_dist/.drone.yml |
||||
|
cp "$ARCHIVE_PATH" ".tmp_dist/$ARCHIVE_NAME" |
||||
|
|
||||
|
# 5. 提交并推送 |
||||
|
cd .tmp_dist |
||||
|
git add -A |
||||
|
git commit -m "$COMMIT_MSG" |
||||
|
|
||||
|
echo "🚀 推送到远程分支 $PROD_BRANCH..." |
||||
|
git push origin $PROD_BRANCH |
||||
|
|
||||
|
# 6. 清理临时文件 |
||||
|
cd .. |
||||
|
rm -rf .tmp_dist |
||||
|
rm -f "$ARCHIVE_PATH" |
||||
|
|
||||
|
echo "✅ 发布完成!" |
||||
@ -0,0 +1,50 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
|
||||
|
# 配置区(只改这里) |
||||
|
GIT_REPO_URL="git@gitee.com:xieyaxin/nuxt4-demo.git" |
||||
|
PROD_BRANCH="deploy" # 你要存放产物的分支名(会自动创建) |
||||
|
BUILD_FOLDER=".output" # 打包产物目录 |
||||
|
ARCHIVE_NAME="build-output.tar.gz" |
||||
|
ARCHIVE_PATH=".tmp_${ARCHIVE_NAME}" |
||||
|
COMMIT_MSG="deploy: build at $(date +'%Y-%m-%d %H:%M:%S')" |
||||
|
|
||||
|
# 1. 先打包 |
||||
|
echo "📦 构建项目..." |
||||
|
bun run build |
||||
|
|
||||
|
# 2. 克隆远程产物分支到临时目录 |
||||
|
echo "⬇️ 拉取产物分支..." |
||||
|
git clone --single-branch --branch $PROD_BRANCH $GIT_REPO_URL .tmp_dist || { |
||||
|
echo "🆕 分支不存在,创建新分支..." |
||||
|
mkdir .tmp_dist |
||||
|
cd .tmp_dist |
||||
|
git init |
||||
|
git checkout -b $PROD_BRANCH |
||||
|
git remote add origin $GIT_REPO_URL |
||||
|
cd .. |
||||
|
} |
||||
|
|
||||
|
# 3. 压缩构建产物 |
||||
|
echo "🗜️ 压缩构建目录..." |
||||
|
tar -czf "$ARCHIVE_PATH" -C "$BUILD_FOLDER" . |
||||
|
|
||||
|
# 4. 删除旧产物,复制新产物 |
||||
|
echo "♻️ 更新产物文件..." |
||||
|
rm -rf .tmp_dist/* |
||||
|
cp -r $BUILD_FOLDER/.drone.yml .tmp_dist/.drone.yml |
||||
|
cp "$ARCHIVE_PATH" ".tmp_dist/$ARCHIVE_NAME" |
||||
|
|
||||
|
# 5. 提交并推送 |
||||
|
cd .tmp_dist |
||||
|
git add -A |
||||
|
git commit -m "$COMMIT_MSG" |
||||
|
|
||||
|
echo "🚀 推送到远程分支 $PROD_BRANCH..." |
||||
|
git push origin $PROD_BRANCH |
||||
|
|
||||
|
# 6. 清理临时文件 |
||||
|
cd .. |
||||
|
rm -rf .tmp_dist |
||||
|
rm -f "$ARCHIVE_PATH" |
||||
|
|
||||
|
echo "✅ 发布完成!" |
||||
@ -0,0 +1,11 @@ |
|||||
|
if [ -f .env.prod ]; then |
||||
|
cp .env.prod .output/.env |
||||
|
else |
||||
|
cp .env.example .output/.env |
||||
|
fi |
||||
|
|
||||
|
cp build-files/run.sh .output/run.sh |
||||
|
mkdir .output/server/migrate |
||||
|
cp -r build-files/migrate/* .output/server/migrate/ |
||||
|
mkdir .output/server/seed |
||||
|
cp -r build-files/* .output/server/seed/ |
||||
@ -0,0 +1,42 @@ |
|||||
|
/** |
||||
|
* 各云平台 / 容器编排常见 HTTP 探针路径(不含 `/`,避免误伤站点根)。 |
||||
|
* 可按负载均衡控制台实际配置增删;若与业务路由同名请从列表中移除对应项。 |
||||
|
*/ |
||||
|
export const CLOUD_PROBE_PATHS = [ |
||||
|
// 通用 / Kubernetes
|
||||
|
"/health", |
||||
|
"/healthz", |
||||
|
"/livez", |
||||
|
"/readyz", |
||||
|
"/liveness", |
||||
|
"/readiness", |
||||
|
"/startup", |
||||
|
"/health/live", |
||||
|
"/health/ready", |
||||
|
"/health/startup", |
||||
|
// Spring Actuator(经网关暴露时偶见)
|
||||
|
"/actuator/health", |
||||
|
"/actuator/health/liveness", |
||||
|
"/actuator/health/readiness", |
||||
|
// Azure App Service 相关默认探测
|
||||
|
"/robots933456.txt", |
||||
|
// 国内云控制台常见示例静态页
|
||||
|
"/check.html", |
||||
|
"/status.html", |
||||
|
"/ping", |
||||
|
"/status", |
||||
|
"/alive", |
||||
|
] as const; |
||||
|
|
||||
|
/** 阿里云等:健康检查为 `/rpc` 或带后缀路径(如控制台填写的 `/rpc/...`),按前缀匹配 */ |
||||
|
export const CLOUD_PROBE_PATH_PREFIXES = ["/rpc"] as const; |
||||
|
|
||||
|
export const CLOUD_PROBE_PATH_SET = new Set<string>(CLOUD_PROBE_PATHS); |
||||
|
|
||||
|
export function isCloudProbePath(pathname: string): boolean { |
||||
|
if (CLOUD_PROBE_PATH_SET.has(pathname)) return true; |
||||
|
for (const prefix of CLOUD_PROBE_PATH_PREFIXES) { |
||||
|
if (pathname === prefix || pathname.startsWith(`${prefix}/`)) return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
import path from "node:path"; |
||||
|
|
||||
|
function trimSlashes(input: string): string { |
||||
|
return input.trim().replace(/^\/+|\/+$/g, ""); |
||||
|
} |
||||
|
|
||||
|
function hasParentSegment(input: string): boolean { |
||||
|
return input |
||||
|
.split("/") |
||||
|
.map((part) => part.trim()) |
||||
|
.some((part) => part === ".."); |
||||
|
} |
||||
|
|
||||
|
/** 允许相对项目根或绝对路径;禁止含 `..` 的路径片段(防止配置逃逸)。 */ |
||||
|
function ensureConfigurableDir(input: string, fallback: string, envName: string): string { |
||||
|
const raw = input.trim(); |
||||
|
if (!raw) { |
||||
|
return fallback; |
||||
|
} |
||||
|
const normalized = path.normalize(raw); |
||||
|
const segments = normalized.split(path.sep); |
||||
|
if (segments.some((part) => part === "..")) { |
||||
|
throw new Error(`${envName} must not contain ".." path segments`); |
||||
|
} |
||||
|
return normalized; |
||||
|
} |
||||
|
|
||||
|
function ensureSafeSubdir(input: string, fallback: string, envName: string): string { |
||||
|
const value = trimSlashes(input); |
||||
|
if (!value) { |
||||
|
return fallback; |
||||
|
} |
||||
|
if (hasParentSegment(value)) { |
||||
|
throw new Error(`${envName} must not contain ".." segments`); |
||||
|
} |
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
/** 静态资源 URL 前缀固定为 `/static`,不允许通过环境变量覆写。 */ |
||||
|
export const STATIC_PUBLIC_PREFIX = "/static"; |
||||
|
|
||||
|
/** 静态资源根目录(相对项目根或绝对路径),默认 `static` */ |
||||
|
export const STATIC_DIR = ensureConfigurableDir(process.env.STATIC_DIR ?? "static", "static", "STATIC_DIR"); |
||||
|
|
||||
|
/** 媒体上传子目录(相对 STATIC_DIR),默认 `upload` */ |
||||
|
export const UPLOAD_SUBDIR = ensureSafeSubdir( |
||||
|
process.env.UPLOAD_SUBDIR ?? "upload", |
||||
|
"upload", |
||||
|
"UPLOAD_SUBDIR", |
||||
|
); |
||||
|
|
||||
|
/** 媒体上传目录(与 STATIC_DIR 同为相对或绝对),默认 `static/upload` */ |
||||
|
export const RELATIVE_ASSETS_DIR = path.join(STATIC_DIR, UPLOAD_SUBDIR); |
||||
|
|
||||
|
/** 与 `media` 返回及静态路径一致,无前导 host */ |
||||
|
export const POST_MEDIA_PUBLIC_PREFIX = `${STATIC_PUBLIC_PREFIX}/${UPLOAD_SUBDIR}/`; |
||||
@ -0,0 +1,25 @@ |
|||||
|
import { getRequestURL } from "h3"; |
||||
|
import { isCloudProbePath } from "#server/constants/cloud-probes"; |
||||
|
|
||||
|
const METHODS = new Set(["GET", "HEAD"]); |
||||
|
|
||||
|
export default eventHandler((event) => { |
||||
|
if (!METHODS.has(event.method)) return; |
||||
|
|
||||
|
const pathname = getRequestURL(event).pathname; |
||||
|
if (!isCloudProbePath(pathname)) return; |
||||
|
|
||||
|
setHeader(event, "content-type", "text/plain; charset=utf-8"); |
||||
|
setHeader( |
||||
|
event, |
||||
|
"cache-control", |
||||
|
"no-store, no-cache, must-revalidate, proxy-revalidate", |
||||
|
); |
||||
|
|
||||
|
if (event.method === "HEAD") { |
||||
|
setResponseStatus(event, 200); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
return "OK"; |
||||
|
}); |
||||
@ -1,8 +0,0 @@ |
|||||
|
|
||||
if (import.meta.dev) { |
|
||||
console.log("plugin: 00.global"); |
|
||||
} |
|
||||
|
|
||||
export default defineNitroPlugin(async () => { |
|
||||
|
|
||||
}) |
|
||||
@ -0,0 +1 @@ |
|||||
|
sadsadsad |
||||
Loading…
Reference in new issue