pnpm's legacy deploy links the deployed project into the target's node_modules when the target sits inside that project. Materializing that link copies the payload into itself and fails with ERR_FS_CP_EINVAL, which broke the release-shaped Python runtime matrix on Linux and Windows. The staged payload never carries the project source, so the staging tree's owner drops the entry and leaves the deploy root's files alone.
67 lines
3.2 KiB
TypeScript
67 lines
3.2 KiB
TypeScript
import { existsSync } from 'node:fs'
|
|
import { mkdir, mkdtemp, readFile, readdir, rm, symlink, writeFile } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { afterEach, expect, it } from 'vitest'
|
|
import { materializeStagedLinks } from './build-exe-for-python-sdk-staging.ts'
|
|
|
|
const temporaryDirectories: string[] = []
|
|
const directoryLinkType = process.platform === 'win32' ? 'junction' : 'dir'
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(temporaryDirectories.splice(0).map(directory => rm(directory, { recursive: true, force: true })))
|
|
})
|
|
|
|
async function fixture(prefix: string) {
|
|
const workspace = await mkdtemp(join(tmpdir(), prefix))
|
|
temporaryDirectories.push(workspace)
|
|
return workspace
|
|
}
|
|
|
|
/** List every symbolic link below a directory. */
|
|
async function listSymlinks(directory: string): Promise<string[]> {
|
|
const entries = await readdir(directory, { withFileTypes: true })
|
|
const nested = await Promise.all(entries.map(async (entry) => {
|
|
const path = join(directory, entry.name)
|
|
if (entry.isSymbolicLink()) return [path]
|
|
if (entry.isDirectory()) return listSymlinks(path)
|
|
return []
|
|
}))
|
|
return nested.flat()
|
|
}
|
|
|
|
it('drops the deploy root link that points back into the payload', async () => {
|
|
const workspace = await fixture('dsh-python-staging-root-')
|
|
const project = join(workspace, 'python', 'sdk-runtime')
|
|
const staging = join(project, 'src', 'runtime', 'node')
|
|
await mkdir(join(staging, 'node_modules'), { recursive: true })
|
|
await writeFile(join(project, 'package.json'), '{"name":"dsh-python-runtime-closure"}\n')
|
|
await symlink(project, join(staging, 'node_modules', 'dsh-python-runtime-closure'), directoryLinkType)
|
|
|
|
await materializeStagedLinks(staging)
|
|
|
|
expect(existsSync(join(staging, 'node_modules', 'dsh-python-runtime-closure'))).toBe(false)
|
|
await expect(readFile(join(project, 'package.json'), 'utf8')).resolves.toContain('dsh-python-runtime-closure')
|
|
})
|
|
|
|
it('copies workspace package links as files and removes command shims', async () => {
|
|
const workspace = await fixture('dsh-python-staging-packages-')
|
|
const staging = join(workspace, 'runtime', 'node')
|
|
const packageDirectory = join(workspace, 'packages', 'dsh-base')
|
|
await mkdir(join(packageDirectory, 'lib'), { recursive: true })
|
|
await writeFile(join(packageDirectory, 'package.json'), '{"name":"@deepseek-ai/dsh-base"}\n')
|
|
await writeFile(join(packageDirectory, 'lib', 'index.js'), 'export {}\n')
|
|
await mkdir(join(staging, 'node_modules', '@deepseek-ai'), { recursive: true })
|
|
await mkdir(join(staging, 'node_modules', '.bin'), { recursive: true })
|
|
await writeFile(join(staging, 'package.json'), '{"name":"dsh-python-runtime-closure"}\n')
|
|
await symlink(packageDirectory, join(staging, 'node_modules', '@deepseek-ai', 'dsh-base'), directoryLinkType)
|
|
await symlink(packageDirectory, join(staging, 'node_modules', '.bin', 'dsh'), directoryLinkType)
|
|
|
|
await materializeStagedLinks(staging)
|
|
|
|
expect(existsSync(join(staging, 'node_modules', '.bin'))).toBe(false)
|
|
await expect(readFile(join(staging, 'node_modules', '@deepseek-ai', 'dsh-base', 'lib', 'index.js'), 'utf8'))
|
|
.resolves.toBe('export {}\n')
|
|
expect(await listSymlinks(staging)).toEqual([])
|
|
})
|