Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ packages/docs/static/spec
packages/vscode-extension/server
# worktrees
.claude/worktrees
# agent scratch
.superpowers

# misc
.DS_Store
Expand Down
93 changes: 93 additions & 0 deletions packages/docs/docs/guides/migrating/from-makefile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
description: Migrate a Makefile to Nadle — targets to tasks, prerequisites to dependsOn, before/after example, and a ready-to-use agent prompt.
keywords: [nadle, migration, makefile, make, targets, prerequisites, migrate]
---

# Migrating from Make

A `Makefile` is a set of **targets**, each with **prerequisites** and a recipe of shell
commands. Nadle keeps that mental model — a task with `dependsOn` and a body — but the recipes
are TypeScript-typed tasks with caching and cross-platform execution instead of tab-indented
shell.

## How concepts map

| Makefile | Nadle |
| -------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| target (`build:`) | `tasks.register("build", { … })` |
| recipe line (a shell command) | `run: ExecTask` with `command`/`args` |
| prerequisites (`build: clean compile`) | `dependsOn: ["clean", "compile"]` |
| phony target (`.PHONY: clean`) | a normal task — every Nadle task is "phony" by default |
| `make build` | `nadle build` |
| file targets / timestamp checks | declared [`inputs`](../configuring-task.md) / [`outputs`](../configuring-task.md) (real content caching) |
| variables (`CC = gcc`) | plain TypeScript `const`s in the config |

Make decides whether to rebuild by comparing file timestamps. Nadle does better: declare
`inputs`/`outputs` and it fingerprints content, so a touched-but-unchanged file does not force
a rebuild.

## Before / after

`Makefile`:

```makefile
.PHONY: clean build test check

clean:
rm -rf dist

build: clean
tsc

test: build
vitest run

check: build test
```

`nadle.config.ts`:

```ts
import { tasks, ExecTask, DeleteTask, Inputs, Outputs } from "nadle";

tasks.register("clean", { run: DeleteTask, options: { paths: ["dist"] } });

tasks.register("build", {
run: ExecTask,
options: { command: "tsc" },
dependsOn: ["clean"],
inputs: [Inputs.dirs("src")],
outputs: [Outputs.dirs("dist")]
});

tasks.register("test", {
run: ExecTask,
options: { command: "vitest", args: ["run"] },
dependsOn: ["build"]
});

tasks.register("check", { dependsOn: ["build", "test"] });
```

