# Organize imports with Droid Exec

Group, sort, and clean up imports across every package in a monorepo with droid exec, then type-check each package before you commit.

Use this recipe to make imports consistent across a JavaScript or TypeScript codebase: grouped, sorted, deduplicated, and free of unused entries. The script works one package at a time and runs Droid inside each package, so path aliases resolve against that package's own `tsconfig.json`. That is what keeps it safe in a monorepo.

<Tip>
  If plain sorting is all you need, a deterministic tool such as `eslint-plugin-import`'s `import/order` rule with `--fix` is faster and cheaper. Use Droid for the parts those tools skip, such as converting `require()` calls and following house conventions that no rule encodes.
</Tip>

## How it works

1. **Pick packages.** You pass one or more package directories. Each must contain a `package.json`.
2. **List files.** `git ls-files` lists tracked `.js`, `.jsx`, `.ts`, and `.tsx` files in the package, so ignored and generated files are skipped.
3. **Refactor in parallel.** Each file gets its own `droid exec` run at `--auto low`, started inside the package with `--cwd`. Only imports change.
4. **Verify.** After a package finishes, the script runs `tsc --noEmit` for it if it has a `tsconfig.json`, and stops on the first failure.

## Prerequisites

- The Droid CLI is installed and `FACTORY_API_KEY` is set. See [Droid Exec installation](/droid-exec/overview#installation).
- TypeScript packages can be type-checked with `npx tsc --noEmit -p <package>`.

## The script

```bash title="droid-organize-imports.sh"
#!/usr/bin/env bash
# Organize imports with droid exec, one package at a time.
# Usage: ./droid-organize-imports.sh <package-dir> [<package-dir> ...]
set -euo pipefail

CONCURRENCY="${CONCURRENCY:-4}"
DRY_RUN="${DRY_RUN:-false}"

PROMPT='Organize the imports in the file named below.
Change only import and require statements.
1. Group imports in this order, with one blank line between groups:
   external packages, then internal aliases (such as @/ or a workspace
   package), then relative paths.
2. Sort alphabetically within each group.
3. Remove imports that are not used. Keep side-effect imports such as
   import "./polyfills", in their original order.
4. Merge duplicate imports from the same module.
5. Convert require() to import only if this package is already an ES module.
Do not change what any import resolves to.
Do not touch code outside the import block.'

organize_file() {
  local pkg="$1" file="$2"
  # --cwd needs an absolute path.
  if droid exec --auto low --cwd "$(cd "$pkg" && pwd)" "$PROMPT
File: $file" > /dev/null; then
    echo "done: $pkg/$file"
  else
    echo "failed: $pkg/$file" >&2
    return 1
  fi
}
export -f organize_file
export PROMPT

for pkg in "$@"; do
  if [ ! -f "$pkg/package.json" ]; then
    echo "skip: $pkg has no package.json" >&2
    continue
  fi

  files=$(git -C "$pkg" ls-files '*.js' '*.jsx' '*.ts' '*.tsx')
  if [ -z "$files" ]; then
    echo "skip: $pkg has no JS or TS files" >&2
    continue
  fi
  echo "$pkg: $(echo "$files" | grep -c .) files"
  [ "$DRY_RUN" = "true" ] && continue

  echo "$files" | tr '\n' '\0' | xargs -0 -P "$CONCURRENCY" -I {} \
    bash -c 'organize_file "$1" "$2"' _ "$pkg" {}

  if [ -f "$pkg/tsconfig.json" ] && ! npx tsc --noEmit -p "$pkg"; then
    echo "type check failed in $pkg" >&2
    exit 1
  fi
done
```

## Run it

Preview file counts first:

```bash
DRY_RUN=true ./droid-organize-imports.sh packages/* apps/*
```

Then run one package and review the diff before you do the rest:

```bash
./droid-organize-imports.sh packages/ui
git diff --stat
```

![The organize imports script processing four files in packages/api and three in packages/ui (sped up)](/docs-assets/images/droid-exec/recipes/organize-imports.gif)

A typical result groups external, alias, and relative imports, merges duplicates, and drops unused imports:

```diff
 import { readFileSync } from 'node:fs';
-import { HttpError, NotFoundError } from './errors';
-import { saveUser, findUser } from './db';
 import { join } from 'node:path';
-import type { User } from './db';
-import { audit } from './db';
+
+import { audit, findUser, saveUser, type User } from './db';
+import { HttpError, NotFoundError } from './errors';
```

```diff
-import { onClick } from './Button';
 import { formatName } from '@/format';
-import { join } from 'node:path';
+
+import { onClick } from './Button';
 export { onClick, formatName };
```

## Adapt the rules

Edit `PROMPT` to match your conventions. Common changes:

- Say whether type-only imports stay as separate `import type` statements. Without a rule, Droid may merge them into inline `type` specifiers, as in the example above.
- Name your internal scopes, such as `@acme/*`, so Droid groups them with internal aliases.
- List modules whose import order matters, such as CSS resets or instrumentation, and tell Droid to leave them first.

When your conventions are settled, add a lint rule for them so new code stays consistent and later runs only touch files that drift.

## Open a pull request

Commit each package separately so reviewers can check them independently:

```bash
git checkout -b chore/organize-imports
git add packages/ui && git commit -m "chore(ui): organize imports"
git add packages/api && git commit -m "chore(api): organize imports"
git push -u origin HEAD
gh pr create --fill
```

<RelatedLinks>
  <RelatedLink href="/droid-exec/overview#batch-and-parallel-patterns" title="Batch and parallel patterns">
    More ways to fan out droid exec across files and directories.
  </RelatedLink>
  <RelatedLink href="/droid-exec/recipes/lint-autofix" title="Lint autofix">
    Fix one ESLint rule across a codebase and verify each file.
  </RelatedLink>
</RelatedLinks>
