mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-14 09:42:37 -04:00
feat(pacquet): expose linkWorkspacePackages in the napi binding (#12844)
Add `linkWorkspacePackages` (`true` / `false` / `"deep"`) to the `@pnpm/napi` install options, mapping it onto `Config::link_workspace_packages` (reusing the config crate's `Deserialize`). Programmatic consumers need this to opt bare-semver dependencies — including an auto-installed peer that names a sibling workspace package — into workspace-package resolution, matching pnpm's `link-workspace-packages` setting. The default stays `false`, so existing behavior is unchanged.
This commit is contained in:
@@ -34,7 +34,8 @@ use std::{
|
||||
use dashmap::DashMap;
|
||||
use indexmap::IndexMap;
|
||||
use pacquet_config::{
|
||||
Config, GetHomeDir, Host, LoadWorkspaceYamlError, NodeLinker, PackageImportMethod,
|
||||
Config, GetHomeDir, Host, LinkWorkspacePackages, LoadWorkspaceYamlError, NodeLinker,
|
||||
PackageImportMethod,
|
||||
};
|
||||
use pacquet_network::{AuthHeaders, ProxyConfig, TlsConfig};
|
||||
use pacquet_store_dir::StoreDir;
|
||||
@@ -52,6 +53,10 @@ pub struct ConfigOverlay {
|
||||
pub proxy: Option<ProxyConfig>,
|
||||
pub tls: Option<TlsConfig>,
|
||||
pub node_linker: Option<NodeLinker>,
|
||||
/// `linkWorkspacePackages` — whether a bare-semver dependency may resolve
|
||||
/// to a workspace package by name. `Off` (the default) matches only
|
||||
/// `workspace:`-prefixed ranges.
|
||||
pub link_workspace_packages: Option<LinkWorkspacePackages>,
|
||||
pub package_import_method: Option<PackageImportMethod>,
|
||||
pub virtual_store_dir_max_length: Option<u64>,
|
||||
pub hoist_pattern: Option<Vec<String>>,
|
||||
@@ -262,6 +267,9 @@ fn build_config(dir: &Path, overlay: &ConfigOverlay) -> Result<Config, LoadWorks
|
||||
if let Some(node_linker) = overlay.node_linker {
|
||||
config.node_linker = node_linker;
|
||||
}
|
||||
if let Some(link_workspace_packages) = overlay.link_workspace_packages {
|
||||
config.link_workspace_packages = link_workspace_packages;
|
||||
}
|
||||
if let Some(method) = overlay.package_import_method {
|
||||
config.package_import_method = method;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@ pub struct InstallOptions {
|
||||
pub proxy_config: Option<ProxyConfigInput>,
|
||||
pub network_config: Option<NetworkConfigInput>,
|
||||
pub node_linker: Option<String>,
|
||||
/// `linkWorkspacePackages` — `true` / `false` / `"deep"`. When enabled, a
|
||||
/// bare-semver dependency may resolve to a workspace package by name (not
|
||||
/// only `workspace:`-prefixed ranges).
|
||||
pub link_workspace_packages: Option<serde_json::Value>,
|
||||
pub hoist_pattern: Option<Vec<String>>,
|
||||
pub public_hoist_pattern: Option<Vec<String>>,
|
||||
pub external_dependencies: Option<Vec<String>>,
|
||||
@@ -416,6 +420,9 @@ fn build_overlay(options: &InstallOptions) -> napi::Result<ConfigOverlay> {
|
||||
proxy: options.proxy_config.as_ref().map(build_proxy_config).transpose()?,
|
||||
tls: network_config.map(build_tls_config).transpose()?,
|
||||
node_linker: options.node_linker.as_deref().and_then(parse_node_linker),
|
||||
link_workspace_packages: parse_link_workspace_packages(
|
||||
options.link_workspace_packages.as_ref(),
|
||||
)?,
|
||||
package_import_method: options
|
||||
.package_import_method
|
||||
.as_deref()
|
||||
@@ -600,6 +607,25 @@ fn parse_node_linker(value: &str) -> Option<pacquet_config::NodeLinker> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the JS `linkWorkspacePackages` value (`true` / `false` / `"deep"`)
|
||||
/// into a [`pacquet_config::LinkWorkspacePackages`], reusing the config
|
||||
/// crate's `Deserialize`. Rejects any other value.
|
||||
fn parse_link_workspace_packages(
|
||||
value: Option<&serde_json::Value>,
|
||||
) -> napi::Result<Option<pacquet_config::LinkWorkspacePackages>> {
|
||||
value
|
||||
.map(|value| {
|
||||
serde_json::from_value::<pacquet_config::LinkWorkspacePackages>(value.clone()).map_err(
|
||||
|error| {
|
||||
napi::Error::from_reason(format!(
|
||||
r#"invalid linkWorkspacePackages (expected true, false, or "deep"): {error}"#,
|
||||
))
|
||||
},
|
||||
)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
fn parse_import_method(value: &str) -> Option<pacquet_config::PackageImportMethod> {
|
||||
match value {
|
||||
"auto" => Some(pacquet_config::PackageImportMethod::Auto),
|
||||
|
||||
@@ -86,6 +86,34 @@ fn build_overlay_maps_supported_install_options() {
|
||||
assert_eq!(tls.local_address.map(|ip| ip.to_string()), Some("127.0.0.1".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_overlay_parses_link_workspace_packages() {
|
||||
use pacquet_config::LinkWorkspacePackages;
|
||||
|
||||
let mut options = install_options();
|
||||
options.link_workspace_packages = Some(serde_json::json!("deep"));
|
||||
assert_eq!(
|
||||
build_overlay(&options).expect("overlay").link_workspace_packages,
|
||||
Some(LinkWorkspacePackages::Deep),
|
||||
);
|
||||
|
||||
options.link_workspace_packages = Some(serde_json::json!(true));
|
||||
assert_eq!(
|
||||
build_overlay(&options).expect("overlay").link_workspace_packages,
|
||||
Some(LinkWorkspacePackages::DirectOnly),
|
||||
);
|
||||
|
||||
options.link_workspace_packages = Some(serde_json::json!(false));
|
||||
assert_eq!(
|
||||
build_overlay(&options).expect("overlay").link_workspace_packages,
|
||||
Some(LinkWorkspacePackages::Off),
|
||||
);
|
||||
|
||||
// Anything other than a boolean or "deep" is rejected.
|
||||
options.link_workspace_packages = Some(serde_json::json!("shallow"));
|
||||
assert!(build_overlay(&options).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_install_options_fail_closed() {
|
||||
let mut options = install_options();
|
||||
@@ -143,6 +171,7 @@ fn install_options() -> InstallOptions {
|
||||
proxy_config: None,
|
||||
network_config: None,
|
||||
node_linker: None,
|
||||
link_workspace_packages: None,
|
||||
hoist_pattern: None,
|
||||
public_hoist_pattern: None,
|
||||
external_dependencies: None,
|
||||
|
||||
6
pacquet/npm/napi/index.d.ts
vendored
6
pacquet/npm/napi/index.d.ts
vendored
@@ -90,6 +90,12 @@ export interface InstallOptions extends SharedEngineOptions {
|
||||
projects: NodeApiProject[]
|
||||
storeDir?: string
|
||||
nodeLinker?: 'hoisted' | 'isolated'
|
||||
/**
|
||||
* pnpm's `linkWorkspacePackages`. When `true`/`'deep'`, a bare-semver
|
||||
* dependency (including an auto-installed peer) may resolve to a workspace
|
||||
* package by name; `false` (the default) matches only `workspace:` ranges.
|
||||
*/
|
||||
linkWorkspacePackages?: boolean | 'deep'
|
||||
hoistPattern?: string[]
|
||||
publicHoistPattern?: string[]
|
||||
/** Packages linked from outside the workspace; excluded from hoisting/pruning. */
|
||||
|
||||
Reference in New Issue
Block a user