perf: slim database migrator and application image layers

This commit is contained in:
WuYongJie 2026-10-13 10:51:37 +08:00
parent e5a20753b6
commit 9270bbb457
8 changed files with 941 additions and 48 deletions

View File

@ -226,3 +226,10 @@ temp_*.sql
!workers/detail-renderer/package.json
!workers/detail-renderer/package-lock.json
!public/detail-generator/fonts/OFL.txt
# Isolated migration tooling, locked to the application's dependency versions.
/workers/database-migrator/node_modules
/workers/database-migrator/node_modules/**
!workers/database-migrator/package.json
!workers/database-migrator/package-lock.json
!scripts/database-migrator-lock.mjs

View File

@ -156,6 +156,22 @@ RUN echo "清理 standalone 目录..." && \
rm -rf .next/standalone/scripts && \
echo "清理完成"
# Install only the migration/backup/seed dependency closure. Its lock is derived
# from the application lock and checked before installation to prevent drift.
FROM base AS migrator-deps
ARG ALPINE_MIRROR
WORKDIR /app
RUN sed -i "s|https://dl-cdn.alpinelinux.org/alpine|${ALPINE_MIRROR}|g" /etc/apk/repositories && \
apk add --no-cache openssl
COPY package-lock.json ./package-lock.json
COPY workers/database-migrator/package.json workers/database-migrator/package-lock.json ./workers/database-migrator/
COPY scripts/database-migrator-lock.mjs ./scripts/database-migrator-lock.mjs
RUN node scripts/database-migrator-lock.mjs --check
# The matching generated client is copied from builder; do not generate a second
# client during installation. The CLI engines still run their normal postinstall.
RUN --mount=type=cache,target=/root/.npm \
cd workers/database-migrator && PRISMA_SKIP_POSTINSTALL_GENERATE=1 npm ci --no-audit --no-fund
# Explicit, one-shot database migrator. It is never used by the application
# container or the normal deploy entry. The operator must supply production DB
# identity, exact source SHA and a hashed backup receipt before --apply works.
@ -171,20 +187,23 @@ RUN sed -i "s|https://dl-cdn.alpinelinux.org/alpine|${ALPINE_MIRROR}|g" /etc/apk
command -v psql >/dev/null
ARG VCS_REF
LABEL org.opencontainers.image.revision=$VCS_REF
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/scripts/safe-production-migrate.mjs ./scripts/safe-production-migrate.mjs
COPY --from=builder /app/scripts/create-verified-production-backup.mjs ./scripts/create-verified-production-backup.mjs
COPY --from=builder /app/scripts/seed-team-function-config.ts ./scripts/seed-team-function-config.ts
COPY --from=builder /app/lib ./lib
COPY --from=builder /app/tsconfig.json ./tsconfig.json
COPY --from=builder /app/data/team-function-initial-content.json ./data/team-function-initial-content.json
COPY --from=builder /app/DEPLOYMENT_COMMIT /app/DEPLOYMENT_COMMIT
COPY --from=migrator-deps --chown=root:root /app/workers/database-migrator/node_modules ./node_modules
COPY --from=builder --chown=root:root /app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder --chown=root:root /app/prisma ./prisma
COPY --from=builder --chown=root:root /app/scripts/safe-production-migrate.mjs ./scripts/safe-production-migrate.mjs
COPY --from=builder --chown=root:root /app/scripts/create-verified-production-backup.mjs ./scripts/create-verified-production-backup.mjs
COPY --from=builder --chown=root:root /app/scripts/seed-team-function-config.ts ./scripts/seed-team-function-config.ts
# Source closure for the team-function seed; no application routes or services.
COPY --from=builder --chown=root:root /app/lib/auto-personnel-rules.ts /app/lib/organization-material-permissions.ts /app/lib/prisma.ts /app/lib/team-function-contract.ts /app/lib/department-permission.ts /app/lib/employee-directory.ts /app/lib/env-validation.ts /app/lib/material-type-mapper.ts /app/lib/material-type-options.ts /app/lib/permission-groups.ts /app/lib/team-functions.ts /app/lib/user-permission-inherit.ts /app/lib/video-material-types.ts ./lib/
COPY --from=builder --chown=root:root /app/tsconfig.json ./tsconfig.json
COPY --from=builder --chown=root:root /app/data/team-function-initial-content.json ./data/team-function-initial-content.json
COPY --from=builder --chown=root:root /app/DEPLOYMENT_COMMIT /app/DEPLOYMENT_COMMIT
RUN test "$(cat /app/DEPLOYMENT_COMMIT)" = "$VCS_REF" && \
test -x /app/node_modules/prisma/node_modules/@prisma/engines/schema-engine-linux-musl && \
chown -R node:node /app/node_modules/prisma/node_modules/@prisma/engines && \
chmod -R a+rX,a-w /app/prisma /app/scripts /app/DEPLOYMENT_COMMIT
test -x /app/node_modules/prisma/node_modules/@prisma/engines/schema-engine-linux-musl-openssl-3.0.x && \
test "$(stat -c '%a' /app/DEPLOYMENT_COMMIT)" = 444
USER node
# Prove the CLI can use the bundled native engine without downloading or writing.
RUN --network=none ./node_modules/.bin/prisma -v
ENTRYPOINT ["node", "scripts/safe-production-migrate.mjs"]
# 生产运行阶段
@ -210,9 +229,9 @@ RUN addgroup --system --gid 1001 nodejs && \
RUN mkdir -p /app/scripts
# 复制必要文件
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/app ./app
COPY --from=builder --chown=root:root /app/.next/standalone ./
COPY --from=builder --chown=root:root /app/.next/static ./.next/static
COPY --from=builder --chown=root:root /app/app ./app
COPY --from=builder --chown=root:root /app/public ./public
COPY --from=builder --chown=root:root /app/DEPLOYMENT_COMMIT /app/DEPLOYMENT_COMMIT
COPY --from=detail-renderer-deps --chown=root:root /opt/detail-renderer /opt/detail-renderer
@ -221,15 +240,15 @@ ENV DETAIL_RENDERER_NODE=/opt/detail-renderer/bin/node
ENV DETAIL_RENDERER_ENTRY=/opt/detail-renderer/cli.mjs
# 只复制运行时必需的 node_modules
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/ag-psd ./node_modules/ag-psd
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/ioredis ./node_modules/ioredis
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/@ioredis ./node_modules/@ioredis
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/@aws-sdk ./node_modules/@aws-sdk
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/@smithy ./node_modules/@smithy
COPY --from=builder --chown=root:root /app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder --chown=root:root /app/node_modules/ag-psd ./node_modules/ag-psd
COPY --from=builder --chown=root:root /app/node_modules/ioredis ./node_modules/ioredis
COPY --from=builder --chown=root:root /app/node_modules/@ioredis ./node_modules/@ioredis
COPY --from=builder --chown=root:root /app/node_modules/@aws-sdk ./node_modules/@aws-sdk
COPY --from=builder --chown=root:root /app/node_modules/@smithy ./node_modules/@smithy
# 复制运行时 schema,以及切流前只读校验所需的精确 migration;仍不复制 engine-cache。
COPY --from=builder --chown=nextjs:nodejs /app/prisma/schema.prisma ./prisma/schema.prisma
COPY --from=builder --chown=root:root /app/prisma/schema.prisma ./prisma/schema.prisma
COPY --from=builder --chown=root:root /app/prisma/migrations/20251118000000_add_upload_id_to_materials ./prisma/migrations/20251118000000_add_upload_id_to_materials
COPY --from=builder --chown=root:root /app/prisma/migrations/20260811_add_video_transcode_lease ./prisma/migrations/20260811_add_video_transcode_lease
COPY --from=builder --chown=root:root /app/prisma/migrations/20260811_update_brand_logo_defaults ./prisma/migrations/20260811_update_brand_logo_defaults
@ -265,29 +284,17 @@ COPY --from=builder --chown=root:root /app/prisma/migrations/20260924_add_certif
COPY --from=builder --chown=root:root /app/prisma/migrations/20261012_detail_generator ./prisma/migrations/20261012_detail_generator
COPY --from=builder --chown=root:root /app/prisma/migrations/20261012_ai_model_registry ./prisma/migrations/20261012_ai_model_registry
COPY --from=builder --chown=root:root /app/prisma/migrations/20261013_ai_model_capabilities ./prisma/migrations/20261013_ai_model_capabilities
COPY --from=builder --chown=nextjs:nodejs /app/lib ./lib
COPY --from=builder --chown=nextjs:nodejs /app/scripts/public-traffic-rollup-from-nginx.py ./scripts/public-traffic-rollup-from-nginx.py
COPY --from=builder --chown=nextjs:nodejs /app/scripts/collapse-performance-survey-template-versions.mjs ./scripts/collapse-performance-survey-template-versions.mjs
COPY --from=builder --chown=root:root /app/lib ./lib
COPY --from=builder --chown=root:root /app/scripts/public-traffic-rollup-from-nginx.py ./scripts/public-traffic-rollup-from-nginx.py
COPY --from=builder --chown=root:root /app/scripts/collapse-performance-survey-template-versions.mjs ./scripts/collapse-performance-survey-template-versions.mjs
# 创建必要目录并收紧权限。
# 应用代码归 root 所有,运行账号只能写上传目录和 Next.js 运行时缓存;
# 即使上层文件路径校验再次出现缺陷,也不能覆盖 server.js 或业务源码。
RUN mkdir -p /app/public /app/uploads && \
mkdir -p /app/detail-generator-private && \
mkdir -p /app/scripts && \
mkdir -p /app/.next/cache && \
mkdir -p /app/uploads/temp && \
mkdir -p /app/uploads/assembled && \
mkdir -p /app/uploads/materials && \
mkdir -p /app/uploads/cleanup && \
chown -R root:root /app && \
chmod -R a+rX /app/public /app/prisma/migrations /app/scripts && \
chown -R nextjs:nodejs /app/.next/cache /app/uploads && \
chown nextjs:nodejs /app/detail-generator-private && \
chmod 0700 /app/detail-generator-private && \
chmod 0750 /app/uploads /app/uploads/temp /app/uploads/assembled /app/uploads/materials /app/uploads/cleanup && \
# 代码在 COPY 时直接归 root,避免再次改动整棵目录产生重复镜像层。
# 仅运行时缓存和存储目录允许 nextjs 写入,代码权限保持只读。
RUN install -d -o nextjs -g nodejs -m 0755 /app/.next/cache && \
install -d -o nextjs -g nodejs -m 0750 /app/uploads /app/uploads/temp /app/uploads/assembled /app/uploads/materials /app/uploads/cleanup && \
install -d -o nextjs -g nodejs -m 0700 /app/detail-generator-private && \
test "$(cat /app/DEPLOYMENT_COMMIT)" = "$VCS_REF" && \
chmod 0444 /app/DEPLOYMENT_COMMIT && \
test "$(stat -c '%a' /app/DEPLOYMENT_COMMIT)" = 444 && \
test -s /app/public/brand/header-logo.png && \
test -s /app/public/brand/login-logo.png && \
test -s /app/public/brand/favicon.png && \

View File

@ -0,0 +1,101 @@
import assert from 'node:assert/strict'
import { readFile, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const MANIFEST = 'workers/database-migrator/package.json'
const LOCK = 'workers/database-migrator/package-lock.json'
const DIRECT_DEPENDENCIES = ['@prisma/client', 'prisma', 'tsx', 'zod']
// Preserve the main lock's exact locations, versions and integrity hashes. In
// particular Prisma 5's nested engines must not resolve to the app's Prisma 6
// engines. npm ci consumes this subset without resolving a new dependency tree.
export function createMigratorLock(rootLock, manifest) {
assert.equal(rootLock.lockfileVersion, 3, 'Main lock must use lockfileVersion 3')
assert.deepEqual(Object.keys(manifest.dependencies).sort(), DIRECT_DEPENDENCIES,
'Migrator direct dependencies must be explicitly reviewed')
assert.ok(!manifest.devDependencies && !manifest.optionalDependencies,
'Migrator manifest cannot introduce unreviewed dependency groups')
const packages = {}
const visit = packagePath => {
if (packages[packagePath]) return
const record = rootLock.packages[packagePath]
assert.ok(record && !record.link, `Missing or linked dependency: ${packagePath}`)
assert.ok(record.version && record.resolved && record.integrity,
`Dependency must have a pinned version, source and integrity: ${packagePath}`)
const copy = structuredClone(record)
// These packages are all runtime dependencies of the one-shot image.
delete copy.dev
delete copy.devOptional
packages[packagePath] = copy
const resolve = name => {
let parent = packagePath
while (parent) {
const candidate = `${parent}/node_modules/${name}`
if (rootLock.packages[candidate]) return candidate
const index = parent.lastIndexOf('/node_modules/')
parent = index < 0 ? '' : parent.slice(0, index)
}
const candidate = `node_modules/${name}`
assert.ok(rootLock.packages[candidate], `Unresolved dependency ${name} from ${packagePath}`)
return candidate
}
for (const name of Object.keys({ ...record.dependencies, ...record.optionalDependencies })) {
visit(resolve(name))
}
for (const name of Object.keys(record.peerDependencies ?? {})) {
// A newly introduced optional peer could change runtime behavior even
// when npm accepts its absence. Review it instead of silently omitting it.
if (record.peerDependenciesMeta?.[name]?.optional) {
assert.ok(packagePath === 'node_modules/@prisma/client' && name === 'prisma',
`Unreviewed optional peer ${name} from ${packagePath}`)
}
visit(resolve(name))
}
}
for (const [name, version] of Object.entries(manifest.dependencies)) {
const packagePath = `node_modules/${name}`
assert.equal(rootLock.packages[packagePath]?.version, version,
`${name} differs from the main lock; review and update the migrator manifest`)
visit(packagePath)
}
return {
name: manifest.name,
version: manifest.version,
lockfileVersion: 3,
requires: true,
packages: {
'': {
name: manifest.name,
version: manifest.version,
dependencies: manifest.dependencies,
engines: manifest.engines,
},
...Object.fromEntries(Object.entries(packages).sort(([a], [b]) => a.localeCompare(b, 'en'))),
},
}
}
async function main() {
const [mode = '--check', ...extra] = process.argv.slice(2)
assert.ok(['--check', '--write'].includes(mode) && extra.length === 0,
'Usage: node scripts/database-migrator-lock.mjs [--check|--write]')
const rootLock = JSON.parse(await readFile(path.join(ROOT, 'package-lock.json'), 'utf8'))
const manifest = JSON.parse(await readFile(path.join(ROOT, MANIFEST), 'utf8'))
const expected = createMigratorLock(rootLock, manifest)
if (mode === '--write') {
await writeFile(path.join(ROOT, LOCK), `${JSON.stringify(expected, null, 2)}\n`)
} else {
const actual = JSON.parse(await readFile(path.join(ROOT, LOCK), 'utf8'))
assert.deepEqual(actual, expected,
'Migrator lock differs from the main lock. Run node scripts/database-migrator-lock.mjs --write and review it.')
}
console.log(`Migrator lock ${mode === '--write' ? 'written' : 'verified'}: ${Object.keys(expected.packages).length - 1} pinned packages`)
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
main().catch(error => { console.error(error.message); process.exitCode = 1 })
}

View File

@ -0,0 +1,112 @@
import assert from 'node:assert/strict'
import { existsSync, readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import test from 'node:test'
import { createMigratorLock } from './database-migrator-lock.mjs'
const read = relative => JSON.parse(readFileSync(fileURLToPath(new URL(relative, import.meta.url)), 'utf8'))
const root = read('../package-lock.json')
const manifest = read('../workers/database-migrator/package.json')
const lock = read('../workers/database-migrator/package-lock.json')
test('committed migrator lock is the exact reproducible main-lock subset', () => {
assert.deepEqual(lock, createMigratorLock(root, manifest))
assert.ok(Object.keys(lock.packages).length < 50)
for (const [name, record] of Object.entries(lock.packages)) {
if (!name) continue
for (const key of ['version', 'resolved', 'integrity']) assert.equal(record[key], root.packages[name][key])
assert.equal(record.dev, undefined)
assert.equal(record.devOptional, undefined)
}
})
test('Prisma CLI uses its matching nested engines without app Prisma 6 or UI/media/test packages', () => {
assert.equal(lock.packages['node_modules/prisma/node_modules/@prisma/engines'].version, '5.22.0')
for (const name of ['@prisma/engines', 'next', 'react', 'canvas', 'sharp', 'jest', 'typescript', '@aws-sdk/client-s3']) {
assert.equal(lock.packages[`node_modules/${name}`], undefined, `Unexpected app package ${name}`)
}
assert.ok(lock.packages['node_modules/@esbuild/linux-x64'])
assert.ok(lock.packages['node_modules/get-tsconfig'])
})
test('direct dependency version drift and new dependency groups fail closed', () => {
const changedRoot = structuredClone(root)
changedRoot.packages['node_modules/prisma'].version = '6.18.0'
assert.throws(() => createMigratorLock(changedRoot, manifest), /differs from the main lock/)
assert.throws(() => createMigratorLock(root, { ...manifest, devDependencies: { next: '14.2.33' } }), /unreviewed dependency groups/)
assert.throws(() => createMigratorLock(root, { ...manifest, dependencies: { ...manifest.dependencies, next: '14.2.33' } }), /explicitly reviewed/)
})
test('missing transitive package and missing integrity fail closed', () => {
const missing = structuredClone(root)
delete missing.packages['node_modules/get-tsconfig']
assert.throws(() => createMigratorLock(missing, manifest), /Unresolved dependency/)
const unsigned = structuredClone(root)
delete unsigned.packages['node_modules/prisma/node_modules/@prisma/engines'].integrity
assert.throws(() => createMigratorLock(unsigned, manifest), /integrity/)
})
test('optional peer additions require explicit review instead of silently disappearing', () => {
const changed = structuredClone(root)
changed.packages['node_modules/zod'].peerDependencies = { next: '*' }
changed.packages['node_modules/zod'].peerDependenciesMeta = { next: { optional: true } }
assert.throws(() => createMigratorLock(changed, manifest), /Unreviewed optional peer/)
})
test('slim migrator copies the full seed source closure and no unreviewed runtime dependency', () => {
const require = createRequire(import.meta.url)
const ts = require('typescript')
const rootDir = fileURLToPath(new URL('../', import.meta.url))
const files = new Set()
const packages = new Set()
const visitFile = relative => {
relative = relative.replaceAll('\\', '/')
if (files.has(relative)) return
files.add(relative)
const absolute = path.join(rootDir, relative)
const ast = ts.createSourceFile(absolute, readFileSync(absolute, 'utf8'), ts.ScriptTarget.Latest, true)
const resolve = specifier => {
if (specifier.startsWith('node:')) return
if (!specifier.startsWith('.') && !specifier.startsWith('@/')) {
packages.add(specifier)
return
}
const base = specifier.startsWith('@/') ? specifier.slice(2) : path.join(path.dirname(relative), specifier)
const next = [base, `${base}.ts`, `${base}.tsx`, `${base}/index.ts`].find(candidate => existsSync(path.join(rootDir, candidate)))
assert.ok(next, `Unresolved seed import ${specifier} from ${relative}`)
visitFile(next)
}
const visitNode = node => {
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier) resolve(node.moduleSpecifier.text)
if (ts.isCallExpression(node) && node.expression.getText(ast) === 'require' && ts.isStringLiteral(node.arguments[0])) resolve(node.arguments[0].text)
ts.forEachChild(node, visitNode)
}
visitNode(ast)
}
visitFile('scripts/seed-team-function-config.ts')
assert.deepEqual([...packages].sort(), ['@prisma/client', 'zod'])
const dockerfile = readFileSync(path.join(rootDir, 'Dockerfile'), 'utf8')
const migrator = dockerfile.slice(dockerfile.search(/^FROM base AS migrator\r?$/m), dockerfile.search(/^FROM base AS runner\r?$/m))
for (const file of files) assert.ok(migrator.includes(`/app/${file}`), `Seed dependency missing from slim image: ${file}`)
assert.equal(files.size, 14, 'Review seed dependency closure before changing the image copy list')
})
test('Docker locks the isolated install and runs Prisma as nonroot with no network', () => {
const dockerfile = readFileSync(fileURLToPath(new URL('../Dockerfile', import.meta.url)), 'utf8')
assert.match(dockerfile, /node scripts\/database-migrator-lock\.mjs --check/)
const depsStart = dockerfile.search(/^FROM base AS migrator-deps\r?$/m)
assert.ok(depsStart >= 0)
const deps = dockerfile.slice(depsStart, dockerfile.indexOf('\nFROM ', depsStart + 1))
assert.match(deps, /npm ci --no-audit --no-fund/)
assert.match(deps, /PRISMA_SKIP_POSTINSTALL_GENERATE=1 npm ci/)
assert.doesNotMatch(deps, /--ignore-scripts/)
assert.doesNotMatch(deps, /npm (?:install|update)/)
assert.match(deps, /apk add --no-cache openssl/)
const start = dockerfile.search(/^FROM base AS migrator\r?$/m)
const migrator = dockerfile.slice(start, dockerfile.indexOf('\nFROM ', start + 1))
assert.match(migrator, /USER node[\s\S]*RUN --network=none[^\n]*prisma (?:--version|-v)/)
assert.match(migrator, /COPY --from=builder --chown=root:root \/app\/node_modules\/\.prisma/)
assert.match(migrator, /ENTRYPOINT \["node", "scripts\/safe-production-migrate\.mjs"\]/)
})

View File

@ -891,7 +891,7 @@ test('container supply chain and migrator image are immutable while SOP keeps co
readFile(new URL('../.dockerignore', import.meta.url), 'utf8'),
readFile(new URL('./git-incremental-deploy.ps1', import.meta.url), 'utf8'),
])
const migratorStart = dockerfile.indexOf('FROM base AS migrator')
const migratorStart = dockerfile.search(/^FROM base AS migrator\r?$/m)
const runnerStart = dockerfile.indexOf('FROM base AS runner', migratorStart)
const migrator = dockerfile.slice(migratorStart, runnerStart)
const depsStart = dockerfile.indexOf('FROM base AS deps')
@ -916,10 +916,12 @@ test('container supply chain and migrator image are immutable while SOP keeps co
assert.match(migrator, /command -v pg_dump >\/dev\/null/)
assert.match(migrator, /command -v pg_restore >\/dev\/null/)
assert.match(migrator, /command -v psql >\/dev\/null/)
assert.match(migrator, /COPY --from=builder \/app\/scripts\/create-verified-production-backup\.mjs/)
assert.match(migrator, /test -x \/app\/node_modules\/prisma\/node_modules\/@prisma\/engines\/schema-engine-linux-musl/)
assert.match(migrator, /chown -R node:node \/app\/node_modules\/prisma\/node_modules\/@prisma\/engines/)
assert.match(migrator, /chmod -R a\+rX,a-w \/app\/prisma \/app\/scripts \/app\/DEPLOYMENT_COMMIT/)
assert.match(migrator, /COPY --from=builder --chown=root:root \/app\/scripts\/create-verified-production-backup\.mjs/)
assert.match(migrator, /test -x \/app\/node_modules\/prisma\/node_modules\/@prisma\/engines\/schema-engine-linux-musl-openssl-3\.0\.x(?:\s|$)/)
assert.doesNotMatch(migrator, /chown\s+(?:-R\s+)?node:node/)
assert.match(migrator, /COPY --from=migrator-deps --chown=root:root \/app\/workers\/database-migrator\/node_modules/)
assert.doesNotMatch(migrator, /COPY[^\n]*\/app\/node_modules\s+\.\/node_modules/)
assert.doesNotMatch(migrator, /COPY[^\n]*\/app\/lib\s+\.\/lib/)
assert.match(migrator, /USER node/)
assert.match(sop, /tar -xOf "\$MIGRATOR_ARCHIVE" manifest\.json/)
assert.match(sop, /entry\.RepoTags\.includes\(image\)/)

View File

@ -0,0 +1,26 @@
# Database migrator dependencies
此目录只为一次性数据库工具提供依赖,不安装完整应用依赖,也不改变生产迁移、备份和恢复的授权门禁。
| 依赖 | 用途 |
| --- | --- |
| `@prisma/client` | 迁移前后的数据库结构、身份核验;团队职能 seed 查询与写入 |
| `prisma` | 执行 `prisma migrate deploy` 及所需的原生 schema engine |
| `tsx` | 执行现有 TypeScript 团队职能 seed,不复制 TypeScript 编译器 |
| `zod` | 复用团队职能输入校验 |
独立 lock 从根 `package-lock.json` 提取完整依赖闭包,保留精确版本、下载来源、integrity 和嵌套路径。Prisma CLI 使用自身锁定的 5.22 引擎,不使用应用根目录的另一版本引擎。生成器会检查直接依赖版本;新增可选 peer 也必须先人工核对,不能静默省略。
根 lock 更新后,先检查这四个依赖的实际锁定版本是否变化;有变化时审核兼容性并同步本目录 `package.json`,然后执行:
```sh
node scripts/database-migrator-lock.mjs --write
node scripts/database-migrator-lock.mjs --check
node --test scripts/database-migrator-lock.test.mjs scripts/safe-production-migrate.test.mjs
```
审核独立 lock 的差异后重新构建 `migrator`。构建过程先执行 `--check`,根 lock 与独立 lock 不一致时直接失败,不重新解析版本。
Docker 的独立依赖阶段先安装 OpenSSL 3,再执行 `PRISMA_SKIP_POSTINSTALL_GENERATE=1 npm ci`,将匹配 Alpine 的 `schema-engine-linux-musl-openssl-3.0.x` 打包。该变量仅跳过此无 schema 目录中多余的 Client 自动生成;`@prisma/engines` 的安装脚本照常执行。生成的 Prisma Client 从同次应用 builder 的 `.prisma` 目录复制,保证与相同版本的 schema 一致。最终镜像以 `node` 用户运行,代码和引擎归 root 所有,并在断网条件下执行 Prisma CLI 验证,避免运行时下载引擎。
团队职能 seed 仍复用项目原有权限、花名册及数据写入逻辑。镜像只复制其 13 个 `lib` 源文件;测试解析脚本的完整导入关系,检查复制列表及外部依赖。修改 seed 的导入关系后,必须重新审核这一依赖闭包。备份工具继续依赖系统提供的 `pg_dump`、`pg_restore` 和 `psql`。

View File

@ -0,0 +1,623 @@
{
"name": "gongxiangp2-database-migrator",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gongxiangp2-database-migrator",
"version": "1.0.0",
"dependencies": {
"@prisma/client": "5.22.0",
"prisma": "5.22.0",
"tsx": "4.21.0",
"zod": "3.25.76"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
"cpu": [
"mips64el"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
"cpu": [
"s390x"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@prisma/client": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz",
"integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"engines": {
"node": ">=16.13"
},
"peerDependencies": {
"prisma": "*"
},
"peerDependenciesMeta": {
"prisma": {
"optional": true
}
}
},
"node_modules/esbuild": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.27.2",
"@esbuild/android-arm": "0.27.2",
"@esbuild/android-arm64": "0.27.2",
"@esbuild/android-x64": "0.27.2",
"@esbuild/darwin-arm64": "0.27.2",
"@esbuild/darwin-x64": "0.27.2",
"@esbuild/freebsd-arm64": "0.27.2",
"@esbuild/freebsd-x64": "0.27.2",
"@esbuild/linux-arm": "0.27.2",
"@esbuild/linux-arm64": "0.27.2",
"@esbuild/linux-ia32": "0.27.2",
"@esbuild/linux-loong64": "0.27.2",
"@esbuild/linux-mips64el": "0.27.2",
"@esbuild/linux-ppc64": "0.27.2",
"@esbuild/linux-riscv64": "0.27.2",
"@esbuild/linux-s390x": "0.27.2",
"@esbuild/linux-x64": "0.27.2",
"@esbuild/netbsd-arm64": "0.27.2",
"@esbuild/netbsd-x64": "0.27.2",
"@esbuild/openbsd-arm64": "0.27.2",
"@esbuild/openbsd-x64": "0.27.2",
"@esbuild/openharmony-arm64": "0.27.2",
"@esbuild/sunos-x64": "0.27.2",
"@esbuild/win32-arm64": "0.27.2",
"@esbuild/win32-ia32": "0.27.2",
"@esbuild/win32-x64": "0.27.2"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/get-tsconfig": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.12.0.tgz",
"integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==",
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/prisma": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz",
"integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/engines": "5.22.0"
},
"bin": {
"prisma": "build/index.js"
},
"engines": {
"node": ">=16.13"
},
"optionalDependencies": {
"fsevents": "2.3.3"
}
},
"node_modules/prisma/node_modules/@prisma/debug": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz",
"integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==",
"license": "Apache-2.0"
},
"node_modules/prisma/node_modules/@prisma/engines": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz",
"integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "5.22.0",
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
"@prisma/fetch-engine": "5.22.0",
"@prisma/get-platform": "5.22.0"
}
},
"node_modules/prisma/node_modules/@prisma/engines-version": {
"version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz",
"integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==",
"license": "Apache-2.0"
},
"node_modules/prisma/node_modules/@prisma/fetch-engine": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz",
"integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==",
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "5.22.0",
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
"@prisma/get-platform": "5.22.0"
}
},
"node_modules/prisma/node_modules/@prisma/get-platform": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz",
"integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==",
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "5.22.0"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/tsx": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
"license": "MIT",
"dependencies": {
"esbuild": "~0.27.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View File

@ -0,0 +1,15 @@
{
"name": "gongxiangp2-database-migrator",
"version": "1.0.0",
"private": true,
"description": "Isolated dependencies for migration, verified backup and team-function seed tools",
"engines": {
"node": ">=18"
},
"dependencies": {
"@prisma/client": "5.22.0",
"prisma": "5.22.0",
"tsx": "4.21.0",
"zod": "3.25.76"
}
}