visual-journal / scripts /patch-standalone-runtime.mjs
gpt-image-playground deploy
Deploy 17eadcc to Docker Space
96bdf6c
Raw
History Blame Contribute Delete
8 kB
#!/usr/bin/env node
import { copyFile, cp, mkdir, readFile, readdir, rm, stat } from 'node:fs/promises';
import { createRequire } from 'node:module';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const requiredFiles = [
'app-route-turbo.runtime.prod.js',
'app-route-turbo.runtime.prod.js.map'
];
const sharpRuntimePackages = ['sharp', 'semver', 'detect-libc'];
const root = process.cwd();
function isSharpBindingPackage(packageName) {
return packageName.startsWith('sharp-') && !packageName.startsWith('sharp-libvips-');
}
function isSharpPlatformRuntimePackage(packageName) {
return packageName === 'colour' || packageName.startsWith('sharp-');
}
async function assertFile(filepath) {
let info;
try {
info = await stat(filepath);
} catch (error) {
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
throw new Error(`文件不存在:${filepath}`);
}
const message = error instanceof Error ? error.message : String(error);
throw new Error(`读取文件状态失败:${filepath}${message}`);
}
if (!info.isFile()) {
throw new Error(`预期是文件,但实际不是文件路径:${filepath}`);
}
}
async function directoryExists(dirpath) {
try {
const info = await stat(dirpath);
return info.isDirectory();
} catch (error) {
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') return false;
throw error;
}
}
async function assertDirectory(dirpath, description) {
if (await directoryExists(dirpath)) return;
throw new Error(`${description}缺失:${dirpath}`);
}
async function copyDirectory(source, target) {
await rm(target, { force: true, recursive: true });
await mkdir(path.dirname(target), { recursive: true });
await cp(source, target, { recursive: true });
}
async function isSharpExternalModule(sourceDir) {
try {
const rawPackageJson = await readFile(path.join(sourceDir, 'package.json'), 'utf8');
const packageJson = JSON.parse(rawPackageJson);
return packageJson && typeof packageJson === 'object' && packageJson.name === 'sharp';
} catch (error) {
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') return false;
const message = error instanceof Error ? error.message : String(error);
throw new Error(`读取 Turbopack sharp 外部模块 package.json 失败:${sourceDir}${message}`);
}
}
export async function copySharpRuntime(rootDir = process.cwd()) {
const standaloneDir = path.join(rootDir, '.next', 'standalone');
for (const packageName of sharpRuntimePackages) {
const sourcePackageDir = path.join(rootDir, 'node_modules', packageName);
await assertDirectory(sourcePackageDir, 'standalone sharp 运行依赖');
const targetPackageDir = path.join(standaloneDir, 'node_modules', packageName);
console.info(`复制 standalone sharp 运行依赖:${sourcePackageDir} -> ${targetPackageDir}`);
await copyDirectory(sourcePackageDir, targetPackageDir);
}
const sourceImgDir = path.join(rootDir, 'node_modules', '@img');
await assertDirectory(sourceImgDir, 'standalone sharp 平台依赖目录');
await assertDirectory(path.join(sourceImgDir, 'colour'), 'standalone sharp colour 依赖');
const targetImgDir = path.join(standaloneDir, 'node_modules', '@img');
const entries = await readdir(sourceImgDir, { withFileTypes: true });
let copiedSharpBindingPackages = 0;
let copiedSharpLibvipsPackages = 0;
for (const entry of entries) {
if (!entry.isDirectory() || !isSharpPlatformRuntimePackage(entry.name)) continue;
const source = path.join(sourceImgDir, entry.name);
const target = path.join(targetImgDir, entry.name);
console.info(`复制 standalone sharp 平台包:${source} -> ${target}`);
await copyDirectory(source, target);
if (isSharpBindingPackage(entry.name)) copiedSharpBindingPackages += 1;
if (entry.name.startsWith('sharp-libvips-')) copiedSharpLibvipsPackages += 1;
}
if (copiedSharpBindingPackages === 0) {
throw new Error(`standalone sharp native binding 依赖缺失:${sourceImgDir} 没有可复制的 @img/sharp-* binding 包`);
}
if (copiedSharpLibvipsPackages === 0) {
throw new Error(`standalone sharp libvips 依赖缺失:${sourceImgDir} 没有可复制的 @img/sharp-libvips-* 包`);
}
const externalNodeModulesDir = path.join(rootDir, '.next', 'node_modules');
if (!(await directoryExists(externalNodeModulesDir))) return;
const externalEntries = await readdir(externalNodeModulesDir, { withFileTypes: true });
const sourceSharpDir = path.join(rootDir, 'node_modules', 'sharp');
if (!(await directoryExists(sourceSharpDir))) return;
for (const entry of externalEntries) {
if (!entry.isDirectory() || !entry.name.startsWith('sharp-')) continue;
const externalSource = path.join(externalNodeModulesDir, entry.name);
if (!(await isSharpExternalModule(externalSource))) continue;
const target = path.join(standaloneDir, '.next', 'node_modules', entry.name);
console.info(`复制 standalone Turbopack sharp 外部模块:${sourceSharpDir} -> ${target}`);
await copyDirectory(sourceSharpDir, target);
}
}
export async function validateStandaloneSharpRuntime(rootDir = process.cwd()) {
const standaloneDir = path.join(rootDir, '.next', 'standalone');
const requireFromStandalone = createRequire(path.join(standaloneDir, 'runtime-check.cjs'));
let sharpModule;
try {
const resolvedSharp = requireFromStandalone.resolve('sharp');
const sharpPackageDir = path.join(standaloneDir, 'node_modules', 'sharp');
for (const cachedPath of Object.keys(requireFromStandalone.cache)) {
if (cachedPath === resolvedSharp || cachedPath.startsWith(`${sharpPackageDir}${path.sep}`)) {
delete requireFromStandalone.cache[cachedPath];
}
}
sharpModule = requireFromStandalone('sharp');
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`standalone sharp 运行时加载失败:${message}`);
}
const sharp = sharpModule?.default ?? sharpModule;
if (typeof sharp !== 'function') {
throw new Error('standalone sharp 运行时加载失败:sharp 模块没有导出函数。');
}
try {
const buffer = await sharp({
create: {
width: 1,
height: 1,
channels: 3,
background: { r: 0, g: 0, b: 0 }
}
})
.webp()
.toBuffer();
if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
throw new Error('sharp self-check returned an empty result');
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`standalone sharp 运行时自检失败:${message}`);
}
}
export async function patchStandaloneRuntime(rootDir = process.cwd()) {
const sourceDir = path.join(rootDir, 'node_modules', 'next', 'dist', 'compiled', 'next-server');
const targetDir = path.join(
rootDir,
'.next',
'standalone',
'node_modules',
'next',
'dist',
'compiled',
'next-server'
);
console.info(`准备补齐 standalone runtime:${targetDir}`);
await mkdir(targetDir, { recursive: true });
for (const filename of requiredFiles) {
const source = path.join(sourceDir, filename);
const target = path.join(targetDir, filename);
console.info(`复制 standalone runtime 文件:${source} -> ${target}`);
await assertFile(source);
await copyFile(source, target);
}
await copySharpRuntime(rootDir);
await validateStandaloneSharpRuntime(rootDir);
console.info('standalone runtime 文件补齐完成。');
}
async function main() {
try {
await patchStandaloneRuntime(root);
} catch (error) {
console.error('补齐 standalone runtime 文件失败。', error);
process.exit(1);
}
}
if (path.resolve(process.argv[1] ?? '') === fileURLToPath(import.meta.url)) {
await main();
}