Skip to content

Any other stack

Next release

The stacks midcode recognises by their files, the command it runs for each, the form it shows when it can't tell, and the .midcode/server.json format.

View as Markdown

This ships with the next release of midcode. The version you can download today (1.1.2) doesn’t have it yet.

midcode opens any folder. When the project isn’t one of the frameworks it starts with its own plugin, it works out how the site is served from the files it finds, runs that command and shows the result on the canvas. When it can’t tell, it asks you, and keeps your answer in .midcode/server.json. Of the projects on this page, midcode 1.1.2 and earlier open only the ones with a dev script in package.json or an index.html, and only to view and comment.

At a glance

Any other stack
Detected byFiles at the project’s root, a script in package.json, or your answer in .midcode/server.json
Runs withThe stack’s own command, in your login shell, on a free port
Elements are marked byMatching each loaded page against the project’s templates, when it has any
EditingText, classes, attributes and structure where an element is found in a template. View and comment otherwise
PagesThe stack’s routes where midcode can read them. Any path can be typed
Stylesmid: utilities. Where midcode.css goes depends on the stack
Tried withLaravel, PHP, Django, Flask, FastAPI, Hugo, WordPress, Shopify and Angular. Rails and Jekyll through server.json

What midcode tries, in order

  1. Your answer. A .midcode/server.json with a command or an address comes before everything else.

  2. A framework it starts itself: Next.js, React Router, Remix, Astro, SvelteKit, Nuxt or Vite in package.json. Each has its own page.

  3. A stack it knows by its files: the table below.

  4. A dev script in package.json, run with your package manager. midcode reads the address from what it prints.

  5. An index.html at the root: plain HTML, served by midcode.

  6. Another script: the first of start, serve, develop and docs:dev.

  7. Nothing fits: the canvas asks “How does this site run?”

At the top of a repository that keeps its site in a subfolder, midcode opens that site instead: see Monorepos.

Stacks midcode recognises

The first row that fits wins.

StackRecognised byCommand
Laravelartisanphp artisan serve --host=127.0.0.1 --port=$PORT
Djangomanage.pypython manage.py runserver 127.0.0.1:$PORT
Railsbin/rails or config/application.rbbin/rails server -b 127.0.0.1 -p $PORT
Shopify themelayout/theme.liquidshopify theme dev --path $THEME --host 127.0.0.1 --port $PORT, plus --store <store> once it’s known
Hugohugo.toml, hugo.yaml, hugo.yml or hugo.json; or config.toml / config.yaml with content/ and layouts/ or themes/hugo server --bind 127.0.0.1 --port $PORT
Jekyll_config.yml or _config.tomljekyll serve --host 127.0.0.1 --port $PORT, through bundle exec when there’s a Gemfile
Zolaconfig.toml with templates/ and content/zola serve --interface 127.0.0.1 --port $PORT
PhoenixA mix.exs that names :phoenixmix phx.server
ASP.NETA .csproj, .fsproj or .vbproj at the top, with Pages/, Views/ or wwwroot/dotnet watch run --urls http://127.0.0.1:$PORT
Flaskflask among the Python dependencies, and a module that makes Flask(…)python -m flask --app <module> run --debug --host 127.0.0.1 --port $PORT
FastAPIfastapi among the Python dependencies, and a module with app = FastAPI(…)python -m uvicorn <module>:<app> --reload --host 127.0.0.1 --port $PORT
MkDocsmkdocs.yml or mkdocs.yamlpython -m mkdocs serve -a 127.0.0.1:$PORT
WordPresswp-load.php, or a theme folder inside a WordPressphp -d auto_prepend_file=$WP_PREPEND -S 127.0.0.1:$PORT
Angularangular.jsonNG_CLI_ANALYTICS=false ./node_modules/.bin/ng serve --host 127.0.0.1 --port $PORT
PHPindex.php at the root, or in public/, web/ or webroot/php -S 127.0.0.1:$PORT, with -t <folder> when it’s not the root
DenoNo package.json, and a dev, start or serve task in deno.json or deno.jsoncdeno task <task>

python is the project’s own: .venv/bin/python, venv/bin/python or env/bin/python when one exists, else uv run python, poetry run python or pipenv run python by the lockfile it finds, else python3.

Some of these do more than the table says, and have their own page:

How $PORT works

Before it runs a command, midcode picks a free port, starting at 4310, and puts it wherever the command or the address says $PORT (or ${PORT}). The same number is in the PORT environment variable, which many tools read by themselves. Some don’t, and would take their usual port: when midcode runs a script of your package.json that is only a call to one of those (eleventy, rsbuild, rspack, vue-cli-service, webpack, webpack-dev-server, docusaurus, gatsby, parcel, vitepress, vuepress) and names no port, it adds --port with that number.

The command runs in your login shell, from the project’s folder, so it finds what your terminal finds. midcode also sets BROWSER=none, so a tool doesn’t open a browser tab of its own.

Then midcode waits, up to two minutes, for something to answer at:

  • the address you gave, if you gave one;

  • a local address the command prints (its “Local:” line when it has one, else the first localhost or 127.0.0.1 address);

  • http://localhost:$PORT and http://127.0.0.1:$PORT, when no address was given.

