debugger: Fix issues with debugging scripts from package.json (#32995)
- [x] Pass in cwd - [x] Use the appropriate package manager - [x] Don't mix up package.json and composer.json Release Notes: - debugger: Fixed wrong arguments being passed to the DAP when debugging scripts from package.json.
This commit is contained in:
parent
e914d84f00
commit
0b228ad12c
5 changed files with 182 additions and 153 deletions
|
@ -31,6 +31,8 @@ use std::{
|
||||||
use task::{AdapterSchemas, TaskTemplate, TaskTemplates, VariableName};
|
use task::{AdapterSchemas, TaskTemplate, TaskTemplates, VariableName};
|
||||||
use util::{ResultExt, archive::extract_zip, fs::remove_matching, maybe, merge_json_value_into};
|
use util::{ResultExt, archive::extract_zip, fs::remove_matching, maybe, merge_json_value_into};
|
||||||
|
|
||||||
|
use crate::PackageJsonData;
|
||||||
|
|
||||||
const SERVER_PATH: &str =
|
const SERVER_PATH: &str =
|
||||||
"node_modules/vscode-langservers-extracted/bin/vscode-json-language-server";
|
"node_modules/vscode-langservers-extracted/bin/vscode-json-language-server";
|
||||||
|
|
||||||
|
@ -47,12 +49,14 @@ impl ContextProvider for JsonTaskProvider {
|
||||||
file: Option<Arc<dyn language::File>>,
|
file: Option<Arc<dyn language::File>>,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> gpui::Task<Option<TaskTemplates>> {
|
) -> gpui::Task<Option<TaskTemplates>> {
|
||||||
let Some(file) = project::File::from_dyn(file.as_ref())
|
let Some(file) = project::File::from_dyn(file.as_ref()).cloned() else {
|
||||||
.filter(|file| file.path.file_name() == Some("package.json".as_ref()))
|
|
||||||
.cloned()
|
|
||||||
else {
|
|
||||||
return Task::ready(None);
|
return Task::ready(None);
|
||||||
};
|
};
|
||||||
|
let is_package_json = file.path.ends_with("package.json");
|
||||||
|
let is_composer_json = file.path.ends_with("composer.json");
|
||||||
|
if !is_package_json && !is_composer_json {
|
||||||
|
return Task::ready(None);
|
||||||
|
}
|
||||||
|
|
||||||
cx.spawn(async move |cx| {
|
cx.spawn(async move |cx| {
|
||||||
let contents = file
|
let contents = file
|
||||||
|
@ -62,53 +66,68 @@ impl ContextProvider for JsonTaskProvider {
|
||||||
.await
|
.await
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
let as_json = serde_json_lenient::Value::from_str(&contents.text).ok()?;
|
let task_templates = if is_package_json {
|
||||||
let gutter_tasks = [
|
let package_json = serde_json_lenient::from_str::<
|
||||||
TaskTemplate {
|
HashMap<String, serde_json_lenient::Value>,
|
||||||
label: "package script $ZED_CUSTOM_script".to_owned(),
|
>(&contents.text)
|
||||||
command: "npm".to_owned(),
|
.ok()?;
|
||||||
args: vec![
|
let package_json = PackageJsonData::new(file.path.clone(), package_json);
|
||||||
"--prefix".into(),
|
let command = package_json.package_manager.unwrap_or("npm").to_owned();
|
||||||
"$ZED_DIRNAME".into(),
|
package_json
|
||||||
"run".into(),
|
.scripts
|
||||||
VariableName::Custom("script".into()).template_value(),
|
.into_iter()
|
||||||
],
|
.map(|(_, key)| TaskTemplate {
|
||||||
tags: vec!["package-script".into()],
|
label: format!("run {key}"),
|
||||||
..TaskTemplate::default()
|
command: command.clone(),
|
||||||
},
|
args: vec!["run".into(), key],
|
||||||
TaskTemplate {
|
cwd: Some(VariableName::Dirname.template_value()),
|
||||||
label: "composer script $ZED_CUSTOM_script".to_owned(),
|
..TaskTemplate::default()
|
||||||
command: "composer".to_owned(),
|
})
|
||||||
args: vec![
|
.chain([TaskTemplate {
|
||||||
"-d".into(),
|
label: "package script $ZED_CUSTOM_script".to_owned(),
|
||||||
"$ZED_DIRNAME".into(),
|
command: command.clone(),
|
||||||
VariableName::Custom("script".into()).template_value(),
|
args: vec![
|
||||||
],
|
"run".into(),
|
||||||
tags: vec!["composer-script".into()],
|
VariableName::Custom("script".into()).template_value(),
|
||||||
..TaskTemplate::default()
|
],
|
||||||
},
|
cwd: Some(VariableName::Dirname.template_value()),
|
||||||
];
|
tags: vec!["package-script".into()],
|
||||||
let tasks = as_json
|
..TaskTemplate::default()
|
||||||
.get("scripts")?
|
}])
|
||||||
.as_object()?
|
.collect()
|
||||||
.keys()
|
} else if is_composer_json {
|
||||||
.map(|key| TaskTemplate {
|
serde_json_lenient::Value::from_str(&contents.text)
|
||||||
label: format!("run {key}"),
|
.ok()?
|
||||||
command: "npm".to_owned(),
|
.get("scripts")?
|
||||||
args: vec![
|
.as_object()?
|
||||||
"--prefix".into(),
|
.keys()
|
||||||
"$ZED_DIRNAME".into(),
|
.map(|key| TaskTemplate {
|
||||||
"run".into(),
|
label: format!("run {key}"),
|
||||||
key.into(),
|
command: "composer".to_owned(),
|
||||||
],
|
args: vec!["-d".into(), "$ZED_DIRNAME".into(), key.into()],
|
||||||
..TaskTemplate::default()
|
..TaskTemplate::default()
|
||||||
})
|
})
|
||||||
.chain(gutter_tasks)
|
.chain([TaskTemplate {
|
||||||
.collect();
|
label: "composer script $ZED_CUSTOM_script".to_owned(),
|
||||||
Some(TaskTemplates(tasks))
|
command: "composer".to_owned(),
|
||||||
|
args: vec![
|
||||||
|
"-d".into(),
|
||||||
|
"$ZED_DIRNAME".into(),
|
||||||
|
VariableName::Custom("script".into()).template_value(),
|
||||||
|
],
|
||||||
|
tags: vec!["composer-script".into()],
|
||||||
|
..TaskTemplate::default()
|
||||||
|
}])
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(TaskTemplates(task_templates))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||||
vec![server_path.into(), "--stdio".into()]
|
vec![server_path.into(), "--stdio".into()]
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ mod c;
|
||||||
mod css;
|
mod css;
|
||||||
mod go;
|
mod go;
|
||||||
mod json;
|
mod json;
|
||||||
|
mod package_json;
|
||||||
mod python;
|
mod python;
|
||||||
mod rust;
|
mod rust;
|
||||||
mod tailwind;
|
mod tailwind;
|
||||||
|
@ -25,6 +26,8 @@ mod typescript;
|
||||||
mod vtsls;
|
mod vtsls;
|
||||||
mod yaml;
|
mod yaml;
|
||||||
|
|
||||||
|
pub(crate) use package_json::{PackageJson, PackageJsonData};
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
#[derive(RustEmbed)]
|
||||||
#[folder = "src/"]
|
#[folder = "src/"]
|
||||||
#[exclude = "*.rs"]
|
#[exclude = "*.rs"]
|
||||||
|
|
106
crates/languages/src/package_json.rs
Normal file
106
crates/languages/src/package_json.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
use chrono::{DateTime, Local};
|
||||||
|
use collections::{BTreeSet, HashMap};
|
||||||
|
use serde_json_lenient::Value;
|
||||||
|
use std::{path::Path, sync::Arc};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PackageJson {
|
||||||
|
pub mtime: DateTime<Local>,
|
||||||
|
pub data: PackageJsonData,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub struct PackageJsonData {
|
||||||
|
pub jest_package_path: Option<Arc<Path>>,
|
||||||
|
pub mocha_package_path: Option<Arc<Path>>,
|
||||||
|
pub vitest_package_path: Option<Arc<Path>>,
|
||||||
|
pub jasmine_package_path: Option<Arc<Path>>,
|
||||||
|
pub scripts: BTreeSet<(Arc<Path>, String)>,
|
||||||
|
pub package_manager: Option<&'static str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageJsonData {
|
||||||
|
pub fn new(path: Arc<Path>, package_json: HashMap<String, Value>) -> Self {
|
||||||
|
let mut scripts = BTreeSet::new();
|
||||||
|
if let Some(Value::Object(package_json_scripts)) = package_json.get("scripts") {
|
||||||
|
scripts.extend(
|
||||||
|
package_json_scripts
|
||||||
|
.keys()
|
||||||
|
.cloned()
|
||||||
|
.map(|name| (path.clone(), name)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut jest_package_path = None;
|
||||||
|
let mut mocha_package_path = None;
|
||||||
|
let mut vitest_package_path = None;
|
||||||
|
let mut jasmine_package_path = None;
|
||||||
|
if let Some(Value::Object(dependencies)) = package_json.get("devDependencies") {
|
||||||
|
if dependencies.contains_key("jest") {
|
||||||
|
jest_package_path.get_or_insert_with(|| path.clone());
|
||||||
|
}
|
||||||
|
if dependencies.contains_key("mocha") {
|
||||||
|
mocha_package_path.get_or_insert_with(|| path.clone());
|
||||||
|
}
|
||||||
|
if dependencies.contains_key("vitest") {
|
||||||
|
vitest_package_path.get_or_insert_with(|| path.clone());
|
||||||
|
}
|
||||||
|
if dependencies.contains_key("jasmine") {
|
||||||
|
jasmine_package_path.get_or_insert_with(|| path.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(Value::Object(dev_dependencies)) = package_json.get("dependencies") {
|
||||||
|
if dev_dependencies.contains_key("jest") {
|
||||||
|
jest_package_path.get_or_insert_with(|| path.clone());
|
||||||
|
}
|
||||||
|
if dev_dependencies.contains_key("mocha") {
|
||||||
|
mocha_package_path.get_or_insert_with(|| path.clone());
|
||||||
|
}
|
||||||
|
if dev_dependencies.contains_key("vitest") {
|
||||||
|
vitest_package_path.get_or_insert_with(|| path.clone());
|
||||||
|
}
|
||||||
|
if dev_dependencies.contains_key("jasmine") {
|
||||||
|
jasmine_package_path.get_or_insert_with(|| path.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let package_manager = package_json
|
||||||
|
.get("packageManager")
|
||||||
|
.and_then(|value| value.as_str())
|
||||||
|
.and_then(|value| {
|
||||||
|
if value.starts_with("pnpm") {
|
||||||
|
Some("pnpm")
|
||||||
|
} else if value.starts_with("yarn") {
|
||||||
|
Some("yarn")
|
||||||
|
} else if value.starts_with("npm") {
|
||||||
|
Some("npm")
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
jest_package_path,
|
||||||
|
mocha_package_path,
|
||||||
|
vitest_package_path,
|
||||||
|
jasmine_package_path,
|
||||||
|
scripts,
|
||||||
|
package_manager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge(&mut self, other: Self) {
|
||||||
|
self.jest_package_path = self.jest_package_path.take().or(other.jest_package_path);
|
||||||
|
self.mocha_package_path = self.mocha_package_path.take().or(other.mocha_package_path);
|
||||||
|
self.vitest_package_path = self
|
||||||
|
.vitest_package_path
|
||||||
|
.take()
|
||||||
|
.or(other.vitest_package_path);
|
||||||
|
self.jasmine_package_path = self
|
||||||
|
.jasmine_package_path
|
||||||
|
.take()
|
||||||
|
.or(other.jasmine_package_path);
|
||||||
|
self.scripts.extend(other.scripts);
|
||||||
|
self.package_manager = self.package_manager.or(other.package_manager);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,6 @@ use smol::{fs, io::BufReader, lock::RwLock, stream::StreamExt};
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
collections::BTreeSet,
|
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -28,6 +27,8 @@ use util::archive::extract_zip;
|
||||||
use util::merge_json_value_into;
|
use util::merge_json_value_into;
|
||||||
use util::{ResultExt, fs::remove_matching, maybe};
|
use util::{ResultExt, fs::remove_matching, maybe};
|
||||||
|
|
||||||
|
use crate::{PackageJson, PackageJsonData};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct TypeScriptContextProvider {
|
pub(crate) struct TypeScriptContextProvider {
|
||||||
last_package_json: PackageJsonContents,
|
last_package_json: PackageJsonContents,
|
||||||
|
@ -57,108 +58,7 @@ const TYPESCRIPT_JASMINE_PACKAGE_PATH_VARIABLE: VariableName =
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct PackageJsonContents(Arc<RwLock<HashMap<PathBuf, PackageJson>>>);
|
struct PackageJsonContents(Arc<RwLock<HashMap<PathBuf, PackageJson>>>);
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct PackageJson {
|
|
||||||
mtime: DateTime<Local>,
|
|
||||||
data: PackageJsonData,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
|
||||||
struct PackageJsonData {
|
|
||||||
jest_package_path: Option<Arc<Path>>,
|
|
||||||
mocha_package_path: Option<Arc<Path>>,
|
|
||||||
vitest_package_path: Option<Arc<Path>>,
|
|
||||||
jasmine_package_path: Option<Arc<Path>>,
|
|
||||||
scripts: BTreeSet<(Arc<Path>, String)>,
|
|
||||||
package_manager: Option<&'static str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PackageJsonData {
|
impl PackageJsonData {
|
||||||
fn new(path: Arc<Path>, package_json: HashMap<String, Value>) -> Self {
|
|
||||||
let mut scripts = BTreeSet::new();
|
|
||||||
if let Some(serde_json::Value::Object(package_json_scripts)) = package_json.get("scripts") {
|
|
||||||
scripts.extend(
|
|
||||||
package_json_scripts
|
|
||||||
.keys()
|
|
||||||
.cloned()
|
|
||||||
.map(|name| (path.clone(), name)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut jest_package_path = None;
|
|
||||||
let mut mocha_package_path = None;
|
|
||||||
let mut vitest_package_path = None;
|
|
||||||
let mut jasmine_package_path = None;
|
|
||||||
if let Some(serde_json::Value::Object(dependencies)) = package_json.get("devDependencies") {
|
|
||||||
if dependencies.contains_key("jest") {
|
|
||||||
jest_package_path.get_or_insert_with(|| path.clone());
|
|
||||||
}
|
|
||||||
if dependencies.contains_key("mocha") {
|
|
||||||
mocha_package_path.get_or_insert_with(|| path.clone());
|
|
||||||
}
|
|
||||||
if dependencies.contains_key("vitest") {
|
|
||||||
vitest_package_path.get_or_insert_with(|| path.clone());
|
|
||||||
}
|
|
||||||
if dependencies.contains_key("jasmine") {
|
|
||||||
jasmine_package_path.get_or_insert_with(|| path.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(serde_json::Value::Object(dev_dependencies)) = package_json.get("dependencies")
|
|
||||||
{
|
|
||||||
if dev_dependencies.contains_key("jest") {
|
|
||||||
jest_package_path.get_or_insert_with(|| path.clone());
|
|
||||||
}
|
|
||||||
if dev_dependencies.contains_key("mocha") {
|
|
||||||
mocha_package_path.get_or_insert_with(|| path.clone());
|
|
||||||
}
|
|
||||||
if dev_dependencies.contains_key("vitest") {
|
|
||||||
vitest_package_path.get_or_insert_with(|| path.clone());
|
|
||||||
}
|
|
||||||
if dev_dependencies.contains_key("jasmine") {
|
|
||||||
jasmine_package_path.get_or_insert_with(|| path.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let package_manager = package_json
|
|
||||||
.get("packageManager")
|
|
||||||
.and_then(|value| value.as_str())
|
|
||||||
.and_then(|value| {
|
|
||||||
if value.starts_with("pnpm") {
|
|
||||||
Some("pnpm")
|
|
||||||
} else if value.starts_with("yarn") {
|
|
||||||
Some("yarn")
|
|
||||||
} else if value.starts_with("npm") {
|
|
||||||
Some("npm")
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
jest_package_path,
|
|
||||||
mocha_package_path,
|
|
||||||
vitest_package_path,
|
|
||||||
jasmine_package_path,
|
|
||||||
scripts,
|
|
||||||
package_manager,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn merge(&mut self, other: Self) {
|
|
||||||
self.jest_package_path = self.jest_package_path.take().or(other.jest_package_path);
|
|
||||||
self.mocha_package_path = self.mocha_package_path.take().or(other.mocha_package_path);
|
|
||||||
self.vitest_package_path = self
|
|
||||||
.vitest_package_path
|
|
||||||
.take()
|
|
||||||
.or(other.vitest_package_path);
|
|
||||||
self.jasmine_package_path = self
|
|
||||||
.jasmine_package_path
|
|
||||||
.take()
|
|
||||||
.or(other.jasmine_package_path);
|
|
||||||
self.scripts.extend(other.scripts);
|
|
||||||
self.package_manager = self.package_manager.or(other.package_manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_task_templates(&self, task_templates: &mut TaskTemplates) {
|
fn fill_task_templates(&self, task_templates: &mut TaskTemplates) {
|
||||||
if self.jest_package_path.is_some() {
|
if self.jest_package_path.is_some() {
|
||||||
task_templates.0.push(TaskTemplate {
|
task_templates.0.push(TaskTemplate {
|
||||||
|
@ -400,8 +300,8 @@ impl TypeScriptContextProvider {
|
||||||
fs.load(&package_json_path).await.with_context(|| {
|
fs.load(&package_json_path).await.with_context(|| {
|
||||||
format!("loading package.json from {package_json_path:?}")
|
format!("loading package.json from {package_json_path:?}")
|
||||||
})?;
|
})?;
|
||||||
let package_json: HashMap<String, serde_json::Value> =
|
let package_json: HashMap<String, serde_json_lenient::Value> =
|
||||||
serde_json::from_str(&package_json_string).with_context(|| {
|
serde_json_lenient::from_str(&package_json_string).with_context(|| {
|
||||||
format!("parsing package.json from {package_json_path:?}")
|
format!("parsing package.json from {package_json_path:?}")
|
||||||
})?;
|
})?;
|
||||||
let new_data =
|
let new_data =
|
||||||
|
|
|
@ -29,8 +29,9 @@ impl DapLocator for NodeLocator {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if build_config.command != TYPESCRIPT_RUNNER_VARIABLE.template_value()
|
if build_config.command != TYPESCRIPT_RUNNER_VARIABLE.template_value()
|
||||||
&& build_config.command != "composer"
|
|
||||||
&& build_config.command != "npm"
|
&& build_config.command != "npm"
|
||||||
|
&& build_config.command != "pnpm"
|
||||||
|
&& build_config.command != "yarn"
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue