mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 03:55:59 +00:00
fix: ditch files v0
This commit is contained in:
@@ -40,7 +40,8 @@ client.archon.servers_v1
|
||||
client.archon.backups_queue_v1
|
||||
client.archon.backups_v1
|
||||
client.archon.content_v0
|
||||
client.kyros.files_v0
|
||||
client.kyros.files_v1
|
||||
client.kyros.upload_sessions_v1
|
||||
client.iso3166.data
|
||||
... etc.
|
||||
```
|
||||
@@ -140,7 +141,8 @@ Uploads go through the feature chain (auth, retry, etc.). Features detect upload
|
||||
### Usage Example (server file upload)
|
||||
|
||||
```ts
|
||||
const uploader = client.kyros.files_v0.uploadFile(path, file, {
|
||||
await client.kyros.files_v1.ensureFile(worldId, path)
|
||||
const uploader = client.kyros.files_v1.uploadFile(worldId, path, file, {
|
||||
onProgress: ({ progress }) => {
|
||||
uploadProgress.value = Math.round(progress * 100)
|
||||
},
|
||||
|
||||
@@ -147,7 +147,8 @@ Built-in features include authentication, node auth, retries, circuit breaking,
|
||||
Upload endpoints return an `UploadHandle<T>` with progress and cancellation support:
|
||||
|
||||
```ts
|
||||
const upload = client.kyros.files_v0.uploadFile(path, file)
|
||||
await client.kyros.files_v1.ensureFile(worldId, path)
|
||||
const upload = client.kyros.files_v1.uploadFile(worldId, path, file)
|
||||
|
||||
upload.onProgress(({ progress }) => {
|
||||
console.log(Math.round(progress * 100))
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user