diff --git a/.gitignore b/.gitignore index 9ae6258d..9c966315 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ packages/docs/static/spec packages/vscode-extension/server # worktrees .claude/worktrees +# agent scratch +.superpowers # misc .DS_Store diff --git a/packages/docs/docs/guides/migrating/from-makefile.md b/packages/docs/docs/guides/migrating/from-makefile.md new file mode 100644 index 00000000..3e1e7cb2 --- /dev/null +++ b/packages/docs/docs/guides/migrating/from-makefile.md @@ -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. +``` diff --git a/packages/docs/docs/guides/migrating/from-npm-scripts.md b/packages/docs/docs/guides/migrating/from-npm-scripts.md new file mode 100644 index 00000000..3393972a --- /dev/null +++ b/packages/docs/docs/guides/migrating/from-npm-scripts.md @@ -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. +``` diff --git a/packages/docs/docs/guides/migrating/from-nx.md b/packages/docs/docs/guides/migrating/from-nx.md new file mode 100644 index 00000000..9235c49c --- /dev/null +++ b/packages/docs/docs/guides/migrating/from-nx.md @@ -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. +``` diff --git a/packages/docs/docs/guides/migrating/from-turborepo.md b/packages/docs/docs/guides/migrating/from-turborepo.md new file mode 100644 index 00000000..a0c09efe --- /dev/null +++ b/packages/docs/docs/guides/migrating/from-turborepo.md @@ -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. +``` diff --git a/packages/docs/sidebars.ts b/packages/docs/sidebars.ts index d6931b24..8ec728bd 100644 --- a/packages/docs/sidebars.ts +++ b/packages/docs/sidebars.ts @@ -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",