Spaces:
Running
Running
File size: 7,401 Bytes
96bdf6c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | 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 });
}
|