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
6 changes: 3 additions & 3 deletions apps/docs/content/docs/cli/files.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ sim files describe <fileId> [options]

| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File identifier. |
| `fileId` | Yes | File identifier, or the file’s VFS path: `files/<folder>/<name>`. |

</CommandTable>

Expand Down Expand Up @@ -303,7 +303,7 @@ sim files rename <fileId> [options]

| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File identifier. |
| `fileId` | Yes | File identifier, or the file’s VFS path: `files/<folder>/<name>`. |

</CommandTable>

Expand Down Expand Up @@ -613,7 +613,7 @@ sim files delete <fileId> [options]

| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File identifier. |
| `fileId` | Yes | File identifier, or the file’s VFS path: `files/<folder>/<name>`. |

</CommandTable>

Expand Down
6 changes: 3 additions & 3 deletions apps/docs/content/docs/cli/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1171,7 +1171,7 @@ sim files delete <fileId> [options]

| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File identifier. |
| `fileId` | Yes | File identifier, or the file’s VFS path: `files/<folder>/<name>`. |

</CommandTable>

Expand Down Expand Up @@ -1416,7 +1416,7 @@ sim files describe <fileId> [options]

| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File identifier. |
| `fileId` | Yes | File identifier, or the file’s VFS path: `files/<folder>/<name>`. |

</CommandTable>

Expand Down Expand Up @@ -1581,7 +1581,7 @@ sim files rename <fileId> [options]

| Argument | Required | Description |
| --- | --- | --- |
| `fileId` | Yes | File identifier. |
| `fileId` | Yes | File identifier, or the file’s VFS path: `files/<folder>/<name>`. |

</CommandTable>

