Skip to content

Add Bruno imports and a file-tree importer API - #706

Open
gschier wants to merge 8 commits into
mainfrom
feat/importer-bruno
Open

gschier wants to merge 8 commits into
mainfrom
feat/importer-bruno

Conversation

@gschier

@gschier gschier commented Sep 23, 2026

Copy link
Copy Markdown
Member

Yaak importers currently receive a text string, which cannot represent Bruno collection directories or ZIP exports. Add onImportFiles(ctx, { files }) with lazy readDir, binary readFile, and UTF-8 readTextFile access for single files, ZIPs, directories, and pasted text. Keep onImport backwards compatible but deprecated, with a one-time migration warning; the file hook takes precedence when both exist.

image

Add Bruno imports for bundled OpenCollection YAML, YAML collection directories/ZIPs, and legacy .bru collections using Bruno’s official file parser. Preserve folders, environments, common auth, and HTTP/GraphQL/gRPC/WebSocket requests. Desktop gains a folder picker; desktop, URL, and CLI import paths preserve binary input and use the existing import planning and linked-reimport flow.

Validation:

  • Upstream compatibility: two unchanged Bruno collections (.bru and YAML), pinned to usebruno/bruno@4eb7e585, now exercise real upstream inputs. All 48 Bruno tests and the importer TypeScript/lint checks pass. Source attribution and the MIT license are included; embedded scripts are not executed.
  • Full JavaScript suite: 63 files / 804 tests passed, including ZIP/directory equivalence, binary reads, invalid archives, and legacy-hook compatibility.
  • Review follow-up: 60 focused runtime/Bruno tests, targeted TypeScript/lint checks, and runtime build passed. ZIP classification covers misleading extensions, extensionless ZIPs, empty archives, and malformed signatures.
  • Native import tests: 33 passed; input-loader tests: 2 passed. API and targeted TypeScript/lint checks, runtime/plugin builds, and CLI build passed.
  • Actual CLI imports of bundled YAML, ZIPs, .bru directories, and an OpenAPI fixture passed in an isolated data directory. Linked reimport preserved request IDs without duplicates.
  • Desktop Rust check passed with packaging-resource checks disabled for a missing local build asset. Desktop UI was not manually tested.

The picker still selects one file or folder; arbitrary multi-select is not included. Collection scripts are not executed, unsupported settings are retained for manual migration, and referenced file paths may need adjustment. ZIPs are detected by signature and read without extraction, with path and size limits. Directory path validation is best-effort under concurrent filesystem changes: a symlink swap can redirect a read or listing outside the selected root. This remains a documented limitation, not a security confinement guarantee; plugins already run with normal Node.js filesystem permissions.

Related: OpenCollection, Bruno file parser.

@gschier
gschier marked this pull request as ready for review September 23, 2026 20:50
@greptile-apps

greptile-apps Bot commented Sep 23, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The latest changes appear safe to merge; no new actionable regressions were identified, although the existing non-blocking directory symlink race remains outstanding.

Findings

  1. P2 Directory checks can race

Summary

The PR adds a file-tree importer API and Bruno collection support across desktop, URL, and CLI import paths.

  • Exposes lazy binary and text access for individual files, directories, ZIP archives, and pasted input.
  • Preserves legacy text importers while preferring the new file-aware hook.
  • Imports bundled OpenCollection documents and legacy Bruno directory or ZIP collections.
  • The latest changes classify ZIPs by signature instead of filename and clarify that directory path validation is not a race-free security boundary.
  • Adds focused coverage for misleading extensions, extensionless and empty ZIPs, and malformed signatures.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Desktop, URL, CLI, or pasted input] --> B[ImportRequest]
  B --> C{Input type}
  C -->|Directory| D[Lazy filesystem tree]
  C -->|File or download| E{ZIP signature}
  E -->|Yes| F[Lazy ZIP tree]
  E -->|No| G[Single-file tree]
  C -->|Pasted text| G
  D --> H[onImportFiles]
  F --> H
  G --> H
  G --> I[Legacy onImport when file is UTF-8]
  H --> J[Importer resources]
  I --> J
  J --> K[Import planning and linked reimport]
  K --> L[Commit selected changes]
Loading

Reviews (2) · Last reviewed commit: "Detect import ZIPs by signature and clar..."

Comment on lines +88 to +104
for await (const entry of await opendir(await resolve(rel))) {
if (!entry.isFile() && !entry.isDirectory()) continue;
entries.push({
name: entry.name,
path: rel ? `${rel}/${entry.name}` : entry.name,
type: entry.isDirectory() ? "directory" : "file",
});
if (entries.length > MAX_ENTRIES)
throw new Error("Import directory contains too many entries");
}
assertOpen();
return entries.sort((a, b) => a.path.localeCompare(b.path));
};
readFile = async (value) => {
const handle = await open(
await resolve(value),
constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Directory checks can race

If an intermediate directory is replaced with a symlink after resolve() validates it but before opendir() or open() uses the returned path, the operation can follow that symlink outside the selected root. O_NOFOLLOW only protects the final file component and does not protect readDir, so a plugin could list or read files outside the selected source. This conflicts with the file-tree API's confinement guarantee; validation and opening should be bound to the same filesystem objects.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Acceptable risk for this feature

Comment thread packages/plugin-runtime/src/importFiles.ts
@gschier

gschier commented Sep 23, 2026

Copy link
Copy Markdown
Member Author

@greptile-apps please review the latest changes.

greptile-apps[bot]
greptile-apps Bot previously approved these changes Sep 23, 2026
@greptile-apps
greptile-apps Bot dismissed their stale review September 23, 2026 23:05

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant