Skip to content
Open
8 changes: 8 additions & 0 deletions .changeset/file-metadata-xattrs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'e2b': minor
'@e2b/python-sdk': minor
---

feat(sdks): expose user-defined file metadata on `sandbox.files`

Adds a `metadata` option to file uploads (`write` / `writeFiles` / `write_files`) and surfaces persisted metadata on every `EntryInfo` / `WriteInfo` returned by `getInfo`, `list`, `rename`, and write responses. On upload, metadata is sent as `X-Metadata-<key>: <value>` request headers; envd persists the values as extended attributes in the `user.e2b.` xattr namespace and returns them on subsequent filesystem reads. Keys and values must be printable US-ASCII and keys are lowercased by the sandbox; the same metadata map is applied to every file in a multi-file upload. Requires envd 0.6.2 or later.
11 changes: 10 additions & 1 deletion packages/js-sdk/src/envd/filesystem/filesystem_pb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type { Message } from '@bufbuild/protobuf'
export const file_filesystem_filesystem: GenFile =
/*@__PURE__*/
fileDesc(
'ChtmaWxlc3lzdGVtL2ZpbGVzeXN0ZW0ucHJvdG8SCmZpbGVzeXN0ZW0iMgoLTW92ZVJlcXVlc3QSDgoGc291cmNlGAEgASgJEhMKC2Rlc3RpbmF0aW9uGAIgASgJIjQKDE1vdmVSZXNwb25zZRIkCgVlbnRyeRgBIAEoCzIVLmZpbGVzeXN0ZW0uRW50cnlJbmZvIh4KDk1ha2VEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkiNwoPTWFrZURpclJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iHQoNUmVtb3ZlUmVxdWVzdBIMCgRwYXRoGAEgASgJIhAKDlJlbW92ZVJlc3BvbnNlIhsKC1N0YXRSZXF1ZXN0EgwKBHBhdGgYASABKAkiNAoMU3RhdFJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8i/QEKCUVudHJ5SW5mbxIMCgRuYW1lGAEgASgJEiIKBHR5cGUYAiABKA4yFC5maWxlc3lzdGVtLkZpbGVUeXBlEgwKBHBhdGgYAyABKAkSDAoEc2l6ZRgEIAEoAxIMCgRtb2RlGAUgASgNEhMKC3Blcm1pc3Npb25zGAYgASgJEg0KBW93bmVyGAcgASgJEg0KBWdyb3VwGAggASgJEjEKDW1vZGlmaWVkX3RpbWUYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhsKDnN5bWxpbmtfdGFyZ2V0GAogASgJSACIAQFCEQoPX3N5bWxpbmtfdGFyZ2V0Ii0KDkxpc3REaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSDQoFZGVwdGgYAiABKA0iOQoPTGlzdERpclJlc3BvbnNlEiYKB2VudHJpZXMYASADKAsyFS5maWxlc3lzdGVtLkVudHJ5SW5mbyIyCg9XYXRjaERpclJlcXVlc3QSDAoEcGF0aBgBIAEoCRIRCglyZWN1cnNpdmUYAiABKAgiRAoPRmlsZXN5c3RlbUV2ZW50EgwKBG5hbWUYASABKAkSIwoEdHlwZRgCIAEoDjIVLmZpbGVzeXN0ZW0uRXZlbnRUeXBlIuABChBXYXRjaERpclJlc3BvbnNlEjgKBXN0YXJ0GAEgASgLMicuZmlsZXN5c3RlbS5XYXRjaERpclJlc3BvbnNlLlN0YXJ0RXZlbnRIABIxCgpmaWxlc3lzdGVtGAIgASgLMhsuZmlsZXN5c3RlbS5GaWxlc3lzdGVtRXZlbnRIABI7CglrZWVwYWxpdmUYAyABKAsyJi5maWxlc3lzdGVtLldhdGNoRGlyUmVzcG9uc2UuS2VlcEFsaXZlSAAaDAoKU3RhcnRFdmVudBoLCglLZWVwQWxpdmVCBwoFZXZlbnQiNwoUQ3JlYXRlV2F0Y2hlclJlcXVlc3QSDAoEcGF0aBgBIAEoCRIRCglyZWN1cnNpdmUYAiABKAgiKwoVQ3JlYXRlV2F0Y2hlclJlc3BvbnNlEhIKCndhdGNoZXJfaWQYASABKAkiLQoXR2V0V2F0Y2hlckV2ZW50c1JlcXVlc3QSEgoKd2F0Y2hlcl9pZBgBIAEoCSJHChhHZXRXYXRjaGVyRXZlbnRzUmVzcG9uc2USKwoGZXZlbnRzGAEgAygLMhsuZmlsZXN5c3RlbS5GaWxlc3lzdGVtRXZlbnQiKgoUUmVtb3ZlV2F0Y2hlclJlcXVlc3QSEgoKd2F0Y2hlcl9pZBgBIAEoCSIXChVSZW1vdmVXYXRjaGVyUmVzcG9uc2UqUgoIRmlsZVR5cGUSGQoVRklMRV9UWVBFX1VOU1BFQ0lGSUVEEAASEgoORklMRV9UWVBFX0ZJTEUQARIXChNGSUxFX1RZUEVfRElSRUNUT1JZEAIqmAEKCUV2ZW50VHlwZRIaChZFVkVOVF9UWVBFX1VOU1BFQ0lGSUVEEAASFQoRRVZFTlRfVFlQRV9DUkVBVEUQARIUChBFVkVOVF9UWVBFX1dSSVRFEAISFQoRRVZFTlRfVFlQRV9SRU1PVkUQAxIVChFFVkVOVF9UWVBFX1JFTkFNRRAEEhQKEEVWRU5UX1RZUEVfQ0hNT0QQBTKfBQoKRmlsZXN5c3RlbRI5CgRTdGF0EhcuZmlsZXN5c3RlbS5TdGF0UmVxdWVzdBoYLmZpbGVzeXN0ZW0uU3RhdFJlc3BvbnNlEkIKB01ha2VEaXISGi5maWxlc3lzdGVtLk1ha2VEaXJSZXF1ZXN0GhsuZmlsZXN5c3RlbS5NYWtlRGlyUmVzcG9uc2USOQoETW92ZRIXLmZpbGVzeXN0ZW0uTW92ZVJlcXVlc3QaGC5maWxlc3lzdGVtLk1vdmVSZXNwb25zZRJCCgdMaXN0RGlyEhouZmlsZXN5c3RlbS5MaXN0RGlyUmVxdWVzdBobLmZpbGVzeXN0ZW0uTGlzdERpclJlc3BvbnNlEj8KBlJlbW92ZRIZLmZpbGVzeXN0ZW0uUmVtb3ZlUmVxdWVzdBoaLmZpbGVzeXN0ZW0uUmVtb3ZlUmVzcG9uc2USRwoIV2F0Y2hEaXISGy5maWxlc3lzdGVtLldhdGNoRGlyUmVxdWVzdBocLmZpbGVzeXN0ZW0uV2F0Y2hEaXJSZXNwb25zZTABElQKDUNyZWF0ZVdhdGNoZXISIC5maWxlc3lzdGVtLkNyZWF0ZVdhdGNoZXJSZXF1ZXN0GiEuZmlsZXN5c3RlbS5DcmVhdGVXYXRjaGVyUmVzcG9uc2USXQoQR2V0V2F0Y2hlckV2ZW50cxIjLmZpbGVzeXN0ZW0uR2V0V2F0Y2hlckV2ZW50c1JlcXVlc3QaJC5maWxlc3lzdGVtLkdldFdhdGNoZXJFdmVudHNSZXNwb25zZRJUCg1SZW1vdmVXYXRjaGVyEiAuZmlsZXN5c3RlbS5SZW1vdmVXYXRjaGVyUmVxdWVzdBohLmZpbGVzeXN0ZW0uUmVtb3ZlV2F0Y2hlclJlc3BvbnNlQmkKDmNvbS5maWxlc3lzdGVtQg9GaWxlc3lzdGVtUHJvdG9QAaICA0ZYWKoCCkZpbGVzeXN0ZW3KAgpGaWxlc3lzdGVt4gIWRmlsZXN5c3RlbVxHUEJNZXRhZGF0YeoCCkZpbGVzeXN0ZW1iBnByb3RvMw',
'ChtmaWxlc3lzdGVtL2ZpbGVzeXN0ZW0ucHJvdG8SCmZpbGVzeXN0ZW0iMgoLTW92ZVJlcXVlc3QSDgoGc291cmNlGAEgASgJEhMKC2Rlc3RpbmF0aW9uGAIgASgJIjQKDE1vdmVSZXNwb25zZRIkCgVlbnRyeRgBIAEoCzIVLmZpbGVzeXN0ZW0uRW50cnlJbmZvIh4KDk1ha2VEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkiNwoPTWFrZURpclJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iHQoNUmVtb3ZlUmVxdWVzdBIMCgRwYXRoGAEgASgJIhAKDlJlbW92ZVJlc3BvbnNlIhsKC1N0YXRSZXF1ZXN0EgwKBHBhdGgYASABKAkiNAoMU3RhdFJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8i5QIKCUVudHJ5SW5mbxIMCgRuYW1lGAEgASgJEiIKBHR5cGUYAiABKA4yFC5maWxlc3lzdGVtLkZpbGVUeXBlEgwKBHBhdGgYAyABKAkSDAoEc2l6ZRgEIAEoAxIMCgRtb2RlGAUgASgNEhMKC3Blcm1pc3Npb25zGAYgASgJEg0KBW93bmVyGAcgASgJEg0KBWdyb3VwGAggASgJEjEKDW1vZGlmaWVkX3RpbWUYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhsKDnN5bWxpbmtfdGFyZ2V0GAogASgJSACIAQESNQoIbWV0YWRhdGEYCyADKAsyIy5maWxlc3lzdGVtLkVudHJ5SW5mby5NZXRhZGF0YUVudHJ5Gi8KDU1ldGFkYXRhRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4AUIRCg9fc3ltbGlua190YXJnZXQiLQoOTGlzdERpclJlcXVlc3QSDAoEcGF0aBgBIAEoCRINCgVkZXB0aBgCIAEoDSI5Cg9MaXN0RGlyUmVzcG9uc2USJgoHZW50cmllcxgBIAMoCzIVLmZpbGVzeXN0ZW0uRW50cnlJbmZvIjIKD1dhdGNoRGlyUmVxdWVzdBIMCgRwYXRoGAEgASgJEhEKCXJlY3Vyc2l2ZRgCIAEoCCJECg9GaWxlc3lzdGVtRXZlbnQSDAoEbmFtZRgBIAEoCRIjCgR0eXBlGAIgASgOMhUuZmlsZXN5c3RlbS5FdmVudFR5cGUi4AEKEFdhdGNoRGlyUmVzcG9uc2USOAoFc3RhcnQYASABKAsyJy5maWxlc3lzdGVtLldhdGNoRGlyUmVzcG9uc2UuU3RhcnRFdmVudEgAEjEKCmZpbGVzeXN0ZW0YAiABKAsyGy5maWxlc3lzdGVtLkZpbGVzeXN0ZW1FdmVudEgAEjsKCWtlZXBhbGl2ZRgDIAEoCzImLmZpbGVzeXN0ZW0uV2F0Y2hEaXJSZXNwb25zZS5LZWVwQWxpdmVIABoMCgpTdGFydEV2ZW50GgsKCUtlZXBBbGl2ZUIHCgVldmVudCI3ChRDcmVhdGVXYXRjaGVyUmVxdWVzdBIMCgRwYXRoGAEgASgJEhEKCXJlY3Vyc2l2ZRgCIAEoCCIrChVDcmVhdGVXYXRjaGVyUmVzcG9uc2USEgoKd2F0Y2hlcl9pZBgBIAEoCSItChdHZXRXYXRjaGVyRXZlbnRzUmVxdWVzdBISCgp3YXRjaGVyX2lkGAEgASgJIkcKGEdldFdhdGNoZXJFdmVudHNSZXNwb25zZRIrCgZldmVudHMYASADKAsyGy5maWxlc3lzdGVtLkZpbGVzeXN0ZW1FdmVudCIqChRSZW1vdmVXYXRjaGVyUmVxdWVzdBISCgp3YXRjaGVyX2lkGAEgASgJIhcKFVJlbW92ZVdhdGNoZXJSZXNwb25zZSpSCghGaWxlVHlwZRIZChVGSUxFX1RZUEVfVU5TUEVDSUZJRUQQABISCg5GSUxFX1RZUEVfRklMRRABEhcKE0ZJTEVfVFlQRV9ESVJFQ1RPUlkQAiqYAQoJRXZlbnRUeXBlEhoKFkVWRU5UX1RZUEVfVU5TUEVDSUZJRUQQABIVChFFVkVOVF9UWVBFX0NSRUFURRABEhQKEEVWRU5UX1RZUEVfV1JJVEUQAhIVChFFVkVOVF9UWVBFX1JFTU9WRRADEhUKEUVWRU5UX1RZUEVfUkVOQU1FEAQSFAoQRVZFTlRfVFlQRV9DSE1PRBAFMp8FCgpGaWxlc3lzdGVtEjkKBFN0YXQSFy5maWxlc3lzdGVtLlN0YXRSZXF1ZXN0GhguZmlsZXN5c3RlbS5TdGF0UmVzcG9uc2USQgoHTWFrZURpchIaLmZpbGVzeXN0ZW0uTWFrZURpclJlcXVlc3QaGy5maWxlc3lzdGVtLk1ha2VEaXJSZXNwb25zZRI5CgRNb3ZlEhcuZmlsZXN5c3RlbS5Nb3ZlUmVxdWVzdBoYLmZpbGVzeXN0ZW0uTW92ZVJlc3BvbnNlEkIKB0xpc3REaXISGi5maWxlc3lzdGVtLkxpc3REaXJSZXF1ZXN0GhsuZmlsZXN5c3RlbS5MaXN0RGlyUmVzcG9uc2USPwoGUmVtb3ZlEhkuZmlsZXN5c3RlbS5SZW1vdmVSZXF1ZXN0GhouZmlsZXN5c3RlbS5SZW1vdmVSZXNwb25zZRJHCghXYXRjaERpchIbLmZpbGVzeXN0ZW0uV2F0Y2hEaXJSZXF1ZXN0GhwuZmlsZXN5c3RlbS5XYXRjaERpclJlc3BvbnNlMAESVAoNQ3JlYXRlV2F0Y2hlchIgLmZpbGVzeXN0ZW0uQ3JlYXRlV2F0Y2hlclJlcXVlc3QaIS5maWxlc3lzdGVtLkNyZWF0ZVdhdGNoZXJSZXNwb25zZRJdChBHZXRXYXRjaGVyRXZlbnRzEiMuZmlsZXN5c3RlbS5HZXRXYXRjaGVyRXZlbnRzUmVxdWVzdBokLmZpbGVzeXN0ZW0uR2V0V2F0Y2hlckV2ZW50c1Jlc3BvbnNlElQKDVJlbW92ZVdhdGNoZXISIC5maWxlc3lzdGVtLlJlbW92ZVdhdGNoZXJSZXF1ZXN0GiEuZmlsZXN5c3RlbS5SZW1vdmVXYXRjaGVyUmVzcG9uc2VCaQoOY29tLmZpbGVzeXN0ZW1CD0ZpbGVzeXN0ZW1Qcm90b1ABogIDRlhYqgIKRmlsZXN5c3RlbcoCCkZpbGVzeXN0ZW3iAhZGaWxlc3lzdGVtXEdQQk1ldGFkYXRh6gIKRmlsZXN5c3RlbWIGcHJvdG8z',
[file_google_protobuf_timestamp]
)

