From 7ec16e69ba1c9602f73e3cdea75539e2a41cc59c Mon Sep 17 00:00:00 2001 From: branchseer Date: Tue, 7 Apr 2026 01:07:43 +0800 Subject: [PATCH 1/3] docs: add RFC for enhanced `dependsOn` syntax Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/depends-on.md | 129 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 docs/depends-on.md diff --git a/docs/depends-on.md b/docs/depends-on.md new file mode 100644 index 00000000..27c9c135 --- /dev/null +++ b/docs/depends-on.md @@ -0,0 +1,129 @@ +# RFC: Enhanced `dependsOn` Syntax + +## Background + +Today, `dependsOn` entries can only refer to a single task by name (`"build"`) or by package-qualified name (`"pkg#build"`). A common pattern in monorepo task runners is "run `build` in all transitive dependencies first" — tools like Nx (`^build`) and Turborepo (`^build`) support this, but each introduces its own symbol with its own meaning. + +The CLI already supports package selection through flags like `--recursive`, `--transitive`, and `--filter`. Rather than invent yet another DSL with new symbols, we reuse the exact same mental model and syntax from `vp run`. + +### Design principle + +**No new mental models.** If you know how to write `vp run`, you know how to write a `dependsOn` entry. The flag names, filter syntax, and task specifier format are identical. + +## Current Syntax + +```jsonc +{ + "tasks": { + "test": { + "dependsOn": [ + "build", // same-package task + "utils#build", // task in a specific package + ], + }, + }, +} +``` + +These simple forms remain valid and unchanged under both proposed styles. + +## Proposed Syntax + +### Style 1: CLI string syntax + +Each `dependsOn` element is a string (or array of strings) written exactly as you would type CLI arguments to `vp run`: + +```jsonc +{ + "tasks": { + "test": { + "dependsOn": [ + // Existing syntax — still works + "build", + "utils#build", + + // Run `build` across all workspace packages + "--recursive build", + + // Run `build` in current package and its transitive dependencies + "--transitive build", + + // Run `build` in packages matching a filter + "--filter @myorg/core build", + "--filter @myorg/core... build", // @myorg/core and its deps + + // Array form — each element is one CLI token + ["--filter", "@myorg/core", "build"], + ["--transitive", "build"], + ], + }, + }, +} +``` + +The parser splits a string element on whitespace (like a shell would) and interprets the tokens as `vp run` arguments. The array form avoids splitting entirely — useful when a filter value contains whitespace or for explicitness. + +**Supported flags:** + +| Flag | Short | Meaning | +| -------------------- | -------------- | ------------------------------------------------------------------ | +| `--recursive` | `-r` | All workspace packages | +| `--transitive` | `-t` | Current package + its transitive dependencies | +| `--filter ` | `-F ` | Packages matching a [filter expression](https://pnpm.io/filtering) | +| `--workspace-root` | `-w` | The workspace root package | + +Everything after the flags is the task specifier (e.g. `build`, `pkg#task`). + +### Style 2: Object syntax + +Each `dependsOn` element can be an object whose keys mirror the CLI flag names: + +```jsonc +{ + "tasks": { + "test": { + "dependsOn": [ + // Existing syntax — still works as plain strings + "build", + "utils#build", + + // Run `build` across all workspace packages + { "recursive": true, "task": "build" }, + + // Run `build` in current package and its transitive dependencies + { "transitive": true, "task": "build" }, + + // Run `build` in packages matching a filter + { "filter": "@myorg/core", "task": "build" }, + { "filter": "@myorg/core...", "task": "build" }, + + // Multiple filters + { "filter": ["@myorg/core", "@myorg/utils"], "task": "build" }, + + // Workspace root + { "workspaceRoot": true, "task": "build" }, + ], + }, + }, +} +``` + +**Object fields:** + +| Field | Type | Meaning | +| --------------- | -------------------- | ---------------------------------------------------------- | +| `task` | `string` | **Required.** Task specifier (`"build"` or `"pkg#build"`). | +| `recursive` | `boolean` | Select all workspace packages. | +| `transitive` | `boolean` | Select current package + transitive dependencies. | +| `filter` | `string \| string[]` | Select packages by filter expression(s). | +| `workspaceRoot` | `boolean` | Select the workspace root package. | + +The same validation rules from the CLI apply: + +- `recursive` and `transitive` are mutually exclusive. +- `filter` cannot be combined with `recursive` or `transitive`. +- When `task` contains a `#` (e.g. `"pkg#build"`), it cannot be combined with `recursive` or `filter`. + +## Context: "Current Package" + +When `--transitive` or a filter with traversal suffixes (e.g. `@myorg/core...`) resolves packages, "current package" means the package that owns the task containing this `dependsOn` entry — the same package that would be inferred from an unqualified `"build"` dependency today. From 1113d51fd4cd281159dc5520edda3b55b552360e Mon Sep 17 00:00:00 2001 From: branchseer Date: Tue, 7 Apr 2026 09:14:32 +0800 Subject: [PATCH 2/3] docs: use shorthand object syntax in dependsOn RFC Task name is passed as the value of the flag key (e.g. `{ "transitive": "build" }`) instead of a separate `task` field with a boolean flag. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/depends-on.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/depends-on.md b/docs/depends-on.md index 27c9c135..2b308b22 100644 --- a/docs/depends-on.md +++ b/docs/depends-on.md @@ -29,6 +29,8 @@ These simple forms remain valid and unchanged under both proposed styles. ## Proposed Syntax +Two equivalent styles are proposed. + ### Style 1: CLI string syntax Each `dependsOn` element is a string (or array of strings) written exactly as you would type CLI arguments to `vp run`: @@ -88,10 +90,10 @@ Each `dependsOn` element can be an object whose keys mirror the CLI flag names: "utils#build", // Run `build` across all workspace packages - { "recursive": true, "task": "build" }, + { "recursive": "build" }, // Run `build` in current package and its transitive dependencies - { "transitive": true, "task": "build" }, + { "transitive": "build" }, // Run `build` in packages matching a filter { "filter": "@myorg/core", "task": "build" }, @@ -101,28 +103,27 @@ Each `dependsOn` element can be an object whose keys mirror the CLI flag names: { "filter": ["@myorg/core", "@myorg/utils"], "task": "build" }, // Workspace root - { "workspaceRoot": true, "task": "build" }, + { "workspaceRoot": "build" }, ], }, }, } ``` -**Object fields:** +**Object forms:** -| Field | Type | Meaning | -| --------------- | -------------------- | ---------------------------------------------------------- | -| `task` | `string` | **Required.** Task specifier (`"build"` or `"pkg#build"`). | -| `recursive` | `boolean` | Select all workspace packages. | -| `transitive` | `boolean` | Select current package + transitive dependencies. | -| `filter` | `string \| string[]` | Select packages by filter expression(s). | -| `workspaceRoot` | `boolean` | Select the workspace root package. | +| Form | Meaning | +| -------------------------------------------------- | ---------------------------------------------------------------- | +| `{ "recursive": "" }` | Run `` across all workspace packages. | +| `{ "transitive": "" }` | Run `` in current package and its transitive dependencies. | +| `{ "filter": "", "task": "" }` | Run `` in packages matching a filter expression. | +| `{ "filter": ["", ""], "task": "" }` | Run `` in packages matching multiple filters. | +| `{ "workspaceRoot": "" }` | Run `` in the workspace root package. | The same validation rules from the CLI apply: -- `recursive` and `transitive` are mutually exclusive. -- `filter` cannot be combined with `recursive` or `transitive`. -- When `task` contains a `#` (e.g. `"pkg#build"`), it cannot be combined with `recursive` or `filter`. +- `recursive`, `transitive`, `filter`, and `workspaceRoot` are mutually exclusive. +- When using `filter`, the task name goes in a separate `task` field (since `filter` takes a pattern as its value). ## Context: "Current Package" From 2ec0c761c5e13ed0101aafadf0f35115dd8a7d39 Mon Sep 17 00:00:00 2001 From: branchseer Date: Tue, 7 Apr 2026 09:20:51 +0800 Subject: [PATCH 3/3] docs: use string arrays for style 1, add comparison table Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/depends-on.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/depends-on.md b/docs/depends-on.md index 2b308b22..eb6a82b4 100644 --- a/docs/depends-on.md +++ b/docs/depends-on.md @@ -33,7 +33,7 @@ Two equivalent styles are proposed. ### Style 1: CLI string syntax -Each `dependsOn` element is a string (or array of strings) written exactly as you would type CLI arguments to `vp run`: +Each `dependsOn` element is a string (existing syntax) or a string array written exactly as you would type CLI arguments to `vp run`: ```jsonc { @@ -45,25 +45,21 @@ Each `dependsOn` element is a string (or array of strings) written exactly as yo "utils#build", // Run `build` across all workspace packages - "--recursive build", + ["--recursive", "build"], // Run `build` in current package and its transitive dependencies - "--transitive build", + ["--transitive", "build"], // Run `build` in packages matching a filter - "--filter @myorg/core build", - "--filter @myorg/core... build", // @myorg/core and its deps - - // Array form — each element is one CLI token ["--filter", "@myorg/core", "build"], - ["--transitive", "build"], + ["--filter", "@myorg/core...", "build"], // @myorg/core and its deps ], }, }, } ``` -The parser splits a string element on whitespace (like a shell would) and interprets the tokens as `vp run` arguments. The array form avoids splitting entirely — useful when a filter value contains whitespace or for explicitness. +Each element in the array is one CLI token, exactly as you would pass to `vp run`. **Supported flags:** @@ -128,3 +124,11 @@ The same validation rules from the CLI apply: ## Context: "Current Package" When `--transitive` or a filter with traversal suffixes (e.g. `@myorg/core...`) resolves packages, "current package" means the package that owns the task containing this `dependsOn` entry — the same package that would be inferred from an unqualified `"build"` dependency today. + +## Comparison + +| | Style 1 (CLI string) | Style 2 (Object) | +| ------------------ | -------------------------------------------------------------- | ------------------------------------------------------------------ | +| Learning curve | None if you already know `vp run` — identical syntax | Minimal — same flag names, written as JSON keys | +| IDE autocompletion | Yes — TypeScript tuple types can constrain each array position | Yes — TypeScript object types can validate keys and suggest fields | +| Config consistency | Unusual — CLI syntax embedded in config arrays | Consistent — matches the object style used elsewhere in the config |