Skip to content

Commit 5b5172b

Browse files
committed
Fix nx generators for modern Nx workspaces
Update the nx completion generators to work with modern Nx (16.3+): - Replace `cat workspace.json` (deprecated in Nx 13+) with `nx show projects` for the apps, e2e_apps, and apps_and_libs generators - Use `nx graph --print` (with tmpfile fallback for Nx < 19.20) for workspace_targets, replacing the slow per-project Node.js approach - Apps generator now uses `nx show projects --type app` for proper filtering - E2E apps generator uses `nx show projects --type e2e` with fallback This fixes completions for `nx run <project>:<target>` and other subcommands that were broken on modern Nx workspaces that no longer use workspace.json. Closes APP-3498 See: warpdotdev/warp#4691 Co-Authored-By: Oz <oz-agent@warp.dev>
1 parent 8fafc3f commit 5b5172b

1 file changed

Lines changed: 57 additions & 80 deletions

File tree

  • command-signatures/src/generators

command-signatures/src/generators/nx.rs

Lines changed: 57 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,25 @@
11
use itertools::Itertools;
22
use lazy_static::lazy_static;
3-
use serde_json::{Result, Value};
3+
use serde_json::Value;
44
use std::collections::HashMap;
55
use warp_completion_metadata::{
66
CommandBuilder, CommandSignatureGenerators, Generator, GeneratorResults,
77
GeneratorResultsCollector, Suggestion,
88
};
99