Expand Down Expand Up @@ -227,6 +227,15 @@ export type EntryInfo = Message<'filesystem.EntryInfo'> & {
* @generated from field: optional string symlink_target = 10;
*/
symlinkTarget?: string

/**
* User-defined metadata stored as extended attributes (xattrs) on the file.
* Keys live under the `user.e2b.` xattr namespace; the prefix is stripped here.
* Plain `user.*` xattrs written by other tooling are not reflected.
*
* @generated from field: map<string, string> metadata = 11;
*/
metadata: { [key: string]: string }
}

/**
Expand Down
29 changes: 28 additions & 1 deletion packages/js-sdk/src/envd/schema.gen.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/js-sdk/src/envd/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export const ENVD_COMMANDS_STDIN = '0.3.0'
export const ENVD_DEFAULT_USER = '0.4.0'
export const ENVD_ENVD_CLOSE = '0.5.2'
export const ENVD_OCTET_STREAM_UPLOAD = '0.5.7'
export const ENVD_FILE_METADATA = '0.6.2'
60 changes: 60 additions & 0 deletions packages/js-sdk/src/sandbox/filesystem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import { compareVersions } from 'compare-versions'
import {
ENVD_DEFAULT_USER,
ENVD_FILE_METADATA,
ENVD_OCTET_STREAM_UPLOAD,
ENVD_VERSION_RECURSIVE_WATCH,
} from '../../envd/versions'
Expand Down Expand Up @@ -77,6 +78,12 @@
* Path to the filesystem object.
*/
path: string
/**
* User-defined metadata persisted on the file as extended attributes.
* Only populated when metadata was supplied on upload and the sandbox's
* envd supports it. `undefined` when no metadata is set.
*/
metadata?: Record<string, string>
Comment on lines 78 to +86

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The docstring on WriteInfo.metadata says it is "Only populated when metadata was supplied on upload and the sandbox's envd supports it." Since EntryInfo extends WriteInfo and inherits this doc, it also describes EntryInfo.metadata returned by getInfo/list/rename — which is populated from any user.e2b.* xattr on the inode, including ones set out-of-band (as the PR's own "metadata set via xattrs is surfaced in getInfo" test demonstrates). Consider dropping the "Only" qualifier or noting that read responses also reflect xattrs set externally. Same wording is duplicated in packages/python-sdk/e2b/sandbox/filesystem/filesystem.py:52-57.

Extended reasoning...

What the docstring says vs. what actually happens

The new metadata field on WriteInfo is documented as:

User-defined metadata persisted on the file as extended attributes. Only populated when metadata was supplied on upload and the sandbox's envd supports it. undefined when no metadata is set.

(packages/js-sdk/src/sandbox/filesystem/index.ts:81-85, mirrored verbatim in packages/python-sdk/e2b/sandbox/filesystem/filesystem.py:52-57.)

The word "Only" makes that statement incorrect because EntryInfo extends WriteInfo (index.ts:89) and inherits this docstring. EntryInfo is returned by Filesystem.getInfo, Filesystem.list, and Filesystem.rename, all of which call into envd RPCs (stat, listDir, move) that surface the proto EntryInfo.metadata field. The proto comment on filesystem.proto:65-68 is the authoritative description and correctly says the field reflects any user.e2b.* xattr on the inode — regardless of whether the SDK set it on upload.

Step-by-step proof from the PR's own test

The PR adds packages/js-sdk/tests/sandbox/files/metadata.test.ts:155 (metadata set via xattrs is surfaced in getInfo):

  1. Write a file with sandbox.files.write(filename, 'content') — no metadata option, so no X-Metadata-* header is sent. Per the docstring, metadata should remain undefined.
  2. Resolve the absolute path with realpath.
  3. Run setfattr -n user.e2b.author -v mish <path> via sandbox.commands.run — this sets a user.e2b.* xattr directly, bypassing the SDK upload entirely.
  4. Call sandbox.files.getInfo(filename) and assert info.metadata deep-equals { author: 'mish' }.

The assertion passes, which means EntryInfo.metadata (the inherited field) is populated from xattrs that were never "supplied on upload". The same scenario is repeated in the Python test suites (packages/python-sdk/tests/{async,sync}/.../test_metadata.py:test_metadata_set_via_xattrs_surfaced_in_get_info). The docstring's "Only" is therefore factually wrong for the EntryInfo use site.

Why this is still only a nit

The refuters are correct that this is purely a documentation cleanup, not a behavior bug — the runtime is right, the OpenAPI description block (envd.yaml:104-124) and the proto comment both document the xattr semantics properly, and any developer looking at EntryInfo.metadata in an IDE would see the cross-cutting proto docs alongside the inherited TS/Python comment. So this should not block the PR.

Suggested fix

Either drop the "Only" qualifier and mention reads, e.g.:

User-defined metadata persisted on the file as extended attributes
(`user.e2b.*` xattrs). On uploads, populated when metadata was supplied
and the sandbox's envd supports it. On reads (`getInfo`/`list`/`rename`),
populated from any `user.e2b.*` xattr on the inode, including ones set
out-of-band. `undefined`/`None` when no such metadata is set.

Apply to both packages/js-sdk/src/sandbox/filesystem/index.ts:78-86 and packages/python-sdk/e2b/sandbox/filesystem/filesystem.py:52-57 so the JS/Python copies stay in sync.

}

export interface EntryInfo extends WriteInfo {
Expand Down Expand Up @@ -153,6 +160,26 @@
)
}

function mapMetadata(
metadata: Record<string, string> | undefined
): Record<string, string> | undefined {
if (!metadata) return undefined
return Object.keys(metadata).length === 0 ? undefined : metadata
}

const METADATA_HEADER_PREFIX = 'X-Metadata-'

function metadataHeaders(
metadata: Record<string, string> | undefined
): Record<string, string> {
if (!metadata) return {}
const headers: Record<string, string> = {}
for (const [key, value] of Object.entries(metadata)) {
headers[`${METADATA_HEADER_PREFIX}${key}`] = value
}
return headers
}

/**
* Options for the sandbox filesystem operations.
*/
Expand Down Expand Up @@ -180,6 +207,14 @@
* the sandbox's envd version, the upload falls back to `multipart/form-data`.
*/
useOctetStream?: boolean
/**
* User-defined metadata to persist on the uploaded file(s) as extended
* attributes. Keys and values must be printable US-ASCII and keys are
* lowercased by the sandbox, so they may differ in case when read back.
* The same metadata is applied to every file in a multi-file upload.
* Requires envd 0.6.2 or later.
*/
metadata?: Record<string, string>
}

/**
Expand Down Expand Up @@ -431,13 +466,26 @@
const useOctetStream =
(writeOpts?.useOctetStream ?? false) && supportsOctetStream

const metadata = writeOpts?.metadata
if (
metadata &&
Object.keys(metadata).length > 0 &&
compareVersions(this.envdApi.version, ENVD_FILE_METADATA) < 0
) {
throw new TemplateError('File metadata requires envd 0.6.2 or later.')

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Staging / JS SDK Tests / JS SDK - Build and test (ubuntu-22.04)

[unit] tests/sandbox/files/metadata.test.ts > overwriting a file clears stale metadata

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:123:23

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Staging / JS SDK Tests / JS SDK - Build and test (ubuntu-22.04)

[unit] tests/sandbox/files/metadata.test.ts > metadata is surfaced after rename

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:111:23

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Staging / JS SDK Tests / JS SDK - Build and test (ubuntu-22.04)

[unit] tests/sandbox/files/metadata.test.ts > metadata is surfaced when listing

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:94:23

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Staging / JS SDK Tests / JS SDK - Build and test (ubuntu-22.04)

[unit] tests/sandbox/files/metadata.test.ts > writeFiles applies metadata to every file

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ Filesystem.writeFiles src/sandbox/filesystem/index.ts:603:17 ❯ tests/sandbox/files/metadata.test.ts:70:39

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Staging / JS SDK Tests / JS SDK - Build and test (ubuntu-22.04)

[unit] tests/sandbox/files/metadata.test.ts > write file with metadata using octet-stream

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:31:38

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Staging / JS SDK Tests / JS SDK - Build and test (ubuntu-22.04)

[unit] tests/sandbox/files/metadata.test.ts > write file with metadata

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:11:36

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Production / JS SDK Tests / JS SDK - Build and test (ubuntu-22.04)

[unit] tests/sandbox/files/metadata.test.ts > overwriting a file clears stale metadata

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:123:23

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Production / JS SDK Tests / JS SDK - Build and test (ubuntu-22.04)

[unit] tests/sandbox/files/metadata.test.ts > metadata is surfaced after rename

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:111:23

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Production / JS SDK Tests / JS SDK - Build and test (ubuntu-22.04)

[unit] tests/sandbox/files/metadata.test.ts > metadata is surfaced when listing

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:94:23

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Production / JS SDK Tests / JS SDK - Build and test (ubuntu-22.04)

[unit] tests/sandbox/files/metadata.test.ts > writeFiles applies metadata to every file

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ Filesystem.writeFiles src/sandbox/filesystem/index.ts:603:17 ❯ tests/sandbox/files/metadata.test.ts:70:39

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Production / JS SDK Tests / JS SDK - Build and test (ubuntu-22.04)

[unit] tests/sandbox/files/metadata.test.ts > write file with metadata using octet-stream

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:31:38

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Production / JS SDK Tests / JS SDK - Build and test (ubuntu-22.04)

[unit] tests/sandbox/files/metadata.test.ts > write file with metadata

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:11:36

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Production / JS SDK Tests / JS SDK - Build and test (windows-latest)

[unit] tests/sandbox/files/metadata.test.ts > overwriting a file clears stale metadata

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:123:23

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Production / JS SDK Tests / JS SDK - Build and test (windows-latest)

[unit] tests/sandbox/files/metadata.test.ts > metadata is surfaced after rename

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:111:23

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Production / JS SDK Tests / JS SDK - Build and test (windows-latest)

[unit] tests/sandbox/files/metadata.test.ts > metadata is surfaced when listing

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:94:23

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Production / JS SDK Tests / JS SDK - Build and test (windows-latest)

[unit] tests/sandbox/files/metadata.test.ts > writeFiles applies metadata to every file

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ Filesystem.writeFiles src/sandbox/filesystem/index.ts:603:17 ❯ tests/sandbox/files/metadata.test.ts:70:39

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Production / JS SDK Tests / JS SDK - Build and test (windows-latest)

[unit] tests/sandbox/files/metadata.test.ts > write file with metadata using octet-stream

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:31:38

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Production / JS SDK Tests / JS SDK - Build and test (windows-latest)

[unit] tests/sandbox/files/metadata.test.ts > write file with metadata

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:11:36

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Staging / JS SDK Tests / JS SDK - Build and test (windows-latest)

[unit] tests/sandbox/files/metadata.test.ts > overwriting a file clears stale metadata

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:123:23

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Staging / JS SDK Tests / JS SDK - Build and test (windows-latest)

[unit] tests/sandbox/files/metadata.test.ts > metadata is surfaced after rename

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:111:23

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Staging / JS SDK Tests / JS SDK - Build and test (windows-latest)

[unit] tests/sandbox/files/metadata.test.ts > metadata is surfaced when listing

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:94:23

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Staging / JS SDK Tests / JS SDK - Build and test (windows-latest)

[unit] tests/sandbox/files/metadata.test.ts > writeFiles applies metadata to every file

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ Filesystem.writeFiles src/sandbox/filesystem/index.ts:603:17 ❯ tests/sandbox/files/metadata.test.ts:70:39

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Staging / JS SDK Tests / JS SDK - Build and test (windows-latest)

[unit] tests/sandbox/files/metadata.test.ts > write file with metadata using octet-stream

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:31:38

Check failure on line 475 in packages/js-sdk/src/sandbox/filesystem/index.ts

View workflow job for this annotation

GitHub Actions / Staging / JS SDK Tests / JS SDK - Build and test (windows-latest)

[unit] tests/sandbox/files/metadata.test.ts > write file with metadata

TemplateError: File metadata requires envd 0.6.2 or later. ❯ Filesystem.write src/sandbox/filesystem/index.ts:475:13 ❯ tests/sandbox/files/metadata.test.ts:11:36
}
// Metadata is sent as request-scoped `X-Metadata-*` headers, so the same
// metadata is applied to every file in a multi-file upload.
const extraHeaders = metadataHeaders(metadata)

const results: WriteInfo[] = []

const useGzip = writeOpts?.gzip === true

if (useOctetStream) {
const headers: Record<string, string> = {
'Content-Type': 'application/octet-stream',
...extraHeaders,
}
if (useGzip) {
headers['Content-Encoding'] = 'gzip'
Expand Down Expand Up @@ -476,6 +524,10 @@
)
}

for (const f of files) {
f.metadata = mapMetadata(f.metadata)
}

return files
})
)
Expand All @@ -501,6 +553,7 @@
},
},
bodySerializer: () => formData,
headers: extraHeaders,
signal: this.connectionConfig.getSignal(
writeOpts?.requestTimeoutMs,
writeOpts?.signal
Expand All @@ -518,6 +571,10 @@
throw new Error('Expected to receive information about written file')
}

for (const f of files) {
f.metadata = mapMetadata(f.metadata)
}

results.push(...files)
}

Expand Down Expand Up @@ -591,6 +648,7 @@
group: e.group,
modifiedTime: mapModifiedTime(e.modifiedTime),
symlinkTarget: e.symlinkTarget,
metadata: mapMetadata(e.metadata),
})
}
}
Expand Down Expand Up @@ -679,6 +737,7 @@
group: entry.group,
modifiedTime: mapModifiedTime(entry.modifiedTime),
symlinkTarget: entry.symlinkTarget,
metadata: mapMetadata(entry.metadata),
}
} catch (err) {
throw handleFilesystemRpcError(err)
Expand Down Expand Up @@ -782,6 +841,7 @@
group: res.entry.group,
modifiedTime: mapModifiedTime(res.entry.modifiedTime),
symlinkTarget: res.entry.symlinkTarget,
metadata: mapMetadata(res.entry.metadata),
}
} catch (err) {
throw handleFilesystemRpcError(err)
Expand Down
162 changes: 162 additions & 0 deletions packages/js-sdk/tests/sandbox/files/metadata.test.ts
Comment thread
mishushakov marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { assert } from 'vitest'

import { WriteEntry } from '../../../src/sandbox/filesystem'
import { isDebug, sandboxTest } from '../../setup.js'

sandboxTest('write file with metadata', async ({ sandbox }) => {
const filename = 'test_metadata.txt'
const content = 'This is a test file with metadata.'
const metadata = { author: 'mish', purpose: 'upload' }

const info = await sandbox.files.write(filename, content, { metadata })
assert.isFalse(Array.isArray(info))
assert.deepEqual(info.metadata, metadata)

// Metadata is persisted and surfaced on subsequent reads.
const stat = await sandbox.files.getInfo(filename)
assert.deepEqual(stat.metadata, metadata)

if (isDebug) {
await sandbox.files.remove(filename)
}
})

sandboxTest(
'write file with metadata using octet-stream',
async ({ sandbox }) => {
const filename = 'test_metadata_octet.txt'
const content = 'This is a test file with metadata.'
const metadata = { author: 'mish', purpose: 'upload' }

const info = await sandbox.files.write(filename, content, {
metadata,
useOctetStream: true,
})
assert.deepEqual(info.metadata, metadata)

const stat = await sandbox.files.getInfo(filename)
assert.deepEqual(stat.metadata, metadata)

if (isDebug) {
await sandbox.files.remove(filename)
}
}
)

sandboxTest('write file without metadata', async ({ sandbox }) => {
const filename = 'test_no_metadata.txt'

const info = await sandbox.files.write(filename, 'no metadata here')
assert.isUndefined(info.metadata)

const stat = await sandbox.files.getInfo(filename)
assert.isUndefined(stat.metadata)

if (isDebug) {
await sandbox.files.remove(filename)
}
})

sandboxTest(
'writeFiles applies metadata to every file',
async ({ sandbox }) => {
// The same metadata is applied to every file in the upload.
const metadata = { source: 'test-suite' }
const files: WriteEntry[] = [
{ path: 'metadata_multi_1.txt', data: 'File 1' },
{ path: 'metadata_multi_2.txt', data: 'File 2' },
]

const infos = await sandbox.files.writeFiles(files, { metadata })
assert.equal(infos.length, files.length)

for (const info of infos) {
assert.deepEqual(info.metadata, metadata)

const stat = await sandbox.files.getInfo(info.path)
assert.deepEqual(stat.metadata, metadata)
}

if (isDebug) {
for (const file of files) {
await sandbox.files.remove(file.path)
}
}
}
)

sandboxTest('metadata is surfaced when listing', async ({ sandbox }) => {
const dirname = 'metadata_list_dir'
const filename = 'listed.txt'
const metadata = { tag: 'listed' }

await sandbox.files.makeDir(dirname)
await sandbox.files.write(`${dirname}/${filename}`, 'content', { metadata })

const entries = await sandbox.files.list(dirname)
const entry = entries.find((e) => e.name === filename)
assert.isDefined(entry)
assert.deepEqual(entry?.metadata, metadata)

if (isDebug) {
await sandbox.files.remove(dirname)
}
})

sandboxTest('metadata is surfaced after rename', async ({ sandbox }) => {
const oldPath = 'metadata_rename_old.txt'
const newPath = 'metadata_rename_new.txt'
const metadata = { stage: 'renamed' }

await sandbox.files.write(oldPath, 'content', { metadata })
const info = await sandbox.files.rename(oldPath, newPath)
assert.deepEqual(info.metadata, metadata)

if (isDebug) {
await sandbox.files.remove(newPath)
}
})

sandboxTest('overwriting a file clears stale metadata', async ({ sandbox }) => {
const filename = 'metadata_overwrite.txt'

await sandbox.files.write(filename, 'first', {
metadata: { author: 'mish' },
})

// Overwriting without metadata removes the previously stored metadata.
const info = await sandbox.files.write(filename, 'second')
assert.isUndefined(info.metadata)

const stat = await sandbox.files.getInfo(filename)
assert.isUndefined(stat.metadata)

if (isDebug) {
await sandbox.files.remove(filename)
}
})

sandboxTest(
'metadata set via xattrs is surfaced in getInfo',
async ({ sandbox }) => {
const filename = 'metadata_xattr.txt'
await sandbox.files.write(filename, 'content')

const { stdout: filePath } = await sandbox.commands.run(
`realpath ${filename}`
)

// Set an xattr directly in the `user.e2b.` namespace; it should surface as
// metadata (with the namespace prefix stripped) when reading the file info.
await sandbox.commands.run(
`setfattr -n user.e2b.author -v mish ${filePath.trim()}`
)

const info = await sandbox.files.getInfo(filename)
assert.deepEqual(info.metadata, { author: 'mish' })

if (isDebug) {
await sandbox.files.remove(filename)
}
}
)
Loading
Loading