diff --git a/CHANGELOG.md b/CHANGELOG.md index 708cbd93d..2c66bcd96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -- **Added** `vp run --dry-run --json` prints what a run would do as one JSON document for scripts, CI and coding agents: each task the run would run, the tasks it waits for, and each step's program, arguments and directory, with whether it would hit the cache and, if not, every change found. Like `vp run --json`, it's indented in a terminal and on one line when captured, and its `$schema` field points to a JSON Schema that describes each field. `--json` with a task requires `--dry-run`. Tasks that run `vp run` aren't supported yet and fail with an error ([#966](https://github.com/voidzero-dev/vite-task/pull/966)). +- **Added** `vp run --dry-run --json` prints what a run would do as one JSON document for scripts, CI and coding agents: each task the run would run, the tasks it waits for, and each step's program, arguments and directory, with whether it would hit the cache and, if not, every change found. Like `vp run --json`, it's indented in a terminal and on one line when captured, and its `$schema` field points to a JSON Schema that describes each field. A task that runs `vp run` shows that run's own plan as its step. `--json` with a task requires `--dry-run` ([#966](https://github.com/voidzero-dev/vite-task/pull/966), [#967](https://github.com/voidzero-dev/vite-task/pull/967)). - **Changed** A cache miss now lists every change to the command, the task's cache settings and the envs it depends on, instead of the first: when a task's `input` and `output` settings changed together, or a tracked env var and a file changed together, `vp run --verbose` and `vp run --last-details` show each change on its own line. Without `-v`, files are checked only up to the first changed one; with `-v`, every input of a miss is checked, and every changed file is listed in path order. When the command, a setting or a `cache.env` var changed, only those changes are listed, since there's no cached run with the new ones to compare files with. A summary saved by an earlier version can't be shown by `vp run --last-details` ([#961](https://github.com/voidzero-dev/vite-task/pull/961), [#963](https://github.com/voidzero-dev/vite-task/pull/963)). - **Added** `vp run --dry-run` shows what a run would do without running anything: each command's line with its cache status, and the summary, as a real run prints them. The cache is checked as in a real run, the remote cache included, and nothing is written: no cache entry, no downloaded or restored outputs, and no change to what `vp run --last-details` shows. With `-v`, the detailed summary shows why each command would miss. Like other `vp run` flags, `--dry-run` goes before the task name; after it, it's passed to the task ([#960](https://github.com/voidzero-dev/vite-task/pull/960)). - **Fixed** `vp run` now exits with code 1 when a task's cache can't be checked, for example because the cache database can't be read. It already stopped there, but exited 0 ([#960](https://github.com/voidzero-dev/vite-task/pull/960)). diff --git a/crates/vt/schemas/run-plan.json b/crates/vt/schemas/run-plan.json index 57439b0b0..89abe1c4e 100644 --- a/crates/vt/schemas/run-plan.json +++ b/crates/vt/schemas/run-plan.json @@ -45,7 +45,7 @@ "type": "string" }, "dependsOn": { - "description": "IDs of the tasks in `plan` that must finish before this one starts. Absent when there are none.", + "description": "IDs of the tasks in the same `plan` that must finish before this one starts. Absent when there are none.", "type": "array", "items": { "type": "string" @@ -66,6 +66,17 @@ ] }, "Step": { + "description": "A step of a task: a command, or a nested `vp run`.", + "anyOf": [ + { + "$ref": "#/definitions/CommandStep" + }, + { + "$ref": "#/definitions/NestedStep" + } + ] + }, + "CommandStep": { "description": "A command the task runs.", "type": "object", "properties": { @@ -397,6 +408,27 @@ "required": [ "changes" ] + }, + "NestedStep": { + "description": "A nested `vp run`, which runs a plan of its own.", + "type": "object", + "properties": { + "expandedFrom": { + "description": "The `vp run …` command this step stands for, as written, with any arguments given after the task name: `vp run -r test`.", + "type": "string" + }, + "plan": { + "description": "The plan of that nested `vp run`, in the same shape. Its tasks' `dependsOn` refer only to tasks in it, so the same ID can appear in more than one plan.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PlanTask" + } + } + }, + "required": [ + "expandedFrom", + "plan" + ] } } } diff --git a/crates/vt/src/session/mod.rs b/crates/vt/src/session/mod.rs index 9b7d28793..acc955d58 100644 --- a/crates/vt/src/session/mod.rs +++ b/crates/vt/src/session/mod.rs @@ -881,20 +881,13 @@ impl<'a> Session<'a> { /// /// # Errors /// - /// Returns an error if a task runs a nested `vp run`, which the run plan - /// doesn't show yet, or if the schema can't be written. + /// Returns an error if the schema can't be written. fn run_plan_reporter( &self, graph: &ExecutionGraph, writer: Box, pretty: bool, ) -> anyhow::Result> { - if has_nested_run(graph) { - let program = &self.program_name; - return Err(anyhow::Error::msg(vt_str::format!( - "`{program} run --dry-run --json` doesn't support tasks that run `{program} run` yet" - ))); - } let schema = json::schema_url(RUN_PLAN_SCHEMA) .context("Failed to write the JSON Schema that `$schema` points to")?; Ok(Box::new(JsonReporterBuilder::new(graph, &self.workspace_path, writer, pretty, schema))) @@ -979,14 +972,6 @@ impl<'a> Session<'a> { } } -/// Whether a task in `graph` runs a nested `vp run`, which is planned as part -/// of the graph. -fn has_nested_run(graph: &ExecutionGraph) -> bool { - graph.graph.node_weights().any(|task| { - task.items.iter().any(|item| matches!(item.kind, vt_plan::ExecutionItemKind::Expanded(_))) - }) -} - /// Print `error` to stderr formatted as the `vp` CLI does: /// /// ```text diff --git a/crates/vt/src/session/reporter/json/document.rs b/crates/vt/src/session/reporter/json/document.rs index e48281055..277b79dbc 100644 --- a/crates/vt/src/session/reporter/json/document.rs +++ b/crates/vt/src/session/reporter/json/document.rs @@ -46,8 +46,8 @@ pub struct PlanTask { /// The task name, such as `build`. pub name: Str, - /// IDs of the tasks in `plan` that must finish before this one starts. Absent when there - /// are none. + /// IDs of the tasks in the same `plan` that must finish before this one starts. Absent + /// when there are none. #[serde(skip_serializing_if = "Vec::is_empty")] pub depends_on: Vec, @@ -57,11 +57,34 @@ pub struct PlanTask { pub steps: Vec, } +/// A step of a task: a command, or a nested `vp run`. +#[derive(Debug, Serialize)] +#[cfg_attr(test, derive(schemars::JsonSchema))] +#[serde(untagged)] +pub enum Step { + Command(CommandStep), + Nested(NestedStep), +} + +/// A nested `vp run`, which runs a plan of its own. +#[derive(Debug, Serialize)] +#[cfg_attr(test, derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct NestedStep { + /// The `vp run …` command this step stands for, as written, with any arguments given + /// after the task name: `vp run -r test`. + pub expanded_from: Str, + + /// The plan of that nested `vp run`, in the same shape. Its tasks' `dependsOn` refer only + /// to tasks in it, so the same ID can appear in more than one plan. + pub plan: BTreeMap, +} + /// A command the task runs. #[derive(Debug, Serialize)] #[cfg_attr(test, derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] -pub struct Step { +pub struct CommandStep { /// The executable that would be started: a path relative to the workspace root if it is /// inside it, such as `apps/web/node_modules/.bin/vite`, /// otherwise an absolute path. A command with shell syntax, such as a pipe, runs through diff --git a/crates/vt/src/session/reporter/json/mod.rs b/crates/vt/src/session/reporter/json/mod.rs index 31d5890a0..259ca21d5 100644 --- a/crates/vt/src/session/reporter/json/mod.rs +++ b/crates/vt/src/session/reporter/json/mod.rs @@ -12,7 +12,8 @@ use std::{ }; use document::{ - Baseline, Cache, Change, Changed, Hit, HowChanged, PlanTask, RunPlan, Setting, Step, + Baseline, Cache, Change, Changed, CommandStep, Hit, HowChanged, NestedStep, PlanTask, RunPlan, + Setting, Step, }; use rustc_hash::FxHashMap; use vt_materialized_artifact::{Artifact, artifact}; @@ -49,28 +50,77 @@ impl JsonReporterBuilder { pretty: bool, schema: Str, ) -> Self { + let mut layout = Layout { workspace_path, steps: FxHashMap::default(), unreported: 0 }; + let plan = layout.plan(graph, &[], &[]); + let Layout { steps, unreported, .. } = layout; + let document = RunPlan { + schema, + version: Version, + // `clean` drops a trailing separator, which a workspace root found from such a cwd keeps. + workspace_root: Arc::from(workspace_path.clean().as_absolute_path()), + plan, + }; + Self(JsonGraphReporter { + state: Rc::new(RefCell::new(State { document, unreported, errors: Vec::new() })), + steps, + writer, + pretty, + }) + } +} + +impl GraphExecutionReporterBuilder for JsonReporterBuilder { + fn build(self: Box) -> Box { + Box::new(self.0) + } +} + +/// Where a step is in the document: the task ID and step index in each plan, +/// outermost first, down through the nested plans of `vp run` steps. +type StepLocation = Vec<(Str, usize)>; + +/// Lays out the plans of a graph and its nested graphs, before any cache is +/// checked. +struct Layout<'a> { + workspace_path: &'a AbsolutePath, + /// Where each spawned or built-in leaf's step is, by the leaf's path in + /// the graph. + steps: FxHashMap, StepLocation>, + /// The number of spawned steps, which each report a cache status. + unreported: usize, +} + +impl Layout<'_> { + /// The plan of `graph`, whose items are at `path` in the root graph and + /// whose steps are at `location` in the document. Each plan's + /// `dependsOn` refers only to its own tasks. + fn plan( + &mut self, + graph: &ExecutionGraph, + path: &LeafPath, + location: &[(Str, usize)], + ) -> BTreeMap { let ids: FxHashMap = graph .graph .node_indices() - .map(|node| (node, task_key(&graph.graph[node], workspace_path).1)) + .map(|node| (node, task_key(&graph.graph[node], self.workspace_path).1)) .collect(); let mut plan = BTreeMap::new(); - let mut unreported = 0; for node in graph.graph.node_indices() { let task = &graph.graph[node]; - let (package, id) = task_key(task, workspace_path); + let (package, id) = task_key(task, self.workspace_path); let mut depends_on: Vec = graph.graph.neighbors(node).map(|dependency| ids[&dependency].clone()).collect(); depends_on.sort_unstable(); let steps = task .items .iter() - .map(|item| { - let step = lay_out_step(item, task, workspace_path); - if matches!(item.kind, ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(_))) { - unreported += 1; - } - step + .enumerate() + .map(|(index, item)| { + let item_path: Vec<_> = path.iter().copied().chain([(node, index)]).collect(); + let item_location: StepLocation = + location.iter().cloned().chain([(id.clone(), index)]).collect(); + self.step(item, task, item_path, item_location) }) .collect(); plan.insert( @@ -78,25 +128,45 @@ impl JsonReporterBuilder { PlanTask { package, name: task.task_display.task_name.clone(), depends_on, steps }, ); } - let document = RunPlan { - schema, - version: Version, - // `clean` drops a trailing separator, which a workspace root found from such a cwd keeps. - workspace_root: Arc::from(workspace_path.clean().as_absolute_path()), - plan, - }; - Self(JsonGraphReporter { - state: Rc::new(RefCell::new(State { document, unreported, errors: Vec::new() })), - ids, - writer, - pretty, - }) + plan } -} -impl GraphExecutionReporterBuilder for JsonReporterBuilder { - fn build(self: Box) -> Box { - Box::new(self.0) + /// The step of `item`, a step of `task`, at `path` in the graph and + /// `location` in the document. A nested `vp run` is its own plan. + fn step( + &mut self, + item: &ExecutionItem, + task: &TaskExecution, + path: Vec<(ExecutionNodeIndex, usize)>, + location: StepLocation, + ) -> Step { + let display = &item.execution_item_display; + // A spawned step runs its spawn command, which a command handler may + // have changed from what the display shows. + let (program, args, cwd) = match &item.kind { + ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(spawn)) => { + self.unreported += 1; + ( + path_key(&spawn.spawn_command.program_path, self.workspace_path), + spawn.spawn_command.args.to_vec(), + &spawn.spawn_command.cwd, + ) + } + ExecutionItemKind::Leaf(LeafExecutionKind::InProcess(in_process)) => { + (Str::from(in_process.name()), in_process.args(), &display.cwd) + } + ExecutionItemKind::Expanded(nested_graph) => { + let plan = self.plan(nested_graph, &path, &location); + return Step::Nested(NestedStep { expanded_from: display.command.clone(), plan }); + } + }; + self.steps.insert(path, location); + let cwd = + (*cwd != task.task_display.package_path).then(|| path_key(cwd, self.workspace_path)); + // A `pre` or `post` script's steps keep the script's own name. + let hook = (display.task_display.task_name != task.task_display.task_name) + .then(|| display.task_display.task_name.clone()); + Step::Command(CommandStep { program, args, cwd, hook, cache: None }) } } @@ -107,30 +177,6 @@ fn task_key(task: &TaskExecution, workspace_path: &AbsolutePath) -> (Str, Str) { (package, id) } -/// A step of `task` as the plan shows it before its cache is checked. -fn lay_out_step(item: &ExecutionItem, task: &TaskExecution, workspace_path: &AbsolutePath) -> Step { - let display = &item.execution_item_display; - // A spawned step runs its spawn command, which a command handler may have - // changed from what the display shows. - let (program, args, cwd) = match &item.kind { - ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(spawn)) => ( - path_key(&spawn.spawn_command.program_path, workspace_path), - spawn.spawn_command.args.to_vec(), - &spawn.spawn_command.cwd, - ), - ExecutionItemKind::Leaf(LeafExecutionKind::InProcess(in_process)) => { - (Str::from(in_process.name()), in_process.args(), &display.cwd) - } - // The session rejects plans with nested runs before building this reporter. - ExecutionItemKind::Expanded(_) => (display.command.clone(), Vec::new(), &display.cwd), - }; - let cwd = (*cwd != task.task_display.package_path).then(|| path_key(cwd, workspace_path)); - // A `pre` or `post` script's steps keep the script's own name. - let hook = (display.task_display.task_name != task.task_display.task_name) - .then(|| display.task_display.task_name.clone()); - Step { program, args, cwd, hook, cache: None } -} - /// The document, filled in as leaves report. struct State { document: RunPlan, @@ -144,8 +190,8 @@ struct State { struct JsonGraphReporter { state: Rc>, - /// The task ID of each node. - ids: FxHashMap, + /// Where each leaf's step is, by the leaf's path in the graph. + steps: FxHashMap, StepLocation>, writer: Box, pretty: bool, } @@ -157,12 +203,8 @@ impl GraphExecutionReporter for JsonGraphReporter { _display: &ExecutionItemDisplay, _leaf_kind: &LeafExecutionKind, ) -> Box { - // Plans with nested runs are rejected, so a leaf is one level deep. - let slot = match path { - [(node, item)] => self.ids.get(node).map(|id| (id.clone(), *item)), - _ => None, - }; - Box::new(JsonLeafReporter { state: Rc::clone(&self.state), slot }) + let location = self.steps.get(path).cloned(); + Box::new(JsonLeafReporter { state: Rc::clone(&self.state), location }) } /// A run stopped by an error, or cancelled by Ctrl-C, leaves some steps @@ -184,16 +226,16 @@ impl GraphExecutionReporter for JsonGraphReporter { struct JsonLeafReporter { state: Rc>, - /// The task ID and step index of the leaf's step. - slot: Option<(Str, usize)>, + /// Where the leaf's step is in the document. + location: Option, } impl JsonLeafReporter { fn report(&self, cache: Option) { - let Some((id, step)) = &self.slot else { return }; + let Some(location) = &self.location else { return }; let mut state = self.state.borrow_mut(); let State { document, unreported, .. } = &mut *state; - if let Some(step) = document.plan.get_mut(id).and_then(|task| task.steps.get_mut(*step)) { + if let Some(step) = find_step(&mut document.plan, location) { if cache.is_some() { *unreported = unreported.saturating_sub(1); } @@ -202,6 +244,19 @@ impl JsonLeafReporter { } } +/// The command step at `location` in `plan`. +fn find_step<'a>( + plan: &'a mut BTreeMap, + location: &[(Str, usize)], +) -> Option<&'a mut CommandStep> { + let ((id, index), rest) = location.split_first()?; + match (plan.get_mut(id)?.steps.get_mut(*index)?, rest) { + (Step::Command(step), []) => Some(step), + (Step::Nested(nested), rest) => find_step(&mut nested.plan, rest), + (Step::Command(_), _) => None, + } +} + impl LeafExecutionReporter for JsonLeafReporter { fn start(&mut self, cache_status: CacheStatus) -> StdioConfig { self.report(cache_of(&cache_status)); @@ -228,7 +283,7 @@ impl LeafExecutionReporter for JsonLeafReporter { ) { if let Some(error) = error { let message = vt_str::format!("{:#}", anyhow::Error::from(error)); - let message = match &self.slot { + let message = match self.location.as_ref().and_then(|location| location.last()) { Some((id, _)) => vt_str::format!("{id}: {message}"), None => message, }; diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/dry_run_json/snapshots.toml b/crates/vt_bin/tests/e2e_snapshots/fixtures/dry_run_json/snapshots.toml index ba2f687d1..f8d34a48f 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/dry_run_json/snapshots.toml +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/dry_run_json/snapshots.toml @@ -128,11 +128,27 @@ steps = [ "--json", "typo", ], comment = "A task that isn't found is an error, not a task list." }, +] + +[[e2e]] +name = "nested_plan" +comment = """ +A nested `vt run` is a step with the plan of that run. +""" +steps = [ + [ + "vt", + "run", + "build", + ], { argv = [ + "vtt", + "pipe-stdout", + "--", "vt", "run", "--dry-run", "--json", "nested", - ], comment = "Nested runs aren't in the run plan yet." }, + ], redact = { '"\$schema":"[^"]*"' = '"$$schema":""', '"program":"[^"]*"' = '"program":""', '"durationMs":\d+' = '"durationMs":' } }, ] diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/dry_run_json/snapshots/errors.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/dry_run_json/snapshots/errors.md index 5e07086e6..312229a72 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/dry_run_json/snapshots/errors.md +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/dry_run_json/snapshots/errors.md @@ -39,13 +39,3 @@ A task that isn't found is an error, not a task list. ``` error: Task "typo" not found ``` - -## `vt run --dry-run --json nested` - -Nested runs aren't in the run plan yet. - -**Exit code:** 1 - -``` -error: `vt run --dry-run --json` doesn't support tasks that run `vt run` yet -``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/dry_run_json/snapshots/nested_plan.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/dry_run_json/snapshots/nested_plan.md new file mode 100644 index 000000000..9fc2029e7 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/dry_run_json/snapshots/nested_plan.md @@ -0,0 +1,18 @@ +# nested_plan + +A nested `vt run` is a step with the plan of that run. + +## `vt run build` + +``` +$ vtt print-file a.txt b.txt +a +b +``` + +## `vtt pipe-stdout -- vt run --dry-run --json nested` + +``` +--- stdout --- +{"$schema":"","version":1,"workspaceRoot":"","plan":{"nested":{"package":".","name":"nested","steps":[{"expandedFrom":"vt run build","plan":{"build":{"package":".","name":"build","steps":[{"program":"","args":["print-file","a.txt","b.txt"],"cache":{"status":"hit","baseline":{"durationMs":}}}]}}}]}}} +``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/package.json b/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/package.json new file mode 100644 index 000000000..77c24e2aa --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/package.json @@ -0,0 +1,10 @@ +{ + "name": "nested", + "private": true, + "scripts": { + "build": "vt run --filter a build", + "ci": "vt run -r build && vt run lint", + "lint": "vtt print lint", + "preview": "vt run --dry-run lint" + } +} diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/packages/a/package.json b/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/packages/a/package.json new file mode 100644 index 000000000..c49cefe59 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/packages/a/package.json @@ -0,0 +1,6 @@ +{ + "name": "a", + "scripts": { + "build": "vtt print a" + } +} diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/packages/b/package.json b/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/packages/b/package.json new file mode 100644 index 000000000..0e2f9bbfa --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/packages/b/package.json @@ -0,0 +1,9 @@ +{ + "name": "b", + "scripts": { + "build": "vtt print b" + }, + "dependencies": { + "a": "workspace:*" + } +} diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/pnpm-workspace.yaml b/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/pnpm-workspace.yaml new file mode 100644 index 000000000..924b55f42 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - packages/* diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/snapshots.toml b/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/snapshots.toml new file mode 100644 index 000000000..d4b096664 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/snapshots.toml @@ -0,0 +1,30 @@ +# A nested `vt run` is a step with its own plan, whose `dependsOn` refers only +# to its own tasks. `vtt` and `vt` are outside the workspace, at absolute +# paths, so they're redacted. + +[[e2e]] +name = "nested" +steps = [ + { argv = [ + "vt", + "run", + "--dry-run", + "--json", + "ci", + ], comment = "`ci` runs two nested plans in turn. The first has its own edges, and the root's `build` in it runs a third plan, so `packages/a#build` is at two levels.", redact = { '"\$schema": "[^"]*"' = '"$$schema": ""', '"program": "[^"]*vtt[^"]*"' = '"program": ""' } }, + { argv = [ + "vt", + "run", + "--dry-run", + "--json", + "ci", + "--flag", + ], comment = "Args after the task name go to the last command, a nested run here.", redact = { '"\$schema": "[^"]*"' = '"$$schema": ""', '"program": "[^"]*vtt[^"]*"' = '"program": ""' } }, + { argv = [ + "vt", + "run", + "--dry-run", + "--json", + "preview", + ], comment = "A nested `vt run --dry-run` stays a spawned step, which a dry run doesn't start.", redact = { '"\$schema": "[^"]*"' = '"$$schema": ""', '"program": "[^"]*/vt(\.exe)?"' = '"program": ""' } }, +] diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/snapshots/nested.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/snapshots/nested.md new file mode 100644 index 000000000..9e9957ca3 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/run_plan_json_nested/snapshots/nested.md @@ -0,0 +1,249 @@ +# nested + +## `vt run --dry-run --json ci` + +`ci` runs two nested plans in turn. The first has its own edges, and the root's `build` in it runs a third plan, so `packages/a#build` is at two levels. + +``` +{ + "$schema": "", + "version": 1, + "workspaceRoot": "", + "plan": { + "ci": { + "package": ".", + "name": "ci", + "steps": [ + { + "expandedFrom": "vt run -r build", + "plan": { + "build": { + "package": ".", + "name": "build", + "steps": [ + { + "expandedFrom": "vt run --filter a build", + "plan": { + "packages/a#build": { + "package": "packages/a", + "name": "build", + "steps": [ + { + "program": "", + "args": [ + "print", + "a" + ], + "cache": { + "status": "disabled" + } + } + ] + } + } + } + ] + }, + "packages/a#build": { + "package": "packages/a", + "name": "build", + "steps": [ + { + "program": "", + "args": [ + "print", + "a" + ], + "cache": { + "status": "disabled" + } + } + ] + }, + "packages/b#build": { + "package": "packages/b", + "name": "build", + "dependsOn": [ + "packages/a#build" + ], + "steps": [ + { + "program": "", + "args": [ + "print", + "b" + ], + "cache": { + "status": "disabled" + } + } + ] + } + } + }, + { + "expandedFrom": "vt run lint", + "plan": { + "lint": { + "package": ".", + "name": "lint", + "steps": [ + { + "program": "", + "args": [ + "print", + "lint" + ], + "cache": { + "status": "disabled" + } + } + ] + } + } + } + ] + } + } +} +``` + +## `vt run --dry-run --json ci --flag` + +Args after the task name go to the last command, a nested run here. + +``` +{ + "$schema": "", + "version": 1, + "workspaceRoot": "", + "plan": { + "ci": { + "package": ".", + "name": "ci", + "steps": [ + { + "expandedFrom": "vt run -r build", + "plan": { + "build": { + "package": ".", + "name": "build", + "steps": [ + { + "expandedFrom": "vt run --filter a build", + "plan": { + "packages/a#build": { + "package": "packages/a", + "name": "build", + "steps": [ + { + "program": "", + "args": [ + "print", + "a" + ], + "cache": { + "status": "disabled" + } + } + ] + } + } + } + ] + }, + "packages/a#build": { + "package": "packages/a", + "name": "build", + "steps": [ + { + "program": "", + "args": [ + "print", + "a" + ], + "cache": { + "status": "disabled" + } + } + ] + }, + "packages/b#build": { + "package": "packages/b", + "name": "build", + "dependsOn": [ + "packages/a#build" + ], + "steps": [ + { + "program": "", + "args": [ + "print", + "b" + ], + "cache": { + "status": "disabled" + } + } + ] + } + } + }, + { + "expandedFrom": "vt run lint --flag", + "plan": { + "lint": { + "package": ".", + "name": "lint", + "steps": [ + { + "program": "", + "args": [ + "print", + "lint", + "--flag" + ], + "cache": { + "status": "disabled" + } + } + ] + } + } + } + ] + } + } +} +``` + +## `vt run --dry-run --json preview` + +A nested `vt run --dry-run` stays a spawned step, which a dry run doesn't start. + +``` +{ + "$schema": "", + "version": 1, + "workspaceRoot": "", + "plan": { + "preview": { + "package": ".", + "name": "preview", + "steps": [ + { + "program": "", + "args": [ + "run", + "--dry-run", + "lint" + ], + "cache": { + "status": "disabled" + } + } + ] + } + } +} +```