1010
lazy_static! {
11-
/// Command that computes the `nx` workspace targets by executing `nx graph --file`.
12-
/// `nx graph` supports printing to stdout, but older versions of `nx` (before 19.20) had a bug
13-
/// where the output of `nx graph` could be truncated when printing to stdout (see https://github.com/nrwl/nx/issues/18689
14-
/// for more details).
15-
/// The workaround here is to write the output to a tmpfile and then `cat` that tmpfile. We execute
16-
/// this within a sh shell to ensure we are running in an environment where we can run POSIX-shell
17-
/// compliant commands to generate the output, even if the user is running a non-POSIX compliant
18-
/// shell (such as fish).
19-
static ref NX_WORKSPACE_TARGETS_COMMAND: CommandBuilder = CommandBuilder::and(
20-
CommandBuilder::single_command_and_ignore_stderr(
21-
"sh -c 'temp=$(mktemp -u).json && nx graph --file $temp"
22-
),
23-
CommandBuilder::single_command("cat $temp && rm $temp'")
11+
/// Command that retrieves the Nx project graph with target information.
12+
///
13+
/// Uses `nx graph --print` (Nx 19.20+) which outputs JSON to stdout.
14+
/// Falls back to `nx graph --file` with a tmpfile for older Nx versions
15+
/// (avoids a truncation bug in stdout output, see
16+
/// https://github.com/nrwl/nx/issues/18689).
17+
static ref NX_WORKSPACE_TARGETS_COMMAND: CommandBuilder = CommandBuilder::single_command(
18+
"sh -c \"nx graph --print 2>/dev/null || { temp=\\$(mktemp -u).json && nx graph --file \\$temp 2>/dev/null && cat \\$temp && rm -f \\$temp; }\""
2419
);
2520
}
2621

27-
#[derive(Debug, serde::Deserialize)]
28-
#[serde(rename_all = "camelCase")]
29-
struct NxOutput {
30-
projects: HashMap<String, NxProject>,
31-
}
32-
33-
#[derive(Debug, serde::Deserialize)]
34-
#[serde(rename_all = "camelCase")]
35-
struct NxProject {
36-
project_type: String,
37-
}
38-
22+
/// Parsed output from `nx graph --print` / `nx graph --file`.
3923
#[derive(Debug, serde::Deserialize)]
4024
#[serde(rename_all = "camelCase")]
4125
struct NXGraphFile {
@@ -62,61 +46,71 @@ struct NXData {
6246
targets: HashMap<String, Value>,
6347
}
6448

65-
fn process_workspace_json(
66-
output: &str,
67-
filter_fn: fn(project: &(String, NxProject)) -> bool,
68-
) -> GeneratorResults {
69-
let json_output: Result<NxOutput> = serde_json::from_str(output);
70-
match json_output {
71-
Ok(output) => output
72-
.projects
73-
.into_iter()
74-
.filter(filter_fn)
75-
.map(|(name, _)| Suggestion::new(name))
76-
.collect_unordered_results(),
77-
Err(e) => {
78-
log::info!("Unable to deserialize nx output: {:?}", e);
79-
GeneratorResults::default()
80-
}
81-
}
82-
}
83-
8449
fn process_generators(output: &str) -> GeneratorResults {
8550
output
8651
.split('\n')
8752
.filter_map(|line| line.split(' ').next().map(Suggestion::new))
8853
.collect_unordered_results()
8954
}
9055

56+
/// Parse the output of `nx show projects` (one project name per line) into suggestions.
57+
fn process_project_list(output: &str) -> GeneratorResults {
58+
output
59+
.trim()
60+
.lines()
61+
.filter(|line| !line.is_empty())
62+
.map(|line| Suggestion::new(line.trim()))
63+
.collect_unordered_results()
64+
}
65+
66+
/// Parse the project graph output into `project:target` suggestions.
67+
fn process_workspace_targets(output: &str) -> GeneratorResults {
68+
let Ok(parsed_output) = serde_json::from_str::<NXGraphFile>(output) else {
69+
return GeneratorResults::default();
70+
};
71+
72+
let suggestions = parsed_output
73+
.graph
74+
.nodes
75+
.into_values()
76+
.flat_map(|node| {
77+
node.data.targets.into_keys().map(move |target| {
78+
let name = format!("{}:{target}", node.name);
79+
Suggestion::with_description(name, "nx target")
80+
})
81+
})
82+
.unique();
83+
GeneratorResults {
84+
suggestions: suggestions.collect(),
85+
is_ordered: false,
86+
}
87+
}
88+
9189
pub fn generator() -> CommandSignatureGenerators {
9290
CommandSignatureGenerators::new("nx")
9391
.add_generator(
9492
"apps",
9593
Generator::script(
96-
CommandBuilder::single_command("cat workspace.json"),
97-
|output| {
98-
process_workspace_json(output, |(name, project)| {
99-
project.project_type == "application" && !name.ends_with("-e2e")
100-
})
101-
},
94+
CommandBuilder::single_command(
95+
"sh -c \"nx show projects --type app 2>/dev/null | grep -v -- '-e2e\\$'\"",
96+
),
97+
process_project_list,
10298
),
10399
)
104100
.add_generator(
105101
"e2e_apps",
106102
Generator::script(
107-
CommandBuilder::single_command("cat workspace.json"),
108-
|output| {
109-
process_workspace_json(output, |(name, project)| {
110-
project.project_type == "application" && name.ends_with("-e2e")
111-
})
112-
},
103+
CommandBuilder::single_command(
104+
"sh -c \"nx show projects --type e2e 2>/dev/null || nx show projects --type app 2>/dev/null | grep -- '-e2e\\$'\"",
105+
),
106+
process_project_list,
113107
),
114108
)
115109
.add_generator(
116110
"apps_and_libs",
117111
Generator::script(
118-
CommandBuilder::single_command("cat workspace.json"),
119-
|output| process_workspace_json(output, |_| true),
112+
CommandBuilder::single_command("nx show projects"),
113+
process_project_list,
120114
),
121115
)
122116
.add_generator(
@@ -135,27 +129,10 @@ pub fn generator() -> CommandSignatureGenerators {
135129
)
136130
.add_generator(
137131
"workspace_targets",
138-
Generator::script(NX_WORKSPACE_TARGETS_COMMAND.clone(), |output| {
139-
let Ok(parsed_output) = serde_json::from_str::<NXGraphFile>(output) else {
140-
return GeneratorResults::default();
141-
};
142-
143-
let suggestions = parsed_output
144-
.graph
145-
.nodes
146-
.into_values()
147-
.flat_map(|node| {
148-
node.data.targets.into_keys().map(move |target| {
149-
let name = format!("{}:{target}", node.name);
150-
Suggestion::with_description(name, "nx target")
151-
})
152-
})
153-
.unique();
154-
GeneratorResults {
155-
suggestions: suggestions.collect(),
156-
is_ordered: false,
157-
}
158-
}),
132+
Generator::script(
133+
NX_WORKSPACE_TARGETS_COMMAND.clone(),
134+
process_workspace_targets,
135+
),
159136
)
160137
.add_generator(
161138
"installed_plugins",

0 commit comments

Comments
 (0)