import {test} from 'node:test'; import assert from 'node:assert/strict'; import {mkdtemp,mkdir,writeFile,readFile,rm,symlink,realpath,lstat} from 'node:fs/promises'; import {join} from 'node:path'; import {tmpdir} from 'node:os'; import {copyReleaseTree} from '../scripts/copy-release-tree.mjs'; test('copyReleaseTree rewrites absolute in-tree links into relocatable relative links',async t=>{ const root=await mkdtemp(join(tmpdir(),'91-copy-tree-'));t.after(()=>rm(root,{recursive:true,force:true})); const source=join(root,'standalone'),dest=join(root,'release'); await mkdir(join(source,'.pnpm/pkg@1/node_modules/pkg'),{recursive:true}); await writeFile(join(source,'.pnpm/pkg@1/node_modules/pkg/index.js'),'module.exports=1;'); await mkdir(join(source,'node_modules'),{recursive:true}); // Absolute link back into the standalone tree (what Node cp would preserve). await symlink(join(source,'.pnpm/pkg@1/node_modules/pkg'),join(source,'node_modules/pkg')); await copyReleaseTree(source,dest,{root}); const link=join(dest,'node_modules/pkg'); assert.ok((await lstat(link)).isSymbolicLink()); assert.equal(await readFile(join(link,'index.js'),'utf8'),'module.exports=1;'); const resolved=await realpath(link),releaseRoot=await realpath(dest); assert.ok(resolved===releaseRoot||resolved.startsWith(releaseRoot+'/')); assert.equal(await readFile(join(resolved,'index.js'),'utf8'),'module.exports=1;'); }); test('copyReleaseTree rejects symlinks that escape the project root',async t=>{ const root=await mkdtemp(join(tmpdir(),'91-copy-escape-'));t.after(()=>rm(root,{recursive:true,force:true})); const outside=await mkdtemp(join(tmpdir(),'91-outside-'));t.after(()=>rm(outside,{recursive:true,force:true})); await writeFile(join(outside,'secret'),'nope'); const source=join(root,'standalone'),dest=join(root,'release'); await mkdir(join(source,'node_modules'),{recursive:true}); await writeFile(join(source,'ok.js'),'1'); await symlink(join(outside,'secret'),join(source,'node_modules/escape')); await assert.rejects(copyReleaseTree(source,dest,{root}),/Release source contains a symbolic link/); });