refactor(package-manager): break the module cycle between install and the repeat-install fast path (#17686)
This commit is contained in:
1 parent
81bdfdfcd0
commit
37ade57d2d
78 files changed
+1050
-961
No files matched your search
+1
-1
@@ -193,7 +193,7 @@ pnpm-git-fetcher = 5
|
||||
pnpm-http = 2
|
||||
pnpm-lockfile-verification = 2
|
||||
pnpm-network-web-auth = 2
|
||||
pnpm-package-manager = 12
|
||||
pnpm-package-manager = 6
|
||||
pnpm-platform-variants = 2
|
||||
pnpm-pnpr-client = 2
|
||||
pnpm-process = 2
|
||||
|
||||
@@ -40,7 +40,7 @@ pub(crate) fn resolution_settings_from_config(config: &Config) -> ResolutionSett
|
||||
dedupe_injected_deps: Some(config.dedupe_injected_deps),
|
||||
dedupe_peer_dependents: Some(config.dedupe_peer_dependents),
|
||||
link_workspace_packages: Some(
|
||||
crate::optimistic_repeat_install::settings::link_workspace_packages_to_json(
|
||||
crate::install_state::settings::link_workspace_packages_to_json(
|
||||
config.link_workspace_packages,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
pub(crate) mod state_options;
|
||||
|
||||
pub use entry_points::apply_deploy_manifest_hook;
|
||||
pub(crate) use entry_points::apply_deploy_manifest_hook_to_arc;
|
||||
pub use deploy_manifest::apply_deploy_manifest_hook;
|
||||
pub(crate) use deploy_manifest::apply_deploy_manifest_hook_to_arc;
|
||||
pub use errors::{InstallError, defer_ignored_builds};
|
||||
pub(crate) use lifecycle::{
|
||||
checked_projects_opts, load_checked_workspace_projects, load_workspace_projects,
|
||||
};
|
||||
pub(crate) use lockfile_freshness::{
|
||||
CheckLockfileSettingsDriftOptions, DirectoryManifests, FreshnessCheckError, FreshnessScope,
|
||||
ImporterSatisfactionCheck, OptionalDependencyExclusions, ProjectManifestsByDir,
|
||||
@@ -15,25 +18,26 @@ pub use lockfile_freshness::{
|
||||
};
|
||||
pub(crate) use modules_state::{
|
||||
frozen_tree_intact, hoisted_linker_workspace_links_intact, hoisted_workspace_packages_present,
|
||||
modules_layout_consistent_with, moved_tree_is_reusable, tree_may_move,
|
||||
modules_layout_consistent_with, moved_tree_is_reusable, project_requires_lifecycle_scripts,
|
||||
tree_may_move,
|
||||
};
|
||||
pub(crate) use run::hook_one_manifest;
|
||||
pub use run::{InstallExecution, InstallLockfilePolicy, ResolutionInputs};
|
||||
pub(crate) use workspace_state::{
|
||||
CarriedSettings, StateProjects, StateWrite, configured_or_discovered_workspace_dir,
|
||||
lockfile_root_dir, recorded_workspace_state, registry_keys_current, replace_project_manifest,
|
||||
update_workspace_state_or_warn, workspace_packages_for_freshness,
|
||||
pub(crate) use run::{
|
||||
check_strict_dep_builds_for_fast_path, gvs_build_markers_complete, hook_one_manifest,
|
||||
register_workspace_in_store,
|
||||
};
|
||||
pub use workspace_state::{
|
||||
GateWorkspace, UpToDateFastPathCheck, UpToDateWorkspace, build_workspace_packages_map,
|
||||
check_deps_status_before_run_at, deps_install_root, install_already_up_to_date,
|
||||
install_already_up_to_date_from_records, install_already_up_to_date_per_project,
|
||||
pub use workspace_state::build_workspace_packages_map;
|
||||
pub(crate) use workspace_state::{
|
||||
CarriedSettings, StateProjects, StateWrite, build_project_manifests_list,
|
||||
configured_or_discovered_workspace_dir, lockfile_root_dir, lockfile_root_for,
|
||||
recorded_workspace_state, registry_keys_current, replace_project_manifest,
|
||||
update_workspace_state_or_warn, workspace_packages_for_freshness,
|
||||
};
|
||||
|
||||
pub(crate) use verification_gate::IsReplaced;
|
||||
pub use verification_gate::LockfileVerificationGate;
|
||||
|
||||
mod entry_points;
|
||||
mod deploy_manifest;
|
||||
|
||||
mod errors;
|
||||
|
||||
@@ -45,9 +49,9 @@ use crate::{
|
||||
HoistedDependencies, InstallFrozenLockfile, InstallWithFreshLockfile,
|
||||
InstallWithFreshLockfileError, LockfileVerificationOverride, OptimisticRepeatInstallCheck,
|
||||
RebuildOptions, ResolvedPackages, UpdateSeedPolicy, build_resolution_verifiers,
|
||||
emit_initial_package_manifest, link_project_bins,
|
||||
optimistic_repeat_install::Decision as OptimisticRepeatInstallDecision,
|
||||
prune_merged_branch_lockfile::prune_merged_branch_lockfile, report_merged_lockfile_conflicts,
|
||||
emit_initial_package_manifest, install_state::Decision as OptimisticRepeatInstallDecision,
|
||||
link_project_bins, prune_merged_branch_lockfile::prune_merged_branch_lockfile,
|
||||
report_merged_lockfile_conflicts,
|
||||
};
|
||||
use pnpm_catalogs_config::get_catalogs_from_workspace_manifest;
|
||||
use pnpm_catalogs_types::Catalogs;
|
||||
@@ -58,8 +62,8 @@ use pnpm_executor::{
|
||||
RunPostinstallHooks, run_project_lifecycle_stages,
|
||||
};
|
||||
use pnpm_lockfile::{
|
||||
LazyLockfile, Lockfile, LockfileEntries, MaybeLazyLockfile, PnpmfileChecksumCheck,
|
||||
StalenessReason, VersionPart, satisfies_package_manifest,
|
||||
Lockfile, LockfileEntries, MaybeLazyLockfile, PnpmfileChecksumCheck, StalenessReason,
|
||||
VersionPart, satisfies_package_manifest,
|
||||
};
|
||||
use pnpm_lockfile_verification::{
|
||||
VerifyLockfileResolutionsOptions, record_lockfile_verified, verify_lockfile_resolutions,
|
||||
@@ -95,7 +99,7 @@ mod prepare_modules_state;
|
||||
mod time_machine;
|
||||
/// The dependency groups the install includes, as `.modules.yaml` records
|
||||
/// them and the dependency-graph walker observes them.
|
||||
pub(super) fn included_dependencies(dependency_groups: &[DependencyGroup]) -> IncludedDependencies {
|
||||
pub(crate) fn included_dependencies(dependency_groups: &[DependencyGroup]) -> IncludedDependencies {
|
||||
IncludedDependencies {
|
||||
dependencies: dependency_groups.contains(&DependencyGroup::Prod),
|
||||
dev_dependencies: dependency_groups.contains(&DependencyGroup::Dev),
|
||||
@@ -120,9 +124,9 @@ mod workspace_state;
|
||||
|
||||
use apply_materialization::{ApplyMaterializationInputs, apply_materialization_result};
|
||||
use lifecycle::{
|
||||
dev_preinstall_already_ran, load_workspace_projects, project_lifecycle_graph,
|
||||
project_script_stages, root_preinstall_already_ran, run_pre_uninstall_scripts,
|
||||
run_projects_lifecycle_scripts, run_root_hook,
|
||||
dev_preinstall_already_ran, project_lifecycle_graph, project_script_stages,
|
||||
root_preinstall_already_ran, run_pre_uninstall_scripts, run_projects_lifecycle_scripts,
|
||||
run_root_hook,
|
||||
};
|
||||
use lockfile_freshness::{
|
||||
FastUpdateLockfileOptions, LockfileFreshnessInputs, check_lockfile_freshness,
|
||||
@@ -134,8 +138,8 @@ use modules_state::{
|
||||
gvs_build_marker_present, gvs_build_markers_may_require_recovery,
|
||||
has_newly_allowed_ignored_builds, keep_recorded_layout, manifest_string_field,
|
||||
merge_filtered_modules_metadata, merge_pending_builds, modules_consistent_with,
|
||||
project_requires_implicit_gyp_build, project_requires_lifecycle_scripts,
|
||||
recorded_allow_builds_differ, refuse_drifted_modules_dir, unapproved_recorded_ignored_builds,
|
||||
project_requires_implicit_gyp_build, recorded_allow_builds_differ, refuse_drifted_modules_dir,
|
||||
unapproved_recorded_ignored_builds,
|
||||
};
|
||||
use prepare_modules_state::{
|
||||
PrepareModulesStateInputs, PreparedModulesState, prepare_modules_state,
|
||||
@@ -143,9 +147,9 @@ use prepare_modules_state::{
|
||||
};
|
||||
use time_machine::TimeMachineExclusions;
|
||||
use workspace_state::{
|
||||
ProjectScriptsInputs, build_project_manifests_list, build_root_importer_project_manifests_list,
|
||||
build_selected_project_manifests_list, lockfile_root_for, mutated_project_dirs,
|
||||
projects_running_own_scripts, selected_manifest_freshness_inputs,
|
||||
ProjectScriptsInputs, build_root_importer_project_manifests_list,
|
||||
build_selected_project_manifests_list, mutated_project_dirs, projects_running_own_scripts,
|
||||
selected_manifest_freshness_inputs,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -211,7 +215,10 @@ fn pnpmfile_checksum_check<'a>(
|
||||
/// manifest. The early host detection and the install paths must
|
||||
/// derive it identically or the pre-spawned host would disagree with
|
||||
/// the one the install would have detected.
|
||||
fn effective_node_version(config: &Config, manifest: &PackageManifest) -> Option<String> {
|
||||
pub(crate) fn effective_node_version(
|
||||
config: &Config,
|
||||
manifest: &PackageManifest,
|
||||
) -> Option<String> {
|
||||
config.node_version.clone().or_else(|| node_version_from_engines_runtime(manifest.value()))
|
||||
}
|
||||
|
||||
@@ -515,81 +522,74 @@ pub struct InstallProjects<DependencyGroupList> {
|
||||
pub discovered_workspace: Option<DiscoveredWorkspace>,
|
||||
}
|
||||
|
||||
struct InstallRunOptions<'install, 'selection> {
|
||||
lockfile_verification_override: Option<LockfileVerificationOverride<'install>>,
|
||||
rebuild: Option<RebuildOptions>,
|
||||
selection: Option<WorkspaceInstallSelection<'selection>>,
|
||||
root_manifest_as_workspace_root: bool,
|
||||
save: InstallSaveOptions,
|
||||
/// pnpm's `lockfileCheck`: the run hands the wanted lockfile to the sink
|
||||
/// instead of writing it, and must leave nothing else on disk changed
|
||||
/// either. Only `pacquet dedupe --check` sets it.
|
||||
lockfile_check: Option<LockfileCheckSink>,
|
||||
pub(crate) struct InstallRunOptions<'install, 'selection> {
|
||||
/// The repeat-install fast path the run consults before it sets up
|
||||
/// anything.
|
||||
pub(crate) repeat_install: crate::install_state::RepeatInstallCheck,
|
||||
pub(crate) lockfile_verification_override: Option<LockfileVerificationOverride<'install>>,
|
||||
pub(crate) rebuild: Option<RebuildOptions>,
|
||||
pub(crate) selection: Option<WorkspaceInstallSelection<'selection>>,
|
||||
pub(crate) root_manifest_as_workspace_root: bool,
|
||||
pub(crate) save: InstallSaveOptions,
|
||||
/// Forces the interactive-prompt eligibility that is otherwise derived
|
||||
/// from the process environment, so tests can exercise both branches.
|
||||
prompt_eligibility_override: Option<bool>,
|
||||
manifests: InstallManifestOptions<'install>,
|
||||
pub(crate) prompt_eligibility_override: Option<bool>,
|
||||
pub(crate) manifests: InstallManifestOptions<'install>,
|
||||
}
|
||||
|
||||
/// The workspace files the run may write besides the installed modules.
|
||||
#[derive(Clone, Copy)]
|
||||
struct InstallSaveOptions {
|
||||
pub(crate) struct InstallSaveOptions {
|
||||
/// pnpm's `saveLockfile`: whether the resolved graph may be written
|
||||
/// to `<workspace_root>/pnpm-lock.yaml`. `false` for an install
|
||||
/// whose resolution belongs to a project other than the one that
|
||||
/// owns that lockfile, so the run must leave it untouched.
|
||||
lockfile: bool,
|
||||
pub(crate) lockfile: bool,
|
||||
/// Whether the run may record itself in
|
||||
/// `node_modules/.pnpm-workspace-state-v1.json`. `false` for an install
|
||||
/// whose importers are not the workspace's projects, so that state
|
||||
/// keeps describing the workspace's last install.
|
||||
workspace_state: bool,
|
||||
pub(crate) workspace_state: bool,
|
||||
/// pnpm's `lockfileCheck`: the run hands the wanted lockfile to the sink
|
||||
/// instead of writing it, and must leave nothing else on disk changed
|
||||
/// either. Only `pacquet dedupe --check` sets it.
|
||||
pub(crate) lockfile_check: Option<LockfileCheckSink>,
|
||||
}
|
||||
|
||||
impl Default for InstallSaveOptions {
|
||||
fn default() -> Self {
|
||||
InstallSaveOptions { lockfile: true, workspace_state: true, lockfile_check: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl InstallSaveOptions {
|
||||
/// Whether the run writes the wanted lockfile to disk.
|
||||
fn writes_lockfile(&self) -> bool {
|
||||
self.lockfile && self.lockfile_check.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct InstallManifestOptions<'install> {
|
||||
deploy_hook: bool,
|
||||
pub(crate) struct InstallManifestOptions<'install> {
|
||||
pub(crate) deploy_hook: bool,
|
||||
/// pnpm's `pruneDirectDependencies`: scan each importer's `node_modules`
|
||||
/// and remove extraneous direct entries not recorded in the wanted
|
||||
/// lockfile or public hoist set. Only `pacquet prune` sets it.
|
||||
prune_direct_dependencies: bool,
|
||||
pub(crate) prune_direct_dependencies: bool,
|
||||
/// Project manifests used only as the source for lockfile importer
|
||||
/// specifiers. `pacquet update --no-save` resolves against an in-memory
|
||||
/// manifest rewrite but must serialize importer specifiers from the
|
||||
/// manifest the user kept on disk. Supplied already
|
||||
/// `readPackage`-transformed.
|
||||
specifier_manifests: Option<Vec<(PathBuf, PackageManifest)>>,
|
||||
pub(crate) specifier_manifests: Option<Vec<(PathBuf, PackageManifest)>>,
|
||||
/// Manifest paths `pacquet update --no-save` already ran `readPackage`
|
||||
/// over before preparing its in-memory resolution rewrite. The hook must
|
||||
/// observe each project manifest exactly once, so the install layer skips
|
||||
/// these and still hooks every project manifest outside the set — the
|
||||
/// workspace projects the non-selected update path never loads. Dependency
|
||||
/// manifests always flow through the resolver's hook path.
|
||||
hooked_paths: HashSet<PathBuf>,
|
||||
pub(crate) hooked_paths: HashSet<PathBuf>,
|
||||
/// See [`crate::ManifestSpecBumps`]. Only `pacquet update` sets it.
|
||||
spec_bumps: Option<&'install crate::ManifestSpecBumps>,
|
||||
pub(crate) spec_bumps: Option<&'install crate::ManifestSpecBumps>,
|
||||
/// Pre-seeded `requiresBuild` verdicts from a `pnpr` resolve stream.
|
||||
known_requires_build: Option<pnpm_deps_restorer::RequiresBuildBySnapshot>,
|
||||
}
|
||||
|
||||
impl Default for InstallRunOptions<'_, '_> {
|
||||
fn default() -> Self {
|
||||
InstallRunOptions {
|
||||
lockfile_verification_override: None,
|
||||
rebuild: None,
|
||||
selection: None,
|
||||
root_manifest_as_workspace_root: false,
|
||||
save: InstallSaveOptions { lockfile: true, workspace_state: true },
|
||||
lockfile_check: None,
|
||||
prompt_eligibility_override: None,
|
||||
manifests: crate::install::InstallManifestOptions {
|
||||
deploy_hook: false,
|
||||
prune_direct_dependencies: false,
|
||||
specifier_manifests: None,
|
||||
hooked_paths: HashSet::new(),
|
||||
spec_bumps: None,
|
||||
known_requires_build: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
pub(crate) known_requires_build: Option<pnpm_deps_restorer::RequiresBuildBySnapshot>,
|
||||
}
|
||||
@@ -18,9 +18,7 @@ use super::{
|
||||
BTreeMap, CarriedSettings, HoistedDependencies, InstallError, Lockfile, Materialized, Reporter,
|
||||
StateWrite, recorded_workspace_state, update_workspace_state_or_warn,
|
||||
};
|
||||
use crate::optimistic_repeat_install::{
|
||||
ResolvedImporters, filesystem_now_ms_in_modules, validation::Vouched,
|
||||
};
|
||||
use crate::install_state::{ResolvedImporters, filesystem_now_ms_in_modules, validation::Vouched};
|
||||
use std::{collections::HashSet, path::PathBuf};
|
||||
|
||||
pub(super) struct ApplyMaterializationInputs<'a, 'selection> {
|
||||
@@ -350,14 +348,12 @@ fn applied_carried_settings<'a>(
|
||||
CarriedSettings {
|
||||
established_auto_dedupe: (config.auto_dedupe && resolved.is_some()).then_some(true),
|
||||
registry_keys_verified: crate::install::registry_keys_current(config, lockfile),
|
||||
registry_routes: Some(
|
||||
crate::optimistic_repeat_install::recorded_registry_routes_after_install(
|
||||
config,
|
||||
inputs.resolution.locked_registry_routes.as_ref(),
|
||||
lockfile,
|
||||
resolution,
|
||||
),
|
||||
),
|
||||
registry_routes: Some(crate::install_state::recorded_registry_routes_after_install(
|
||||
config,
|
||||
inputs.resolution.locked_registry_routes.as_ref(),
|
||||
lockfile,
|
||||
resolution,
|
||||
)),
|
||||
kept_settings: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn apply_deploy_manifest_hook(manifest: &mut serde_json::Value) {
|
||||
let names =
|
||||
deploy_dependency_names(manifest, "workspace:").map(str::to_owned).collect::<Vec<_>>();
|
||||
inject_deploy_dependencies_meta(manifest, names);
|
||||
copy_deploy_linked_dependencies(manifest);
|
||||
}
|
||||
pub(crate) fn apply_deploy_manifest_hook_to_arc(
|
||||
mut manifest: Arc<serde_json::Value>,
|
||||
) -> Arc<serde_json::Value> {
|
||||
if deploy_dependency_names(&manifest, "workspace:").next().is_none()
|
||||
&& deploy_dependency_names(&manifest, "link:").next().is_none()
|
||||
{
|
||||
return manifest;
|
||||
}
|
||||
apply_deploy_manifest_hook(Arc::make_mut(&mut manifest));
|
||||
manifest
|
||||
}
|
||||
fn deploy_dependency_names<'a>(
|
||||
manifest: &'a serde_json::Value,
|
||||
protocol: &'static str,
|
||||
) -> impl Iterator<Item = &'a str> {
|
||||
["optionalDependencies", "dependencies", "devDependencies"]
|
||||
.into_iter()
|
||||
.filter_map(move |field| manifest.get(field)?.as_object())
|
||||
.flat_map(|dependencies| dependencies.iter())
|
||||
.filter_map(move |(name, specifier)| {
|
||||
specifier
|
||||
.as_str()
|
||||
.is_some_and(|specifier| specifier.starts_with(protocol))
|
||||
.then_some(name.as_str())
|
||||
})
|
||||
}
|
||||
|
||||
fn copy_deploy_linked_dependencies(manifest: &mut serde_json::Value) {
|
||||
for field in ["optionalDependencies", "dependencies", "devDependencies"] {
|
||||
let Some(dependencies) = manifest.get_mut(field).and_then(serde_json::Value::as_object_mut)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
for specifier in dependencies.values_mut() {
|
||||
let Some(directory) = specifier
|
||||
.as_str()
|
||||
.and_then(|value| value.strip_prefix("link:"))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
*specifier = serde_json::Value::String(format!("file:{directory}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn inject_deploy_dependencies_meta(
|
||||
manifest: &mut serde_json::Value,
|
||||
names: Vec<String>,
|
||||
) {
|
||||
if names.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Some(object) = manifest.as_object_mut() else { return };
|
||||
let dependencies_meta = object
|
||||
.entry("dependenciesMeta")
|
||||
.or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
|
||||
let Some(meta_object) = dependencies_meta.as_object_mut() else { return };
|
||||
for name in names {
|
||||
let dependency_meta = meta_object.entry(name).or_insert(serde_json::Value::Null);
|
||||
match dependency_meta {
|
||||
serde_json::Value::Object(object) => {
|
||||
object.insert("injected".to_owned(), serde_json::Value::Bool(true));
|
||||
}
|
||||
value => *value = serde_json::json!({ "injected": true }),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ use std::sync::Mutex;
|
||||
/// — the install isn't a workspace install, so the caller should use
|
||||
/// the top-level `Install.manifest` as its only importer and pass
|
||||
/// `None` for the `workspace:`-spec lookup.
|
||||
pub(super) fn load_workspace_projects(
|
||||
pub(crate) fn load_workspace_projects(
|
||||
workspace_root: &std::path::Path,
|
||||
workspace_manifest: Option<&pnpm_workspace::WorkspaceManifest>,
|
||||
ignored_directories: &[std::path::PathBuf],
|
||||
@@ -50,7 +50,7 @@ pub(super) fn workspace_projects_opts(
|
||||
/// [`load_workspace_projects`] for the repeat-install checks, which read the
|
||||
/// projects the install itself would read: the `--workspace-packages`
|
||||
/// patterns when given, else the ones `workspace_manifest` declares.
|
||||
pub(super) fn load_checked_workspace_projects(
|
||||
pub(crate) fn load_checked_workspace_projects(
|
||||
config: &Config,
|
||||
workspace_root: &std::path::Path,
|
||||
workspace_manifest: Option<&pnpm_workspace::WorkspaceManifest>,
|
||||
@@ -64,7 +64,7 @@ pub(super) fn load_checked_workspace_projects(
|
||||
}
|
||||
|
||||
/// The discovery options of [`load_checked_workspace_projects`].
|
||||
pub(super) fn checked_projects_opts(
|
||||
pub(crate) fn checked_projects_opts(
|
||||
config: &Config,
|
||||
workspace_manifest: &pnpm_workspace::WorkspaceManifest,
|
||||
) -> pnpm_workspace::FindWorkspaceProjectsOpts {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub(crate) use crate::install_state::ProjectManifestsByDir;
|
||||
pub(crate) use importer_deps::check_directory_dependencies_freshness;
|
||||
pub use injected_manifest::ProjectManifestsByDir;
|
||||
pub(super) use injected_manifest::{InjectedDirectory, is_deploy_file_url_key, unhooked_manifest};
|
||||
pub(crate) use injected_manifest::{dependency_manifests_by_dir, project_manifests_by_dir};
|
||||
pub(crate) use nested::{
|
||||
|
||||
+1
-4
@@ -1,13 +1,10 @@
|
||||
use crate::install_state::ProjectManifestsByDir;
|
||||
use pnpm_package_manifest::PackageManifest;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// Project manifests keyed by their lexically normalized project directory.
|
||||
pub type ProjectManifestsByDir<'a> = HashMap<PathBuf, &'a PackageManifest>;
|
||||
|
||||
/// Each project's manifest as another importer's injected dependency reads
|
||||
/// it: the project's entry in `dependency_manifests` when it has one, else
|
||||
/// its importer manifest.
|
||||
|
||||
@@ -494,7 +494,7 @@ where
|
||||
/// Whether the project defines any of `stages`, counting the executor's
|
||||
/// implicit `node-gyp rebuild` fallback for `install` (which is skipped
|
||||
/// when the project opts out with `gypfile: false`).
|
||||
pub(super) fn project_requires_lifecycle_scripts(
|
||||
pub(crate) fn project_requires_lifecycle_scripts(
|
||||
project_dir: &Path,
|
||||
manifest: &PackageManifest,
|
||||
stages: &[&str],
|
||||
|
||||
@@ -14,9 +14,7 @@ use super::{
|
||||
Config, Host, InstallError, Lockfile, Modules, NodeLinker, PackageManifest, PathBuf, Reporter,
|
||||
WorkspaceState, modules_layout_consistent_with, tree_may_move,
|
||||
};
|
||||
use crate::{
|
||||
install::state_options::RecordedWorkspace, optimistic_repeat_install::recorded_elsewhere,
|
||||
};
|
||||
use crate::{install::state_options::RecordedWorkspace, install_state::recorded_elsewhere};
|
||||
use pnpm_workspace_state::load_workspace_state_in_modules;
|
||||
|
||||
pub(super) struct PrepareModulesStateInputs<'a, 'install> {
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::super::{
|
||||
moved_tree_is_reusable, recorded_allow_builds_differ, unapproved_recorded_ignored_builds,
|
||||
update_workspace_state_or_warn, verify_lockfile_eagerly,
|
||||
};
|
||||
use crate::optimistic_repeat_install::{
|
||||
use crate::install_state::{
|
||||
filesystem_now_ms_in_modules, materialized_shape_matches,
|
||||
validation::{project_bins_digest, project_bins_match, workspace_bins_digest},
|
||||
};
|
||||
@@ -76,11 +76,11 @@ pub(super) fn frozen_tree_up_to_date<'a>(
|
||||
// `extendNodePath` change the bin shims, without touching the lockfile or
|
||||
// `.modules.yaml`, so the unchanged-layout premise doesn't hold.
|
||||
if !modules_consistent_with(modules, config, context.tree.node_linker, context.tree.included)
|
||||
|| !crate::optimistic_repeat_install::recorded_supported_architectures_match(
|
||||
|| !crate::install_state::recorded_supported_architectures_match(
|
||||
context.recorded.state,
|
||||
context.repeat.supported_architectures,
|
||||
)
|
||||
|| !crate::optimistic_repeat_install::recorded_extend_node_path_matches(
|
||||
|| !crate::install_state::recorded_extend_node_path_matches(
|
||||
context.recorded.state,
|
||||
config.extend_node_path,
|
||||
)
|
||||
@@ -297,17 +297,15 @@ pub(super) fn refresh_up_to_date_workspace<Reporter: self::Reporter>(
|
||||
context.tree.config,
|
||||
Some(context.wanted_lockfile),
|
||||
),
|
||||
registry_routes: Some(
|
||||
crate::optimistic_repeat_install::recorded_registry_routes_after_install(
|
||||
context.tree.config,
|
||||
context.carried_state.recorded_registry_routes.as_ref(),
|
||||
Some(context.wanted_lockfile),
|
||||
crate::optimistic_repeat_install::ResolvedImporters::None,
|
||||
),
|
||||
),
|
||||
registry_routes: Some(crate::install_state::recorded_registry_routes_after_install(
|
||||
context.tree.config,
|
||||
context.carried_state.recorded_registry_routes.as_ref(),
|
||||
Some(context.wanted_lockfile),
|
||||
crate::install_state::ResolvedImporters::None,
|
||||
)),
|
||||
kept_settings: None,
|
||||
},
|
||||
vouched: &crate::optimistic_repeat_install::validation::Vouched::All,
|
||||
vouched: &crate::install_state::validation::Vouched::All,
|
||||
});
|
||||
update_workspace_state_or_warn::<Reporter>(
|
||||
context.tree.workspace_root,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub(super) use fast_path::{
|
||||
pub(crate) use fast_path::{
|
||||
check_strict_dep_builds_for_fast_path, gvs_build_markers_complete, register_workspace_in_store,
|
||||
};
|
||||
pub(crate) use manifests::hook_one_manifest;
|
||||
@@ -70,7 +70,7 @@ where
|
||||
/// Both live out here because every success path of
|
||||
/// [`Self::run_inner_impl`] — including the short-circuits that do
|
||||
/// nothing but rewrite the lockfile — has to run them.
|
||||
pub(super) async fn run_inner<Reporter: self::Reporter + 'static>(
|
||||
pub(crate) async fn run_inner<Reporter: self::Reporter + 'static>(
|
||||
self,
|
||||
options: InstallRunOptions<'a, '_>,
|
||||
) -> Result<(), InstallError> {
|
||||
@@ -83,8 +83,7 @@ where
|
||||
// resolutions no file is left holding.
|
||||
let merge_will_be_saved = self.context.config.merge_git_branch_lockfiles
|
||||
&& self.context.config.lockfile
|
||||
&& options.save.lockfile
|
||||
&& options.lockfile_check.is_none()
|
||||
&& options.save.writes_lockfile()
|
||||
&& !self.execution.dry_run;
|
||||
let branch_lockfiles_to_clean = merge_will_be_saved
|
||||
.then(|| {
|
||||
@@ -144,8 +143,7 @@ where
|
||||
self.lockfile_policy.excludes == PolicyExcludes::Persist
|
||||
&& matches!(self.execution.mutation, ProjectMutation::InstallWorkspace)
|
||||
&& self.context.config.lockfile
|
||||
&& options.save.lockfile
|
||||
&& options.lockfile_check.is_none()
|
||||
&& options.save.writes_lockfile()
|
||||
&& !self.execution.dry_run
|
||||
&& (self.context.config.minimum_release_age_exclude_prune
|
||||
|| self.context.config.trust_policy_exclude_prune)
|
||||
@@ -256,8 +254,7 @@ pub(super) struct InstallView<'a> {
|
||||
impl InstallView<'_> {
|
||||
fn should_prune_catalogs(&self, options: &InstallRunOptions<'_, '_>) -> bool {
|
||||
self.context.config.catalog_prune
|
||||
&& options.save.lockfile
|
||||
&& options.lockfile_check.is_none()
|
||||
&& options.save.writes_lockfile()
|
||||
&& !self.execution.dry_run
|
||||
&& self.execution.mutation.is_full_install()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::{
|
||||
InstallScope, RunExecution,
|
||||
settled::Settled,
|
||||
};
|
||||
use crate::{ProjectMutation, optimistic_repeat_install::current_settings_with_catalogs};
|
||||
use crate::{ProjectMutation, install_state::current_settings_with_catalogs};
|
||||
|
||||
impl RunExecution<'_> {
|
||||
pub(super) fn wanted_lockfile_path(&self) -> PathBuf {
|
||||
|
||||
@@ -342,7 +342,7 @@ async fn frozen_path<Reporter: self::Reporter>(
|
||||
fn no_project_has_dependencies(manifests: &[(String, &PackageManifest)]) -> bool {
|
||||
manifests
|
||||
.iter()
|
||||
.all(|(_, manifest)| !crate::optimistic_repeat_install::manifest_has_runtime_deps(manifest))
|
||||
.all(|(_, manifest)| !crate::install_state::manifest_has_runtime_deps(manifest))
|
||||
}
|
||||
fn report_unresolved_optional_dependencies<Reporter: self::Reporter>(
|
||||
skipped: &[UnresolvedOptionalDependency],
|
||||
|
||||
@@ -286,7 +286,7 @@ impl<'a> RunExecution<'a> {
|
||||
deploy_manifest_hook: self.options.manifests.deploy_hook,
|
||||
manifest_spec_bumps: self.options.manifests.spec_bumps,
|
||||
known_requires_build: self.options.manifests.known_requires_build.take(),
|
||||
lockfile_check_sink: self.options.lockfile_check.take(),
|
||||
lockfile_check_sink: self.options.save.lockfile_check.take(),
|
||||
inputs: std::mem::take(&mut self.owned.resolution),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use super::super::{
|
||||
gvs_build_marker_present, gvs_build_markers_may_require_recovery,
|
||||
unapproved_recorded_ignored_builds,
|
||||
};
|
||||
use crate::install_state::RepeatInstallCheck;
|
||||
use pnpm_config::Config;
|
||||
use pnpm_store_dir::StoreOperationLock;
|
||||
use std::path::Path;
|
||||
@@ -25,7 +26,7 @@ pub(super) fn lock_store(config: &Config) -> Result<StoreOperationLock, InstallE
|
||||
pub(super) struct UpToDateCheck<'a> {
|
||||
pub(super) workspace: OptimisticRepeatInstallCheck<'a>,
|
||||
/// The projects the install covers.
|
||||
pub(super) scope: crate::optimistic_repeat_install::Scope<'a>,
|
||||
pub(super) scope: crate::install_state::Scope<'a>,
|
||||
pub(super) mutation: crate::ProjectMutation,
|
||||
pub(super) update_seed_policy: &'a UpdateSeedPolicy,
|
||||
pub(super) frozen_lockfile: bool,
|
||||
@@ -59,6 +60,7 @@ pub(super) struct UpToDateCheck<'a> {
|
||||
/// filtered install would materialize.
|
||||
pub(super) fn repeat_install_verdict(
|
||||
check: &UpToDateCheck<'_>,
|
||||
repeat_install: RepeatInstallCheck,
|
||||
) -> Result<RepeatInstallVerdict, InstallError> {
|
||||
if !check.resolve_only {
|
||||
register_workspace_in_store(check.workspace.config, check.workspace.workspace_root)?;
|
||||
@@ -71,10 +73,7 @@ pub(super) fn repeat_install_verdict(
|
||||
return Ok(RepeatInstallVerdict::Changed);
|
||||
}
|
||||
if let OptimisticRepeatInstallDecision::Skipped { reason } =
|
||||
crate::optimistic_repeat_install::check_optimistic_repeat_install_in(
|
||||
&check.workspace,
|
||||
check.scope,
|
||||
)
|
||||
repeat_install(&check.workspace, check.scope)
|
||||
{
|
||||
tracing::debug!(
|
||||
target: "pacquet::install",
|
||||
@@ -140,7 +139,7 @@ pub(super) fn build_state_allows_short_circuit(
|
||||
|
||||
/// `Err` names what stops the fast path: a build marker the full install has
|
||||
/// to recover, or a wanted lockfile it cannot read to look for one.
|
||||
pub(in crate::install) fn gvs_build_markers_complete(
|
||||
pub(crate) fn gvs_build_markers_complete(
|
||||
config: &Config,
|
||||
lockfile: pnpm_lockfile::MaybeLazyLockfile<'_>,
|
||||
lockfile_root: &Path,
|
||||
@@ -163,9 +162,7 @@ pub(in crate::install) fn gvs_build_markers_complete(
|
||||
}
|
||||
|
||||
/// Whether `strictDepBuilds` allows the fast path to stand.
|
||||
pub(in crate::install) fn check_strict_dep_builds_for_fast_path(
|
||||
config: &Config,
|
||||
) -> Result<bool, InstallError> {
|
||||
pub(crate) fn check_strict_dep_builds_for_fast_path(config: &Config) -> Result<bool, InstallError> {
|
||||
if !config.strict_dep_builds {
|
||||
return Ok(true);
|
||||
}
|
||||
@@ -288,8 +285,13 @@ impl super::RunExecution<'_> {
|
||||
) -> Result<RepeatInstallVerdict, InstallError> {
|
||||
let embedder_hooks = self.owned.projects.pnpmfile_hook_override.clone();
|
||||
self.check_custom_fetcher_reuse().await?;
|
||||
let verdict =
|
||||
scope.repeat_install_verdict(self.install, &self.owned, &self.mode, &self.workspace)?;
|
||||
let verdict = scope.repeat_install_verdict(
|
||||
self.install,
|
||||
&self.owned,
|
||||
&self.mode,
|
||||
&self.workspace,
|
||||
self.options.repeat_install,
|
||||
)?;
|
||||
let changed = match verdict {
|
||||
RepeatInstallVerdict::Changed => true,
|
||||
RepeatInstallVerdict::Unchanged | RepeatInstallVerdict::UnchangedFrozen => false,
|
||||
|
||||
@@ -102,8 +102,8 @@ async fn verify_frozen_tarballs(settled: Settled<'_, '_>) -> Result<(), InstallE
|
||||
let importer_ids = installed_importer_ids(settled, lockfile);
|
||||
let (skipped, groups) = compute_frozen_skip_set(&settled, lockfile, &importer_ids).await?;
|
||||
|
||||
let targets = crate::optimistic_repeat_install::frozen_local_tarballs_to_verify(
|
||||
&crate::optimistic_repeat_install::FrozenLocalTarballCheck {
|
||||
let targets = crate::install_state::frozen_local_tarballs_to_verify(
|
||||
&crate::install_state::FrozenLocalTarballCheck {
|
||||
workspace_root: &settled.projects.workspace.dirs.workspace_root,
|
||||
importer_ids: &importer_ids,
|
||||
groups: &groups,
|
||||
|
||||
@@ -233,7 +233,7 @@ impl<'w> InstallScope<'w> {
|
||||
workspace_projects_are_overridden,
|
||||
config: install.context.config,
|
||||
});
|
||||
let bin_projects = crate::optimistic_repeat_install::validation::dedicated_bin_projects(
|
||||
let bin_projects = crate::install_state::validation::dedicated_bin_projects(
|
||||
install.context.config,
|
||||
&project_manifests,
|
||||
workspace_projects,
|
||||
@@ -264,49 +264,62 @@ impl<'w> InstallScope<'w> {
|
||||
Some(dirs)
|
||||
}
|
||||
|
||||
/// The manifests the repeat-install check reads besides the importers'.
|
||||
fn repeat_install_manifests<'s>(
|
||||
&'s self,
|
||||
owned: &'s InstallOwned,
|
||||
) -> crate::RepeatInstallManifests<'s> {
|
||||
crate::RepeatInstallManifests {
|
||||
dependency_manifests: self.dependency_manifests.as_ref(),
|
||||
workspace_projects: self.workspace_projects.as_deref(),
|
||||
hooks: owned.projects.pnpmfile_hook_override
|
||||
.as_ref()
|
||||
.map_or(crate::ManifestHooks::Pnpmfiles, crate::ManifestHooks::InMemory),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn repeat_install_verdict(
|
||||
&self,
|
||||
install: InstallView<'_>,
|
||||
owned: &InstallOwned,
|
||||
mode: &RunMode,
|
||||
workspace: &InstallWorkspace<'_>,
|
||||
repeat_install: crate::install_state::RepeatInstallCheck,
|
||||
) -> Result<RepeatInstallVerdict, InstallError> {
|
||||
let selected_dirs = self.requested_project_dirs(&workspace.dirs.workspace_root);
|
||||
repeat_install_verdict(&UpToDateCheck {
|
||||
scope: selected_dirs
|
||||
.as_deref()
|
||||
.map_or(crate::optimistic_repeat_install::Scope::Workspace, |selected| {
|
||||
crate::optimistic_repeat_install::Scope::Selected(selected)
|
||||
}),
|
||||
workspace: super::super::OptimisticRepeatInstallCheck {
|
||||
config: install.context.config,
|
||||
workspace_root: &workspace.dirs.workspace_root,
|
||||
project_manifests: self.declared_manifests(),
|
||||
is_workspace_install: workspace.workspace_manifest.is_some(),
|
||||
lockfile: install.context.lockfile,
|
||||
catalogs: &workspace.catalogs,
|
||||
layout: crate::RepeatInstallLayout {
|
||||
node_linker: install.execution.node_linker,
|
||||
included: mode.included,
|
||||
supported_architectures: owned.projects
|
||||
.supported_architectures
|
||||
.as_ref(),
|
||||
},
|
||||
manifests: crate::RepeatInstallManifests {
|
||||
dependency_manifests: self.dependency_manifests.as_ref(),
|
||||
workspace_projects: self.workspace_projects.as_deref(),
|
||||
hooks: owned.projects.pnpmfile_hook_override
|
||||
.as_ref()
|
||||
.map_or(crate::ManifestHooks::Pnpmfiles, crate::ManifestHooks::InMemory),
|
||||
repeat_install_verdict(
|
||||
&UpToDateCheck {
|
||||
scope: selected_dirs
|
||||
.as_deref()
|
||||
.map_or(crate::install_state::Scope::Workspace, |selected| {
|
||||
crate::install_state::Scope::Selected(selected)
|
||||
}),
|
||||
workspace: super::super::OptimisticRepeatInstallCheck {
|
||||
config: install.context.config,
|
||||
workspace_root: &workspace.dirs.workspace_root,
|
||||
project_manifests: self.declared_manifests(),
|
||||
is_workspace_install: workspace.workspace_manifest.is_some(),
|
||||
lockfile: install.context.lockfile,
|
||||
catalogs: &workspace.catalogs,
|
||||
layout: crate::RepeatInstallLayout {
|
||||
node_linker: install.execution.node_linker,
|
||||
included: mode.included,
|
||||
supported_architectures: owned.projects
|
||||
.supported_architectures
|
||||
.as_ref(),
|
||||
},
|
||||
manifests: self.repeat_install_manifests(owned),
|
||||
},
|
||||
mutation: install.execution.mutation,
|
||||
update_seed_policy: &owned.resolution.update_seed_policy,
|
||||
frozen_lockfile: install.lockfile_policy.frozen,
|
||||
resolve_only: mode.resolve_only,
|
||||
disable_optimistic_repeat_install: install.lockfile_policy
|
||||
.disable_optimistic_repeat,
|
||||
effective_node_version: mode.effective_node_version.as_deref(),
|
||||
},
|
||||
mutation: install.execution.mutation,
|
||||
update_seed_policy: &owned.resolution.update_seed_policy,
|
||||
frozen_lockfile: install.lockfile_policy.frozen,
|
||||
resolve_only: mode.resolve_only,
|
||||
disable_optimistic_repeat_install: install.lockfile_policy.disable_optimistic_repeat,
|
||||
effective_node_version: mode.effective_node_version.as_deref(),
|
||||
})
|
||||
repeat_install,
|
||||
)
|
||||
}
|
||||
}
|
||||
/// Whether anything could declare a catalog this install has to resolve
|
||||
|
||||
@@ -222,14 +222,12 @@ fn corp_lockfile() -> pnpm_lockfile::Lockfile {
|
||||
/// leaves a plain key under a route that moved since.
|
||||
#[test]
|
||||
fn recorded_registry_routes_follow_the_install_that_resolved_the_plain_keys() {
|
||||
use crate::optimistic_repeat_install::{
|
||||
ResolvedImporters, recorded_registry_routes_after_install,
|
||||
};
|
||||
use crate::install_state::{ResolvedImporters, recorded_registry_routes_after_install};
|
||||
let old = "https://old.example.test/";
|
||||
let new = "https://new.example.test/";
|
||||
let mut config = Config::new();
|
||||
config.registries_by_scope.insert("@corp".to_string(), new.to_string());
|
||||
let current = crate::optimistic_repeat_install::settings::recorded_registry_routes(&config);
|
||||
let current = crate::install_state::settings::recorded_registry_routes(&config);
|
||||
let mut previous = current.clone();
|
||||
previous.insert("@corp".to_string(), old.to_string());
|
||||
let lockfile = corp_lockfile();
|
||||
|
||||
@@ -959,14 +959,14 @@ async fn frozen_install_short_circuits_when_modules_and_lockfile_are_consistent(
|
||||
let written = load_workspace_state(&dirs.project_root)
|
||||
.expect("read workspace state")
|
||||
.expect("workspace state must be written");
|
||||
let manifest_mtime = crate::optimistic_repeat_install::file_mtime(manifest.path())
|
||||
.expect("manifest should have an mtime");
|
||||
let manifest_mtime =
|
||||
crate::install_state::file_mtime(manifest.path()).expect("manifest should have an mtime");
|
||||
assert_eq!(
|
||||
manifest_mtime.ns, 1_700_000_000_000_500_000,
|
||||
"the short-circuit must leave the manifest untouched",
|
||||
);
|
||||
assert!(
|
||||
!crate::optimistic_repeat_install::modified_at_or_after(
|
||||
!crate::install_state::modified_at_or_after(
|
||||
manifest_mtime,
|
||||
written.last_validated_timestamp,
|
||||
),
|
||||
|
||||
@@ -99,7 +99,7 @@ async fn frozen_lockfile_disables_optimistic_short_circuit() {
|
||||
has_modules_dir: false,
|
||||
},
|
||||
);
|
||||
let settings = crate::optimistic_repeat_install::settings::current_settings(
|
||||
let settings = crate::install_state::settings::current_settings(
|
||||
config,
|
||||
pnpm_config::NodeLinker::Isolated,
|
||||
included,
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
use super::{
|
||||
super::{
|
||||
Install, InstallError, ProjectMutation, UpToDateFastPathCheck, install_already_up_to_date,
|
||||
},
|
||||
super::{Install, InstallError, ProjectMutation},
|
||||
InstallDirs, PARTIAL_INSTALL_LOCKFILE, assert_package_absent, assert_package_present,
|
||||
fresh_lockfile_only_with_overrides, install_then_go_offline, install_with_pnpmfile,
|
||||
touch_manifest,
|
||||
};
|
||||
use crate::PolicyExcludes;
|
||||
use crate::{PolicyExcludes, UpToDateFastPathCheck, install_already_up_to_date};
|
||||
use pnpm_config::Config;
|
||||
use pnpm_lockfile::{Lockfile, MaybeLazyLockfile};
|
||||
use pnpm_modules_yaml::{Host, LayoutVersion, Modules, NodeLinker, write_modules_manifest};
|
||||
@@ -198,7 +196,7 @@ fn sync_fast_path_reads_the_workspace_root_wanted_lockfile_from_a_member() {
|
||||
filtered_install: false,
|
||||
config_dependencies: None,
|
||||
validation: None,
|
||||
settings: crate::optimistic_repeat_install::settings::current_settings(
|
||||
settings: crate::install_state::settings::current_settings(
|
||||
config,
|
||||
pnpm_config::NodeLinker::Isolated,
|
||||
included,
|
||||
@@ -330,7 +328,7 @@ pub(super) async fn optimistic_repeat_install_does_not_short_circuit_when_lockfi
|
||||
has_modules_dir: false,
|
||||
},
|
||||
);
|
||||
let settings = crate::optimistic_repeat_install::settings::current_settings(
|
||||
let settings = crate::install_state::settings::current_settings(
|
||||
config,
|
||||
pnpm_config::NodeLinker::Isolated,
|
||||
included,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use super::{
|
||||
super::{
|
||||
Install, InstallError, ProjectMutation, UpToDateFastPathCheck, install_already_up_to_date,
|
||||
},
|
||||
super::{Install, InstallError, ProjectMutation},
|
||||
InstallDirs, assert_purge_diagnostic, fresh_lockfile_only_with_compatibility_db,
|
||||
install_then_go_offline, recorded_verified_file_integrity_report, touch_manifest,
|
||||
};
|
||||
use crate::PolicyExcludes;
|
||||
use crate::{PolicyExcludes, UpToDateFastPathCheck, install_already_up_to_date};
|
||||
use pnpm_config::Config;
|
||||
use pnpm_lockfile::{Lockfile, MaybeLazyLockfile};
|
||||
use pnpm_modules_yaml::{Host, LayoutVersion, Modules, NodeLinker, write_modules_manifest};
|
||||
@@ -128,12 +126,10 @@ async fn optimistic_repeat_install_skips_entire_pipeline_when_state_is_fresh() {
|
||||
carried: crate::install::CarriedSettings {
|
||||
established_auto_dedupe: None,
|
||||
registry_keys_verified: true,
|
||||
registry_routes: Some(
|
||||
crate::optimistic_repeat_install::settings::recorded_registry_routes(config),
|
||||
),
|
||||
registry_routes: Some(crate::install_state::settings::recorded_registry_routes(config)),
|
||||
kept_settings: None,
|
||||
},
|
||||
vouched: &crate::optimistic_repeat_install::validation::Vouched::All,
|
||||
vouched: &crate::install_state::validation::Vouched::All,
|
||||
});
|
||||
workspace_state::update_workspace_state(&dirs.project_root, &seed_state)
|
||||
.expect("seed workspace state");
|
||||
@@ -276,12 +272,10 @@ fn sync_fast_path_matches_optimistic_short_circuit() {
|
||||
carried: crate::install::CarriedSettings {
|
||||
established_auto_dedupe: None,
|
||||
registry_keys_verified: true,
|
||||
registry_routes: Some(
|
||||
crate::optimistic_repeat_install::settings::recorded_registry_routes(config),
|
||||
),
|
||||
registry_routes: Some(crate::install_state::settings::recorded_registry_routes(config)),
|
||||
kept_settings: None,
|
||||
},
|
||||
vouched: &crate::optimistic_repeat_install::validation::Vouched::All,
|
||||
vouched: &crate::install_state::validation::Vouched::All,
|
||||
});
|
||||
workspace_state::update_workspace_state(&project_root, &seed_state)
|
||||
.expect("seed workspace state");
|
||||
@@ -430,12 +424,10 @@ async fn partial_install_disables_optimistic_short_circuit() {
|
||||
carried: crate::install::CarriedSettings {
|
||||
established_auto_dedupe: None,
|
||||
registry_keys_verified: true,
|
||||
registry_routes: Some(
|
||||
crate::optimistic_repeat_install::settings::recorded_registry_routes(config),
|
||||
),
|
||||
registry_routes: Some(crate::install_state::settings::recorded_registry_routes(config)),
|
||||
kept_settings: None,
|
||||
},
|
||||
vouched: &crate::optimistic_repeat_install::validation::Vouched::All,
|
||||
vouched: &crate::install_state::validation::Vouched::All,
|
||||
});
|
||||
workspace_state::update_workspace_state(&dirs.project_root, &seed_state)
|
||||
.expect("seed workspace state");
|
||||
|
||||
@@ -308,13 +308,10 @@ async fn install_writes_workspace_state() {
|
||||
"lastValidatedTimestamp should be populated, got {}",
|
||||
state.last_validated_timestamp,
|
||||
);
|
||||
let manifest_mtime = crate::optimistic_repeat_install::file_mtime(manifest.path())
|
||||
.expect("manifest should have an mtime");
|
||||
let manifest_mtime =
|
||||
crate::install_state::file_mtime(manifest.path()).expect("manifest should have an mtime");
|
||||
assert!(
|
||||
!crate::optimistic_repeat_install::modified_at_or_after(
|
||||
manifest_mtime,
|
||||
state.last_validated_timestamp,
|
||||
),
|
||||
!crate::install_state::modified_at_or_after(manifest_mtime, state.last_validated_timestamp,),
|
||||
"fresh install state should cover the validated manifest mtime",
|
||||
);
|
||||
|
||||
|
||||
@@ -1,38 +1,22 @@
|
||||
pub use discovery::{GateWorkspace, check_deps_status_before_run_at, deps_install_root};
|
||||
pub use per_project::install_already_up_to_date_per_project;
|
||||
pub(crate) use projects::replace_project_manifest;
|
||||
pub(super) use projects::{
|
||||
ProjectScriptsInputs, build_project_manifests_list, build_root_importer_project_manifests_list,
|
||||
ProjectScriptsInputs, build_root_importer_project_manifests_list,
|
||||
build_selected_project_manifests_list, mutated_project_dirs, projects_running_own_scripts,
|
||||
selected_manifest_freshness_inputs,
|
||||
};
|
||||
pub(crate) use projects::{build_project_manifests_list, replace_project_manifest};
|
||||
pub(crate) use write::{CarriedSettings, StateProjects, StateWrite, recorded_workspace_state};
|
||||
|
||||
mod projects;
|
||||
|
||||
mod discovery;
|
||||
|
||||
mod from_files;
|
||||
|
||||
mod per_project;
|
||||
|
||||
mod stand_ins;
|
||||
|
||||
mod nothing_to_install;
|
||||
|
||||
mod write;
|
||||
|
||||
use super::{
|
||||
BTreeMap, Catalogs, Clock, Config, DependencyGroup, IncludedDependencies, LazyLockfile,
|
||||
MaybeLazyLockfile, NodeLinker, OptimisticRepeatInstallCheck, OptimisticRepeatInstallDecision,
|
||||
PackageManifest, Path, PathBuf, ProjectEntry, WorkspaceState, manifest_string_field,
|
||||
};
|
||||
use crate::optimistic_repeat_install::{
|
||||
Scope, check_optimistic_repeat_install_with_state, config_blocks_fast_path,
|
||||
last_validated_timestamp,
|
||||
BTreeMap, Catalogs, Clock, Config, IncludedDependencies, NodeLinker, PackageManifest, Path,
|
||||
PathBuf, ProjectEntry, WorkspaceState, manifest_string_field,
|
||||
};
|
||||
use crate::install_state::last_validated_timestamp;
|
||||
use pnpm_reporter::Reporter;
|
||||
use pnpm_workspace_state::{load_workspace_state, update_workspace_state_in_modules};
|
||||
use pnpm_workspace_state::update_workspace_state_in_modules;
|
||||
|
||||
/// Persist `.pnpm-workspace-state-v1.json`, warning instead of failing
|
||||
/// the command when the write is lost.
|
||||
@@ -59,219 +43,6 @@ pub(crate) fn update_workspace_state_or_warn<Reporter: self::Reporter>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Inputs for [`install_already_up_to_date`].
|
||||
pub struct UpToDateFastPathCheck<'a> {
|
||||
pub config: &'a Config,
|
||||
pub manifest: &'a PackageManifest,
|
||||
pub dependency_groups: Vec<DependencyGroup>,
|
||||
pub node_linker: NodeLinker,
|
||||
/// The CLI-merged effective `supportedArchitectures` (yaml plus
|
||||
/// `--cpu` / `--os` / `--libc`) — the fast path must not report
|
||||
/// "Already up to date" when a flag changed the target platforms.
|
||||
pub supported_architectures: Option<pnpm_package_is_installable::SupportedArchitectures>,
|
||||
}
|
||||
|
||||
/// What an up-to-date install reports about the workspace it covered.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct UpToDateWorkspace {
|
||||
/// The workspace root — the reporter `prefix` for the "Already up to
|
||||
/// date" emission.
|
||||
pub root: PathBuf,
|
||||
/// How many projects the workspace has, or `None` when there is no
|
||||
/// `pnpm-workspace.yaml`. Feeds the `pnpm:scope` report, which the
|
||||
/// full install path derives from the same walk.
|
||||
pub project_count: Option<usize>,
|
||||
}
|
||||
|
||||
/// [`crate::check_optimistic_repeat_install`] inputs, callable from a
|
||||
/// synchronous context so the CLI can finish an up-to-date install
|
||||
/// before paying for the async runtime, the HTTP client, and the
|
||||
/// state setup. Returns what the short-circuit covered when the install
|
||||
/// can take it, after registering the workspace in the store as
|
||||
/// [`super::Install::run`] does.
|
||||
///
|
||||
/// `Err` carries the reason the shortcut was not taken, for the
|
||||
/// `pacquet::install` trace. Failures deliberately collapse into it: the
|
||||
/// caller falls through to the full install path, which reproduces the
|
||||
/// failure with its established error shape.
|
||||
///
|
||||
/// The phases that read the project tree run inside `pacquet::install`
|
||||
/// spans, so a trace of a slow up-to-date install shows which of them took
|
||||
/// the time.
|
||||
pub fn install_already_up_to_date(
|
||||
check: &UpToDateFastPathCheck<'_>,
|
||||
) -> Result<UpToDateWorkspace, &'static str> {
|
||||
already_up_to_date(check, Decide::FromRecordsOrFiles)
|
||||
}
|
||||
|
||||
/// [`install_already_up_to_date`] decided from the manifest records alone. A
|
||||
/// caller whose install reads the project manifests anyway when the
|
||||
/// shortcut is not taken uses this, so a declined attempt reads none of them.
|
||||
pub fn install_already_up_to_date_from_records(
|
||||
check: &UpToDateFastPathCheck<'_>,
|
||||
) -> Result<UpToDateWorkspace, &'static str> {
|
||||
already_up_to_date(check, Decide::FromRecords)
|
||||
}
|
||||
|
||||
/// What an up-to-date verdict may be decided from.
|
||||
#[derive(Clone, Copy)]
|
||||
enum Decide {
|
||||
FromRecords,
|
||||
FromRecordsOrFiles,
|
||||
}
|
||||
|
||||
fn already_up_to_date(
|
||||
check: &UpToDateFastPathCheck<'_>,
|
||||
decide: Decide,
|
||||
) -> Result<UpToDateWorkspace, &'static str> {
|
||||
let manifest_dir = check.manifest
|
||||
.path()
|
||||
.parent()
|
||||
.ok_or("the manifest has no directory")?;
|
||||
let workspace_dir_opt = configured_or_discovered_workspace_dir(check.config, manifest_dir)
|
||||
.map_err(|_| "the workspace root cannot be located")?;
|
||||
let workspace_root = workspace_dir_opt.as_deref().unwrap_or(manifest_dir);
|
||||
// The lockfile the install wrote sits at its `lockfileDir`, which
|
||||
// both `sharedWorkspaceLockfile: false` and an explicit pin move away
|
||||
// from the discovered workspace root. The workspace *state* keeps its
|
||||
// own root, which only a pin moves — `state_root` below.
|
||||
let lockfile_root = lockfile_root_for(check.config, workspace_dir_opt.as_deref(), manifest_dir);
|
||||
let state_root = check.config.lockfile_dir.as_deref().unwrap_or(workspace_root);
|
||||
let state = load_fast_path_state(check.config, &lockfile_root, state_root)?;
|
||||
let (workspace_manifest, catalogs) =
|
||||
fast_path_workspace_context(check.config, workspace_dir_opt.as_deref())
|
||||
.ok_or("the workspace manifest or its catalogs cannot be read")?;
|
||||
let lockfile = lazy_wanted_lockfile(check.config, &lockfile_root);
|
||||
let context = FastPathContext {
|
||||
state_root,
|
||||
lockfile_root: &lockfile_root,
|
||||
workspace_root,
|
||||
workspace_manifest: workspace_manifest.as_ref(),
|
||||
lockfile: &lockfile,
|
||||
catalogs: &catalogs,
|
||||
};
|
||||
let project_count = match stand_ins::check_from_records(check, &context, &state) {
|
||||
stand_ins::RecordsCheck::UpToDate(project_count) => Some(project_count),
|
||||
stand_ins::RecordsCheck::Undecided(listed) => match decide {
|
||||
Decide::FromRecordsOrFiles => {
|
||||
from_files::check_from_files(check, &context, &state, listed)?
|
||||
}
|
||||
Decide::FromRecords => return Err("the recorded manifests cannot decide"),
|
||||
},
|
||||
};
|
||||
register_fast_path_workspace(check.config, &lockfile_root)?;
|
||||
Ok(UpToDateWorkspace { root: state_root.to_path_buf(), project_count })
|
||||
}
|
||||
|
||||
fn load_fast_path_state(
|
||||
config: &Config,
|
||||
lockfile_root: &Path,
|
||||
state_root: &Path,
|
||||
) -> Result<WorkspaceState, &'static str> {
|
||||
if let Some(reason) = config_blocks_fast_path(config) {
|
||||
return Err(reason);
|
||||
}
|
||||
if !matches!(super::run::check_strict_dep_builds_for_fast_path(config), Ok(true)) {
|
||||
return Err("strictDepBuilds has to re-check the recorded ignored builds");
|
||||
}
|
||||
if crate::agent_skills::agent_skill_targets_changed(config, lockfile_root) {
|
||||
return Err("an agent skill directory changed");
|
||||
}
|
||||
load_workspace_state(state_root)
|
||||
.ok()
|
||||
.flatten()
|
||||
.ok_or("no workspace state on disk")
|
||||
}
|
||||
|
||||
/// What [`install_already_up_to_date`] has established about the workspace
|
||||
/// before it reads any project.
|
||||
struct FastPathContext<'a> {
|
||||
state_root: &'a Path,
|
||||
lockfile_root: &'a Path,
|
||||
workspace_root: &'a Path,
|
||||
/// `None` when no `pnpm-workspace.yaml` drives the install.
|
||||
workspace_manifest: Option<&'a pnpm_workspace::WorkspaceManifest>,
|
||||
lockfile: &'a LazyLockfile,
|
||||
catalogs: &'a Catalogs,
|
||||
}
|
||||
|
||||
/// The projects one repeat-install check covers.
|
||||
#[derive(Clone, Copy)]
|
||||
struct FastPathProjects<'a> {
|
||||
state: &'a WorkspaceState,
|
||||
manifests: &'a [(PathBuf, &'a PackageManifest)],
|
||||
/// See [`crate::DeclaredManifests::with_stand_ins`].
|
||||
recorded_digests: Option<&'a std::collections::HashMap<PathBuf, String>>,
|
||||
/// See [`crate::RepeatInstallManifests::workspace_projects`].
|
||||
bin_projects: Option<&'a [(PathBuf, &'a PackageManifest)]>,
|
||||
}
|
||||
|
||||
/// [`check_optimistic_repeat_install_with_state`] inside the
|
||||
/// `check_repeat_install` span, with a skip surfaced as the fast path's reason.
|
||||
fn check_fast_path_repeat_install(
|
||||
check: &UpToDateFastPathCheck<'_>,
|
||||
context: &FastPathContext<'_>,
|
||||
projects: FastPathProjects<'_>,
|
||||
) -> Result<(), &'static str> {
|
||||
let _span = tracing::debug_span!(target: "pacquet::install", "check_repeat_install").entered();
|
||||
let project_manifests = match projects.recorded_digests {
|
||||
Some(digests) => crate::DeclaredManifests::with_stand_ins(projects.manifests, digests),
|
||||
None => crate::DeclaredManifests::new(projects.manifests),
|
||||
};
|
||||
let decision = check_optimistic_repeat_install_with_state(
|
||||
&OptimisticRepeatInstallCheck {
|
||||
workspace_root: context.state_root,
|
||||
config: check.config,
|
||||
project_manifests,
|
||||
is_workspace_install: context.workspace_manifest.is_some(),
|
||||
lockfile: MaybeLazyLockfile::Lazy(context.lockfile),
|
||||
catalogs: context.catalogs,
|
||||
layout: crate::RepeatInstallLayout {
|
||||
node_linker: check.node_linker,
|
||||
included: super::included_dependencies(&check.dependency_groups),
|
||||
supported_architectures: check.supported_architectures.as_ref(),
|
||||
},
|
||||
manifests: crate::RepeatInstallManifests {
|
||||
workspace_projects: projects.bin_projects,
|
||||
..crate::RepeatInstallManifests::ON_DISK
|
||||
},
|
||||
},
|
||||
projects.state,
|
||||
Scope::Workspace,
|
||||
);
|
||||
match decision {
|
||||
OptimisticRepeatInstallDecision::UpToDate => super::run::gvs_build_markers_complete(
|
||||
check.config,
|
||||
MaybeLazyLockfile::Lazy(context.lockfile),
|
||||
context.lockfile_root,
|
||||
super::effective_node_version(check.config, check.manifest).as_deref(),
|
||||
),
|
||||
OptimisticRepeatInstallDecision::Skipped { reason } => Err(reason),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`super::run::register_workspace_in_store`] inside the
|
||||
/// `register_workspace_in_store` span.
|
||||
fn register_fast_path_workspace(config: &Config, lockfile_root: &Path) -> Result<(), &'static str> {
|
||||
let _span =
|
||||
tracing::debug_span!(target: "pacquet::install", "register_workspace_in_store").entered();
|
||||
super::run::register_workspace_in_store(config, lockfile_root)
|
||||
.map_err(|_| "the workspace cannot be registered in the store")
|
||||
}
|
||||
|
||||
fn fast_path_workspace_context(
|
||||
config: &Config,
|
||||
workspace_dir: Option<&Path>,
|
||||
) -> Option<(Option<pnpm_workspace::WorkspaceManifest>, super::Catalogs)> {
|
||||
let workspace_manifest = workspace_dir
|
||||
.map(pnpm_workspace::read_workspace_manifest)
|
||||
.transpose()
|
||||
.ok()?
|
||||
.flatten();
|
||||
let catalogs = discovery::configured_catalogs(config, workspace_manifest.as_ref())?;
|
||||
Some((workspace_manifest, catalogs))
|
||||
}
|
||||
|
||||
/// The workspace root the run belongs to: the configured one, else the
|
||||
/// nearest ancestor carrying a `pnpm-workspace.yaml`.
|
||||
///
|
||||
@@ -320,7 +91,7 @@ pub(crate) fn lockfile_root_dir(
|
||||
|
||||
/// [`lockfile_root_dir`] for a caller that has already resolved the
|
||||
/// workspace dir, so the ancestor walk is not repeated.
|
||||
pub(super) fn lockfile_root_for(
|
||||
pub(crate) fn lockfile_root_for(
|
||||
config: &Config,
|
||||
workspace_dir: Option<&Path>,
|
||||
manifest_dir: &Path,
|
||||
@@ -448,7 +219,7 @@ pub(super) fn build_projects_map(
|
||||
|
||||
/// Assemble the [`WorkspaceState`] payload for
|
||||
/// [`pnpm_workspace_state::update_workspace_state`], for
|
||||
/// [`crate::optimistic_repeat_install::validation::record_validation`] to
|
||||
/// [`crate::install_state::validation::record_validation`] to
|
||||
/// complete once the caller has settled its settings.
|
||||
///
|
||||
/// Records the projects pacquet just materialized plus the resolved
|
||||
@@ -480,17 +251,17 @@ pub(crate) fn build_workspace_state<Sys: Clock>(
|
||||
filesystem_now_ms,
|
||||
),
|
||||
projects: build_projects_map(project_manifests),
|
||||
pnpmfiles: crate::optimistic_repeat_install::current_pnpmfiles(workspace_root, config),
|
||||
pnpmfiles: crate::install_state::current_pnpmfiles(workspace_root, config),
|
||||
filtered_install: false,
|
||||
config_dependencies: config.config_dependencies.clone(),
|
||||
validation: None,
|
||||
// Settings construction is shared with
|
||||
// `optimistic_repeat_install::current_settings` so the
|
||||
// `install_state::current_settings` so the
|
||||
// freshness check sees the same byte shape this writer
|
||||
// produces. Keeping the construction in one place guarantees
|
||||
// adding a field on one side doesn't silently flip the other
|
||||
// into "drift" on the next install.
|
||||
settings: crate::optimistic_repeat_install::current_settings_with_catalogs(
|
||||
settings: crate::install_state::current_settings_with_catalogs(
|
||||
config,
|
||||
node_linker,
|
||||
included,
|
||||
@@ -546,17 +317,7 @@ fn record_hoisted_modules_dirs(
|
||||
for (root_dir, _) in project_manifests {
|
||||
if let Some(entry) = projects.get_mut(&*root_dir.to_string_lossy()) {
|
||||
entry.has_modules_dir =
|
||||
crate::optimistic_repeat_install::hoisted_project_modules_dir(root_dir).is_dir();
|
||||
crate::install_state::hoisted_project_modules_dir(root_dir).is_dir();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The wanted lockfile, read on first use — or a stand-in that never reads
|
||||
/// one when the install is configured without a lockfile.
|
||||
fn lazy_wanted_lockfile(config: &Config, lockfile_root: &Path) -> LazyLockfile {
|
||||
if config.lockfile {
|
||||
LazyLockfile::deferred(lockfile_root.to_path_buf(), config.wanted_lockfile_selection())
|
||||
} else {
|
||||
LazyLockfile::disabled()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::super::{DependencyGroup, HashSet, PackageManifest, Path, PathBuf, ProjectMutation};
|
||||
|
||||
pub(in super::super) fn build_project_manifests_list<'a>(
|
||||
pub(crate) fn build_project_manifests_list<'a>(
|
||||
root_manifest: &'a PackageManifest,
|
||||
workspace_projects: Option<&'a [pnpm_workspace::Project]>,
|
||||
) -> Vec<(std::path::PathBuf, &'a PackageManifest)> {
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(crate) struct StateWrite<'a> {
|
||||
/// per [`build_workspace_state`].
|
||||
pub(crate) filesystem_now_ms: Option<i64>,
|
||||
pub(crate) carried: CarriedSettings<'a>,
|
||||
pub(crate) vouched: &'a crate::optimistic_repeat_install::validation::Vouched<'a>,
|
||||
pub(crate) vouched: &'a crate::install_state::validation::Vouched<'a>,
|
||||
}
|
||||
|
||||
/// The projects a write records.
|
||||
@@ -28,7 +28,7 @@ pub(crate) struct StateProjects<'a> {
|
||||
pub(crate) manifests: crate::DeclaredManifests<'a>,
|
||||
/// See [`crate::RepeatInstallManifests::workspace_projects`].
|
||||
pub(crate) workspace_projects: Option<&'a [(PathBuf, &'a PackageManifest)]>,
|
||||
/// See [`RecordValidation::lockfile`](crate::optimistic_repeat_install::validation::RecordValidation::lockfile).
|
||||
/// See [`RecordValidation::lockfile`](crate::install_state::validation::RecordValidation::lockfile).
|
||||
pub(crate) lockfile: Option<&'a pnpm_lockfile::Lockfile>,
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ pub(crate) struct CarriedSettings<'a> {
|
||||
/// [`WorkspaceStateSettings::registry_prefixes`](pnpm_workspace_state::WorkspaceStateSettings::registry_prefixes)).
|
||||
pub(crate) registry_keys_verified: bool,
|
||||
/// The `registryRoutes` to record, per
|
||||
/// [`recorded_registry_routes_after_install`](crate::optimistic_repeat_install::recorded_registry_routes_after_install)
|
||||
/// [`recorded_registry_routes_after_install`](crate::install_state::recorded_registry_routes_after_install)
|
||||
/// for an install and the recorded ones for a passing check.
|
||||
pub(crate) registry_routes: Option<std::collections::BTreeMap<String, String>>,
|
||||
/// The recorded dependency groups, runtime setting and store location to
|
||||
@@ -68,9 +68,9 @@ pub(crate) fn recorded_workspace_state(write: &StateWrite<'_>) -> WorkspaceState
|
||||
recorded_auto_dedupe(write.config, write.carried.established_auto_dedupe);
|
||||
if write.carried.registry_keys_verified {
|
||||
state.settings.registry_prefixes =
|
||||
crate::optimistic_repeat_install::recorded_registry_prefixes(write.config);
|
||||
crate::install_state::recorded_registry_prefixes(write.config);
|
||||
state.settings.default_registry =
|
||||
crate::optimistic_repeat_install::recorded_default_registry(write.config);
|
||||
crate::install_state::recorded_default_registry(write.config);
|
||||
}
|
||||
state.settings.registry_routes.clone_from(&write.carried.registry_routes);
|
||||
if let Some(kept) = write.carried.kept_settings {
|
||||
@@ -81,9 +81,9 @@ pub(crate) fn recorded_workspace_state(write: &StateWrite<'_>) -> WorkspaceState
|
||||
state.settings.store_dir.clone_from(&kept.store_dir);
|
||||
state.settings.virtual_store_dir.clone_from(&kept.virtual_store_dir);
|
||||
}
|
||||
crate::optimistic_repeat_install::validation::record_validation(
|
||||
crate::install_state::validation::record_validation(
|
||||
&mut state,
|
||||
&crate::optimistic_repeat_install::validation::RecordValidation {
|
||||
&crate::install_state::validation::RecordValidation {
|
||||
workspace_root: write.workspace_root,
|
||||
config: write.config,
|
||||
project_manifests: write.projects.manifests,
|
||||
|
||||
+46
-99
@@ -1,8 +1,15 @@
|
||||
use super::{
|
||||
Install, InstallRunOptions, InstallSaveOptions, ProjectMutation, WorkspaceInstallSelection,
|
||||
errors::InstallError,
|
||||
//! The public ways to run an [`Install`]. They sit above both `install` and
|
||||
//! `optimistic_repeat_install`, so they are what hands an install the
|
||||
//! repeat-install fast path.
|
||||
|
||||
use crate::{
|
||||
LockfileCheckSink, LockfileVerificationOverride, RebuildOptions, ResolvedPackages,
|
||||
install::{
|
||||
Install, InstallError, InstallManifestOptions, InstallRunOptions, InstallSaveOptions,
|
||||
ProjectMutation, WorkspaceInstallSelection,
|
||||
},
|
||||
optimistic_repeat_install::check_optimistic_repeat_install_in,
|
||||
};
|
||||
use crate::{LockfileCheckSink, LockfileVerificationOverride, RebuildOptions, ResolvedPackages};
|
||||
use pnpm_config::Config;
|
||||
use pnpm_lockfile::MaybeLazyLockfile;
|
||||
use pnpm_network::ThrottledClient;
|
||||
@@ -75,7 +82,7 @@ where
|
||||
) -> Result<(), InstallError> {
|
||||
Box::pin(self.run_inner::<Reporter>(InstallRunOptions {
|
||||
selection,
|
||||
manifests: crate::install::InstallManifestOptions {
|
||||
manifests: InstallManifestOptions {
|
||||
prune_direct_dependencies: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -102,8 +109,11 @@ where
|
||||
self.lockfile_policy.disable_optimistic_repeat = true;
|
||||
let sink = LockfileCheckSink::default();
|
||||
Box::pin(self.run_inner::<Reporter>(InstallRunOptions {
|
||||
lockfile_check: Some(Arc::clone(&sink)),
|
||||
selection,
|
||||
save: InstallSaveOptions {
|
||||
lockfile_check: Some(Arc::clone(&sink)),
|
||||
..InstallSaveOptions::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await?;
|
||||
@@ -122,7 +132,7 @@ where
|
||||
read_package_hooked_manifest_paths: HashSet<PathBuf>,
|
||||
) -> Result<(), InstallError> {
|
||||
Box::pin(self.run_inner::<Reporter>(InstallRunOptions {
|
||||
manifests: crate::install::InstallManifestOptions {
|
||||
manifests: InstallManifestOptions {
|
||||
specifier_manifests: Some(lockfile_specifier_project_manifests),
|
||||
hooked_paths: read_package_hooked_manifest_paths,
|
||||
..Default::default()
|
||||
@@ -176,7 +186,7 @@ where
|
||||
) -> Result<(), InstallError> {
|
||||
Box::pin(self.run_inner::<Reporter>(InstallRunOptions {
|
||||
selection: Some(selection),
|
||||
manifests: crate::install::InstallManifestOptions {
|
||||
manifests: InstallManifestOptions {
|
||||
specifier_manifests: Some(lockfile_specifier_project_manifests),
|
||||
hooked_paths: read_package_hooked_manifest_paths,
|
||||
..Default::default()
|
||||
@@ -194,10 +204,7 @@ where
|
||||
bumps: &'a crate::ManifestSpecBumps,
|
||||
) -> Result<(), InstallError> {
|
||||
Box::pin(self.run_inner::<Reporter>(InstallRunOptions {
|
||||
manifests: crate::install::InstallManifestOptions {
|
||||
spec_bumps: Some(bumps),
|
||||
..Default::default()
|
||||
},
|
||||
manifests: InstallManifestOptions { spec_bumps: Some(bumps), ..Default::default() },
|
||||
..Default::default()
|
||||
}))
|
||||
.await
|
||||
@@ -212,10 +219,7 @@ where
|
||||
) -> Result<(), InstallError> {
|
||||
Box::pin(self.run_inner::<Reporter>(InstallRunOptions {
|
||||
selection: Some(selection),
|
||||
manifests: crate::install::InstallManifestOptions {
|
||||
spec_bumps: Some(bumps),
|
||||
..Default::default()
|
||||
},
|
||||
manifests: InstallManifestOptions { spec_bumps: Some(bumps), ..Default::default() },
|
||||
..Default::default()
|
||||
}))
|
||||
.await
|
||||
@@ -239,10 +243,7 @@ where
|
||||
known_requires_build: Option<pnpm_deps_restorer::RequiresBuildBySnapshot>,
|
||||
) -> Result<(), InstallError> {
|
||||
Box::pin(self.run_inner::<Reporter>(InstallRunOptions {
|
||||
manifests: crate::install::InstallManifestOptions {
|
||||
known_requires_build,
|
||||
..Default::default()
|
||||
},
|
||||
manifests: InstallManifestOptions { known_requires_build, ..Default::default() },
|
||||
..Default::default()
|
||||
}))
|
||||
.await
|
||||
@@ -255,10 +256,7 @@ where
|
||||
) -> Result<(), InstallError> {
|
||||
Box::pin(self.run_inner::<Reporter>(InstallRunOptions {
|
||||
selection: Some(selection),
|
||||
manifests: crate::install::InstallManifestOptions {
|
||||
known_requires_build,
|
||||
..Default::default()
|
||||
},
|
||||
manifests: InstallManifestOptions { known_requires_build, ..Default::default() },
|
||||
..Default::default()
|
||||
}))
|
||||
.await
|
||||
@@ -279,11 +277,12 @@ where
|
||||
self.execution.mutation = ProjectMutation::Deploy;
|
||||
Box::pin(self.run_inner::<Reporter>(InstallRunOptions {
|
||||
root_manifest_as_workspace_root: true,
|
||||
save: crate::install::InstallSaveOptions { lockfile: false, workspace_state: false },
|
||||
manifests: crate::install::InstallManifestOptions {
|
||||
deploy_hook: true,
|
||||
..Default::default()
|
||||
save: InstallSaveOptions {
|
||||
lockfile: false,
|
||||
workspace_state: false,
|
||||
lockfile_check: None,
|
||||
},
|
||||
manifests: InstallManifestOptions { deploy_hook: true, ..Default::default() },
|
||||
..Default::default()
|
||||
}))
|
||||
.await
|
||||
@@ -326,76 +325,24 @@ where
|
||||
.await
|
||||
}
|
||||
}
|
||||
pub fn apply_deploy_manifest_hook(manifest: &mut serde_json::Value) {
|
||||
let names =
|
||||
deploy_dependency_names(manifest, "workspace:").map(str::to_owned).collect::<Vec<_>>();
|
||||
inject_deploy_dependencies_meta(manifest, names);
|
||||
copy_deploy_linked_dependencies(manifest);
|
||||
}
|
||||
pub(crate) fn apply_deploy_manifest_hook_to_arc(
|
||||
mut manifest: Arc<serde_json::Value>,
|
||||
) -> Arc<serde_json::Value> {
|
||||
if deploy_dependency_names(&manifest, "workspace:").next().is_none()
|
||||
&& deploy_dependency_names(&manifest, "link:").next().is_none()
|
||||
{
|
||||
return manifest;
|
||||
}
|
||||
apply_deploy_manifest_hook(Arc::make_mut(&mut manifest));
|
||||
manifest
|
||||
}
|
||||
fn deploy_dependency_names<'a>(
|
||||
manifest: &'a serde_json::Value,
|
||||
protocol: &'static str,
|
||||
) -> impl Iterator<Item = &'a str> {
|
||||
["optionalDependencies", "dependencies", "devDependencies"]
|
||||
.into_iter()
|
||||
.filter_map(move |field| manifest.get(field)?.as_object())
|
||||
.flat_map(|dependencies| dependencies.iter())
|
||||
.filter_map(move |(name, specifier)| {
|
||||
specifier
|
||||
.as_str()
|
||||
.is_some_and(|specifier| specifier.starts_with(protocol))
|
||||
.then_some(name.as_str())
|
||||
})
|
||||
}
|
||||
|
||||
fn copy_deploy_linked_dependencies(manifest: &mut serde_json::Value) {
|
||||
for field in ["optionalDependencies", "dependencies", "devDependencies"] {
|
||||
let Some(dependencies) = manifest.get_mut(field).and_then(serde_json::Value::as_object_mut)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
for specifier in dependencies.values_mut() {
|
||||
let Some(directory) = specifier
|
||||
.as_str()
|
||||
.and_then(|value| value.strip_prefix("link:"))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
*specifier = serde_json::Value::String(format!("file:{directory}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn inject_deploy_dependencies_meta(
|
||||
manifest: &mut serde_json::Value,
|
||||
names: Vec<String>,
|
||||
) {
|
||||
if names.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Some(object) = manifest.as_object_mut() else { return };
|
||||
let dependencies_meta = object
|
||||
.entry("dependenciesMeta")
|
||||
.or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
|
||||
let Some(meta_object) = dependencies_meta.as_object_mut() else { return };
|
||||
for name in names {
|
||||
let dependency_meta = meta_object.entry(name).or_insert(serde_json::Value::Null);
|
||||
match dependency_meta {
|
||||
serde_json::Value::Object(object) => {
|
||||
object.insert("injected".to_owned(), serde_json::Value::Bool(true));
|
||||
}
|
||||
value => *value = serde_json::json!({ "injected": true }),
|
||||
impl Default for InstallRunOptions<'_, '_> {
|
||||
fn default() -> Self {
|
||||
InstallRunOptions {
|
||||
repeat_install: check_optimistic_repeat_install_in,
|
||||
lockfile_verification_override: None,
|
||||
rebuild: None,
|
||||
selection: None,
|
||||
root_manifest_as_workspace_root: false,
|
||||
save: InstallSaveOptions::default(),
|
||||
prompt_eligibility_override: None,
|
||||
manifests: InstallManifestOptions {
|
||||
deploy_hook: false,
|
||||
prune_direct_dependencies: false,
|
||||
specifier_manifests: None,
|
||||
hooked_paths: HashSet::new(),
|
||||
spec_bumps: None,
|
||||
known_requires_build: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -409,7 +356,7 @@ fn rebuild_run_options<'install, 'selection>(
|
||||
) -> InstallRunOptions<'install, 'selection> {
|
||||
InstallRunOptions {
|
||||
rebuild: Some(rebuild),
|
||||
save: InstallSaveOptions { lockfile: true, workspace_state: false },
|
||||
save: InstallSaveOptions { workspace_state: false, ..InstallSaveOptions::default() },
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
//! What a completed install records in the workspace state, and the inputs
|
||||
//! a later command compares with those records. The install writes the
|
||||
//! records and the repeat-install fast path reads them. Both depend on this
|
||||
//! module, so the install never depends on the fast path.
|
||||
|
||||
pub(crate) mod local_file_deps;
|
||||
pub(crate) mod settings;
|
||||
pub(crate) mod timestamps;
|
||||
pub(crate) mod validation;
|
||||
pub(crate) use local_file_deps::{
|
||||
FrozenLocalTarballCheck, frozen_local_tarballs_to_verify, has_local_file_dep_requiring_install,
|
||||
has_local_file_override, has_local_file_package_extension,
|
||||
};
|
||||
pub use manifest_source::{DeclaredManifests, ManifestHooks, RepeatInstallManifests};
|
||||
pub(crate) use settings::{
|
||||
ResolvedImporters, catalogs_cache_matches, current_settings_with_catalogs, first_setting_drift,
|
||||
recorded_default_registry, recorded_extend_node_path_matches, recorded_registry_prefixes,
|
||||
recorded_registry_routes_after_install, recorded_supported_architectures_match,
|
||||
records_registry_keys_of,
|
||||
};
|
||||
pub(crate) use timestamps::{
|
||||
file_mtime, file_mtime_from_metadata, filesystem_now_ms_in_modules,
|
||||
filesystem_now_ns_in_modules, last_validated_timestamp, lockfile_modified_since,
|
||||
modified_at_or_after,
|
||||
};
|
||||
|
||||
mod manifest_source;
|
||||
|
||||
use pnpm_catalogs_types::Catalogs;
|
||||
use pnpm_config::{Config, NodeLinker};
|
||||
use pnpm_lockfile::{Lockfile, MaybeLazyLockfile};
|
||||
use pnpm_modules_yaml::IncludedDependencies;
|
||||
use pnpm_package_is_installable::SupportedArchitectures;
|
||||
use pnpm_package_manifest::PackageManifest;
|
||||
use pnpm_workspace_state::WorkspaceState;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// The repeat-install check an install consults before it sets up anything,
|
||||
/// for the projects `Scope` covers. The fast path supplies it, so that the
|
||||
/// install does not depend on the fast path.
|
||||
pub(crate) type RepeatInstallCheck = fn(&OptimisticRepeatInstallCheck<'_>, Scope<'_>) -> Decision;
|
||||
|
||||
/// Project manifests keyed by their lexically normalized project directory.
|
||||
pub(crate) type ProjectManifestsByDir<'a> = HashMap<PathBuf, &'a PackageManifest>;
|
||||
|
||||
/// Outcome of [`crate::check_optimistic_repeat_install`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Decision {
|
||||
/// The install is fully up to date — emit "Already up to date"
|
||||
/// and exit before any of the install setup runs.
|
||||
UpToDate,
|
||||
/// Fall through to the full install path. `reason` is a short
|
||||
/// diagnostic string surfaced via `tracing::debug!` for
|
||||
/// diagnosability without contaminating the reporter stream.
|
||||
Skipped { reason: &'static str },
|
||||
}
|
||||
|
||||
/// Inputs to [`crate::check_optimistic_repeat_install`].
|
||||
pub struct OptimisticRepeatInstallCheck<'a> {
|
||||
/// The root the install recorded its lockfile and workspace state
|
||||
/// against, which importer ids and relative local `pnpm.overrides`
|
||||
/// targets are named from too. The directory containing
|
||||
/// `pnpm-workspace.yaml` (or the project root when no workspace
|
||||
/// manifest exists — same fallback as
|
||||
/// [`Install::run`](crate::Install::run)), unless the configuration
|
||||
/// pins the lockfile somewhere else.
|
||||
pub workspace_root: &'a Path,
|
||||
pub config: &'a Config,
|
||||
/// Every importer's `(root_dir, manifest)` pair. For a
|
||||
/// single-project install it's just the root manifest; for a
|
||||
/// workspace install it's every project the resolver would
|
||||
/// otherwise walk. The caller passes this in (rather than this
|
||||
/// function rediscovering it) so the same walk seeds the regular
|
||||
/// install path on the fall-through.
|
||||
pub project_manifests: DeclaredManifests<'a>,
|
||||
/// `true` when a `pnpm-workspace.yaml` drives the install — that
|
||||
/// selects the workspace branch, which keys the manifest and
|
||||
/// lockfile comparisons off `lastValidatedTimestamp`. `false` (no
|
||||
/// workspace manifest) selects the single-project branch, which
|
||||
/// additionally requires `pnpm-lock.yaml` to exist on disk —
|
||||
/// `RUN_CHECK_DEPS_LOCKFILE_NOT_FOUND` is raised otherwise, which
|
||||
/// resolves to not-up-to-date — and keys its comparisons off the
|
||||
/// lockfile mtimes instead.
|
||||
pub is_workspace_install: bool,
|
||||
/// The wanted lockfile (`None` once loaded when `pnpm-lock.yaml`
|
||||
/// is absent or empty). Consulted only by the modified-manifests
|
||||
/// content re-check; the pure-mtime fast path never parses it —
|
||||
/// which is why it arrives lazily, so the common repeat-install
|
||||
/// run skips the YAML parse entirely. A separately bounded byte
|
||||
/// scan only runs when lockfile metadata changed. When absent and
|
||||
/// `<install_state_dir>/lock.yaml` exists, the current lockfile
|
||||
/// stands in as the wanted one — it records exactly what the
|
||||
/// previous install materialized — and `pnpm-lock.yaml` is
|
||||
/// regenerated from it before the check reports up-to-date.
|
||||
pub lockfile: MaybeLazyLockfile<'a>,
|
||||
/// Catalogs from the workspace manifest or an `updateConfig`
|
||||
/// pnpmfile hook, for resolving `catalog:` values inside
|
||||
/// `pnpm.overrides` before the lockfile settings comparison.
|
||||
pub catalogs: &'a Catalogs,
|
||||
pub layout: RepeatInstallLayout<'a>,
|
||||
pub manifests: RepeatInstallManifests<'a>,
|
||||
}
|
||||
|
||||
impl<'a> OptimisticRepeatInstallCheck<'a> {
|
||||
/// The projects whose commands the importers link, per
|
||||
/// [`RepeatInstallManifests::workspace_projects`].
|
||||
pub(crate) fn bin_projects(&self) -> &'a [(PathBuf, &'a PackageManifest)] {
|
||||
self.manifests.workspace_projects.unwrap_or(self.project_manifests.as_slice())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RepeatInstallLayout<'a> {
|
||||
pub node_linker: NodeLinker,
|
||||
pub included: IncludedDependencies,
|
||||
/// The CLI-merged effective `supportedArchitectures` this run would
|
||||
/// install with (yaml plus `--cpu` / `--os` / `--libc`), compared
|
||||
/// against the recorded value like `included`.
|
||||
pub supported_architectures: Option<&'a SupportedArchitectures>,
|
||||
}
|
||||
|
||||
/// The projects a gate holds to being installed. Whatever the gate reports
|
||||
/// must be something the install it leads to can fix: a filtered install
|
||||
/// leaves every project outside its selection as it is.
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum Scope<'a> {
|
||||
/// Every project, for an unfiltered install.
|
||||
Workspace,
|
||||
/// The selected projects and the workspace projects they depend on, for a
|
||||
/// filtered install or a filtered command.
|
||||
Selected(&'a [&'a Path]),
|
||||
/// An unfiltered command run from the selected projects. Its install
|
||||
/// covers the whole workspace, so a project whose recorded install went
|
||||
/// stale is held anywhere. A project never installed is held only where
|
||||
/// the selection depends on it, so an install filtered to other projects
|
||||
/// does not make every command install again.
|
||||
Command(&'a [&'a Path]),
|
||||
}
|
||||
|
||||
pub(crate) fn manifest_has_runtime_deps(manifest: &PackageManifest) -> bool {
|
||||
let value = manifest.value();
|
||||
[value.get("dependencies"), value.get("devDependencies"), value.get("optionalDependencies")]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.any(|deps| {
|
||||
deps.as_object()
|
||||
.is_some_and(|map| !map.is_empty())
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether the manifest declares at least one peer that is not marked
|
||||
/// optional. The caller decides whether `autoInstallPeers` fetches them.
|
||||
pub(crate) fn manifest_has_required_peers(manifest: &PackageManifest) -> bool {
|
||||
let Some(peers) = manifest
|
||||
.value()
|
||||
.get("peerDependencies")
|
||||
.and_then(serde_json::Value::as_object)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let meta = manifest
|
||||
.value()
|
||||
.get("peerDependenciesMeta")
|
||||
.and_then(serde_json::Value::as_object);
|
||||
peers
|
||||
.keys()
|
||||
.any(|name| !peer_dependency_is_optional(meta, name))
|
||||
}
|
||||
|
||||
fn peer_dependency_is_optional(
|
||||
meta: Option<&serde_json::Map<String, serde_json::Value>>,
|
||||
name: &str,
|
||||
) -> bool {
|
||||
meta.and_then(|meta| meta.get(name))
|
||||
.and_then(serde_json::Value::as_object)
|
||||
.and_then(|entry| entry.get("optional"))
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
== Some(true)
|
||||
}
|
||||
|
||||
/// The pnpmfile list recorded in the workspace state and compared by
|
||||
/// the freshness check: every pnpmfile the install loads. An install
|
||||
/// that ignores the pnpmfile records none, so the next install that
|
||||
/// honors it again sees the list change and re-validates.
|
||||
pub(crate) fn current_pnpmfiles(workspace_root: &Path, config: &Config) -> Vec<String> {
|
||||
if config.ignore_pnpmfile {
|
||||
return Vec::new();
|
||||
}
|
||||
crate::find_install_pnpmfiles(config, workspace_root)
|
||||
.into_iter()
|
||||
.map(|path| path.to_string_lossy().into_owned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Whether no current project dir is a key of `state`.
|
||||
pub(crate) fn recorded_elsewhere(
|
||||
state: &WorkspaceState,
|
||||
project_manifests: &[(PathBuf, &PackageManifest)],
|
||||
) -> bool {
|
||||
!project_manifests
|
||||
.iter()
|
||||
.any(|(root_dir, _)| state.projects.contains_key(root_dir.to_string_lossy().as_ref()))
|
||||
}
|
||||
|
||||
/// Whether `current` already records what materializing `wanted` would
|
||||
/// produce.
|
||||
///
|
||||
/// The current lockfile keeps only what the importers reach
|
||||
/// ([`crate::filter_lockfile_for_current`]) and none of the top-level keys
|
||||
/// pnpm does not define, so a wanted lockfile carrying a snapshot no
|
||||
/// importer reaches any more, or an embedder's extension block, can never
|
||||
/// equal it. Comparing the same shape both sides is what lets such a tree
|
||||
/// settle instead of re-materializing on every run.
|
||||
///
|
||||
/// The equal case is the common one and answers without building the
|
||||
/// filtered shape at all. Under `config.skip_runtimes` the direct runtimes are
|
||||
/// left out of the shape, so a tree that still holds one does not match.
|
||||
pub(crate) fn materialized_shape_matches(
|
||||
wanted: &Lockfile,
|
||||
current: &Lockfile,
|
||||
included: IncludedDependencies,
|
||||
config: &Config,
|
||||
) -> bool {
|
||||
let mut skipped = crate::SkippedSnapshots::new();
|
||||
if config.skip_runtimes
|
||||
&& let Some(packages) = wanted.packages.as_ref()
|
||||
{
|
||||
crate::add_direct_runtime_skips(&mut skipped, &wanted.importers, packages);
|
||||
}
|
||||
if skipped.is_empty() && wanted == current {
|
||||
return true;
|
||||
}
|
||||
// A transient skip (a failed optional fetch) prunes the current lockfile
|
||||
// further, and its set is not known here. Such a tree falls
|
||||
// through to materialization, which retries the fetch anyway.
|
||||
current
|
||||
== &crate::filter_lockfile_for_current(
|
||||
wanted,
|
||||
&crate::GroupSelection::classify(wanted, included, config.peer_edge_options()),
|
||||
&skipped,
|
||||
)
|
||||
}
|
||||
|
||||
/// Where the hoisted linker nests the dependencies of the workspace project at
|
||||
/// `root_dir` that it cannot hoist to the root. The walker always uses
|
||||
/// `node_modules` here, whatever `modulesDir` says.
|
||||
pub(crate) fn hoisted_project_modules_dir(root_dir: &Path) -> PathBuf {
|
||||
root_dir.join("node_modules")
|
||||
}
|
||||
+12
-6
@@ -6,17 +6,23 @@ pub(crate) use specs::{
|
||||
|
||||
mod injected;
|
||||
|
||||
use super::{
|
||||
CatalogAnchor, CatalogResolutionResult, Catalogs, DependencyGroup, Lockfile,
|
||||
OptimisticRepeatInstallCheck, Path, PathBuf, WantedDependency, resolve_from_catalog,
|
||||
};
|
||||
use super::OptimisticRepeatInstallCheck;
|
||||
use injected::{has_unvouched_injected_dep, settings_add_workspace_path};
|
||||
use pnpm_catalogs_resolver::{
|
||||
CatalogAnchor, CatalogResolutionResult, WantedDependency, resolve_from_catalog,
|
||||
};
|
||||
use pnpm_catalogs_types::Catalogs;
|
||||
use pnpm_config_parse_overrides::VersionOverride;
|
||||
use pnpm_lockfile::{LockfileResolution, PkgName, is_local_tarball_path};
|
||||
use pnpm_lockfile::{Lockfile, LockfileResolution, PkgName, is_local_tarball_path};
|
||||
use pnpm_package_manifest::DependencyGroup;
|
||||
use pnpm_resolving_local_resolver::local_tarball_path;
|
||||
use pnpm_workspace::importer_id_from_root_dir;
|
||||
use ssri::Integrity;
|
||||
use std::{borrow::Cow, collections::HashSet};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::HashSet,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
struct LocalTarballDependency {
|
||||
project_dir: PathBuf,
|
||||
File renamed without changes.
+6
-3
@@ -1,9 +1,12 @@
|
||||
use crate::optimistic_repeat_install::{
|
||||
CatalogAnchor, CatalogResolutionResult, Catalogs, Config, DependencyGroup,
|
||||
IncludedDependencies, WantedDependency, resolve_from_catalog,
|
||||
use pnpm_catalogs_resolver::{
|
||||
CatalogAnchor, CatalogResolutionResult, WantedDependency, resolve_from_catalog,
|
||||
};
|
||||
use pnpm_catalogs_types::Catalogs;
|
||||
use pnpm_config::Config;
|
||||
use pnpm_config_parse_overrides::VersionOverride;
|
||||
use pnpm_lockfile::is_local_tarball_path;
|
||||
use pnpm_modules_yaml::IncludedDependencies;
|
||||
use pnpm_package_manifest::DependencyGroup;
|
||||
|
||||
/// Whether a `catalog:` spec dereferences (through the workspace
|
||||
/// catalogs) to a local file specifier. A misconfigured catalog entry
|
||||
File renamed without changes.
+2
-2
@@ -1,6 +1,6 @@
|
||||
//! How the repeat-install check reads the project manifests it is handed.
|
||||
|
||||
use crate::install::ProjectManifestsByDir;
|
||||
use super::ProjectManifestsByDir;
|
||||
use pnpm_hooks::PnpmfileHooks;
|
||||
use pnpm_package_manifest::PackageManifest;
|
||||
use std::{
|
||||
@@ -27,7 +27,7 @@ impl<'a> DeclaredManifests<'a> {
|
||||
}
|
||||
|
||||
/// Manifests some of which the last validation's records stand in for,
|
||||
/// per [`crate::optimistic_repeat_install::validation::manifest_stand_ins`].
|
||||
/// per [`crate::install_state::validation::manifest_stand_ins`].
|
||||
/// `recorded_digests` holds the digest of each of those, by project
|
||||
/// directory. The check then never writes: what it would write is
|
||||
/// derived from the files.
|
||||
+12
-43
@@ -1,6 +1,10 @@
|
||||
//! Comparing the settings a previous install recorded against the current ones.
|
||||
|
||||
pub(crate) use install_policy::recorded_extend_node_path_matches;
|
||||
pub(crate) use recorded_values::{
|
||||
catalogs_to_json, empty_json_object, filter_null_object_values,
|
||||
link_workspace_packages_to_json, map_node_linker,
|
||||
};
|
||||
pub(crate) use registry_prefixes::{
|
||||
ResolvedImporters, recorded_default_registry, recorded_registry_prefixes,
|
||||
recorded_registry_routes, recorded_registry_routes_after_install, records_registry_keys_of,
|
||||
@@ -10,6 +14,7 @@ pub(crate) use registry_prefixes::{
|
||||
mod agent_skills;
|
||||
mod equivalence;
|
||||
mod install_policy;
|
||||
mod recorded_values;
|
||||
mod registry_prefixes;
|
||||
mod store_location;
|
||||
use agent_skills::agent_skills_settings;
|
||||
@@ -18,12 +23,14 @@ use equivalence::{
|
||||
patched_dependencies_match,
|
||||
};
|
||||
use install_policy::install_policy_settings;
|
||||
use recorded_values::map_trust_policy;
|
||||
|
||||
use super::{
|
||||
Catalogs, Config, IncludedDependencies, LinkWorkspacePackages, NodeLinker, Path,
|
||||
SupportedArchitectures, TrustPolicy, WorkspaceState, WorkspaceStateNodeLinker,
|
||||
WorkspaceStateSettings, WorkspaceStateTrustPolicy,
|
||||
};
|
||||
use pnpm_catalogs_types::Catalogs;
|
||||
use pnpm_config::{Config, NodeLinker};
|
||||
use pnpm_modules_yaml::IncludedDependencies;
|
||||
use pnpm_package_is_installable::SupportedArchitectures;
|
||||
use pnpm_workspace_state::{WorkspaceState, WorkspaceStateSettings};
|
||||
use std::path::Path;
|
||||
|
||||
/// Whether the `supportedArchitectures` recorded by the last install
|
||||
/// matches `live` (today's CLI-merged value). Read from the workspace
|
||||
@@ -426,44 +433,6 @@ pub(crate) fn catalogs_cache_matches(
|
||||
recorded == current
|
||||
}
|
||||
|
||||
pub(crate) fn catalogs_to_json(catalogs: &Catalogs) -> serde_json::Value {
|
||||
serde_json::to_value(catalogs).expect("Catalogs serialize to a JSON object")
|
||||
}
|
||||
|
||||
pub(crate) fn empty_json_object() -> serde_json::Value {
|
||||
serde_json::Value::Object(serde_json::Map::new())
|
||||
}
|
||||
|
||||
pub(crate) fn filter_null_object_values(value: serde_json::Value) -> serde_json::Value {
|
||||
let serde_json::Value::Object(mut map) = value else { return value };
|
||||
map.retain(|_, value| !value.is_null());
|
||||
serde_json::Value::Object(map)
|
||||
}
|
||||
|
||||
pub(crate) fn link_workspace_packages_to_json(value: LinkWorkspacePackages) -> serde_json::Value {
|
||||
match value {
|
||||
LinkWorkspacePackages::Off => serde_json::Value::Bool(false),
|
||||
LinkWorkspacePackages::DirectOnly => serde_json::Value::Bool(true),
|
||||
LinkWorkspacePackages::Deep => serde_json::Value::String("deep".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_node_linker(linker: NodeLinker) -> WorkspaceStateNodeLinker {
|
||||
match linker {
|
||||
NodeLinker::Isolated => WorkspaceStateNodeLinker::Isolated,
|
||||
NodeLinker::Hoisted => WorkspaceStateNodeLinker::Hoisted,
|
||||
NodeLinker::Pnp => WorkspaceStateNodeLinker::Pnp,
|
||||
NodeLinker::Loaded => WorkspaceStateNodeLinker::Loaded,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_trust_policy(policy: TrustPolicy) -> WorkspaceStateTrustPolicy {
|
||||
match policy {
|
||||
TrustPolicy::Off => WorkspaceStateTrustPolicy::Off,
|
||||
TrustPolicy::NoDowngrade => WorkspaceStateTrustPolicy::NoDowngrade,
|
||||
}
|
||||
}
|
||||
|
||||
fn recorded_overrides(config: &Config) -> Option<std::collections::BTreeMap<String, String>> {
|
||||
config.overrides
|
||||
.as_ref()
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,45 @@
|
||||
//! Config values in the form the workspace state records them.
|
||||
|
||||
use pnpm_catalogs_types::Catalogs;
|
||||
use pnpm_config::{LinkWorkspacePackages, NodeLinker, TrustPolicy};
|
||||
use pnpm_workspace_state::{
|
||||
NodeLinker as WorkspaceStateNodeLinker, TrustPolicy as WorkspaceStateTrustPolicy,
|
||||
};
|
||||
|
||||
pub(crate) fn catalogs_to_json(catalogs: &Catalogs) -> serde_json::Value {
|
||||
serde_json::to_value(catalogs).expect("Catalogs serialize to a JSON object")
|
||||
}
|
||||
|
||||
pub(crate) fn empty_json_object() -> serde_json::Value {
|
||||
serde_json::Value::Object(serde_json::Map::new())
|
||||
}
|
||||
|
||||
pub(crate) fn filter_null_object_values(value: serde_json::Value) -> serde_json::Value {
|
||||
let serde_json::Value::Object(mut map) = value else { return value };
|
||||
map.retain(|_, value| !value.is_null());
|
||||
serde_json::Value::Object(map)
|
||||
}
|
||||
|
||||
pub(crate) fn link_workspace_packages_to_json(value: LinkWorkspacePackages) -> serde_json::Value {
|
||||
match value {
|
||||
LinkWorkspacePackages::Off => serde_json::Value::Bool(false),
|
||||
LinkWorkspacePackages::DirectOnly => serde_json::Value::Bool(true),
|
||||
LinkWorkspacePackages::Deep => serde_json::Value::String("deep".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_node_linker(linker: NodeLinker) -> WorkspaceStateNodeLinker {
|
||||
match linker {
|
||||
NodeLinker::Isolated => WorkspaceStateNodeLinker::Isolated,
|
||||
NodeLinker::Hoisted => WorkspaceStateNodeLinker::Hoisted,
|
||||
NodeLinker::Pnp => WorkspaceStateNodeLinker::Pnp,
|
||||
NodeLinker::Loaded => WorkspaceStateNodeLinker::Loaded,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn map_trust_policy(policy: TrustPolicy) -> WorkspaceStateTrustPolicy {
|
||||
match policy {
|
||||
TrustPolicy::Off => WorkspaceStateTrustPolicy::Off,
|
||||
TrustPolicy::NoDowngrade => WorkspaceStateTrustPolicy::NoDowngrade,
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
+8
-1
@@ -1,7 +1,14 @@
|
||||
//! Modification times, and the baseline a recorded validation is compared against.
|
||||
|
||||
use super::{Config, Lockfile, PackageManifest, Path, PathBuf, SystemTime, fs};
|
||||
use pnpm_config::Config;
|
||||
use pnpm_lockfile::Lockfile;
|
||||
use pnpm_modules_yaml::Clock;
|
||||
use pnpm_package_manifest::PackageManifest;
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
pub(crate) const NANOS_PER_MILLI: i64 = 1_000_000;
|
||||
|
||||
+6
-3
@@ -9,21 +9,24 @@
|
||||
//! others survive it unchanged.
|
||||
|
||||
pub(crate) use bins::{
|
||||
dedicated_bin_projects, held_workspace_bins_match, links_workspace_commands,
|
||||
project_bins_digest, project_bins_match, workspace_bins_digest,
|
||||
dedicated_bin_projects, links_workspace_commands, project_bins_digest, project_bins_match,
|
||||
workspace_bins_digest,
|
||||
};
|
||||
pub(crate) use file_stat::{FileStat, trusts_every_file};
|
||||
pub(crate) use local_dependencies::changed as local_dependency_changed;
|
||||
pub(crate) use manifest_files::{ManifestStandIns, manifest_stand_ins, records_need_refresh};
|
||||
|
||||
use super::{Config, Lockfile, PackageManifest, Path, PathBuf};
|
||||
use file_stat::{signature_trusted, stat_trusted};
|
||||
use pnpm_config::Config;
|
||||
use pnpm_lockfile::Lockfile;
|
||||
use pnpm_package_manifest::PackageManifest;
|
||||
use pnpm_workspace::importer_id_from_root_dir;
|
||||
use pnpm_workspace_state::{FileSignature, Validation, WorkspaceState};
|
||||
use sha2::{Digest as _, Sha256};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
mod bins;
|
||||
+2
-24
@@ -1,8 +1,6 @@
|
||||
use super::{
|
||||
Config, PackageManifest, Path, PathBuf, Validation, WorkspaceState, importer_id_from_root_dir,
|
||||
value_digest,
|
||||
Config, PackageManifest, Path, PathBuf, Validation, importer_id_from_root_dir, value_digest,
|
||||
};
|
||||
use crate::optimistic_repeat_install::{ManifestStat, OptimisticRepeatInstallCheck};
|
||||
|
||||
pub(crate) fn workspace_bins_digest(
|
||||
workspace_root: &Path,
|
||||
@@ -49,7 +47,7 @@ pub(crate) fn project_bins_digest<'a>(
|
||||
/// Whether the project declares a dependency or a peer, through which a
|
||||
/// workspace project's commands reach its `node_modules/.bin`.
|
||||
pub(crate) fn links_workspace_commands(manifest: &PackageManifest) -> bool {
|
||||
crate::optimistic_repeat_install::manifest_has_runtime_deps(manifest)
|
||||
crate::install_state::manifest_has_runtime_deps(manifest)
|
||||
|| manifest
|
||||
.value()
|
||||
.get("peerDependencies")
|
||||
@@ -70,26 +68,6 @@ pub(crate) fn project_bins_match(
|
||||
validation.workspace_bins.get(importer_id).map(String::as_str) == bins
|
||||
}
|
||||
|
||||
pub(crate) fn held_workspace_bins_match(
|
||||
check: &OptimisticRepeatInstallCheck<'_>,
|
||||
state: &WorkspaceState,
|
||||
held: &[&ManifestStat<'_>],
|
||||
) -> bool {
|
||||
if held.is_empty() || state.validation.is_none() {
|
||||
return true;
|
||||
}
|
||||
let bins = workspace_bins_digest(check.workspace_root, check.bin_projects());
|
||||
held.iter()
|
||||
.all(|stat| {
|
||||
let id = importer_id_from_root_dir(check.workspace_root, stat.root_dir);
|
||||
project_bins_match(
|
||||
state.validation.as_ref(),
|
||||
&id,
|
||||
project_bins_digest(&id, stat.manifest, bins.as_deref()),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// [`crate::RepeatInstallManifests::workspace_projects`] for an install with
|
||||
/// dedicated lockfiles whose projects link workspace commands. `None` with a
|
||||
/// shared lockfile, whose projects already are the whole workspace.
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -76,7 +76,7 @@ fn recordable(
|
||||
return false;
|
||||
}
|
||||
let now = *filesystem_now_ns.get_or_insert_with(|| {
|
||||
crate::optimistic_repeat_install::filesystem_now_ns_in_modules(
|
||||
crate::install_state::filesystem_now_ns_in_modules(
|
||||
&check.config.workspace_state_modules_dir(check.workspace_root),
|
||||
)
|
||||
});
|
||||
File renamed without changes.
@@ -17,6 +17,7 @@ pub(crate) use hoisted_selection::{
|
||||
selected_materialization_closure, selected_materialization_ids,
|
||||
};
|
||||
pub use install::*;
|
||||
pub use install_state::*;
|
||||
pub use install_with_fresh_lockfile::*;
|
||||
pub use link_manifest_link_deps::*;
|
||||
pub use linked_peer_dependencies::linked_peer_dependencies_warning;
|
||||
@@ -70,6 +71,8 @@ mod fast_update_patched_dependencies;
|
||||
mod fast_update_settings;
|
||||
mod hoisted_selection;
|
||||
mod install;
|
||||
mod install_entry;
|
||||
mod install_state;
|
||||
mod install_with_fresh_lockfile;
|
||||
mod link_manifest_link_deps;
|
||||
mod linked_peer_dependencies;
|
||||
|
||||
@@ -14,43 +14,35 @@
|
||||
pub(crate) mod conflict_markers;
|
||||
pub(crate) mod deps_status;
|
||||
pub(crate) mod freshness;
|
||||
pub(crate) mod local_file_deps;
|
||||
pub(crate) mod manifest_agreement;
|
||||
pub(crate) mod settings;
|
||||
pub(crate) mod timestamps;
|
||||
pub(crate) mod validation;
|
||||
pub(crate) use conflict_markers::{
|
||||
LockfileConflictCheckFailure, first_lockfile_requiring_conflict_safe_install,
|
||||
};
|
||||
pub(crate) use current_lockfile::materialized_shape_matches;
|
||||
pub use deps_status::{RunDepsStatus, check_deps_status_before_run};
|
||||
pub(crate) use freshness::Scope;
|
||||
pub(crate) use local_file_deps::{
|
||||
FrozenLocalTarballCheck, frozen_local_tarballs_to_verify, has_local_file_dep_requiring_install,
|
||||
has_local_file_override, has_local_file_package_extension,
|
||||
};
|
||||
pub(crate) use manifest_agreement::{ManifestStat, held_projects_match_lockfile};
|
||||
pub use manifest_source::{DeclaredManifests, ManifestHooks, RepeatInstallManifests};
|
||||
pub(crate) use modules_dirs::{hoisted_project_modules_dir, project_id};
|
||||
pub(crate) use relocation::recorded_elsewhere;
|
||||
pub(crate) use settings::{
|
||||
ResolvedImporters, catalogs_cache_matches, current_settings_with_catalogs, first_setting_drift,
|
||||
recorded_default_registry, recorded_extend_node_path_matches, recorded_registry_prefixes,
|
||||
recorded_registry_routes_after_install, recorded_supported_architectures_match,
|
||||
records_registry_keys_of,
|
||||
pub(crate) use modules_dirs::project_id;
|
||||
pub use up_to_date::{
|
||||
GateWorkspace, UpToDateFastPathCheck, UpToDateWorkspace, check_deps_status_before_run_at,
|
||||
deps_install_root, install_already_up_to_date, install_already_up_to_date_from_records,
|
||||
install_already_up_to_date_per_project,
|
||||
};
|
||||
pub(crate) use timestamps::{
|
||||
file_mtime, file_mtime_from_metadata, filesystem_now_ms_in_modules,
|
||||
filesystem_now_ns_in_modules, last_validated_timestamp, lockfile_modified_since,
|
||||
modified_at_or_after,
|
||||
|
||||
use crate::install_state::{
|
||||
Decision, OptimisticRepeatInstallCheck, Scope, catalogs_cache_matches, current_pnpmfiles,
|
||||
file_mtime, file_mtime_from_metadata, filesystem_now_ms_in_modules, first_setting_drift,
|
||||
has_local_file_dep_requiring_install, has_local_file_override,
|
||||
has_local_file_package_extension, hoisted_project_modules_dir, local_file_deps,
|
||||
lockfile_modified_since, manifest_has_required_peers, manifest_has_runtime_deps,
|
||||
materialized_shape_matches, modified_at_or_after, recorded_elsewhere, records_registry_keys_of,
|
||||
validation,
|
||||
};
|
||||
|
||||
mod current_lockfile;
|
||||
mod manifest_source;
|
||||
mod modules_dirs;
|
||||
mod project_structure;
|
||||
mod relocation;
|
||||
mod settle;
|
||||
mod up_to_date;
|
||||
use freshness::{
|
||||
Gate, GatePolicy, MissingWantedLockfile, NewerPatch, RecordedGroups, Stale, check_freshness,
|
||||
};
|
||||
@@ -66,98 +58,17 @@ use std::{
|
||||
fs,
|
||||
io::{ErrorKind, Read},
|
||||
path::{Path, PathBuf},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use pnpm_catalogs_resolver::{
|
||||
CatalogAnchor, CatalogResolutionResult, WantedDependency, resolve_from_catalog,
|
||||
};
|
||||
use pnpm_catalogs_types::Catalogs;
|
||||
use pnpm_config::{Config, LinkWorkspacePackages, NodeLinker, TrustPolicy};
|
||||
use pnpm_lockfile::{ImporterDepVersion, Lockfile, MaybeLazyLockfile, ProjectSnapshot};
|
||||
use pnpm_modules_yaml::{Host, IncludedDependencies};
|
||||
use pnpm_package_is_installable::SupportedArchitectures;
|
||||
use pnpm_config::{Config, LinkWorkspacePackages, NodeLinker};
|
||||
use pnpm_lockfile::{ImporterDepVersion, Lockfile, ProjectSnapshot};
|
||||
use pnpm_modules_yaml::Host;
|
||||
use pnpm_package_manifest::{DependencyGroup, PackageManifest};
|
||||
use pnpm_workspace_state::{
|
||||
NodeLinker as WorkspaceStateNodeLinker, TrustPolicy as WorkspaceStateTrustPolicy,
|
||||
WorkspaceState, WorkspaceStateSettings, load_workspace_state,
|
||||
};
|
||||
|
||||
/// Outcome of [`check_optimistic_repeat_install`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Decision {
|
||||
/// The install is fully up to date — emit "Already up to date"
|
||||
/// and exit before any of the install setup runs.
|
||||
UpToDate,
|
||||
/// Fall through to the full install path. `reason` is a short
|
||||
/// diagnostic string surfaced via `tracing::debug!` for
|
||||
/// diagnosability without contaminating the reporter stream.
|
||||
Skipped { reason: &'static str },
|
||||
}
|
||||
|
||||
/// Inputs to [`check_optimistic_repeat_install`].
|
||||
pub struct OptimisticRepeatInstallCheck<'a> {
|
||||
/// The root the install recorded its lockfile and workspace state
|
||||
/// against, which importer ids and relative local `pnpm.overrides`
|
||||
/// targets are named from too. The directory containing
|
||||
/// `pnpm-workspace.yaml` (or the project root when no workspace
|
||||
/// manifest exists — same fallback as
|
||||
/// [`Install::run`](crate::Install::run)), unless the configuration
|
||||
/// pins the lockfile somewhere else.
|
||||
pub workspace_root: &'a Path,
|
||||
pub config: &'a Config,
|
||||
/// Every importer's `(root_dir, manifest)` pair. For a
|
||||
/// single-project install it's just the root manifest; for a
|
||||
/// workspace install it's every project the resolver would
|
||||
/// otherwise walk. The caller passes this in (rather than this
|
||||
/// function rediscovering it) so the same walk seeds the regular
|
||||
/// install path on the fall-through.
|
||||
pub project_manifests: DeclaredManifests<'a>,
|
||||
/// `true` when a `pnpm-workspace.yaml` drives the install — that
|
||||
/// selects the workspace branch, which keys the manifest and
|
||||
/// lockfile comparisons off `lastValidatedTimestamp`. `false` (no
|
||||
/// workspace manifest) selects the single-project branch, which
|
||||
/// additionally requires `pnpm-lock.yaml` to exist on disk —
|
||||
/// `RUN_CHECK_DEPS_LOCKFILE_NOT_FOUND` is raised otherwise, which
|
||||
/// resolves to not-up-to-date — and keys its comparisons off the
|
||||
/// lockfile mtimes instead.
|
||||
pub is_workspace_install: bool,
|
||||
/// The wanted lockfile (`None` once loaded when `pnpm-lock.yaml`
|
||||
/// is absent or empty). Consulted only by the modified-manifests
|
||||
/// content re-check; the pure-mtime fast path never parses it —
|
||||
/// which is why it arrives lazily, so the common repeat-install
|
||||
/// run skips the YAML parse entirely. A separately bounded byte
|
||||
/// scan only runs when lockfile metadata changed. When absent and
|
||||
/// `<install_state_dir>/lock.yaml` exists, the current lockfile
|
||||
/// stands in as the wanted one — it records exactly what the
|
||||
/// previous install materialized — and `pnpm-lock.yaml` is
|
||||
/// regenerated from it before the check reports up-to-date.
|
||||
pub lockfile: MaybeLazyLockfile<'a>,
|
||||
/// Catalogs from the workspace manifest or an `updateConfig`
|
||||
/// pnpmfile hook, for resolving `catalog:` values inside
|
||||
/// `pnpm.overrides` before the lockfile settings comparison.
|
||||
pub catalogs: &'a Catalogs,
|
||||
pub layout: RepeatInstallLayout<'a>,
|
||||
pub manifests: RepeatInstallManifests<'a>,
|
||||
}
|
||||
|
||||
impl<'a> OptimisticRepeatInstallCheck<'a> {
|
||||
/// The projects whose commands the importers link, per
|
||||
/// [`RepeatInstallManifests::workspace_projects`].
|
||||
pub(crate) fn bin_projects(&self) -> &'a [(PathBuf, &'a PackageManifest)] {
|
||||
self.manifests.workspace_projects.unwrap_or(self.project_manifests.as_slice())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RepeatInstallLayout<'a> {
|
||||
pub node_linker: NodeLinker,
|
||||
pub included: IncludedDependencies,
|
||||
/// The CLI-merged effective `supportedArchitectures` this run would
|
||||
/// install with (yaml plus `--cpu` / `--os` / `--libc`), compared
|
||||
/// against the recorded value like `included`.
|
||||
pub supported_architectures: Option<&'a SupportedArchitectures>,
|
||||
}
|
||||
use pnpm_workspace_state::{WorkspaceState, load_workspace_state};
|
||||
|
||||
/// Run the workspace-state freshness fast path. Returns
|
||||
/// [`Decision::UpToDate`] when the install can short-circuit.
|
||||
@@ -319,47 +230,6 @@ fn recorded_local_dependency_changed(
|
||||
.is_some_and(|validation| validation::local_dependency_changed(validation, &workspace_dirs))
|
||||
}
|
||||
|
||||
pub(crate) fn manifest_has_runtime_deps(manifest: &PackageManifest) -> bool {
|
||||
let value = manifest.value();
|
||||
[value.get("dependencies"), value.get("devDependencies"), value.get("optionalDependencies")]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.any(|deps| {
|
||||
deps.as_object()
|
||||
.is_some_and(|map| !map.is_empty())
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether the manifest declares at least one peer that is not marked
|
||||
/// optional. The caller decides whether `autoInstallPeers` fetches them.
|
||||
pub(crate) fn manifest_has_required_peers(manifest: &PackageManifest) -> bool {
|
||||
let Some(peers) = manifest
|
||||
.value()
|
||||
.get("peerDependencies")
|
||||
.and_then(serde_json::Value::as_object)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let meta = manifest
|
||||
.value()
|
||||
.get("peerDependenciesMeta")
|
||||
.and_then(serde_json::Value::as_object);
|
||||
peers
|
||||
.keys()
|
||||
.any(|name| !peer_dependency_is_optional(meta, name))
|
||||
}
|
||||
|
||||
fn peer_dependency_is_optional(
|
||||
meta: Option<&serde_json::Map<String, serde_json::Value>>,
|
||||
name: &str,
|
||||
) -> bool {
|
||||
meta.and_then(|meta| meta.get(name))
|
||||
.and_then(serde_json::Value::as_object)
|
||||
.and_then(|entry| entry.get("optional"))
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
== Some(true)
|
||||
}
|
||||
|
||||
fn manifest_string_field(manifest: &PackageManifest, key: &str) -> Option<String> {
|
||||
manifest
|
||||
.value()
|
||||
@@ -420,20 +290,6 @@ fn held_selection<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// The pnpmfile list recorded in the workspace state and compared by
|
||||
/// the freshness check: every pnpmfile the install loads. An install
|
||||
/// that ignores the pnpmfile records none, so the next install that
|
||||
/// honors it again sees the list change and re-validates.
|
||||
pub(crate) fn current_pnpmfiles(workspace_root: &Path, config: &Config) -> Vec<String> {
|
||||
if config.ignore_pnpmfile {
|
||||
return Vec::new();
|
||||
}
|
||||
crate::find_install_pnpmfiles(config, workspace_root)
|
||||
.into_iter()
|
||||
.map(|path| path.to_string_lossy().into_owned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// How the pnpmfiles changed since the last validation, in pnpm's issue
|
||||
/// wording: the recorded list must match the current one, and every recorded
|
||||
/// pnpmfile must still exist with the content it was validated with.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::{DependencyGroup, Lockfile, OptimisticRepeatInstallCheck, Path, WorkspaceState};
|
||||
use pnpm_config::Config;
|
||||
use pnpm_modules_yaml::IncludedDependencies;
|
||||
use std::{cell::OnceCell, collections::HashSet};
|
||||
|
||||
@@ -69,45 +68,6 @@ pub(crate) fn assert_loaded_current_lockfile_records(
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `current` already records what materializing `wanted` would
|
||||
/// produce.
|
||||
///
|
||||
/// The current lockfile keeps only what the importers reach
|
||||
/// ([`crate::filter_lockfile_for_current`]) and none of the top-level keys
|
||||
/// pnpm does not define, so a wanted lockfile carrying a snapshot no
|
||||
/// importer reaches any more, or an embedder's extension block, can never
|
||||
/// equal it. Comparing the same shape both sides is what lets such a tree
|
||||
/// settle instead of re-materializing on every run.
|
||||
///
|
||||
/// The equal case is the common one and answers without building the
|
||||
/// filtered shape at all. Under `config.skip_runtimes` the direct runtimes are
|
||||
/// left out of the shape, so a tree that still holds one does not match.
|
||||
pub(crate) fn materialized_shape_matches(
|
||||
wanted: &Lockfile,
|
||||
current: &Lockfile,
|
||||
included: IncludedDependencies,
|
||||
config: &Config,
|
||||
) -> bool {
|
||||
let mut skipped = crate::SkippedSnapshots::new();
|
||||
if config.skip_runtimes
|
||||
&& let Some(packages) = wanted.packages.as_ref()
|
||||
{
|
||||
crate::add_direct_runtime_skips(&mut skipped, &wanted.importers, packages);
|
||||
}
|
||||
if skipped.is_empty() && wanted == current {
|
||||
return true;
|
||||
}
|
||||
// A transient skip (a failed optional fetch) prunes the current lockfile
|
||||
// further, and its set is not known here. Such a tree falls
|
||||
// through to materialization, which retries the fetch anyway.
|
||||
current
|
||||
== &crate::filter_lockfile_for_current(
|
||||
wanted,
|
||||
&crate::GroupSelection::classify(wanted, included, config.peer_edge_options()),
|
||||
&skipped,
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether `current` records exactly what `wanted` materializes for
|
||||
/// `importer_ids` in the dependency groups `state` recorded. Every group
|
||||
/// `current` records counts, so a tree that still holds a group the install
|
||||
|
||||
@@ -9,7 +9,7 @@ use missing_lockfile::settle_missing_wanted_lockfile;
|
||||
use registry_keys::state_unverified_by_changed_lockfile;
|
||||
|
||||
use super::{
|
||||
LockfileConflictCheckFailure, OptimisticRepeatInstallCheck, Path, PathBuf, WorkspaceState,
|
||||
LockfileConflictCheckFailure, OptimisticRepeatInstallCheck, PathBuf, Scope, WorkspaceState,
|
||||
catalogs_cache_matches,
|
||||
current_lockfile::CurrentLockfile,
|
||||
current_lockfile_file_has_content,
|
||||
@@ -81,24 +81,6 @@ pub(super) enum RecordedGroups {
|
||||
Kept,
|
||||
}
|
||||
|
||||
/// The projects a gate holds to being installed. Whatever the gate reports
|
||||
/// must be something the install it leads to can fix: a filtered install
|
||||
/// leaves every project outside its selection as it is.
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum Scope<'a> {
|
||||
/// Every project, for an unfiltered install.
|
||||
Workspace,
|
||||
/// The selected projects and the workspace projects they depend on, for a
|
||||
/// filtered install or a filtered command.
|
||||
Selected(&'a [&'a Path]),
|
||||
/// An unfiltered command run from the selected projects. Its install
|
||||
/// covers the whole workspace, so a project whose recorded install went
|
||||
/// stale is held anywhere. A project never installed is held only where
|
||||
/// the selection depends on it, so an install filtered to other projects
|
||||
/// does not make every command install again.
|
||||
Command(&'a [&'a Path]),
|
||||
}
|
||||
|
||||
/// Why [`check_freshness`] cannot report the tree current. Each gate words it
|
||||
/// for its own audience.
|
||||
pub(crate) enum Stale {
|
||||
@@ -157,7 +139,7 @@ pub(super) fn check_freshness(
|
||||
check_static_freshness(check, state, policy, scope)?;
|
||||
let drift = InputDrift::compute(check, state);
|
||||
let held = drift.held(check, scope);
|
||||
if !super::validation::held_workspace_bins_match(check, state, &held) {
|
||||
if !drift::held_workspace_bins_match(check, state, &held) {
|
||||
return Err(Stale::Content("workspace package bins changed since the last install"));
|
||||
}
|
||||
stand_in_writes::refuse(check, moved || !held.is_empty() || drift.patches_modified)?;
|
||||
|
||||
@@ -5,7 +5,9 @@ use super::{
|
||||
super::{
|
||||
ManifestStat, OptimisticRepeatInstallCheck, Path, PathBuf, WorkspaceState,
|
||||
current_lockfile::CurrentLockfile,
|
||||
validation::{FileDigest, FileDigests},
|
||||
validation::{
|
||||
FileDigest, FileDigests, project_bins_digest, project_bins_match, workspace_bins_digest,
|
||||
},
|
||||
},
|
||||
Scope,
|
||||
};
|
||||
@@ -208,7 +210,7 @@ fn project_stats<'a>(
|
||||
if record.lockfile != lockfile_digest {
|
||||
ProjectStatus::LockfileChanged
|
||||
} else if record.manifest != digest
|
||||
|| !super::super::validation::project_bins_match(
|
||||
|| !project_bins_match(
|
||||
recorded,
|
||||
&importer_id,
|
||||
super::super::validation::project_bins_digest(
|
||||
@@ -375,3 +377,23 @@ fn reached_importers(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub(super) fn held_workspace_bins_match(
|
||||
check: &OptimisticRepeatInstallCheck<'_>,
|
||||
state: &WorkspaceState,
|
||||
held: &[&ManifestStat<'_>],
|
||||
) -> bool {
|
||||
if held.is_empty() || state.validation.is_none() {
|
||||
return true;
|
||||
}
|
||||
let bins = workspace_bins_digest(check.workspace_root, check.bin_projects());
|
||||
held.iter()
|
||||
.all(|stat| {
|
||||
let id = importer_id_from_root_dir(check.workspace_root, stat.root_dir);
|
||||
project_bins_match(
|
||||
state.validation.as_ref(),
|
||||
&id,
|
||||
project_bins_digest(&id, stat.manifest, bins.as_deref()),
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -3,8 +3,8 @@ pub(super) mod direct_links;
|
||||
pub(super) use direct_links::direct_dependency_link_dangling;
|
||||
|
||||
use super::{
|
||||
OptimisticRepeatInstallCheck, manifest_has_required_peers, manifest_has_runtime_deps,
|
||||
manifest_string_field,
|
||||
OptimisticRepeatInstallCheck, hoisted_project_modules_dir, manifest_has_required_peers,
|
||||
manifest_has_runtime_deps, manifest_string_field,
|
||||
};
|
||||
use pnpm_config::{Config, NodeLinker};
|
||||
use pnpm_fs::lexical_normalize;
|
||||
@@ -109,13 +109,6 @@ fn first_project_missing_recorded_hoisted_modules_dir(
|
||||
})
|
||||
}
|
||||
|
||||
/// Where the hoisted linker nests the dependencies of the workspace project at
|
||||
/// `root_dir` that it cannot hoist to the root. The walker always uses
|
||||
/// `node_modules` here, whatever `modulesDir` says.
|
||||
pub(crate) fn hoisted_project_modules_dir(root_dir: &Path) -> PathBuf {
|
||||
root_dir.join("node_modules")
|
||||
}
|
||||
|
||||
/// A project's `name`, falling back to its root directory.
|
||||
pub(crate) fn project_id(root_dir: &Path, manifest: &PackageManifest) -> String {
|
||||
manifest_string_field(manifest, "name")
|
||||
|
||||
@@ -9,7 +9,7 @@ use super::{
|
||||
current_pnpmfiles,
|
||||
freshness::{InputDrift, validation_now},
|
||||
manifest_agreement::check_projects_content,
|
||||
project_structure_matches,
|
||||
project_structure_matches, recorded_elsewhere,
|
||||
};
|
||||
use std::{collections::BTreeSet, path::Component};
|
||||
|
||||
@@ -45,16 +45,6 @@ pub(super) fn relocated_state(
|
||||
project_structure_matches(&relocated, project_manifests, config, catalogs).then_some(relocated)
|
||||
}
|
||||
|
||||
/// Whether no current project dir is a key of `state`.
|
||||
pub(crate) fn recorded_elsewhere(
|
||||
state: &WorkspaceState,
|
||||
project_manifests: &[(PathBuf, &PackageManifest)],
|
||||
) -> bool {
|
||||
!project_manifests
|
||||
.iter()
|
||||
.any(|(root_dir, _)| state.projects.contains_key(root_dir.to_string_lossy().as_ref()))
|
||||
}
|
||||
|
||||
/// The unique old root reproducing every current workspace-relative project
|
||||
/// path. The workspace root itself need not have a manifest or recorded entry.
|
||||
fn recorded_root<'a>(
|
||||
|
||||
@@ -35,8 +35,8 @@ mod settings_classification;
|
||||
use super::{
|
||||
Decision, OptimisticRepeatInstallCheck, check_optimistic_repeat_install,
|
||||
deps_status::{RunDepsStatus, check_deps_status_before_run},
|
||||
settings::current_settings,
|
||||
};
|
||||
use crate::install_state::settings::current_settings;
|
||||
use pnpm_catalogs_types::Catalogs;
|
||||
use pnpm_config::Config;
|
||||
use pnpm_lockfile::{Lockfile, MaybeLazyLockfile};
|
||||
@@ -427,9 +427,7 @@ fn record_test_workspace_state(
|
||||
carried: crate::install::CarriedSettings {
|
||||
established_auto_dedupe: config.auto_dedupe.then_some(true),
|
||||
registry_keys_verified: true,
|
||||
registry_routes: Some(
|
||||
crate::optimistic_repeat_install::settings::recorded_registry_routes(config),
|
||||
),
|
||||
registry_routes: Some(crate::install_state::settings::recorded_registry_routes(config)),
|
||||
kept_settings: None,
|
||||
},
|
||||
vouched: &super::validation::Vouched::All,
|
||||
@@ -584,8 +582,10 @@ fn date_lockfile_after(workspace_root: &std::path::Path, recorded: i64) {
|
||||
/// that records a file records a stat the next check trusts.
|
||||
fn wait_for_the_next_filesystem_tick(workspace_root: &std::path::Path) {
|
||||
let now = || {
|
||||
super::timestamps::filesystem_now_ns_in_modules(&workspace_root.join("node_modules"))
|
||||
.unwrap()
|
||||
crate::install_state::timestamps::filesystem_now_ns_in_modules(&workspace_root.join(
|
||||
"node_modules",
|
||||
))
|
||||
.unwrap()
|
||||
};
|
||||
let written = now();
|
||||
while now() <= written {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use super::{
|
||||
super::{
|
||||
Decision, deps_status::RunDepsStatus, first_setting_drift, settings::current_settings,
|
||||
},
|
||||
super::{Decision, deps_status::RunDepsStatus, first_setting_drift},
|
||||
MTIME_STEP_MS, backdate_validated_files, check, isolated_included, recorded_timestamp,
|
||||
single_project_deps_status, workspace_deps_status, write_empty_lockfile, write_state,
|
||||
};
|
||||
use crate::install_state::settings::current_settings;
|
||||
use pnpm_config::Config;
|
||||
use pnpm_lockfile::Lockfile;
|
||||
use pnpm_package_manifest::PackageManifest;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use super::{
|
||||
super::{
|
||||
Decision, OptimisticRepeatInstallCheck, check_optimistic_repeat_install,
|
||||
settings::current_settings_with_catalogs,
|
||||
},
|
||||
super::{Decision, OptimisticRepeatInstallCheck, check_optimistic_repeat_install},
|
||||
backdate_validated_files, check_with_catalogs, check_workspace, isolated_included,
|
||||
setup_fresh_install, setup_fresh_install_with_config, write_empty_lockfile, write_state,
|
||||
};
|
||||
use crate::install_state::settings::current_settings_with_catalogs;
|
||||
use indexmap::IndexMap;
|
||||
use pnpm_catalogs_types::Catalogs;
|
||||
use pnpm_config::Config;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use super::{
|
||||
super::{Decision, current_pnpmfiles, settings::current_settings},
|
||||
super::{Decision, current_pnpmfiles},
|
||||
backdate_validated_files, check, isolated_included, setup_fresh_install,
|
||||
write_state_with_pnpmfiles,
|
||||
};
|
||||
use crate::install_state::settings::current_settings;
|
||||
use pnpm_workspace_state::ProjectEntry;
|
||||
use std::{collections::BTreeMap, fs};
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ fn injected_sibling_decision(
|
||||
fs::create_dir_all(sibling_dir.join("node_modules")).unwrap();
|
||||
let config = config.leak();
|
||||
|
||||
let settings = super::super::settings::current_settings(
|
||||
let settings = crate::install_state::settings::current_settings(
|
||||
config,
|
||||
pnpm_config::NodeLinker::Isolated,
|
||||
isolated_included(),
|
||||
|
||||
@@ -2,14 +2,16 @@ use super::{
|
||||
super::{
|
||||
Decision, OptimisticRepeatInstallCheck, check_optimistic_repeat_install,
|
||||
deps_status::install_args_from_state,
|
||||
settings::current_settings,
|
||||
timestamps::{FileMtime, modified_at_or_after},
|
||||
},
|
||||
assert_deps_status_converges_after_collision, backdate_validated_files, check,
|
||||
check_with_lockfile, content_check_decision, isolated_included, setup_content_check_project,
|
||||
setup_fresh_install, setup_fresh_install_with_config, validate_existing_files,
|
||||
write_empty_lockfile, write_registry_lockfile, write_state,
|
||||
};
|
||||
use crate::install_state::{
|
||||
settings::current_settings,
|
||||
timestamps::{FileMtime, modified_at_or_after},
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use pnpm_config::Config;
|
||||
use pnpm_lockfile::MaybeLazyLockfile;
|
||||
@@ -528,16 +530,16 @@ fn returns_skipped_when_a_scope_moves_to_another_registry() {
|
||||
},
|
||||
);
|
||||
let project_manifests = [(dir.path().to_path_buf(), &manifest)];
|
||||
let recorded = super::super::recorded_registry_prefixes(recorded_config);
|
||||
let recorded = crate::install_state::recorded_registry_prefixes(recorded_config);
|
||||
assert_eq!(recorded, Some(BTreeMap::from([("work".to_string(), work.to_string())])));
|
||||
|
||||
let mut routed = recorded_config.clone();
|
||||
routed.registries_by_scope.insert("@corp".to_string(), work.to_string());
|
||||
let routed = routed.leak();
|
||||
|
||||
assert_eq!(super::super::recorded_registry_prefixes(routed), recorded);
|
||||
assert_eq!(crate::install_state::recorded_registry_prefixes(routed), recorded);
|
||||
assert_eq!(
|
||||
super::super::settings::recorded_registry_routes(routed).get("@corp"),
|
||||
crate::install_state::settings::recorded_registry_routes(routed).get("@corp"),
|
||||
Some(&work.to_string()),
|
||||
);
|
||||
assert!(matches!(
|
||||
@@ -916,7 +918,7 @@ fn install_args_reproduce_the_recorded_dependency_groups() {
|
||||
|
||||
#[test]
|
||||
fn install_already_up_to_date_bails_before_walking_projects_when_workspace_state_is_missing() {
|
||||
use crate::install::{UpToDateFastPathCheck, install_already_up_to_date};
|
||||
use crate::{UpToDateFastPathCheck, install_already_up_to_date};
|
||||
use pnpm_package_manifest::DependencyGroup;
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use super::{
|
||||
super::{Decision, settings::current_settings},
|
||||
backdate_validated_files, check, isolated_included, write_empty_lockfile, write_state,
|
||||
super::Decision, backdate_validated_files, check, isolated_included, write_empty_lockfile,
|
||||
write_state,
|
||||
};
|
||||
use crate::install_state::settings::current_settings;
|
||||
use pnpm_config::Config;
|
||||
use pnpm_package_manifest::PackageManifest;
|
||||
use pnpm_workspace_state::ProjectEntry;
|
||||
|
||||
@@ -3,8 +3,6 @@ use super::{
|
||||
Decision, OptimisticRepeatInstallCheck, check_optimistic_repeat_install,
|
||||
deps_status::{RunDepsStatus, check_deps_status_before_run},
|
||||
freshness::InputDrift,
|
||||
settings::current_settings,
|
||||
timestamps::{FileMtime, lockfile_modified_since, modified_at_or_after},
|
||||
},
|
||||
FOO_LOCKFILE, FOO_LOCKFILE_WITHOUT_PACKAGES, FOO_MANIFEST, MTIME_STEP_MS,
|
||||
backdate_validated_files, check, check_with_lockfile, check_workspace, content_check_decision,
|
||||
@@ -12,6 +10,10 @@ use super::{
|
||||
setup_content_check_project, setup_fresh_install, setup_fresh_install_with_config,
|
||||
single_project_deps_status, validate_existing_files, write_bare_tarball_lockfile, write_state,
|
||||
};
|
||||
use crate::install_state::{
|
||||
settings::current_settings,
|
||||
timestamps::{FileMtime, lockfile_modified_since, modified_at_or_after},
|
||||
};
|
||||
use pnpm_config::Config;
|
||||
use pnpm_lockfile::{LazyLockfile, Lockfile, MaybeLazyLockfile};
|
||||
use pnpm_package_manifest::PackageManifest;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use super::{
|
||||
super::{
|
||||
Decision, OptimisticRepeatInstallCheck, check_optimistic_repeat_install,
|
||||
settings::current_settings,
|
||||
},
|
||||
super::{Decision, OptimisticRepeatInstallCheck, check_optimistic_repeat_install},
|
||||
backdate_validated_files, isolated_included, setup_fresh_install_with_config, write_state,
|
||||
};
|
||||
use crate::install_state::settings::current_settings;
|
||||
use indexmap::IndexMap;
|
||||
use pnpm_config::{Config, ProjectConfig};
|
||||
use pnpm_lockfile::MaybeLazyLockfile;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::{
|
||||
super::{Decision, settings::current_settings_with_catalogs},
|
||||
backdate_validated_files, check, check_with_catalogs, isolated_included,
|
||||
super::Decision, backdate_validated_files, check, check_with_catalogs, isolated_included,
|
||||
setup_fresh_install_with_config, write_state,
|
||||
};
|
||||
use crate::install_state::settings::current_settings_with_catalogs;
|
||||
use indexmap::IndexMap;
|
||||
use pnpm_catalogs_types::Catalogs;
|
||||
use pnpm_lockfile::Lockfile;
|
||||
@@ -260,7 +260,7 @@ fn returns_up_to_date_when_a_linked_workspace_dependency_is_overridden_and_manif
|
||||
let sibling_manifest =
|
||||
pnpm_package_manifest::PackageManifest::from_path(sibling_dir.join("package.json"))
|
||||
.unwrap();
|
||||
let settings = super::super::settings::current_settings(
|
||||
let settings = crate::install_state::settings::current_settings(
|
||||
config,
|
||||
pnpm_config::NodeLinker::Isolated,
|
||||
isolated_included(),
|
||||
|
||||
@@ -2,11 +2,11 @@ use super::{
|
||||
super::{
|
||||
Decision,
|
||||
manifest_agreement::{LinkedPackagesContext, linked_packages_are_up_to_date},
|
||||
settings::current_settings,
|
||||
},
|
||||
backdate_validated_files, check, isolated_included, linked_sibling_decision,
|
||||
setup_fresh_install_with_config, write_state,
|
||||
};
|
||||
use crate::install_state::settings::current_settings;
|
||||
use indexmap::IndexMap;
|
||||
use pnpm_config::Config;
|
||||
use pnpm_lockfile::Lockfile;
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
//! The other buckets carry the reason, which the test cannot check.
|
||||
|
||||
use super::{super::config_blocks_fast_path, current_settings, isolated_included};
|
||||
use crate::optimistic_repeat_install::first_setting_drift;
|
||||
use crate::install_state::first_setting_drift;
|
||||
use indexmap::IndexMap;
|
||||
use pnpm_config::{Config, LinkWorkspacePackages, NodeLinker, TrustPolicy};
|
||||
use pnpm_workspace_state::WorkspaceState;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use super::{
|
||||
super::{Decision, settings::current_settings},
|
||||
backdate_validated_files, check, isolated_included, setup_fresh_install, write_state,
|
||||
super::Decision, backdate_validated_files, check, isolated_included, setup_fresh_install,
|
||||
write_state,
|
||||
};
|
||||
use crate::install_state::settings::current_settings;
|
||||
use pnpm_config::Config;
|
||||
use pnpm_package_manifest::PackageManifest;
|
||||
use pnpm_workspace_state::ProjectEntry;
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
#[cfg(unix)]
|
||||
use super::FOO_LOCKFILE;
|
||||
use super::{
|
||||
super::{
|
||||
Decision, OptimisticRepeatInstallCheck, check_optimistic_repeat_install,
|
||||
settings::current_settings,
|
||||
},
|
||||
super::{Decision, OptimisticRepeatInstallCheck, check_optimistic_repeat_install},
|
||||
FOO_MANIFEST, RunDepsStatus, assert_content_check_converges_after_collision,
|
||||
backdate_validated_files, check, check_with_lockfile, collide_mtimes_with_recorded_state,
|
||||
content_check_decision, isolated_included, linked_sibling_decision_for_spec,
|
||||
setup_content_check_project, setup_fresh_install, setup_fresh_install_with_config,
|
||||
validate_existing_files, workspace_deps_status, write_local_tarball_lockfile, write_state,
|
||||
};
|
||||
use crate::install_state::settings::current_settings;
|
||||
use pnpm_config::Config;
|
||||
use pnpm_lockfile::{Lockfile, MaybeLazyLockfile};
|
||||
use pnpm_modules_yaml::IncludedDependencies;
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
//! The repeat-install check a command runs on its own, before it sets up an
|
||||
//! install: the `pnpm install` shortcut the CLI takes before starting the
|
||||
//! async runtime, and the verify-deps gate before `pnpm run` and `pnpm exec`.
|
||||
|
||||
pub use discovery::{GateWorkspace, check_deps_status_before_run_at, deps_install_root};
|
||||
pub use per_project::install_already_up_to_date_per_project;
|
||||
|
||||
mod discovery;
|
||||
|
||||
mod from_files;
|
||||
|
||||
mod per_project;
|
||||
|
||||
mod stand_ins;
|
||||
|
||||
mod nothing_to_install;
|
||||
|
||||
use super::{check_optimistic_repeat_install_with_state, config_blocks_fast_path};
|
||||
use crate::{
|
||||
install::{
|
||||
build_project_manifests_list, check_strict_dep_builds_for_fast_path,
|
||||
configured_or_discovered_workspace_dir, effective_node_version, gvs_build_markers_complete,
|
||||
included_dependencies, load_workspace_projects, lockfile_root_for,
|
||||
project_requires_lifecycle_scripts, register_workspace_in_store,
|
||||
},
|
||||
install_state::{Decision, OptimisticRepeatInstallCheck, Scope},
|
||||
};
|
||||
use pnpm_catalogs_config::get_catalogs_from_workspace_manifest;
|
||||
use pnpm_catalogs_types::Catalogs;
|
||||
use pnpm_config::{Config, NodeLinker};
|
||||
use pnpm_executor::PROJECT_LIFECYCLE_STAGES;
|
||||
use pnpm_lockfile::{LazyLockfile, MaybeLazyLockfile};
|
||||
use pnpm_modules_yaml::IncludedDependencies;
|
||||
use pnpm_package_manifest::{DependencyGroup, PackageManifest};
|
||||
use pnpm_workspace_state::{WorkspaceState, load_workspace_state};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Inputs for [`install_already_up_to_date`].
|
||||
pub struct UpToDateFastPathCheck<'a> {
|
||||
pub config: &'a Config,
|
||||
pub manifest: &'a PackageManifest,
|
||||
pub dependency_groups: Vec<DependencyGroup>,
|
||||
pub node_linker: NodeLinker,
|
||||
/// The CLI-merged effective `supportedArchitectures` (yaml plus
|
||||
/// `--cpu` / `--os` / `--libc`) — the fast path must not report
|
||||
/// "Already up to date" when a flag changed the target platforms.
|
||||
pub supported_architectures: Option<pnpm_package_is_installable::SupportedArchitectures>,
|
||||
}
|
||||
|
||||
/// What an up-to-date install reports about the workspace it covered.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct UpToDateWorkspace {
|
||||
/// The workspace root — the reporter `prefix` for the "Already up to
|
||||
/// date" emission.
|
||||
pub root: PathBuf,
|
||||
/// How many projects the workspace has, or `None` when there is no
|
||||
/// `pnpm-workspace.yaml`. Feeds the `pnpm:scope` report, which the
|
||||
/// full install path derives from the same walk.
|
||||
pub project_count: Option<usize>,
|
||||
}
|
||||
|
||||
/// [`crate::check_optimistic_repeat_install`] inputs, callable from a
|
||||
/// synchronous context so the CLI can finish an up-to-date install
|
||||
/// before paying for the async runtime, the HTTP client, and the
|
||||
/// state setup. Returns what the short-circuit covered when the install
|
||||
/// can take it, after registering the workspace in the store as
|
||||
/// [`crate::Install::run`] does.
|
||||
///
|
||||
/// `Err` carries the reason the shortcut was not taken, for the
|
||||
/// `pacquet::install` trace. Failures deliberately collapse into it: the
|
||||
/// caller falls through to the full install path, which reproduces the
|
||||
/// failure with its established error shape.
|
||||
///
|
||||
/// The phases that read the project tree run inside `pacquet::install`
|
||||
/// spans, so a trace of a slow up-to-date install shows which of them took
|
||||
/// the time.
|
||||
pub fn install_already_up_to_date(
|
||||
check: &UpToDateFastPathCheck<'_>,
|
||||
) -> Result<UpToDateWorkspace, &'static str> {
|
||||
already_up_to_date(check, Decide::FromRecordsOrFiles)
|
||||
}
|
||||
|
||||
/// [`install_already_up_to_date`] decided from the manifest records alone. A
|
||||
/// caller whose install reads the project manifests anyway when the
|
||||
/// shortcut is not taken uses this, so a declined attempt reads none of them.
|
||||
pub fn install_already_up_to_date_from_records(
|
||||
check: &UpToDateFastPathCheck<'_>,
|
||||
) -> Result<UpToDateWorkspace, &'static str> {
|
||||
already_up_to_date(check, Decide::FromRecords)
|
||||
}
|
||||
|
||||
/// What an up-to-date verdict may be decided from.
|
||||
#[derive(Clone, Copy)]
|
||||
enum Decide {
|
||||
FromRecords,
|
||||
FromRecordsOrFiles,
|
||||
}
|
||||
|
||||
fn already_up_to_date(
|
||||
check: &UpToDateFastPathCheck<'_>,
|
||||
decide: Decide,
|
||||
) -> Result<UpToDateWorkspace, &'static str> {
|
||||
let manifest_dir = check.manifest
|
||||
.path()
|
||||
.parent()
|
||||
.ok_or("the manifest has no directory")?;
|
||||
let workspace_dir_opt = configured_or_discovered_workspace_dir(check.config, manifest_dir)
|
||||
.map_err(|_| "the workspace root cannot be located")?;
|
||||
let workspace_root = workspace_dir_opt.as_deref().unwrap_or(manifest_dir);
|
||||
// The lockfile the install wrote sits at its `lockfileDir`, which
|
||||
// both `sharedWorkspaceLockfile: false` and an explicit pin move away
|
||||
// from the discovered workspace root. The workspace *state* keeps its
|
||||
// own root, which only a pin moves — `state_root` below.
|
||||
let lockfile_root = lockfile_root_for(check.config, workspace_dir_opt.as_deref(), manifest_dir);
|
||||
let state_root = check.config.lockfile_dir.as_deref().unwrap_or(workspace_root);
|
||||
let state = load_fast_path_state(check.config, &lockfile_root, state_root)?;
|
||||
let (workspace_manifest, catalogs) =
|
||||
fast_path_workspace_context(check.config, workspace_dir_opt.as_deref())
|
||||
.ok_or("the workspace manifest or its catalogs cannot be read")?;
|
||||
let lockfile = lazy_wanted_lockfile(check.config, &lockfile_root);
|
||||
let context = FastPathContext {
|
||||
state_root,
|
||||
lockfile_root: &lockfile_root,
|
||||
workspace_root,
|
||||
workspace_manifest: workspace_manifest.as_ref(),
|
||||
lockfile: &lockfile,
|
||||
catalogs: &catalogs,
|
||||
};
|
||||
let project_count = match stand_ins::check_from_records(check, &context, &state) {
|
||||
stand_ins::RecordsCheck::UpToDate(project_count) => Some(project_count),
|
||||
stand_ins::RecordsCheck::Undecided(listed) => match decide {
|
||||
Decide::FromRecordsOrFiles => {
|
||||
from_files::check_from_files(check, &context, &state, listed)?
|
||||
}
|
||||
Decide::FromRecords => return Err("the recorded manifests cannot decide"),
|
||||
},
|
||||
};
|
||||
register_fast_path_workspace(check.config, &lockfile_root)?;
|
||||
Ok(UpToDateWorkspace { root: state_root.to_path_buf(), project_count })
|
||||
}
|
||||
|
||||
fn load_fast_path_state(
|
||||
config: &Config,
|
||||
lockfile_root: &Path,
|
||||
state_root: &Path,
|
||||
) -> Result<WorkspaceState, &'static str> {
|
||||
if let Some(reason) = config_blocks_fast_path(config) {
|
||||
return Err(reason);
|
||||
}
|
||||
if !matches!(check_strict_dep_builds_for_fast_path(config), Ok(true)) {
|
||||
return Err("strictDepBuilds has to re-check the recorded ignored builds");
|
||||
}
|
||||
if crate::agent_skills::agent_skill_targets_changed(config, lockfile_root) {
|
||||
return Err("an agent skill directory changed");
|
||||
}
|
||||
load_workspace_state(state_root)
|
||||
.ok()
|
||||
.flatten()
|
||||
.ok_or("no workspace state on disk")
|
||||
}
|
||||
|
||||
/// What [`install_already_up_to_date`] has established about the workspace
|
||||
/// before it reads any project.
|
||||
struct FastPathContext<'a> {
|
||||
state_root: &'a Path,
|
||||
lockfile_root: &'a Path,
|
||||
workspace_root: &'a Path,
|
||||
/// `None` when no `pnpm-workspace.yaml` drives the install.
|
||||
workspace_manifest: Option<&'a pnpm_workspace::WorkspaceManifest>,
|
||||
lockfile: &'a LazyLockfile,
|
||||
catalogs: &'a Catalogs,
|
||||
}
|
||||
|
||||
/// The projects one repeat-install check covers.
|
||||
#[derive(Clone, Copy)]
|
||||
struct FastPathProjects<'a> {
|
||||
state: &'a WorkspaceState,
|
||||
manifests: &'a [(PathBuf, &'a PackageManifest)],
|
||||
/// See [`crate::DeclaredManifests::with_stand_ins`].
|
||||
recorded_digests: Option<&'a std::collections::HashMap<PathBuf, String>>,
|
||||
/// See [`crate::RepeatInstallManifests::workspace_projects`].
|
||||
bin_projects: Option<&'a [(PathBuf, &'a PackageManifest)]>,
|
||||
}
|
||||
|
||||
/// [`check_optimistic_repeat_install_with_state`] inside the
|
||||
/// `check_repeat_install` span, with a skip surfaced as the fast path's reason.
|
||||
fn check_fast_path_repeat_install(
|
||||
check: &UpToDateFastPathCheck<'_>,
|
||||
context: &FastPathContext<'_>,
|
||||
projects: FastPathProjects<'_>,
|
||||
) -> Result<(), &'static str> {
|
||||
let _span = tracing::debug_span!(target: "pacquet::install", "check_repeat_install").entered();
|
||||
let project_manifests = match projects.recorded_digests {
|
||||
Some(digests) => crate::DeclaredManifests::with_stand_ins(projects.manifests, digests),
|
||||
None => crate::DeclaredManifests::new(projects.manifests),
|
||||
};
|
||||
let decision = check_optimistic_repeat_install_with_state(
|
||||
&OptimisticRepeatInstallCheck {
|
||||
workspace_root: context.state_root,
|
||||
config: check.config,
|
||||
project_manifests,
|
||||
is_workspace_install: context.workspace_manifest.is_some(),
|
||||
lockfile: MaybeLazyLockfile::Lazy(context.lockfile),
|
||||
catalogs: context.catalogs,
|
||||
layout: crate::RepeatInstallLayout {
|
||||
node_linker: check.node_linker,
|
||||
included: included_dependencies(&check.dependency_groups),
|
||||
supported_architectures: check.supported_architectures.as_ref(),
|
||||
},
|
||||
manifests: crate::RepeatInstallManifests {
|
||||
workspace_projects: projects.bin_projects,
|
||||
..crate::RepeatInstallManifests::ON_DISK
|
||||
},
|
||||
},
|
||||
projects.state,
|
||||
Scope::Workspace,
|
||||
);
|
||||
match decision {
|
||||
Decision::UpToDate => gvs_build_markers_complete(
|
||||
check.config,
|
||||
MaybeLazyLockfile::Lazy(context.lockfile),
|
||||
context.lockfile_root,
|
||||
effective_node_version(check.config, check.manifest).as_deref(),
|
||||
),
|
||||
Decision::Skipped { reason } => Err(reason),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`register_workspace_in_store`] inside the
|
||||
/// `register_workspace_in_store` span.
|
||||
fn register_fast_path_workspace(config: &Config, lockfile_root: &Path) -> Result<(), &'static str> {
|
||||
let _span =
|
||||
tracing::debug_span!(target: "pacquet::install", "register_workspace_in_store").entered();
|
||||
register_workspace_in_store(config, lockfile_root)
|
||||
.map_err(|_| "the workspace cannot be registered in the store")
|
||||
}
|
||||
|
||||
fn fast_path_workspace_context(
|
||||
config: &Config,
|
||||
workspace_dir: Option<&Path>,
|
||||
) -> Option<(Option<pnpm_workspace::WorkspaceManifest>, Catalogs)> {
|
||||
let workspace_manifest = workspace_dir
|
||||
.map(pnpm_workspace::read_workspace_manifest)
|
||||
.transpose()
|
||||
.ok()?
|
||||
.flatten();
|
||||
let catalogs = discovery::configured_catalogs(config, workspace_manifest.as_ref())?;
|
||||
Some((workspace_manifest, catalogs))
|
||||
}
|
||||
|
||||
/// The wanted lockfile, read on first use — or a stand-in that never reads
|
||||
/// one when the install is configured without a lockfile.
|
||||
fn lazy_wanted_lockfile(config: &Config, lockfile_root: &Path) -> LazyLockfile {
|
||||
if config.lockfile {
|
||||
LazyLockfile::deferred(lockfile_root.to_path_buf(), config.wanted_lockfile_selection())
|
||||
} else {
|
||||
LazyLockfile::disabled()
|
||||
}
|
||||
}
|
||||
+11
-14
@@ -1,16 +1,13 @@
|
||||
use super::{
|
||||
super::{
|
||||
Catalogs, Config, IncludedDependencies, MaybeLazyLockfile, OptimisticRepeatInstallCheck,
|
||||
PackageManifest, Path, PathBuf, get_catalogs_from_workspace_manifest,
|
||||
},
|
||||
build_project_manifests_list, configured_or_discovered_workspace_dir, lazy_wanted_lockfile,
|
||||
lockfile_root_for,
|
||||
nothing_to_install::projects_have_nothing_to_install,
|
||||
Catalogs, Config, IncludedDependencies, MaybeLazyLockfile, OptimisticRepeatInstallCheck,
|
||||
PackageManifest, Path, PathBuf, build_project_manifests_list,
|
||||
configured_or_discovered_workspace_dir, get_catalogs_from_workspace_manifest,
|
||||
lazy_wanted_lockfile, lockfile_root_for, nothing_to_install::projects_have_nothing_to_install,
|
||||
stand_ins,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Discovery twin of [`install_already_up_to_date`](crate::install::workspace_state::install_already_up_to_date) for the
|
||||
/// Discovery twin of [`super::install_already_up_to_date`] for the
|
||||
/// verify-deps-before-run gate: assemble the same
|
||||
/// [`OptimisticRepeatInstallCheck`] inputs from a bare directory and run
|
||||
/// [`crate::check_deps_status_before_run`].
|
||||
@@ -122,13 +119,13 @@ impl GateWorkspace {
|
||||
let discovered = self.discovered
|
||||
.get_or_init(|| {
|
||||
let manifest = pnpm_workspace::read_workspace_manifest(&self.root).ok()??;
|
||||
let opts = super::super::lifecycle::checked_projects_opts(config, &manifest);
|
||||
let opts = crate::install::checked_projects_opts(config, &manifest);
|
||||
let projects = pnpm_workspace::find_workspace_projects(&self.root, &opts).ok()?;
|
||||
let key = opts.discovery_key(&self.root);
|
||||
Some(DiscoveredProjects { manifest, key, projects })
|
||||
})
|
||||
.as_ref()?;
|
||||
let key = super::super::lifecycle::checked_projects_opts(config, &discovered.manifest)
|
||||
let key = crate::install::checked_projects_opts(config, &discovered.manifest)
|
||||
.discovery_key(&self.root);
|
||||
(discovered.key == key).then_some(discovered.projects.as_slice())
|
||||
}
|
||||
@@ -324,7 +321,7 @@ fn gate_workspace_projects<'a>(
|
||||
workspace,
|
||||
..
|
||||
} = inputs;
|
||||
let links = crate::optimistic_repeat_install::validation::links_workspace_commands(manifest);
|
||||
let links = crate::install_state::validation::links_workspace_commands(manifest);
|
||||
let discovered = workspace
|
||||
.filter(|workspace| workspace.root == workspace_root)
|
||||
.filter(|_| !config.shares_one_lockfile() && links)
|
||||
@@ -388,13 +385,13 @@ fn load_gated_workspace(
|
||||
listed: Option<Vec<pnpm_workspace::ProjectManifestPaths>>,
|
||||
) -> Result<Option<Vec<pnpm_workspace::Project>>, pnpm_workspace::FindWorkspaceProjectsError> {
|
||||
let needed = config.shares_one_lockfile()
|
||||
|| crate::optimistic_repeat_install::validation::links_workspace_commands(manifest);
|
||||
|| crate::install_state::validation::links_workspace_commands(manifest);
|
||||
if !needed {
|
||||
return Ok(None);
|
||||
}
|
||||
match listed {
|
||||
Some(listed) => pnpm_workspace::read_workspace_projects(listed).map(Some),
|
||||
None => super::super::lifecycle::load_checked_workspace_projects(
|
||||
None => crate::install::load_checked_workspace_projects(
|
||||
config,
|
||||
workspace_root,
|
||||
workspace_manifest,
|
||||
@@ -415,7 +412,7 @@ fn gated_projects<'a>(
|
||||
) -> (Vec<(PathBuf, &'a PackageManifest)>, Option<Vec<(PathBuf, &'a PackageManifest)>>) {
|
||||
let shared = workspace_projects.filter(|_| config.shares_one_lockfile());
|
||||
let project_manifests = build_project_manifests_list(manifest, shared);
|
||||
let bin_projects = crate::optimistic_repeat_install::validation::dedicated_bin_projects(
|
||||
let bin_projects = crate::install_state::validation::dedicated_bin_projects(
|
||||
config,
|
||||
&project_manifests,
|
||||
workspace_projects,
|
||||
File renamed without changes.
+2
-2
@@ -30,7 +30,7 @@ pub(super) fn check_from_files(
|
||||
Ok(workspace_projects.as_ref().map(Vec::len))
|
||||
}
|
||||
|
||||
/// [`super::super::lifecycle::load_checked_workspace_projects`] inside the
|
||||
/// [`crate::install::load_checked_workspace_projects`] inside the
|
||||
/// `discover_workspace_projects`
|
||||
/// span, skipping the pnpm-managed directories, or the read of the projects
|
||||
/// `listed` names.
|
||||
@@ -43,7 +43,7 @@ fn discover_fast_path_projects(
|
||||
tracing::debug_span!(target: "pacquet::install", "discover_workspace_projects").entered();
|
||||
let discovered = match listed {
|
||||
Some(listed) => pnpm_workspace::read_workspace_projects(listed).map(Some),
|
||||
None => super::super::lifecycle::load_checked_workspace_projects(
|
||||
None => crate::install::load_checked_workspace_projects(
|
||||
config,
|
||||
context.workspace_root,
|
||||
context.workspace_manifest,
|
||||
+5
-9
@@ -1,10 +1,6 @@
|
||||
use super::{
|
||||
super::{
|
||||
Config, PROJECT_LIFECYCLE_STAGES, PackageManifest, Path, load_workspace_projects,
|
||||
project_requires_lifecycle_scripts,
|
||||
},
|
||||
build_project_manifests_list,
|
||||
discovery::GateInputs,
|
||||
Config, PROJECT_LIFECYCLE_STAGES, PackageManifest, Path, build_project_manifests_list,
|
||||
discovery::GateInputs, load_workspace_projects, project_requires_lifecycle_scripts,
|
||||
};
|
||||
use pnpm_executor::DEV_PREINSTALL_STAGE;
|
||||
|
||||
@@ -30,7 +26,7 @@ pub(super) fn projects_have_nothing_to_install(inputs: &GateInputs<'_>) -> bool
|
||||
if manifest.path().parent() != Some(lockfile_root)
|
||||
|| project_has_install_work(config, manifest)
|
||||
|| runs_dev_preinstall(config, manifest, workspace_root)
|
||||
|| !crate::optimistic_repeat_install::current_pnpmfiles(lockfile_root, config).is_empty()
|
||||
|| !crate::install_state::current_pnpmfiles(lockfile_root, config).is_empty()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -52,9 +48,9 @@ fn project_has_install_work(config: &Config, manifest: &PackageManifest) -> bool
|
||||
.path()
|
||||
.parent()
|
||||
.expect("manifest path always has a parent dir");
|
||||
crate::optimistic_repeat_install::manifest_has_runtime_deps(manifest)
|
||||
crate::install_state::manifest_has_runtime_deps(manifest)
|
||||
|| (config.auto_install_peers
|
||||
&& crate::optimistic_repeat_install::manifest_has_required_peers(manifest))
|
||||
&& crate::install_state::manifest_has_required_peers(manifest))
|
||||
|| (!config.ignore_scripts
|
||||
&& project_requires_lifecycle_scripts(project_dir, manifest, &PROJECT_LIFECYCLE_STAGES))
|
||||
}
|
||||
+2
-2
@@ -8,7 +8,7 @@ use super::{
|
||||
configured_or_discovered_workspace_dir, fast_path_workspace_context, lazy_wanted_lockfile,
|
||||
load_fast_path_state, register_fast_path_workspace,
|
||||
};
|
||||
use crate::optimistic_repeat_install::validation::{dedicated_bin_projects, manifest_stand_ins};
|
||||
use crate::install_state::validation::{dedicated_bin_projects, manifest_stand_ins};
|
||||
use pnpm_workspace::{Project, ProjectManifestPaths};
|
||||
use rayon::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
@@ -111,7 +111,7 @@ fn recorded_projects(
|
||||
let listed = {
|
||||
let _span =
|
||||
tracing::debug_span!(target: "pacquet::install", "list_workspace_projects").entered();
|
||||
let opts = super::super::lifecycle::checked_projects_opts(check.config, workspace.manifest);
|
||||
let opts = crate::install::checked_projects_opts(check.config, workspace.manifest);
|
||||
pnpm_workspace::find_workspace_project_manifests(workspace.root, &opts)
|
||||
.map_err(|_| "the workspace projects cannot be listed")?
|
||||
};
|
||||
+2
-3
@@ -7,7 +7,7 @@ use super::{
|
||||
UpToDateFastPathCheck, WorkspaceState, build_project_manifests_list,
|
||||
check_fast_path_repeat_install,
|
||||
};
|
||||
use crate::optimistic_repeat_install::validation::{ManifestStandIns, manifest_stand_ins};
|
||||
use crate::install_state::validation::{ManifestStandIns, manifest_stand_ins};
|
||||
use pnpm_workspace::ProjectManifestPaths;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -105,8 +105,7 @@ pub(super) fn list_stand_ins(workspace: &ListedWorkspace<'_>) -> Listing {
|
||||
let listed = {
|
||||
let _span =
|
||||
tracing::debug_span!(target: "pacquet::install", "list_workspace_projects").entered();
|
||||
let opts =
|
||||
super::super::lifecycle::checked_projects_opts(workspace.config, workspace_manifest);
|
||||
let opts = crate::install::checked_projects_opts(workspace.config, workspace_manifest);
|
||||
pnpm_workspace::find_workspace_project_manifests(workspace.workspace_root, &opts)
|
||||
};
|
||||
let Ok(listed) = listed else { return Listing::Files(None) };
|
||||
@@ -94,7 +94,7 @@ fn omits_settings_that_are_none() {
|
||||
/// `packageExtensions` round-trips through `WorkspaceStateSettings`
|
||||
/// as a JSON object preserving the wire shape (`<selector>:
|
||||
/// { dependencies: { … } }`). The drift gate in
|
||||
/// `optimistic_repeat_install::settings_match` reads this field, so
|
||||
/// `first_setting_drift` in `pnpm-package-manager` reads this field, so
|
||||
/// the on-disk shape has to stay stable for the gate to be byte-
|
||||
/// comparable with pnpm's writer.
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user