# Convert a Vue App From JavaScript to TypeScript

Yes. An agent adds TypeScript to your Vue 3 app, converts stores, services, router and components file by file, and opens a pull request that type-checks.

[Run this on Rush](https://cloud.getrush.ai/run/convert-javascript-to-typescript/vue/), in a Project that has your repository.

## What the agent does

The agent reads the shape of the app before it renames anything. In a Vue app like `realworld-apps/vue-realworld-example-app`, `package.json` lists `vue ^3.5`, `pinia ^3`, `vue-router ^5.1` and `vite ^8` with `@vitejs/plugin-vue`, a `bun.lock`, and scripts for `build`, `lint`, `format` and a Playwright `test`; there is no `typescript`, no `tsconfig.json` and not one `.ts` file under `src`, only twelve `.js` files and twenty `.vue` components, all written as `<script setup>`. `vite.config.js` already lists `.ts` and `.tsx` in `resolve.extensions`, and its comment explains why `.vue` is there too: the router imports views as `@/views/Home` with no extension, a Vue CLI habit the conversion has to keep working.

It sets up the toolchain in one commit: `typescript` and `vue-tsc` as dev dependencies, a `tsconfig.json` with `strict` on, the `@/*` path alias that matches the Vite config, and `allowJs` so the build and the type check pass while files are still mixed. It adds a `typecheck` script that runs `vue-tsc --noEmit`, changes the ESLint `files` glob in `eslint.config.mjs` from `src/**/*.{js,vue}` to include `.ts`, adds the TypeScript parser for `<script lang="ts">` blocks, and widens the Prettier globs in the `format` scripts the same way. `env.d.ts` declares `*.vue` modules and the `window.__conduit_debug__` object that `main.js` installs for the end-to-end tests.

Then it converts from the leaves inward. `src/common/jwt.service.js` and `config.js` are first because everything imports them. `api.service.js` is the interesting one: its `request` function sets `error.response = { status, data }` on a plain `Error`, so the agent writes an `ApiError` class with a typed `response` and makes `extractErrors` in `errors.js` take it, instead of sprinkling `as any`. A `src/types` module declares `User`, `Article`, `Comment`, `Profile` and the paginated article list the way the stores already use them, the Pinia stores in `src/store` get typed `state()` returns and typed action parameters, and `router/index.js` becomes `index.ts` with the `RouteRecordRaw` array and a typed `meta.requiresAuth`.

Components come last. Each `<script setup>` gains `lang="ts"`, and runtime prop declarations such as the `pages: { type: Array, required: true }` in `VPagination.vue` become `defineProps<{ pages: number[]; currentPage: number }>()`. After every batch the agent runs `bun run typecheck`, `bun run lint` and `bun run build`, and it keeps `allowJs` until the last file is converted, then removes it so a stray `.js` cannot come back unnoticed. It runs `bun run test:e2e` where the sandbox can reach the API the app talks to, and otherwise says so in the pull request rather than claiming the suite passed.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Convert this Vue 3 app from JavaScript to TypeScript and open a pull request. Read package.json, vite.config.js, eslint.config.mjs, playwright.config.ts, src/main.js, src/router/index.js, every file under src/common and src/store, and the script blocks of every .vue file under src/components and src/views first. Add typescript and vue-tsc as dev dependencies with bun, a strict tsconfig.json whose @/* alias matches vite.config.js, an env.d.ts for .vue modules and the window.__conduit_debug__ global, a typecheck script that runs vue-tsc --noEmit, and extend the ESLint and Prettier globs to .ts files. Convert the .js files under src/common, src/store and src/router to .ts, replacing the response property patched onto Error in api.service.js with a typed ApiError class, and declare User, Article, Comment and Profile types from the shapes the stores use. Then convert every <script setup> block to lang=\"ts\" with typed defineProps and defineEmits. Keep imports without extensions working. Run bun run typecheck, bun run lint and bun run build after each batch and before you push; run bun run test:e2e if the sandbox can reach the API, and say in the pull request if it cannot. Do not change behaviour, rename components or reformat files you did not convert.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request with one commit for the toolchain and one commit per batch of files, so a reviewer can read the `api.service.ts` change on its own instead of inside a rename of thirty files. The description lists every renamed file, the new `tsconfig.json`, `env.d.ts` and `src/types` module, and the exact `bun run typecheck`, `bun run lint` and `bun run build` output from the last commit, plus whether the Playwright suite ran.

Git sees most of the conversion as renames with small edits, because the agent changes the extension and the types but not the logic, the formatting or the import order. Where it had to decide on a type, such as the `errors` object the auth store starts as `null` and later assigns an object to, the pull request says what it chose and why.

A reviewer checks that `strict` is on and no `any` was used to make a file compile, reads the `ApiError` class and the `src/types` module since those shapes outlive this change, and runs the existing CI workflows, which still install with bun and run the same scripts. Merging leaves `allowJs` off, so the next JavaScript file someone adds fails `typecheck` at once.

## Run it on every repository still in JavaScript

A team with several Vue apps to convert starts one run per repository with `POST /o/{org}/sessions`, this prompt and that repository in `resources`. Each run gets its own sandbox and its own pull request, and a script can start them all in a minute; the key's `max_concurrent` limit decides how many run at once, and one more answers `429` until a run finishes.

With a webhook on the key, each pull request's arrival comes as a `run.completed` event carrying the `session_url`, so the script can collect the pull request links as they land instead of polling.

## Why run it on Rush?

A conversion like this is hours of mechanical work interrupted by a few real decisions, and on a laptop it competes with everything else; the half-converted branch sits for a week and rots against main. In a Rush sandbox the agent does the whole pass in one sitting, runs `vue-tsc`, ESLint and the Vite build after each batch, and the output of every run is kept in the transcript, so a type error that appeared and was fixed is visible rather than lost in a terminal.

The result is a pull request whose checks are the proof. The `typecheck` script the pull request adds is the same one the agent ran, so the reviewer can see on the branch that `strict` mode passes, and the per-batch commits let the team merge the toolchain and the services now and the components next week if that is how they want to land it.

## Frequently asked questions

### Will the app behave differently after the conversion?

No. The agent changes types, extensions and script attributes and leaves the logic alone; `bun run build` and the Playwright suite from the `realworld` submodule, when the sandbox can reach the API, confirm it. Where an untyped value was being used two different ways, the pull request points at it rather than silently changing one path.

### Does it use `any` to get files compiling?

The prompt forbids it and `strict` is on, so each shape has to be declared. The `ApiError` class replaces the `response` property that `api.service.js` patches onto a plain `Error`, and `src/types` declares the API responses once so the stores and components share them.

### Can I merge the conversion in pieces?

Yes. The commits are ordered toolchain first, then `src/common`, stores and router, then components, and `allowJs` stays on until the final commit, so the branch type-checks and builds at every commit and the team can land it in parts.

### What about the ESLint and Prettier setup?

`eslint.config.mjs` currently lints `src/**/*.{js,vue}` with `@eslint/js` and `eslint-plugin-vue`. The pull request extends the glob to `.ts`, adds the TypeScript parser for the `<script lang="ts">` blocks, and widens the `format` and `format:check` globs in `package.json`, so the existing `lint.yml` workflow keeps checking every file.