Expand Down
21 changes: 9 additions & 12 deletions apps/docs/openapi-v2-files-audit.json
Original file line number Diff line number Diff line change
Expand Up @@ -2151,13 +2151,12 @@
"name": "fileId",
"in": "path",
"required": true,
"description": "File identifier.",
"description": "File identifier, or the file’s VFS path: `files/<folder>/<name>`.",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 128,
"pattern": "^[A-Za-z0-9_-]+$",
"description": "File identifier."
"maxLength": 4096,
"description": "File identifier, or the file’s VFS path: `files/<folder>/<name>`."
}
},
{
Expand Down Expand Up @@ -2238,13 +2237,12 @@
"name": "fileId",
"in": "path",
"required": true,
"description": "File identifier.",
"description": "File identifier, or the file’s VFS path: `files/<folder>/<name>`.",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 128,
"pattern": "^[A-Za-z0-9_-]+$",
"description": "File identifier."
"maxLength": 4096,
"description": "File identifier, or the file’s VFS path: `files/<folder>/<name>`."
}
}
],
Expand Down Expand Up @@ -2489,13 +2487,12 @@
"name": "fileId",
"in": "path",
"required": true,
"description": "File identifier.",
"description": "File identifier, or the file’s VFS path: `files/<folder>/<name>`.",
"schema": {
"type": "string",
"minLength": 1,
"maxLength": 128,
"pattern": "^[A-Za-z0-9_-]+$",
"description": "File identifier."
"maxLength": 4096,
"description": "File identifier, or the file’s VFS path: `files/<folder>/<name>`."
}
},
{
Expand Down
18 changes: 15 additions & 3 deletions apps/sim/lib/api/contracts/v2/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
import { MAX_FOLDER_PATH_BYTES, MAX_FOLDER_PATH_SEGMENTS } from '@/lib/folders/paths'
import { MAX_WORKSPACE_FILE_SIZE } from '@/lib/uploads/shared/types'
import { MAX_TEXT_EXTRACTION_BYTES } from '@/lib/uploads/utils/file'
import { isWorkspaceFileVfsPath } from '@/lib/vfs/path'
import { MAX_ZIP_DOWNLOAD_FILES } from '@/lib/workspace-files/limits'
import {
FILE_SEARCH_DEFAULT_MAX_RESULTS,
Expand Down Expand Up @@ -302,6 +303,17 @@ export const v2FileReferenceParamsSchema = z.object({

export type V2FileReferenceParams = z.output<typeof v2FileReferenceParamsSchema>

/** Also takes the `files/…` path `glob` and listings print, so no listing round-trip finds the id. */
const v2FileIdOrPathParamsSchema = z.object({
fileId: requiredFieldSchema('File reference is required')
.max(MAX_FOLDER_PATH_BYTES, 'File reference is too long')
.refine(
(value) => isWorkspaceFileVfsPath(value) || workspaceFileIdSchema.safeParse(value).success,
'Invalid file id or path: pass a file id or its `files/<folder>/<name>` VFS path'
)
.describe('File identifier, or the file’s VFS path: `files/<folder>/<name>`.'),
})

export const v2CreateFileBodySchema = z
.object({
workspaceId: workspaceIdSchema.describe('Workspace in which to create the file.'),
Expand Down Expand Up @@ -943,7 +955,7 @@ export const v2ReadFileTextContract = defineRouteContract({
export const v2GetFileContract = defineRouteContract({
method: 'GET',
path: '/api/v2/files/[fileId]/metadata',
params: v2FileParamsSchema,
params: v2FileIdOrPathParamsSchema,
query: v2GetFileMetadataQuerySchema,
response: {
mode: 'json',
Expand All @@ -955,7 +967,7 @@ export const v2RenameFileContract = defineRouteContract({
method: 'PATCH',
path: '/api/v2/files/[fileId]',
query: noInputSchema,
params: v2FileParamsSchema,
params: v2FileIdOrPathParamsSchema,
body: v2RenameFileBodySchema,
response: {
mode: 'json',
Expand All @@ -966,7 +978,7 @@ export const v2RenameFileContract = defineRouteContract({
export const v2DeleteFileContract = defineRouteContract({
method: 'DELETE',
path: '/api/v2/files/[fileId]',
params: v2FileParamsSchema,
params: v2FileIdOrPathParamsSchema,
query: v2FileWorkspaceQuerySchema,
response: {
mode: 'json',
Expand Down
34 changes: 34 additions & 0 deletions apps/sim/lib/uploads/contexts/workspace/file-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2342,6 +2342,40 @@ async function getWorkspaceFileByExactReference(
return folderId ? getWorkspaceFileByName(workspaceId, segments.at(-1) ?? '', { folderId }) : null
}

/**
* The live workspace file whose canonical `files/…` path — as listings print it — is `vfsPath`,
* or null. Unlike {@link findWorkspaceFileRecord} there is no virtual `/content` or `/meta.json`
* suffix and no leaf-name fallback, so a mutation addressed by path lands only on the file it
* names. Two names that display alike are ambiguous and refused. Throws `VfsPathError` if
* malformed.
*/
export async function getWorkspaceFileByVfsPath(
workspaceId: string,
vfsPath: string
): Promise<WorkspaceFileRecord | null> {
const segments = decodeVfsReferenceSegments(vfsPath.slice('files/'.length))
const leaf = segments.at(-1)
if (leaf === undefined) return null
const pathKey = segments.map(normalizeVfsSegment).join('/')
const candidates = await listWorkspaceFileCandidates(workspaceId, {
ids: [],
leafNames: [normalizeDisplaySegment(leaf)],
})
const matches = candidates.filter(
(file) =>
canonicalWorkspaceFilePath({ folderPath: file.folderPath, name: file.name }).slice(
'files/'.length
) === pathKey
)
if (matches.length > 1) {
throw new OrchestrationError(
'validation',
`"${vfsPath}" names more than one file whose names display alike; pass the file id instead`
)
}
return matches[0] ?? null
}

/**
* Resolve a workspace file record from either its id or a VFS/name reference.
*
Expand Down
5 changes: 5 additions & 0 deletions apps/sim/lib/vfs/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export class VfsPathError extends Error {
}
}

/** A `files/…` workspace-file VFS path. File ids are drawn from `[A-Za-z0-9_-]`, so no id is one. */
export function isWorkspaceFileVfsPath(reference: string): boolean {
return reference.startsWith('files/')
}

/**
* The name a VFS path segment displays: NFC-composed, trimmed, control characters removed,
* whitespace runs collapsed. Its SQL twin is `displaySegmentKey` in `@sim/db/schema`, which an
Expand Down
Loading