Skip to content

Commit 3aae4cd

Browse files
authored
Merge pull request #945 from charlespierce/scoped_link
[Medium] Add extra directory when linking scoped package
2 parents 01f6e04 + 9787385 commit 3aae4cd

File tree

4 files changed

+81
-17
lines changed

4 files changed

+81
-17
lines changed

crates/volta-core/src/run/executor.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,23 @@ impl PackageInstallCommand {
237237
})
238238
}
239239

240+
pub fn for_npm_link<A, S>(args: A, platform: Platform, name: String) -> Fallible<Self>
241+
where
242+
A: IntoIterator<Item = S>,
243+
S: AsRef<OsStr>,
244+
{
245+
let installer = DirectInstall::with_name(PackageManager::Npm, name)?;
246+
247+
let mut command = create_command("npm");
248+
command.args(args);
249+
250+
Ok(PackageInstallCommand {
251+
command,
252+
installer,
253+
platform,
254+
})
255+
}
256+
240257
/// Adds or updates environment variables that the command will use
241258
pub fn envs<E, K, V>(&mut self, envs: E)
242259
where

crates/volta-core/src/run/npm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub(super) fn command(args: &[OsString], session: &mut Session) -> Fallible<Exec
3535
CommandArg::Intercepted(InterceptedCommand::Link(link)) => {
3636
// For link commands, only intercept if a platform exists
3737
if let Some(platform) = Platform::current(session)? {
38-
return link.executor(platform);
38+
return link.executor(platform, current_project_name(session));
3939
}
4040
}
4141
CommandArg::Intercepted(InterceptedCommand::Unlink) => {

crates/volta-core/src/run/parser.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -345,12 +345,16 @@ pub struct LinkArgs<'a> {
345345
}
346346

347347
impl<'a> LinkArgs<'a> {
348-
pub fn executor(self, platform: Platform) -> Fallible<Executor> {
348+
pub fn executor(self, platform: Platform, project_name: Option<String>) -> Fallible<Executor> {
349349
if self.tools.is_empty() {
350350
// If no tools are specified, then this is a bare link command, linking the current
351-
// project as a global package. We treat this exactly like a global install
352-
PackageInstallCommand::new(self.common_args, platform, PackageManager::Npm)
353-
.map(Into::into)
351+
// project as a global package. We treat this like a global install except we look up
352+
// the name from the current directory first.
353+
match project_name {
354+
Some(name) => PackageInstallCommand::for_npm_link(self.common_args, platform, name),
355+
None => PackageInstallCommand::new(self.common_args, platform, PackageManager::Npm),
356+
}
357+
.map(Into::into)
354358
} else {
355359
// If there are tools specified, then this represents a command to link a global
356360
// package into the current project. We handle each tool separately to support Volta's

crates/volta-core/src/tool/package/mod.rs

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub struct Package {
3535

3636
impl Package {
3737
pub fn new(name: String, version: VersionSpec) -> Fallible<Self> {
38-
let staging = setup_staging_directory(PackageManager::Npm)?;
38+
let staging = setup_staging_directory(PackageManager::Npm, NeedsScope::No)?;
3939

4040
Ok(Package {
4141
name,
@@ -123,16 +123,34 @@ impl Display for Package {
123123
///
124124
/// Provides methods to simplify installing into a staging directory and then moving that install
125125
/// into the proper location after it is complete.
126+
///
127+
/// Note: We don't always know the name of the package up-front, as the install could be from a
128+
/// tarball or a git coordinate. If we do know ahead of time, then we can skip looking it up
126129
pub struct DirectInstall {
127130
staging: TempDir,
128131
manager: PackageManager,
132+
name: Option<String>,
129133
}
130134

131135
impl DirectInstall {
132136
pub fn new(manager: PackageManager) -> Fallible<Self> {
133-
let staging = setup_staging_directory(manager)?;
137+
let staging = setup_staging_directory(manager, NeedsScope::No)?;
138+
139+
Ok(DirectInstall {
140+
staging,
141+
manager,
142+
name: None,
143+
})
144+
}
134145

135-
Ok(DirectInstall { staging, manager })
146+
pub fn with_name(manager: PackageManager, name: String) -> Fallible<Self> {
147+
let staging = setup_staging_directory(manager, name.contains('/').into())?;
148+
149+
Ok(DirectInstall {
150+
staging,
151+
manager,
152+
name: Some(name),
153+
})
136154
}
137155

138156
pub fn setup_command(&self, command: &mut Command) {
@@ -141,16 +159,20 @@ impl DirectInstall {
141159
}
142160

143161
pub fn complete_install(self, image: &Image) -> Fallible<()> {
144-
let name = self
145-
.manager
146-
.get_installed_package(self.staging.path().to_owned())
162+
let DirectInstall {
163+
staging,
164+
name,
165+
manager,
166+
} = self;
167+
168+
let name = name
169+
.or_else(|| manager.get_installed_package(staging.path().to_owned()))
147170
.ok_or(ErrorKind::InstalledPackageNameError)?;
148-
let manifest =
149-
configure::parse_manifest(&name, self.staging.path().to_owned(), self.manager)?;
171+
let manifest = configure::parse_manifest(&name, staging.path().to_owned(), manager)?;
150172

151-
persist_install(&name, &manifest.version, self.staging.path())?;
152-
link_package_to_shared_dir(&name, self.manager)?;
153-
configure::write_config_and_shims(&name, &manifest, image, self.manager)
173+
persist_install(&name, &manifest.version, staging.path())?;
174+
link_package_to_shared_dir(&name, manager)?;
175+
configure::write_config_and_shims(&name, &manifest, image, manager)
154176
}
155177
}
156178

@@ -209,19 +231,40 @@ impl InPlaceUpgrade {
209231
}
210232
}
211233

234+
#[derive(Clone, Copy, PartialEq)]
235+
enum NeedsScope {
236+
Yes,
237+
No,
238+
}
239+
240+
impl From<bool> for NeedsScope {
241+
fn from(value: bool) -> Self {
242+
if value {
243+
NeedsScope::Yes
244+
} else {
245+
NeedsScope::No
246+
}
247+
}
248+
}
249+
212250
/// Create the temporary staging directory we will use to install and ensure expected
213251
/// subdirectories exist within it
214-
fn setup_staging_directory(manager: PackageManager) -> Fallible<TempDir> {
252+
fn setup_staging_directory(manager: PackageManager, needs_scope: NeedsScope) -> Fallible<TempDir> {
215253
// Workaround to ensure relative symlinks continue to work.
216254
// The final installed location of packages is:
217255
// $VOLTA_HOME/tools/image/packages/{name}/
218256
// To ensure that the temp directory has the same amount of nesting, we use:
219257
// $VOLTA_HOME/tmp/image/packages/{tempdir}/
220258
// This way any relative symlinks will have the same amount of nesting and will remain valid
221259
// even when the directory is persisted.
260+
// We also need to handle the case when the linked package has a scope, which requires another
261+
// level of nesting
222262
let mut staging_root = volta_home()?.tmp_dir().to_owned();
223263
staging_root.push("image");
224264
staging_root.push("packages");
265+
if needs_scope == NeedsScope::Yes {
266+
staging_root.push("scope");
267+
}
225268
create_dir_all(&staging_root).with_context(|| ErrorKind::ContainingDirError {
226269
path: staging_root.clone(),
227270
})?;

0 commit comments

Comments
 (0)