fix: install managed Python on Linux filesystems
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build Linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build Linux x64 Agent (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build Linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build Linux x64 Agent (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
Managed Python installation rejected valid case-distinct terminfo symlinks in the pinned Astral archives on Linux x64 and arm64. Archive uniqueness now follows the target filesystem: Linux preserves case-distinct paths, while every target still rejects exact duplicates and unsafe entries. Move the unpublished 0.12.0 bilingual release notes to the immutable-tag recovery version 0.12.1 and record the failed tag evidence. Release note: 修复 Linux x64/ARM64 上托管 Python 因官方归档包含大小写不同路径而无法安装的问题。
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { lstat, mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { gzipSync, zipSync } from 'fflate'
|
||||
@@ -29,7 +29,7 @@ function tarField(value: string | number, length: number): Buffer {
|
||||
|
||||
function tarArchive(entries: {
|
||||
path: string
|
||||
type?: '0' | '2'
|
||||
type?: '0' | '2' | '5'
|
||||
content?: string
|
||||
linkPath?: string
|
||||
mode?: number
|
||||
@@ -77,7 +77,11 @@ describe('Managed Python archive extraction', () => {
|
||||
}))
|
||||
const destinationDirectory = join(directory, 'install')
|
||||
await extractPythonArtifact({
|
||||
artifact: { archiveFormat: 'nuget-zip', payloadRoot: 'tools' },
|
||||
artifact: {
|
||||
platform: 'win32',
|
||||
archiveFormat: 'nuget-zip',
|
||||
payloadRoot: 'tools'
|
||||
},
|
||||
archivePath,
|
||||
destinationDirectory
|
||||
})
|
||||
@@ -98,7 +102,11 @@ describe('Managed Python archive extraction', () => {
|
||||
'tools/../escaped': Buffer.from('bad')
|
||||
}))
|
||||
await expect(extractPythonArtifact({
|
||||
artifact: { archiveFormat: 'nuget-zip', payloadRoot: 'tools' },
|
||||
artifact: {
|
||||
platform: 'win32',
|
||||
archiveFormat: 'nuget-zip',
|
||||
payloadRoot: 'tools'
|
||||
},
|
||||
archivePath,
|
||||
destinationDirectory: join(directory, 'install')
|
||||
})).rejects.toThrow(/Unsafe archive path/u)
|
||||
@@ -121,7 +129,11 @@ describe('Managed Python archive extraction', () => {
|
||||
}, ['python'])
|
||||
const destinationDirectory = join(directory, 'install')
|
||||
await extractPythonArtifact({
|
||||
artifact: { archiveFormat: 'tar.gz', payloadRoot: 'python' },
|
||||
artifact: {
|
||||
platform: 'linux',
|
||||
archiveFormat: 'tar.gz',
|
||||
payloadRoot: 'python'
|
||||
},
|
||||
archivePath,
|
||||
destinationDirectory
|
||||
})
|
||||
@@ -129,6 +141,58 @@ describe('Managed Python archive extraction', () => {
|
||||
.toBe('python')
|
||||
})
|
||||
|
||||
it('uses target filesystem case semantics for TAR paths', async () => {
|
||||
const directory = await temporaryDirectory()
|
||||
const archivePath = join(directory, 'case-sensitive.tar.gz')
|
||||
await writeFile(archivePath, gzipSync(tarArchive([
|
||||
{ path: 'python/share/terminfo/2/2621A', type: '5' },
|
||||
{ path: 'python/share/terminfo/2/2621a', type: '5' }
|
||||
])))
|
||||
const destinationDirectory = join(directory, 'linux-install')
|
||||
await extractPythonArtifact({
|
||||
artifact: {
|
||||
platform: 'linux',
|
||||
archiveFormat: 'tar.gz',
|
||||
payloadRoot: 'python'
|
||||
},
|
||||
archivePath,
|
||||
destinationDirectory
|
||||
})
|
||||
expect(
|
||||
(await lstat(join(destinationDirectory, 'share/terminfo/2/2621A')))
|
||||
.isDirectory()
|
||||
).toBe(true)
|
||||
expect(
|
||||
(await lstat(join(destinationDirectory, 'share/terminfo/2/2621a')))
|
||||
.isDirectory()
|
||||
).toBe(true)
|
||||
|
||||
await expect(extractPythonArtifact({
|
||||
artifact: {
|
||||
platform: 'darwin',
|
||||
archiveFormat: 'tar.gz',
|
||||
payloadRoot: 'python'
|
||||
},
|
||||
archivePath,
|
||||
destinationDirectory: join(directory, 'darwin-install')
|
||||
})).rejects.toThrow(/duplicate path/u)
|
||||
|
||||
const duplicateArchivePath = join(directory, 'duplicate.tar.gz')
|
||||
await writeFile(duplicateArchivePath, gzipSync(tarArchive([
|
||||
{ path: 'python/lib/duplicate', content: 'first' },
|
||||
{ path: 'python/lib/duplicate', content: 'second' }
|
||||
])))
|
||||
await expect(extractPythonArtifact({
|
||||
artifact: {
|
||||
platform: 'linux',
|
||||
archiveFormat: 'tar.gz',
|
||||
payloadRoot: 'python'
|
||||
},
|
||||
archivePath: duplicateArchivePath,
|
||||
destinationDirectory: join(directory, 'duplicate-install')
|
||||
})).rejects.toThrow(/duplicate path/u)
|
||||
})
|
||||
|
||||
it('validates relative symlinks and rejects escaping links', async () => {
|
||||
const directory = await temporaryDirectory()
|
||||
if (process.platform !== 'win32') {
|
||||
@@ -143,7 +207,11 @@ describe('Managed Python archive extraction', () => {
|
||||
])))
|
||||
const destinationDirectory = join(directory, 'safe-install')
|
||||
await extractPythonArtifact({
|
||||
artifact: { archiveFormat: 'tar.gz', payloadRoot: 'python' },
|
||||
artifact: {
|
||||
platform: 'linux',
|
||||
archiveFormat: 'tar.gz',
|
||||
payloadRoot: 'python'
|
||||
},
|
||||
archivePath: safeArchive,
|
||||
destinationDirectory
|
||||
})
|
||||
@@ -160,7 +228,11 @@ describe('Managed Python archive extraction', () => {
|
||||
}
|
||||
])))
|
||||
await expect(extractPythonArtifact({
|
||||
artifact: { archiveFormat: 'tar.gz', payloadRoot: 'python' },
|
||||
artifact: {
|
||||
platform: 'linux',
|
||||
archiveFormat: 'tar.gz',
|
||||
payloadRoot: 'python'
|
||||
},
|
||||
archivePath: unsafeArchive,
|
||||
destinationDirectory: join(directory, 'unsafe-install')
|
||||
})).rejects.toThrow(/escapes payload root/u)
|
||||
|
||||
Reference in New Issue
Block a user