fix: ditch files v0

This commit is contained in:
Calum H. (IMB11)
2026-06-26 17:09:23 +01:00
parent 2e08fe7950
commit ef07172006
21 changed files with 308 additions and 380 deletions
-2
View File
@@ -14,7 +14,6 @@ import { ArchonServersV1Module } from './archon/servers/v1'
import { ArchonTransfersInternalModule } from './archon/transfers/internal'
import { ISO3166Module } from './iso3166'
import { KyrosContentV1Module } from './kyros/content/v1'
import { KyrosFilesV0Module } from './kyros/files/v0'
import { KyrosFilesV1Module } from './kyros/files/v1'
import { KyrosLogsV1Module } from './kyros/logs/v1'
import { KyrosUploadSessionsV1Module } from './kyros/upload-sessions/v1'
@@ -88,7 +87,6 @@ export const MODULE_REGISTRY = {
mclogs_logs_v1: MclogsLogsV1Module,
launchermeta_manifest_v0: LauncherMetaManifestV0Module,
kyros_content_v1: KyrosContentV1Module,
kyros_files_v0: KyrosFilesV0Module,
kyros_files_v1: KyrosFilesV1Module,
kyros_logs_v1: KyrosLogsV1Module,
kyros_upload_sessions_v1: KyrosUploadSessionsV1Module,
@@ -1,232 +0,0 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { UploadHandle, UploadProgress } from '../../../types/upload'
import { getNodeBaseUrl } from '../../../utils/node-url'
import type { Archon } from '../../archon/types'
import type { Kyros } from '../types'
type NodeFsAuth = Pick<Archon.Servers.v0.JWTAuth, 'url' | 'token'>
export class KyrosFilesV0Module extends AbstractModule {
public getModuleID(): string {
return 'kyros_files_v0'
}
private getNodeBaseUrl(auth: NodeFsAuth): string {
return getNodeBaseUrl(auth.url)
}
/**
* List directory contents with pagination
*
* @param path - Directory path (e.g., "/")
* @param page - Page number (1-indexed)
* @param pageSize - Items per page
* @returns Directory listing with items and pagination info
*/
public async listDirectory(
path: string,
page: number = 1,
pageSize: number = 100,
): Promise<Kyros.Files.v0.DirectoryResponse> {
return this.client.request<Kyros.Files.v0.DirectoryResponse>('/fs/list', {
api: '',
version: 'modrinth/v0',
method: 'GET',
params: { path, page, page_size: pageSize },
useNodeAuth: true,
})
}
/**
* Create a file or directory
*
* @param path - Path for new item (e.g., "/new-folder")
* @param type - Type of item to create
*/
public async createFileOrFolder(path: string, type: 'file' | 'directory'): Promise<void> {
return this.client.request<void>('/fs/create', {
api: '',
version: 'modrinth/v0',
method: 'POST',
params: { path, type },
headers: { 'Content-Type': 'application/octet-stream' },
useNodeAuth: true,
})
}
/**
* Download a file from a server's filesystem
*
* @param path - File path (e.g., "/server-icon-original.png")
* @returns Promise resolving to file Blob
*/
public async downloadFile(path: string): Promise<Blob> {
return this.client.request<Blob>('/fs/download', {
api: '',
version: 'modrinth/v0',
method: 'GET',
params: { path },
useNodeAuth: true,
})
}
/**
* Download a file using explicit filesystem auth credentials.
*
* @param auth - Filesystem auth (url + token) from Archon
* @param path - File path (e.g., "/server-icon.png")
* @returns Promise resolving to file Blob
*/
public async downloadFileWithAuth(auth: NodeFsAuth, path: string): Promise<Blob> {
return this.client.request<Blob>('/fs/download', {
api: this.getNodeBaseUrl(auth),
version: 'modrinth/v0',
method: 'GET',
params: { path },
headers: { Authorization: `Bearer ${auth.token}` },
skipAuth: true,
})
}
/**
* Upload a file to a server's filesystem with progress tracking
*
* @param path - Destination path (e.g., "/server-icon.png")
* @param file - File to upload
* @param options - Optional progress callback and feature overrides
* @returns UploadHandle with promise, onProgress, and cancel
* @deprecated Use `kyros.upload_sessions_v1` for bulk uploads so cancellation can remove staged files before finalize.
*/
public uploadFile(
path: string,
file: File | Blob,
options?: {
onProgress?: (progress: UploadProgress) => void
retry?: boolean | number
},
): UploadHandle<void> {
return this.client.upload<void>('/fs/create', {
api: '',
version: 'modrinth/v0',
file,
params: { path, type: 'file' },
onProgress: options?.onProgress,
retry: options?.retry,
useNodeAuth: true,
})
}
/**
* Upload a file using explicit filesystem auth credentials.
*
* @param auth - Filesystem auth (url + token) from Archon
* @param path - Destination path (e.g., "/server-icon.png")
* @param file - File to upload
* @param options - Optional progress callback and feature overrides
* @returns UploadHandle with promise, onProgress, and cancel
*/
public uploadFileWithAuth(
auth: NodeFsAuth,
path: string,
file: File | Blob,
options?: {
onProgress?: (progress: UploadProgress) => void
retry?: boolean | number
},
): UploadHandle<void> {
return this.client.upload<void>('/fs/create', {
api: this.getNodeBaseUrl(auth),
version: 'modrinth/v0',
file,
params: { path, type: 'file' },
headers: { Authorization: `Bearer ${auth.token}` },
onProgress: options?.onProgress,
retry: options?.retry,
skipAuth: true,
})
}
/**
* Update file contents
*
* @param path - File path to update
* @param content - New file content (string or Blob)
*/
public async updateFile(path: string, content: string | Blob): Promise<void> {
const blob = typeof content === 'string' ? new Blob([content]) : content
return this.client.request<void>('/fs/update', {
api: '',
version: 'modrinth/v0',
method: 'PUT',
params: { path },
body: blob,
headers: { 'Content-Type': 'application/octet-stream' },
useNodeAuth: true,
})
}
/**
* Delete a file or folder using explicit filesystem auth credentials.
*
* @param auth - Filesystem auth (url + token) from Archon
* @param path - Path to delete
* @param recursive - If true, delete directory contents recursively
*/
public async deleteFileOrFolderWithAuth(
auth: NodeFsAuth,
path: string,
recursive: boolean,
): Promise<void> {
return this.client.request<void>('/fs/delete', {
api: this.getNodeBaseUrl(auth),
version: 'modrinth/v0',
method: 'DELETE',
params: { path, recursive },
headers: { Authorization: `Bearer ${auth.token}` },
skipAuth: true,
})
}
/**
* Extract an archive file (zip, tar, etc.)
*
* Uses v1 API endpoint.
*
* @param path - Path to archive file
* @param override - If true, overwrite existing files
* @param dry - If true, perform dry run (returns conflicts without extracting)
* @returns Extract result with modpack name and conflicting files
*/
public async extractFile(
path: string,
override: boolean = true,
dry: boolean = false,
): Promise<Kyros.Files.v0.ExtractResult> {
return this.client.request<Kyros.Files.v0.ExtractResult>('/fs/unarchive', {
api: '',
version: 'v1',
method: 'POST',
params: { src: path, trg: '/', override, dry },
useNodeAuth: true,
})
}
/**
* Modify a filesystem operation (dismiss or cancel)
*
* Uses v1 API endpoint.
*
* @param opId - Operation ID (UUID)
* @param action - Action to perform
*/
public async modifyOperation(opId: string, action: 'dismiss' | 'cancel'): Promise<void> {
return this.client.request<void>(`/fs/ops/${action}`, {
api: '',
version: 'v1',
method: 'POST',
params: { id: opId },
useNodeAuth: true,
})
}
}
@@ -1,4 +1,5 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { UploadHandle, UploadProgress } from '../../../types/upload'
import type { Kyros } from '../types'
export class KyrosFilesV1Module extends AbstractModule {
@@ -6,6 +7,25 @@ export class KyrosFilesV1Module extends AbstractModule {
return 'kyros_files_v1'
}
private isConflict(error: unknown): boolean {
const err = error as { statusCode?: number; response?: { status?: number } }
return (err.statusCode ?? err.response?.status) === 409
}
public async createDownloadSession(
worldId: string,
path: string,
zipped: boolean,
): Promise<void> {
return this.client.request<void>(`/worlds/${worldId}/files/contents`, {
api: '',
version: 'v1',
method: 'POST',
body: { path, zipped },
useNodeAuth: true,
})
}
public async listDescendants(
worldId: string,
path: string,
@@ -47,6 +67,70 @@ export class KyrosFilesV1Module extends AbstractModule {
})
}
public async editFile(worldId: string, path: string, content: string | Blob): Promise<void> {
const body = typeof content === 'string' ? new Blob([content]) : content
return this.client.request<void>(`/worlds/${worldId}/files/edit`, {
api: '',
version: 'v1',
method: 'POST',
params: { path },
body,
headers: { 'Content-Type': 'application/octet-stream' },
useNodeAuth: true,
})
}
public uploadFile(
worldId: string,
path: string,
file: File | Blob,
options?: {
onProgress?: (progress: UploadProgress) => void
retry?: boolean | number
},
): UploadHandle<void> {
return this.client.upload<void>(`/worlds/${worldId}/files/edit`, {
api: '',
version: 'v1',
file,
params: { path },
onProgress: options?.onProgress,
retry: options?.retry,
useNodeAuth: true,
})
}
public async touchFile(worldId: string, path: string): Promise<void> {
return this.client.request<void>(`/worlds/${worldId}/files/touch`, {
api: '',
version: 'v1',
method: 'POST',
body: { path },
useNodeAuth: true,
})
}
public async mkdirFile(worldId: string, path: string): Promise<void> {
return this.client.request<void>(`/worlds/${worldId}/files/mkdir`, {
api: '',
version: 'v1',
method: 'POST',
body: { path },
useNodeAuth: true,
})
}
public async ensureFile(worldId: string, path: string): Promise<void> {
try {
await this.touchFile(worldId, path)
} catch (error) {
if (!this.isConflict(error)) {
throw error
}
}
}
public async deleteFile(worldId: string, path: string): Promise<void> {
return this.client.request<void>(`/worlds/${worldId}/files/delete`, {
api: '',
@@ -90,4 +174,49 @@ export class KyrosFilesV1Module extends AbstractModule {
},
)
}
public unzipFile(
worldId: string,
request: Kyros.Files.v1.UnzipFileRequest,
): Promise<ReadableStream<Uint8Array>> {
return this.client.stream(`/worlds/${worldId}/files/unzip`, {
api: '',
version: 'v1',
method: 'POST',
body: request,
headers: { Accept: 'application/json-seq' },
useNodeAuth: true,
})
}
public uploadZip(
worldId: string,
path: string,
file: File | Blob,
options?: {
onProgress?: (progress: UploadProgress) => void
retry?: boolean | number
},
): UploadHandle<void> {
return this.client.upload<void>(`/worlds/${worldId}/files/upload-zip`, {
api: '',
version: 'v1',
file,
params: { path },
headers: { 'Content-Type': 'application/zip' },
onProgress: options?.onProgress,
retry: options?.retry,
useNodeAuth: true,
})
}
public async modifyOperation(opId: string, action: 'dismiss' | 'cancel'): Promise<void> {
return this.client.request<void>(`/fs/ops/${action}`, {
api: '',
version: 'v1',
method: 'POST',
params: { id: opId },
useNodeAuth: true,
})
}
}
+22 -29
View File
@@ -2,27 +2,25 @@ export namespace Kyros {
export namespace UploadSessions {
export namespace v1 {
export type Scope = 'content' | 'files'
export type UploadSessionStatus =
| 'active'
| 'uploading'
| 'finalizing'
| 'cancelled'
| 'finalized'
| 'expired'
export type UploadSessionFile = {
file: File | Blob
filename: string
}
export interface UploadSessionResponse {
upload_id: string
status: UploadSessionStatus
status: string
created_at: number
updated_at: number
last_upload_at: number | null
last_upload_at?: number | null
expires_at: number
entry_count: number
uploaded_byte_count: number
}
export interface GetUploadSessionResponse {
session: UploadSessionResponse | null
session?: UploadSessionResponse | null
}
}
}
@@ -31,6 +29,16 @@ export namespace Kyros {
export namespace v1 {
export type DescendantType = 'regular' | 'directory' | 'symlink' | 'other'
export type UnzipSource =
| {
type: 'zip_url'
url: string
}
| {
type: 'zip_path'
path: string
}
export interface CreateDownloadSessionRequest {
path: string
zipped: boolean
@@ -81,29 +89,14 @@ export namespace Kyros {
path: string
name: string
}
}
export namespace v0 {
export interface DirectoryItem {
name: string
type: 'file' | 'directory' | 'symlink'
export interface PathMutationRequest {
path: string
modified: number
created: number
size?: number
count?: number
target?: string
}
export interface DirectoryResponse {
items: DirectoryItem[]
total: number
current: number
}
export interface ExtractResult {
modpack_name: string | null
conflicting_files: string[]
export interface UnzipFileRequest {
source: UnzipSource
target: string
}
}
}
@@ -2,11 +2,6 @@ import { AbstractModule } from '../../../core/abstract-module'
import type { UploadHandle, UploadProgress } from '../../../types/upload'
import type { Kyros } from '../types'
export type UploadSessionFile = {
file: File | Blob
filename: string
}
export class KyrosUploadSessionsV1Module extends AbstractModule {
public getModuleID(): string {
return 'kyros_upload_sessions_v1'
@@ -46,7 +41,7 @@ export class KyrosUploadSessionsV1Module extends AbstractModule {
scope: Kyros.UploadSessions.v1.Scope,
worldId: string,
uploadId: string,
files: UploadSessionFile[],
files: Kyros.UploadSessions.v1.UploadSessionFile[],
options?: {
onProgress?: (progress: UploadProgress) => void
retry?: boolean | number