Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-singers-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@transloadit/node": minor
---

Add upload CLI flags for create/resume tus endpoints and support expected uploads for out-of-band tus flows.
71 changes: 48 additions & 23 deletions docs/fingerprint/transloadit-baseline.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"packageDir": "/home/kvz/code/node-sdk/packages/transloadit",
"tarball": {
"filename": "transloadit-4.3.0.tgz",
"sizeBytes": 1229255,
"sha256": "0b1adb20b160254719eedb2dbcd6f28c7561b2258840643f20867fa664090531"
"filename": "transloadit-4.3.1.tgz",
"sizeBytes": 1232790,
"sha256": "089e952a8e6665659675b0647fed80d99d3f4252a0f0845863a9ed218ee9d166"
},
"packageJson": {
"name": "transloadit",
"version": "4.3.0",
"version": "4.3.1",
"main": "./dist/Transloadit.js",
"exports": {
".": "./dist/Transloadit.js",
Expand Down Expand Up @@ -388,8 +388,8 @@
},
{
"path": "dist/cli/commands/index.js",
"sizeBytes": 1794,
"sha256": "cea0e51dbb809beef425325c681fc3ce087a082f02ff66b6474001a11b2fbd37"
"sizeBytes": 1896,
"sha256": "64bc6458f6f1cd1db339646519dcbab942ccabe965b44d50b1e74dac9ded060a"
},
{
"path": "dist/inputFiles.js",
Expand Down Expand Up @@ -583,8 +583,8 @@
},
{
"path": "dist/Transloadit.js",
"sizeBytes": 36726,
"sha256": "1b3ded5575fb9e02032831df6f5ca10b6c33b0181b59cf44a195248f78bd68ef"
"sizeBytes": 36883,
"sha256": "ef4717bcc8930b750f5d8466097eec96b5f636eeeffe314cb5bde141562ae4d5"
},
{
"path": "dist/alphalib/tryCatch.js",
Expand All @@ -611,6 +611,11 @@
"sizeBytes": 2710,
"sha256": "0da0cf7c28a54af82ac125af0129f885b111b9e48cd64c477865d5438e29974d"
},
{
"path": "dist/cli/commands/upload.js",
"sizeBytes": 3831,
"sha256": "c9267bff59d4b62ed455773ff56ee915a8c196b46c095ef4e9f2ee9542077a9a"
},
{
"path": "dist/alphalib/types/robots/video-adaptive.js",
"sizeBytes": 6059,
Expand Down Expand Up @@ -678,8 +683,8 @@
},
{
"path": "package.json",
"sizeBytes": 2392,
"sha256": "da426af5fcb55e65975b94d1e31b01b3e046ee561db0742e0e2a621d6a837b8b"
"sizeBytes": 2391,
"sha256": "156ee2b53de31d01d43e3a9d5e8299c6e512d63d6ef63ced6428a0685b51effe"
},
{
"path": "dist/alphalib/types/robots/_index.d.ts.map",
Expand Down Expand Up @@ -1414,12 +1419,12 @@
{
"path": "dist/cli/commands/index.d.ts.map",
"sizeBytes": 198,
"sha256": "391845fbe56809711ea35e1667befbf872dd9072875c6983b0990b247d5496cc"
"sha256": "d9bef1f3fe0f9ff442cdd7e4929e0ec89c711911bc732d337bc557c880a5a6cd"
},
{
"path": "dist/cli/commands/index.js.map",
"sizeBytes": 1640,
"sha256": "52cc8c7351fa5905ce7541db198cef4fc55f504a868ca673e843ee8dcb988d16"
"sizeBytes": 1735,
"sha256": "d57ecf1ffc0409c294f83829b8deef9e7e369987439d0f6d089db41ddcf8e901"
},
{
"path": "dist/inputFiles.d.ts.map",
Expand Down Expand Up @@ -1803,13 +1808,13 @@
},
{
"path": "dist/Transloadit.d.ts.map",
"sizeBytes": 6364,
"sha256": "d04fe4e23e6f9c46f828838b60d0e3999a0d3f33f7e7ff0e193d280e5d6e8da5"
"sizeBytes": 6406,
"sha256": "51f9faaa4e12826a77c2ab0818cbb850a044e6d27d686c02578b8ff14c414313"
},
{
"path": "dist/Transloadit.js.map",
"sizeBytes": 26804,
"sha256": "42b0aada7680ba8686ce130b06b70fff5a0c75f2f81aa28f834eaea49fd58a4a"
"sizeBytes": 26946,
"sha256": "33b7676224bfd7d0b2a6c6e448cce67d0929eda5fefc05beb70f57550e6a93d5"
},
{
"path": "dist/alphalib/tryCatch.d.ts.map",
Expand Down Expand Up @@ -1861,6 +1866,16 @@
"sizeBytes": 1547,
"sha256": "5565507cacaccfbae0ed8af22fa55d0f2e1764db6d5b512ca275d2dc0baa52c7"
},
{
"path": "dist/cli/commands/upload.d.ts.map",
"sizeBytes": 768,
"sha256": "787cbbb01c715dc37945cf86faa3429f220a6bdf4fdbd9b7a0d37b9413eb1406"
},
{
"path": "dist/cli/commands/upload.js.map",
"sizeBytes": 2896,
"sha256": "ddf7dac4f6d709cd551705e61fb68626d2c54752ca8289dea58831ca0da615c7"
},
{
"path": "dist/alphalib/types/robots/video-adaptive.d.ts.map",
"sizeBytes": 3703,
Expand Down Expand Up @@ -2733,8 +2748,8 @@
},
{
"path": "src/cli/commands/index.ts",
"sizeBytes": 1711,
"sha256": "a4646e7d078b97e32d7a3c0c0f61aeb32898d1b25bda89ba20703a23b302f6f2"
"sizeBytes": 1808,
"sha256": "f0ab91c3da3ee8de42cc005ef25d4f6972746606082b4030e596d4c946f4963e"
},
{
"path": "dist/inputFiles.d.ts",
Expand Down Expand Up @@ -3123,13 +3138,13 @@
},
{
"path": "dist/Transloadit.d.ts",
"sizeBytes": 11723,
"sha256": "dee5f012aaf6faef6ca2154f3566c97aeaaf95ff07433e2573628e215dbbf9d3"
"sizeBytes": 11847,
"sha256": "5a280d61733d751cb2c9137fb19557dab9e4b2cc8e48c89bfad7474ce0bc0e06"
},
{
"path": "src/Transloadit.ts",
"sizeBytes": 41153,
"sha256": "198560ba943a5c33862e8b735b66a2bb7483d76d29e43efdc7354283217202f1"
"sizeBytes": 41433,
"sha256": "02d6ca96421b0ec39e31c4fb40a06537f2a61ba87787f9e904907f6463f854ea"
},
{
"path": "dist/alphalib/tryCatch.d.ts",
Expand Down Expand Up @@ -3181,6 +3196,16 @@
"sizeBytes": 3439,
"sha256": "4bf3de4456a3aa53d370f4568a0ab1c5423ac8f251d83c62b593fe7d8f814a9d"
},
{
"path": "dist/cli/commands/upload.d.ts",
"sizeBytes": 869,
"sha256": "6078873d1b89afa675ea4aa6bd6b87b7ae14147d7b1982b8d89f2d5ef55376aa"
},
{
"path": "src/cli/commands/upload.ts",
"sizeBytes": 3927,
"sha256": "19ba88a9bccab46628a9c1c0e909ab7cf5a2605b5a08c85ec320d2ff46a4da9f"
},
{
"path": "dist/alphalib/types/robots/video-adaptive.d.ts",
"sizeBytes": 213318,
Expand Down
4 changes: 2 additions & 2 deletions docs/fingerprint/transloadit-baseline.package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "transloadit",
"version": "4.3.0",
"version": "4.3.1",
"description": "Node.js SDK for Transloadit",
"type": "module",
"keywords": [
Expand All @@ -19,7 +19,7 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.891.0",
"@aws-sdk/s3-request-presigner": "^3.891.0",
"@transloadit/sev-logger": "^0.0.15",
"@transloadit/sev-logger": "^0.1.9",
"@transloadit/utils": "^4.3.0",
"clipanion": "^4.0.0-rc.4",
"debug": "^4.4.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ the request body stays small and no extra MCP/LLM token budget is consumed.

- `path` inputs only work when the MCP server can read the same filesystem (local/stdio).
- Hosted MCP cannot access your disk. Use `url`/`base64` for small files, or upload locally with:
`npx @transloadit/node upload ./file.ext <tus_url> --assembly <assembly_url> --field :original`
`npx -y @transloadit/node upload ./file.ext --create-upload-endpoint <tus_url> --assembly <assembly_url> --field :original`
- For remote flows, create the Assembly with `expected_uploads` so it stays open for out‑of‑band
tus uploads.

Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ export const createTransloaditMcpServer = (
} catch (error) {
if (isErrnoException(error) && error.code === 'ENOENT') {
return buildToolError('mcp_file_not_found', error.message, {
hint: 'Path inputs only work when the MCP server can read local files. For hosted MCP, use url/base64 or upload via `npx @transloadit/node upload`.',
hint: 'Path inputs only work when the MCP server can read local files. For hosted MCP, use url/base64 or upload via `npx -y @transloadit/node upload`.',
})
}
throw error
Expand Down
47 changes: 42 additions & 5 deletions packages/node/src/cli/commands/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,36 @@ import { UnauthenticatedCommand } from './BaseCommand.ts'

export interface UploadOptions {
file: string
tusEndpoint: string
createUploadEndpoint?: string
resumeUploadEndpoint?: string
assemblyUrl: string
field?: string
}

const deriveEndpointFromUploadUrl = (uploadUrl: string): string => {
const url = new URL(uploadUrl)
url.pathname = url.pathname.replace(/\/[^/]*$/, '/')
return url.toString()
}

export async function upload(
output: IOutputCtl,
{ file, tusEndpoint, assemblyUrl, field = ':original' }: UploadOptions,
{
file,
createUploadEndpoint,
resumeUploadEndpoint,
assemblyUrl,
field = ':original',
}: UploadOptions,
): Promise<void> {
const tusEndpoint =
createUploadEndpoint ??
(resumeUploadEndpoint ? deriveEndpointFromUploadUrl(resumeUploadEndpoint) : undefined)

if (!tusEndpoint) {
throw new Error('Provide --create-upload-endpoint or --resume-upload-endpoint.')
}

const stream = fs.createReadStream(file)
const streamsMap = {
[field]: { path: file, stream },
Expand All @@ -32,6 +53,7 @@ export async function upload(
requestedChunkSize: Number.POSITIVE_INFINITY,
uploadConcurrency: 1,
onProgress: () => {},
uploadUrls: resumeUploadEndpoint ? { [field]: resumeUploadEndpoint } : undefined,
})

const uploadUrl = uploadUrls[field]
Expand All @@ -42,6 +64,7 @@ export async function upload(
field,
assembly_url: assemblyUrl,
tus_endpoint: tusEndpoint,
resume_upload_endpoint: resumeUploadEndpoint,
upload_url: uploadUrl,
})
}
Expand All @@ -54,23 +77,36 @@ export class UploadCommand extends UnauthenticatedCommand {
description: 'Upload a local file to a tus endpoint for an Assembly',
details: `
Upload a local file to a tus endpoint and attach it to an existing Assembly.
Use --create-upload-endpoint for new uploads or --resume-upload-endpoint to resume.
`,
examples: [
[
'Upload a file to an Assembly',
'transloadit upload ./video.mp4 https://api2.transloadit.com/resumable --assembly https://api2.transloadit.com/assemblies/ASSEMBLY_ID',
'transloadit upload ./video.mp4 --create-upload-endpoint https://api2.transloadit.com/resumable/files/ --assembly https://api2.transloadit.com/assemblies/ASSEMBLY_ID',
],
[
'Resume a file upload',
'transloadit upload ./video.mp4 --resume-upload-endpoint https://api2.transloadit.com/resumable/files/UPLOAD_ID --assembly https://api2.transloadit.com/assemblies/ASSEMBLY_ID',
],
],
})

file = Option.String({ required: true })
tusEndpoint = Option.String({ required: true })
tusEndpoint = Option.String({ required: false })

assemblyUrl = Option.String('--assembly', {
description: 'Assembly URL to attach this upload to',
required: true,
})

createUploadEndpoint = Option.String('--create-upload-endpoint', {
description: 'Tus create endpoint (e.g. https://api2.transloadit.com/resumable/files/)',
})

resumeUploadEndpoint = Option.String('--resume-upload-endpoint', {
description: 'Tus upload URL to resume (e.g. https://.../resumable/files/<id>)',
})

field = Option.String('--field', {
description: 'Field name for the upload (default: :original)',
})
Expand All @@ -79,7 +115,8 @@ export class UploadCommand extends UnauthenticatedCommand {
try {
await upload(this.output, {
file: this.file,
tusEndpoint: this.tusEndpoint,
createUploadEndpoint: this.createUploadEndpoint ?? this.tusEndpoint,
resumeUploadEndpoint: this.resumeUploadEndpoint,
assemblyUrl: this.assemblyUrl,
field: this.field,
})
Expand Down
2 changes: 1 addition & 1 deletion packages/node/test/e2e/cli/upload.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('CLI upload', () => {
await writeFile(filePath, 'hello from CLI upload', 'utf8')

const { stdout, stderr } = await runCli(
`upload ${filePath} ${tusEndpoint} --assembly ${assemblyUrl} --field :original --json`,
`upload ${filePath} --create-upload-endpoint ${tusEndpoint} --assembly ${assemblyUrl} --field :original --json`,
)

expect(stderr).toEqual('')
Expand Down
2 changes: 1 addition & 1 deletion packages/transloadit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "transloadit",
"version": "4.3.0",
"version": "4.3.1",
"description": "Node.js SDK for Transloadit",
"type": "module",
"keywords": [
Expand Down