task: Support path property on VS Code npm tasks (#62044)
# Objective npm tasks in VS Code support a `path` property which works like `options.cwd` but it's always relative to the workspace folder. ## Solution `cwd` is being set based on `path` of npm tasks. If `options.cwd` is also set, it wins over the `path` property. This matches the behavior of VS Code. ## Testing A new test case has been added, testing deserialization of `path` and `options.cwd`. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [ ] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [ ] Performance impact has been considered and is acceptable --- Release Notes: - Added support for `path` property on VS Code npm tasks
This commit is contained in:
1 parent
e8fcbd476d
commit
6706fa4651
5 files changed
+242
-100
No files matched your search
@@ -59,6 +59,7 @@ impl<'de> serde::Deserialize<'de> for VsCodeTaskDefinition {
|
||||
enum Command {
|
||||
Npm {
|
||||
script: String,
|
||||
path: Option<String>,
|
||||
},
|
||||
Shell {
|
||||
command: String,
|
||||
@@ -72,7 +73,7 @@ enum Command {
|
||||
|
||||
fn generate_label(command: &Option<Command>) -> String {
|
||||
match command {
|
||||
Some(Command::Npm { script }) => format!("npm: {}", script),
|
||||
Some(Command::Npm { script, .. }) => format!("npm: {}", script),
|
||||
Some(Command::Gulp { task }) => format!("gulp: {}", task),
|
||||
Some(Command::Shell { command, .. }) => {
|
||||
if command.trim().is_empty() {
|
||||
@@ -105,10 +106,16 @@ impl VsCodeTaskDefinition {
|
||||
bail!("Missing `type` field in task");
|
||||
};
|
||||
|
||||
let (command, args) = match command {
|
||||
Command::Npm { script } => ("npm".to_owned(), vec!["run".to_string(), script]),
|
||||
Command::Shell { command, args } => (command, args),
|
||||
Command::Gulp { task } => ("gulp".to_owned(), vec![task]),
|
||||
let (command, args, cwd) = match command {
|
||||
Command::Npm { script, path } => (
|
||||
"npm".to_owned(),
|
||||
vec!["run".to_string(), script],
|
||||
path.map(|path| {
|
||||
format!("{}/{}", VariableName::WorktreeRoot.template_value(), path)
|
||||
}),
|
||||
),
|
||||
Command::Shell { command, args } => (command, args, None),
|
||||
Command::Gulp { task } => ("gulp".to_owned(), vec![task], None),
|
||||
};
|
||||
// Per VSC docs, only `command`, `args` and `options` support variable substitution.
|
||||
let command = replacer.replace(&command);
|
||||
@@ -117,10 +124,13 @@ impl VsCodeTaskDefinition {
|
||||
label: self.label,
|
||||
command,
|
||||
args,
|
||||
cwd: cwd.map(|cwd| replacer.replace(&cwd)),
|
||||
..TaskTemplate::default()
|
||||
};
|
||||
if let Some(options) = self.options {
|
||||
template.cwd = options.cwd.map(|cwd| replacer.replace(&cwd));
|
||||
if let Some(cwd) = options.cwd {
|
||||
template.cwd = Some(replacer.replace(&cwd));
|
||||
}
|
||||
template.env = options.env;
|
||||
}
|
||||
Ok(Some(template))
|
||||
@@ -213,49 +223,87 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_deserialize_ts_tasks() {
|
||||
const TYPESCRIPT_TASKS: &str = include_str!("../test_data/typescript.json");
|
||||
let vscode_definitions: VsCodeTaskFile =
|
||||
serde_json_lenient::from_str(TYPESCRIPT_TASKS).unwrap();
|
||||
fn can_deserialize_gulp_tasks() {
|
||||
const GULP_TASKS: &str = include_str!("../test_data/tasks-gulp.json");
|
||||
let vscode_definitions: VsCodeTaskFile = serde_json_lenient::from_str(GULP_TASKS).unwrap();
|
||||
|
||||
let expected = vec![VsCodeTaskDefinition {
|
||||
label: "gulp: build tests".to_string(),
|
||||
command: Some(Command::Gulp {
|
||||
task: "build".to_string(),
|
||||
}),
|
||||
other_attributes: Default::default(),
|
||||
options: None,
|
||||
}];
|
||||
|
||||
assert_eq!(vscode_definitions.tasks.len(), expected.len());
|
||||
vscode_definitions
|
||||
.tasks
|
||||
.iter()
|
||||
.zip(expected)
|
||||
.for_each(|(lhs, rhs)| compare_without_other_attributes(lhs.clone(), rhs));
|
||||
|
||||
let expected = vec![TaskTemplate {
|
||||
label: "gulp: build tests".to_string(),
|
||||
command: "gulp".to_string(),
|
||||
args: vec!["build".to_string()],
|
||||
..Default::default()
|
||||
}];
|
||||
|
||||
let tasks: TaskTemplates = vscode_definitions.try_into().unwrap();
|
||||
assert_eq!(tasks.0, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_deserialize_npm_tasks() {
|
||||
const NPM_TASKS: &str = include_str!("../test_data/tasks-npm.json");
|
||||
let vscode_definitions: VsCodeTaskFile = serde_json_lenient::from_str(NPM_TASKS).unwrap();
|
||||
|
||||
let expected = vec![
|
||||
VsCodeTaskDefinition {
|
||||
label: "gulp: tests".to_string(),
|
||||
label: "With env".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "build:tests:notypecheck".to_string(),
|
||||
script: "build".to_string(),
|
||||
path: None,
|
||||
}),
|
||||
other_attributes: Default::default(),
|
||||
options: Some(super::TaskOptions {
|
||||
cwd: None,
|
||||
env: HashMap::from_iter([("NODE_ENV".to_string(), "production".to_string())]),
|
||||
}),
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "With path".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "build".to_string(),
|
||||
path: Some("packages/components".to_string()),
|
||||
}),
|
||||
other_attributes: Default::default(),
|
||||
options: None,
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "tsc: watch ./src".to_string(),
|
||||
command: Some(Command::Shell {
|
||||
command: "node".to_string(),
|
||||
args: vec![
|
||||
"${workspaceFolder}/node_modules/typescript/lib/tsc.js".to_string(),
|
||||
"--build".to_string(),
|
||||
"${workspaceFolder}/src".to_string(),
|
||||
"--watch".to_string(),
|
||||
],
|
||||
label: "With cwd".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "build".to_string(),
|
||||
path: None,
|
||||
}),
|
||||
other_attributes: Default::default(),
|
||||
options: None,
|
||||
options: Some(super::TaskOptions {
|
||||
cwd: Some("${workspaceFolder}/packages/app".to_string()),
|
||||
env: Default::default(),
|
||||
}),
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "npm: build:compiler".to_string(),
|
||||
label: "With path and cwd".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "build:compiler".to_string(),
|
||||
script: "build".to_string(),
|
||||
path: Some("packages/components".to_string()),
|
||||
}),
|
||||
other_attributes: Default::default(),
|
||||
options: None,
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "npm: build:tests".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "build:tests:notypecheck".to_string(),
|
||||
options: Some(super::TaskOptions {
|
||||
cwd: Some("${workspaceFolder}/packages/app".to_string()),
|
||||
env: Default::default(),
|
||||
}),
|
||||
other_attributes: Default::default(),
|
||||
options: None,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -268,13 +316,87 @@ mod tests {
|
||||
|
||||
let expected = vec![
|
||||
TaskTemplate {
|
||||
label: "gulp: tests".to_string(),
|
||||
label: "With env".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "build:tests:notypecheck".to_string()],
|
||||
args: vec!["run".to_string(), "build".to_string()],
|
||||
env: HashMap::from_iter([("NODE_ENV".to_string(), "production".to_string())]),
|
||||
..Default::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: "tsc: watch ./src".to_string(),
|
||||
label: "With path".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "build".to_string()],
|
||||
cwd: Some("$ZED_WORKTREE_ROOT/packages/components".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: "With cwd".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "build".to_string()],
|
||||
cwd: Some("${ZED_WORKTREE_ROOT}/packages/app".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: "With path and cwd".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "build".to_string()],
|
||||
cwd: Some("${ZED_WORKTREE_ROOT}/packages/app".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
let tasks: TaskTemplates = vscode_definitions.try_into().unwrap();
|
||||
assert_eq!(tasks.0, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_deserialize_shell_tasks() {
|
||||
const SHELL_TASKS: &str = include_str!("../test_data/tasks-shell.json");
|
||||
let vscode_definitions: VsCodeTaskFile = serde_json_lenient::from_str(SHELL_TASKS).unwrap();
|
||||
let expected = vec![
|
||||
VsCodeTaskDefinition {
|
||||
label: "Without args".to_string(),
|
||||
command: Some(Command::Shell {
|
||||
command: "cargo build --package rust-analyzer".to_string(),
|
||||
args: Default::default(),
|
||||
}),
|
||||
options: None,
|
||||
other_attributes: Default::default(),
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "With args".to_string(),
|
||||
command: Some(Command::Shell {
|
||||
command: "node".to_string(),
|
||||
args: vec![
|
||||
"${workspaceFolder}/node_modules/typescript/lib/tsc.js".to_string(),
|
||||
"--build".to_string(),
|
||||
"${workspaceFolder}/src".to_string(),
|
||||
"--watch".to_string(),
|
||||
],
|
||||
}),
|
||||
options: None,
|
||||
other_attributes: Default::default(),
|
||||
},
|
||||
VsCodeTaskDefinition {
|
||||
label: "Ignored task without type".to_string(),
|
||||
command: None,
|
||||
options: None,
|
||||
other_attributes: Default::default(),
|
||||
},
|
||||
];
|
||||
assert_eq!(vscode_definitions.tasks.len(), expected.len());
|
||||
vscode_definitions
|
||||
.tasks
|
||||
.iter()
|
||||
.zip(expected)
|
||||
.for_each(|(lhs, rhs)| compare_without_other_attributes(lhs.clone(), rhs));
|
||||
let expected = vec![
|
||||
TaskTemplate {
|
||||
label: "Without args".to_string(),
|
||||
command: "cargo build --package rust-analyzer".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: "With args".to_string(),
|
||||
command: "node".to_string(),
|
||||
args: vec![
|
||||
"${ZED_WORKTREE_ROOT}/node_modules/typescript/lib/tsc.js".to_string(),
|
||||
@@ -284,20 +406,7 @@ mod tests {
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: "npm: build:compiler".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "build:compiler".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: "npm: build:tests".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "build:tests:notypecheck".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
|
||||
let tasks: TaskTemplates = vscode_definitions.try_into().unwrap();
|
||||
assert_eq!(tasks.0, expected);
|
||||
}
|
||||
@@ -312,6 +421,7 @@ mod tests {
|
||||
label: "Build Extension in Background".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "watch".to_string(),
|
||||
path: Some("editors/code/".to_string()),
|
||||
}),
|
||||
options: None,
|
||||
other_attributes: Default::default(),
|
||||
@@ -320,6 +430,7 @@ mod tests {
|
||||
label: "Build Extension".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "build".to_string(),
|
||||
path: Some("editors/code/".to_string()),
|
||||
}),
|
||||
options: None,
|
||||
other_attributes: Default::default(),
|
||||
@@ -346,6 +457,7 @@ mod tests {
|
||||
label: "Pretest".to_string(),
|
||||
command: Some(Command::Npm {
|
||||
script: "pretest".to_string(),
|
||||
path: Some("editors/code/".to_string()),
|
||||
}),
|
||||
options: None,
|
||||
other_attributes: Default::default(),
|
||||
@@ -374,12 +486,14 @@ mod tests {
|
||||
label: "Build Extension in Background".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "watch".to_string()],
|
||||
cwd: Some("$ZED_WORKTREE_ROOT/editors/code/".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: "Build Extension".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "build".to_string()],
|
||||
cwd: Some("$ZED_WORKTREE_ROOT/editors/code/".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
@@ -396,6 +510,7 @@ mod tests {
|
||||
label: "Pretest".to_string(),
|
||||
command: "npm".to_string(),
|
||||
args: vec!["run".to_string(), "pretest".to_string()],
|
||||
cwd: Some("$ZED_WORKTREE_ROOT/editors/code/".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
@@ -420,7 +535,8 @@ mod tests {
|
||||
fn test_generate_label() {
|
||||
assert_eq!(
|
||||
generate_label(&Some(Command::Npm {
|
||||
script: "start".to_string()
|
||||
script: "start".to_string(),
|
||||
path: None,
|
||||
})),
|
||||
"npm: start"
|
||||
);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "gulp: build tests",
|
||||
"type": "gulp",
|
||||
"task": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "With env",
|
||||
"type": "npm",
|
||||
"script": "build",
|
||||
"options": {
|
||||
"env": {
|
||||
"NODE_ENV": "production"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "With path",
|
||||
"type": "npm",
|
||||
"script": "build",
|
||||
"path": "packages/components"
|
||||
},
|
||||
{
|
||||
"label": "With cwd",
|
||||
"type": "npm",
|
||||
"script": "build",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/packages/app"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "With path and cwd",
|
||||
"type": "npm",
|
||||
"script": "build",
|
||||
"path": "packages/components",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/packages/app"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Without args",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "cargo build --package rust-analyzer",
|
||||
"problemMatcher": "$rustc"
|
||||
},
|
||||
{
|
||||
"label": "With args",
|
||||
"type": "shell",
|
||||
"command": "node",
|
||||
"args": [
|
||||
"${workspaceFolder}/node_modules/typescript/lib/tsc.js",
|
||||
"--build",
|
||||
"${workspaceFolder}/src",
|
||||
"--watch"
|
||||
],
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"label": "Ignored task without type",
|
||||
"dependsOn": ["Without args", "With args"],
|
||||
"problemMatcher": "$rustc"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
// Kept for backwards compat for old launch.json files so it's
|
||||
// less annoying if moving up to the new build or going back to
|
||||
// the old build.
|
||||
//
|
||||
// This is first because the actual "npm: build:tests" task
|
||||
// below has the same script value, and VS Code ignores labels
|
||||
// and deduplicates them.
|
||||
// https://github.com/microsoft/vscode/issues/93001
|
||||
"label": "gulp: tests",
|
||||
"type": "npm",
|
||||
"script": "build:tests:notypecheck",
|
||||
"group": "build",
|
||||
"hide": true,
|
||||
"problemMatcher": ["$tsc"]
|
||||
},
|
||||
{
|
||||
"label": "tsc: watch ./src",
|
||||
"type": "shell",
|
||||
"command": "node",
|
||||
"args": [
|
||||
"${workspaceFolder}/node_modules/typescript/lib/tsc.js",
|
||||
"--build",
|
||||
"${workspaceFolder}/src",
|
||||
"--watch"
|
||||
],
|
||||
"group": "build",
|
||||
"isBackground": true,
|
||||
"problemMatcher": ["$tsc-watch"]
|
||||
},
|
||||
{
|
||||
"label": "npm: build:compiler",
|
||||
"type": "npm",
|
||||
"script": "build:compiler",
|
||||
"group": "build",
|
||||
"problemMatcher": ["$tsc"]
|
||||
},
|
||||
{
|
||||
"label": "npm: build:tests",
|
||||
"type": "npm",
|
||||
"script": "build:tests:notypecheck",
|
||||
"group": "build",
|
||||
"problemMatcher": ["$tsc"]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user