
# Exporter

`exporter` adds one command, **Export…**, to a folder's context menu in the explorer and to the
command palette. Running it asks where the folder should go — a `.tar.gz`, a `.zip`, straight into
a folder on your disk, or the clipboard as text — then walks it and puts it there.

```ts
import { exporter } from "codelet/extensions/exporter";
import { FileSystem, Workbench } from "codelet/workbench";

const workbench = new Workbench({
  parent: document.getElementById("app")!,
  fs: new FileSystem({ "/src/index.ts": "export const hi = 1;\n" }),
  extensions: [exporter],
});
```

`exporter` takes no options.

## Try it

Right-click the `src` folder in the explorer, choose **Export…**, and pick a format — your
browser downloads `src.tar.gz` or `src.zip`, holding `src/index.ts` and `src/greet.ts`. On a
Chromium browser there is a third row, **Folder**, which writes the same tree onto your disk
instead.

::codelet-playground{mode="workbench" extensions="exporter" active="src/index.ts" height="420"}

```ts [src/index.ts]
import { greet } from "./greet";

console.log(greet("codelet"));
```

```ts [src/greet.ts]
export const greet = (name: string) => `hi ${name}`;
```

```md [README.md]
# greet

Right-click `src` in the tree to take it with you.
```

::

## Where the command appears

Right-click any folder's row for that folder alone, under its own name. For the whole workspace,
as `workspace`: the button in the explorer's heading, the tree's own context menu below the last
row, or `Export: Export…` from the palette. The format is asked as a second step rather than
offered as a second command, so the item reads the same wherever you meet it — and it is asked
before anything is read, so escaping the pick costs nothing.

## No library, either direction

Nothing is fetched and nothing is bundled. Both formats are written out directly: ustar is a
512-byte header per entry, and zip is its three records with a CRC-32 of this package's own
writing. Compression is the browser's — `CompressionStream` as gzip around the tarball, as raw
deflate per zip entry — and a browser that has none downloads a plain `.tar`, or a zip whose
entries are stored rather than deflated, instead of failing.

Either archive always has a single top-level directory named after the folder, so extracting it
can't spray files over wherever you extract it.

## Straight onto your disk

The third row, **Folder**, skips the archive: it opens your browser's directory picker and writes
the tree out as itself. It needs `showDirectoryPicker`, which today means a Chromium browser —
where that is missing the row isn't offered, rather than offered and failing.

What lands is the same shape as the archives: a single directory named after the folder you
exported, created inside the one you picked, so choosing `Downloads` can't spray a workspace over
it. It's a merge rather than a mirror — a file of the same name is overwritten, everything else
in the target is left alone, and nothing is ever deleted.

::note
Picking the folder is part of the same click as picking the format, because the browser only
opens a directory picker while a gesture of yours is still live. That's why you're asked before
the tree is read rather than after.
::

Chrome refuses some directories outright — system paths, and your home directory itself, though
not folders inside it. That's the browser's own blocklist rather than anything this extension can
grant: pick a folder one level down and it goes through.

## Onto the clipboard

The last row, **Contents**, copies the whole folder as one markdown document instead of writing a
file: a fenced block per file, headed by its language and path.

````md
```ts src/index.ts
import { greet } from "./greet";
```
````

The fence is always a backtick longer than the longest run inside the file, so a markdown file
can't close its own block. Binary files are named with their size rather than dumped, and a file
the tree holds only the address of says so instead of being fetched — copying text is the one
export not worth pulling four megabytes down for.

Copying shows a notification saying what went — no file arrives and nothing on disk changes, so
there'd otherwise be nothing to tell you it worked.

::note
An actual `.zip` on the clipboard isn't possible from a web page: browsers accept only text, HTML
and a couple of image types on a clipboard write, and putting a _file_ there — the kind you could
paste into Finder or Explorer — isn't exposed to the web at all. Hence text.
::

## One file

Right-clicking a **file** row gets **Copy Contents** instead of **Export…** — an archive of a
single file being nothing anyone wanted. It copies that file's text as-is, with no fence and no
path around it, and it's in the palette too, where it works on whatever you have open.

What the workspace holds is what you get: a binary file mirrored in by
[`remote`](/extensions/remote) copies as the address the tree holds for it, the same string the
editor shows you. A file whose text is really bytes — a dropped-in image, say — refuses with a
warning rather than filling your clipboard with a megabyte of base64.

## What lands in the archive

A `FileSystem` holds every file as text, so a file's bytes in the archive are usually its text,
UTF-8 encoded — unless that text is where the bytes actually are, the same address form
[`media`](/extensions/media) reads:

| The file holds                | Where it comes from                               | In the archive                       |
| ----------------------------- | ------------------------------------------------- | ------------------------------------ |
| `data:image/png;base64,…`     | dropping a file into the workbench                | the bytes it stands for              |
| A URL, or a path off the page | [`remote`](/extensions/remote) mirroring a binary | the bytes, fetched                   |
| One line of bare base64       | a host that seeded the tree that way              | the bytes, if they aren't valid text |

That's what makes dropping a folder of images into the workbench and exporting it again give you
the images back, and what makes exporting a remote-backed workspace give you its binaries rather
than a folder of one-line text files.

::note
The fetch for a remote-backed file is the page's own — a remote whose authentication is a
wrapped `fetch` (the `fetch` option on [`httpTransport`](/extensions/remote)) isn't reachable
from here, so its files come back as the addresses they are. A fetch that fails leaves the file
as that address and is reported in an output channel named `Export`, which the
[Logs extension](/extensions/logs) shows if you're running it — the download still happens.
::

Directories are kept, including empty ones.

:read-more{to="/extensions"}
