Spaces:
Running
Running
| import assert from 'node:assert/strict'; | |
| import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; | |
| import { writeFile } from 'node:fs/promises'; | |
| import { tmpdir } from 'node:os'; | |
| import { join } from 'node:path'; | |
| import { after, before, describe, it } from 'node:test'; | |
| import { patchStandaloneRuntime, validateStandaloneSharpRuntime } from './patch-standalone-runtime.mjs'; | |
| const tempDirectories = []; | |
| const originalConsoleInfo = console.info; | |
| before(() => { | |
| console.info = () => {}; | |
| }); | |
| after(() => { | |
| console.info = originalConsoleInfo; | |
| for (const directory of tempDirectories) { | |
| rmSync(directory, { recursive: true, force: true, maxRetries: 3 }); | |
| } | |
| }); | |
| describe('standalone runtime patch', () => { | |
| it('把完整 sharp 运行文件复制到 standalone 和 Turbopack 外部目录', async () => { | |
| const root = createTempRepo(); | |
| await writeFakeSharpPackage(root); | |
| await writeFile(join(root, 'node_modules/semver/index.js'), 'module.exports = {};\n'); | |
| await writeFile(join(root, 'node_modules/semver/package.json'), '{"name":"semver"}\n'); | |
| await writeFile(join(root, 'node_modules/detect-libc/lib/detect-libc.js'), 'module.exports = {};\n'); | |
| await writeFile(join(root, 'node_modules/detect-libc/package.json'), '{"name":"detect-libc"}\n'); | |
| await writeFile(join(root, 'node_modules/@img/colour/index.cjs'), 'module.exports = {};\n'); | |
| await writeFile(join(root, 'node_modules/@img/colour/package.json'), '{"name":"@img/colour"}\n'); | |
| await writeFile(join(root, 'node_modules/@img/sharp-linuxmusl-x64/package.json'), '{"name":"@img/sharp-linuxmusl-x64"}\n'); | |
| await writeFile(join(root, 'node_modules/@img/sharp-libvips-linuxmusl-x64/package.json'), '{"name":"@img/sharp-libvips-linuxmusl-x64"}\n'); | |
| await writeFile(join(root, 'node_modules/@img/not-sharp/package.json'), '{"name":"@img/not-sharp"}\n'); | |
| await writeFile(join(root, '.next/node_modules/sharp-hashed/package.json'), '{"name":"sharp"}\n'); | |
| await writeFile(join(root, '.next/node_modules/sharp-unrelated/package.json'), '{"name":"sharp-unrelated"}\n'); | |
| await patchStandaloneRuntime(root); | |
| assert.equal(existsSync(join(root, '.next/standalone/node_modules/sharp/dist/index.mjs')), true); | |
| assert.equal(existsSync(join(root, '.next/standalone/node_modules/sharp/dist/index.cjs')), true); | |
| assert.equal(existsSync(join(root, '.next/standalone/node_modules/semver/index.js')), true); | |
| assert.equal(existsSync(join(root, '.next/standalone/node_modules/detect-libc/lib/detect-libc.js')), true); | |
| assert.equal(existsSync(join(root, '.next/standalone/node_modules/@img/colour/index.cjs')), true); | |
| assert.equal(existsSync(join(root, '.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json')), true); | |
| assert.equal(existsSync(join(root, '.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json')), true); | |
| assert.equal(existsSync(join(root, '.next/standalone/node_modules/@img/not-sharp/package.json')), false); | |
| assert.equal(existsSync(join(root, '.next/standalone/.next/node_modules/sharp-hashed/dist/index.mjs')), true); | |
| assert.equal(existsSync(join(root, '.next/standalone/.next/node_modules/sharp-unrelated/dist/index.mjs')), false); | |
| assert.equal( | |
| readFileSync(join(root, '.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js'), 'utf8'), | |
| 'runtime\n' | |
| ); | |
| }); | |
| it('在 standalone 布局下真实加载 sharp 并运行自检', async () => { | |
| const root = createTempRepo(); | |
| await writeFakeSharpPackage(root); | |
| await patchStandaloneRuntime(root); | |
| await validateStandaloneSharpRuntime(root); | |
| }); | |
| it('standalone sharp 运行时无法加载时显式失败', async () => { | |
| const root = createTempRepo(); | |
| await writeFakeSharpPackage(root); | |
| await patchStandaloneRuntime(root); | |
| await writeFile(join(root, '.next/standalone/node_modules/sharp/dist/index.cjs'), 'throw new Error("broken sharp");\n'); | |
| await assert.rejects( | |
| () => validateStandaloneSharpRuntime(root), | |
| /standalone sharp 运行时加载失败/ | |
| ); | |
| }); | |
| it('缺少 sharp 核心运行包时显式失败', async () => { | |
| const root = createTempRepo(); | |
| await writeFakeSharpPackage(root); | |
| rmSync(join(root, 'node_modules/semver'), { force: true, recursive: true }); | |
| await assert.rejects( | |
| () => patchStandaloneRuntime(root), | |
| /standalone sharp 运行依赖缺失/ | |
| ); | |
| }); | |
| it('缺少 sharp native binding 平台包时显式失败', async () => { | |
| const root = createTempRepo(); | |
| await writeFakeSharpPackage(root); | |
| rmSync(join(root, 'node_modules/@img/sharp-linuxmusl-x64'), { force: true, recursive: true }); | |
| await assert.rejects( | |
| () => patchStandaloneRuntime(root), | |
| /standalone sharp native binding 依赖缺失/ | |
| ); | |
| }); | |
| it('缺少 sharp libvips 平台包时显式失败', async () => { | |
| const root = createTempRepo(); | |
| await writeFakeSharpPackage(root); | |
| rmSync(join(root, 'node_modules/@img/sharp-libvips-linuxmusl-x64'), { force: true, recursive: true }); | |
| await assert.rejects( | |
| () => patchStandaloneRuntime(root), | |
| /standalone sharp libvips 依赖缺失/ | |
| ); | |
| }); | |
| it('缺少 sharp colour 平台包时显式失败', async () => { | |
| const root = createTempRepo(); | |
| await writeFakeSharpPackage(root); | |
| rmSync(join(root, 'node_modules/@img/colour'), { force: true, recursive: true }); | |
| await assert.rejects( | |
| () => patchStandaloneRuntime(root), | |
| /standalone sharp colour 依赖缺失/ | |
| ); | |
| }); | |
| }); | |
| function createTempRepo() { | |
| const root = mkdtempSync(join(tmpdir(), 'standalone-runtime-test-')); | |
| tempDirectories.push(root); | |
| const runtimeDir = join(root, 'node_modules/next/dist/compiled/next-server'); | |
| mkdirSyncRecursive(runtimeDir); | |
| mkdirSyncRecursive(join(root, 'node_modules/sharp/dist')); | |
| mkdirSyncRecursive(join(root, 'node_modules/semver')); | |
| mkdirSyncRecursive(join(root, 'node_modules/detect-libc/lib')); | |
| mkdirSyncRecursive(join(root, 'node_modules/@img/colour')); | |
| mkdirSyncRecursive(join(root, 'node_modules/@img/sharp-linuxmusl-x64')); | |
| mkdirSyncRecursive(join(root, 'node_modules/@img/sharp-libvips-linuxmusl-x64')); | |
| mkdirSyncRecursive(join(root, 'node_modules/@img/not-sharp')); | |
| mkdirSyncRecursive(join(root, '.next/node_modules/sharp-hashed')); | |
| mkdirSyncRecursive(join(root, '.next/node_modules/sharp-unrelated')); | |
| mkdirSyncRecursive(join(root, '.next/standalone')); | |
| writeFileSync(join(runtimeDir, 'app-route-turbo.runtime.prod.js'), 'runtime\n'); | |
| writeFileSync(join(runtimeDir, 'app-route-turbo.runtime.prod.js.map'), '{}\n'); | |
| return root; | |
| } | |
| async function writeFakeSharpPackage(root) { | |
| const fakeSharp = ` | |
| function sharp() { | |
| return { | |
| webp() { | |
| return this; | |
| }, | |
| async toBuffer() { | |
| return Buffer.from('fake-webp'); | |
| } | |
| }; | |
| } | |
| module.exports = sharp; | |
| `; | |
| await writeFile(join(root, 'node_modules/sharp/dist/index.mjs'), 'export default function sharp() {}\n'); | |
| await writeFile(join(root, 'node_modules/sharp/dist/index.cjs'), fakeSharp); | |
| await writeFile(join(root, 'node_modules/sharp/package.json'), '{"name":"sharp","main":"dist/index.cjs"}\n'); | |
| } | |
| function mkdirSyncRecursive(directory) { | |
| rmSync(directory, { force: true, recursive: true }); | |
| mkdirSync(directory, { recursive: true }); | |
| } | |