`nadle check` replaces `make check`, and the `clean` recipe maps to the built-in
[`DeleteTask`](../file-operation-tasks.md#deletetask) so it works on every platform.

## Migrate with an agent

Paste this prompt into your coding agent with the `Makefile` open:

```text
Convert my Makefile to a nadle.config.ts at the project root.

For each target:
- Create tasks.register(name, { run: ExecTask, options: { command, args }, dependsOn }).
- Split each recipe command into command (first token) + args (rest, as an array).
- Map prerequisites to dependsOn arrays.
- A target with multiple recipe lines: ask me whether to make each line its own task (preferred)
or keep them as one task with a custom run function.
- Replace shell file-ops with built-ins where obvious: `rm -rf` -> DeleteTask, `cp` -> CopyTask.
- Resolve Makefile variables to plain TypeScript consts.
- Ignore .PHONY (every Nadle task is phony).

Import only what you use from "nadle". Show me the resulting nadle.config.ts.
```
89 changes: 89 additions & 0 deletions packages/docs/docs/guides/migrating/from-npm-scripts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
description: Migrate a package.json scripts block to a nadle.config.ts — mapping table, before/after example, and a ready-to-use agent prompt.
keywords: [nadle, migration, npm scripts, package.json, migrate, npm run]
---

# Migrating from npm Scripts

A `package.json` `scripts` block is a flat list of shell commands keyed by name. Nadle gives
each of those scripts a real task with a dependency graph, caching, and workspace awareness.
This guide maps the common patterns and ends with a [prompt](#migrate-with-an-agent) you can
hand to a coding agent to do the conversion for you.

## How concepts map

| npm scripts | Nadle |
| ------------------------------------------ | ----------------------------------------------------------------------------- |
| `"build": "tsc"` | `tasks.register("build", { run: ExecTask, options: { command: "tsc" } })` |
| `"test": "vitest run"` | a task whose `run` is `ExecTask` with `command: "vitest"`, `args: ["run"]` |
| `npm run lint && npm run build` (chained) | one task with `dependsOn: ["lint"]` — Nadle orders them |
| `pre`/`post` hooks (`prebuild`) | an explicit dependency via [`dependsOn`](../configuring-task.md#dependson) |
| `npm run a & npm run b` (parallel) | two independent tasks, run with `nadle a b --parallel` |
| workspace scripts (`npm run build -w pkg`) | a per-[workspace](../../concepts/workspace.md) task, run as `nadle pkg:build` |

The big shift: chains and `pre`/`post` hooks become **declared dependencies** instead of
shell `&&`. Nadle then schedules them, runs each at most once, and caches results.

## Before / after

`package.json`:

```json
{
"scripts": {
"clean": "rimraf dist",
"prebuild": "npm run clean",
"build": "tsc",
"test": "vitest run",
"check": "npm run build && npm run test"
}
}
```

`nadle.config.ts`:

```ts
import { tasks, ExecTask } from "nadle";

tasks.register("clean", { run: ExecTask, options: { command: "rimraf", args: ["dist"] } });

tasks.register("build", {
run: ExecTask,
options: { command: "tsc" },
dependsOn: ["clean"] // was the `prebuild` hook
});

tasks.register("test", { run: ExecTask, options: { command: "vitest", args: ["run"] } });

tasks.register("check", {
dependsOn: ["build", "test"] // was `npm run build && npm run test`
});
```

`nadle check` now runs `clean → build` and `test`, then `check`, skipping anything already
cached. See [Registering Task](../registering-task.md) and
[Configuring Task](../configuring-task.md) for the full spec.

:::tip
Scripts that just call the package manager (`npm install`, `npm publish`) can use the
[`NpmTask`](../../api/index/variables/NpmTask.md) helper instead of `ExecTask` —
`{ run: NpmTask, options: { args: ["publish"] } }`.
:::

## Migrate with an agent

Paste this prompt into your coding agent with both files open:

```text
Convert my package.json `scripts` block into a nadle.config.ts at the project root.

Rules:
- One `tasks.register(name, { run: ExecTask, options: { command, args } })` per script.
- Split each script string into `command` (first token) and `args` (the rest, as an array).
- Replace `&&` chains and `pre`/`post` hooks with `dependsOn` arrays — do NOT keep the `&&`.
- For scripts that only invoke npm (e.g. "npm publish"), use NpmTask with options.args instead.
- Import only the tasks you use from "nadle".
- Leave package.json scripts in place; just add the equivalent nadle.config.ts.

Show me the resulting nadle.config.ts.
```
88 changes: 88 additions & 0 deletions packages/docs/docs/guides/migrating/from-nx.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
description: Migrate Nx targets and project.json/nx.json to Nadle — concept mapping, before/after example, and a ready-to-use agent prompt.
keywords: [nadle, migration, nx, project.json, nx.json, targets, monorepo, migrate]
---

# Migrating from Nx

Nx describes work as **targets** on projects, configured in `project.json` (per project) and
`nx.json` (defaults and the task graph). Nadle expresses the same targets as tasks in a
`nadle.config.ts` per workspace, with dependencies and caching declared inline.

## How concepts map

| Nx | Nadle |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| target (`build`, `test`, …) in `project.json` | `tasks.register(name, { run: ExecTask, … })` |
| `executor` + `options` | `ExecTask` with `command`/`args` (or a [reusable task](../registering-task.md#3-reusable-task)) |
| `dependsOn: ["^build"]` (project deps) | [workspace dependencies](../../concepts/workspace.md) + task `dependsOn` |
| `dependsOn: ["build"]` (same project) | `dependsOn: ["build"]` |
| `inputs` / `outputs` (caching) | [`inputs`](../configuring-task.md) / [`outputs`](../configuring-task.md) |
| project = a directory with `project.json` | a [workspace](../../concepts/workspace.md) |
| `nx run app:build` / `nx build app` | `nadle app:build` |
| `nx run-many -t build` | `nadle build` (runs the task across workspaces) |

Nx targets usually wrap an executor (`@nx/vite:build`). In Nadle you call the underlying tool
directly through `ExecTask`, or wrap a repeated pattern in your own
[reusable task](../registering-task.md#3-reusable-task).

## Before / after

`project.json`:

```json
{
"name": "app",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"options": { "outputPath": "dist/app" },
"dependsOn": ["^build"],
"outputs": ["{projectRoot}/dist"]
},
"test": { "executor": "@nx/vite:test", "dependsOn": ["build"] }
}
}
```

`nadle.config.ts` (in the `app` workspace):

```ts
import { tasks, ExecTask, Inputs, Outputs } from "nadle";

tasks.register("build", {
run: ExecTask,
options: { command: "tsc" },
inputs: [Inputs.dirs("src")],
outputs: [Outputs.dirs("dist")]
});

tasks.register("test", {
run: ExecTask,
options: { command: "vitest", args: ["run"] },
dependsOn: ["build"]
});
```

`nadle app:build` replaces `nx build app`. See
[Monorepo](../../getting-started/features/monorepo.md) for wiring workspaces together.

## Migrate with an agent

Paste this prompt into your coding agent with `nx.json` and the relevant `project.json` open:

```text
Convert my Nx targets to Nadle.

For each target in each project.json:
- Create tasks.register(name, { run: ExecTask, options: { command, args }, ... }).
- Resolve the executor to the real CLI it runs (e.g. @nx/vite:test -> vitest run, @nx/js:tsc -> tsc)
and put that in command/args. If you can't resolve an executor, leave a TODO and ask me.
- Map same-project "dependsOn": ["x"] to dependsOn: ["x"].
- Map "inputs"/"outputs" to Inputs.dirs/files(...) and Outputs.dirs/files(...).
- For "^build" (upstream project) deps, leave a TODO noting it needs workspace dependencies,
and ask me which projects this one depends on.
- Put each project's tasks in that project's directory as nadle.config.ts (it becomes a workspace).

Import only what you use from "nadle". Show me the files you produced.
```
87 changes: 87 additions & 0 deletions packages/docs/docs/guides/migrating/from-turborepo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
description: Migrate a turbo.json pipeline to Nadle — concept mapping, before/after example, and a ready-to-use agent prompt.
keywords: [nadle, migration, turborepo, turbo.json, pipeline, monorepo, migrate]
---

# Migrating from Turborepo

Turborepo orchestrates `package.json` scripts across a monorepo using a `turbo.json`
pipeline. Nadle covers the same ground — task graph, caching, workspace scoping — but the
task bodies and their wiring live in TypeScript instead of being split between `turbo.json`
and each package's scripts.

## How concepts map

| Turborepo | Nadle |
| --------------------------------------------- | ------------------------------------------------------------------------ |
| `turbo.json` `tasks` (formerly `pipeline`) | task definitions in `nadle.config.ts` |
| `"dependsOn": ["^build"]` (upstream packages) | [workspace dependencies](../../concepts/workspace.md) + task `dependsOn` |
| `"dependsOn": ["build"]` (same package) | `dependsOn: ["build"]` on the task |
| `"outputs": ["dist/**"]` | [`outputs`](../configuring-task.md) on the task |
| `"inputs": ["src/**"]` | [`inputs`](../configuring-task.md) on the task |
| `turbo run build` | `nadle build` |
| per-package `scripts` | per-[workspace](../../concepts/workspace.md) `tasks.register` |
| remote cache | local cache (see the [caching concept](../../concepts/task.md)) |

The `^` upstream-dependency operator has no single keyword in Nadle: a task depending on its
workspace dependencies' `build` is expressed by declaring those workspace deps and depending
on their tasks explicitly. Most pipelines only need same-package `dependsOn`, which is a
direct rename.

## Before / after

`turbo.json`:

```json
{
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
"test": { "dependsOn": ["build"] },
"lint": {}
}
}
```

`nadle.config.ts` (in the package that owns these tasks):

```ts
import { tasks, ExecTask, Inputs, Outputs } from "nadle";

tasks.register("build", {
run: ExecTask,
options: { command: "tsc" },
inputs: [Inputs.dirs("src")],
outputs: [Outputs.dirs("dist")]
});

tasks.register("test", {
run: ExecTask,
options: { command: "vitest", args: ["run"] },
dependsOn: ["build"]
});

tasks.register("lint", { run: ExecTask, options: { command: "eslint", args: ["."] } });
```

Run `nadle build` across the monorepo the same way `turbo run build` did; declared
`inputs`/`outputs` make the result cacheable. See
[Monorepo](../../getting-started/features/monorepo.md) for workspace setup.

## Migrate with an agent

Paste this prompt into your coding agent with `turbo.json` and the package scripts open:

```text
Convert my Turborepo setup to Nadle.

For each entry in turbo.json's `tasks` (or `pipeline`):
- Create a tasks.register(name, { run: ExecTask, options: { command, args }, ... }) where the
command/args come from that package's matching package.json script.
- Map "dependsOn": ["x"] (same-package) to dependsOn: ["x"].
- Map "outputs" to outputs: [Outputs.dirs(...)] / Outputs.files(...) and "inputs" to inputs: [Inputs.dirs(...)].
- For "^build" (upstream) deps, leave a TODO comment noting it must be wired via workspace
dependencies, and ask me which workspaces the package depends on.
- Put each package's tasks in that package's nadle.config.ts.

Import only the tasks/helpers you use from "nadle". Show me the files you produced.
```
12 changes: 11 additions & 1 deletion packages/docs/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,17 @@ const sidebars: SidebarsConfig = {
"guides/file-operation-tasks",
"guides/authoring-plugin",
"guides/configuring-nadle",
"guides/eslint-plugin"
"guides/eslint-plugin",
{
label: "Migrating",
type: "category",
items: [
"guides/migrating/from-npm-scripts",
"guides/migrating/from-turborepo",
"guides/migrating/from-nx",
"guides/migrating/from-makefile"
]
}
]
},
"config-reference",
Expand Down
Loading