A stack that may already be running is asked first. A Laravel app that answers at its APP_URL, or a WordPress at the WP_HOME of its wp-config.php, is shown as it is, and the log says so: Already running at http://shop.test: midcode shows it.

When midcode asks

When nothing fits, the canvas shows a form instead of the site:

  • How does this site run? “Give midcode the command that starts it, the address where it’s already running, or both.”

  • Command, with the example bin/rails server -p $PORT.

  • Address, with the example http://localhost:$PORT.

  • A note under them: “$PORT is a free port midcode picks. With no command, midcode only shows the address. Saved in .midcode/server.json.”

  • Start saves your answer and starts the site.

The same form is behind Change how it runs, the link under the card “The server didn’t start”. There it opens filled in with your saved answer, or with the stack midcode recognised (“It looks like Rails. Change the command if yours is another.”). Reset (“Forget this and let midcode work it out”) clears a saved answer.

.midcode/server.json

The form writes one small file in your project. You can write it yourself too:

FieldWhat it is
commandWhat starts the site, as you’d type it in a terminal at the project’s root. May be empty
urlWhere the site answers. Without http://, midcode adds it. Only the origin counts: a path after it is dropped. May be empty

With both empty, or with a file that isn’t valid JSON, midcode goes back to working it out by itself.

A command. midcode finds the address in what the command prints, or at the port it handed over:

.midcode/server.json
{
  "command": "php -S 127.0.0.1:$PORT -t public_html",
  "url": ""
}

A command and an address. Here the stack is one midcode knows, but with a Ruby that isn’t on your PATH:

.midcode/server.json
{
  "command": "/opt/homebrew/opt/ruby/bin/bundle exec jekyll serve --host 127.0.0.1 --port $PORT",
  "url": "http://127.0.0.1:$PORT"
}

If the command chooses its own port, write that address instead (http://localhost:8080).

Only an address. The site already runs somewhere (Herd, Valet, Docker, another terminal), and midcode starts nothing:

.midcode/server.json
{
  "command": "",
  "url": "https://shop.test"
}

midcode waits 20 seconds for it to answer, then says Nothing answers at https://shop.test. Start the site, then try again. Over https, a certificate the server made for itself is accepted on localhost, 127.0.0.1 and *.localhost. Any other host needs a certificate macOS trusts.

After changing the file by hand, open the Dev server log in the top bar and press Restart. Whether to commit the file is up to you: it’s right for everyone who runs the project the same way. What midcode adds to your project lists it with the rest of .midcode/.

What you can edit

For the stacks on this page, that depends on what the folder holds:

  • Templates a server fills in (.html, .php, .blade.php, .erb, .ejs, .twig, .njk, .liquid, .hbs and the like): when a page loads, midcode looks each element up in those files and marks the ones it’s sure about. Text, classes, attributes and structure are then written into the template. On sites built from Markdown (Hugo, Jekyll, Eleventy, Zola, MkDocs), the words of the content’s .md files are found and edited the same way. Sites a server renders explains the matching and its limits.

  • JSX or Vue built with webpack or Rspack: marked as the site builds. See Create React App, Vue CLI, Gatsby, Docusaurus.

  • Neither: nothing is marked. You can check every breakpoint, use the site in Preview (P) and leave comments for your agent.

Pages

Where midcode can read a stack’s routes, they’re the page list: routes/web.php in Laravel, config/routes.rb in Rails, urls.py in Django, the @app.route lines of Flask, Angular’s route files, the content files of Hugo, Jekyll and Eleventy, and a Shopify theme’s templates. Anywhere else the list has the home page alone, and any path can be typed in the page menu in the top bar (“Search, or type a path and press Enter”).

Styles

Without Tailwind 4, midcode writes its own mid: utilities and keeps their CSS in midcode.css (Without Tailwind). Where midcode knows which folder a stack serves as it is (static/ in Hugo, public/ in Rails, Laravel and Angular, the document root in PHP), the file goes there and every layout gets a <link> to it. For a stack it doesn’t know, midcode may not find where to link it. It creates midcode.css, tells you it couldn’t tell where to import it, and you link the file from your layout yourself.

Limits

  • Phoenix, ASP.NET, Zola, MkDocs and Deno are recognised by their files, but were never tried.

  • There’s no terminal behind the command. One that stops to ask a question can’t be answered.

  • midcode starts one process. If the site needs a second one (a database, an API, a queue), start it yourself.

  • The free canvas is for Next.js projects on the App Router.

  • Pages are added from midcode in Hugo, Jekyll, Eleventy and Laravel only, and Site settings needs a layout that closes a <head>: see New pages and Site settings.

Troubleshooting

“… isn’t installed on this Mac (or not on your shell’s PATH).” The shell didn’t find the command’s first word. If it works in your terminal, write its full path in the command, as in the Jekyll example above.

“The server stopped (code 1)”. The command ran and quit. View log on the card shows what it printed.

“Nothing answers at …” or “The server did not answer in time”. The command is running, but not where midcode is looking. Either it ignores $PORT (give the address it really uses), or it takes longer than two minutes to come up.

The wrong site shows. Something else was already answering at that address. Stop it, or give the command a $PORT of its own.