Skip to content

Commit d393beb

Browse files
committed
wip
1 parent 76e0ad8 commit d393beb

File tree

6 files changed

+706
-361
lines changed

6 files changed

+706
-361
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = "2021"
88
[dependencies]
99
fancy-regex = "0.11.0"
1010
lazy_static = "1.4.0"
11+
pathdiff = "0.2.1"
1112
radix_trie = "0.2.1"
1213
regex = "1.7.1"
1314
relative-path = { version = "1.7.3", features = ["serde"] }

src/lib.rs

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
mod util;
2+
3+
use std::{path::{Path, PathBuf, Component}, fs, collections::{HashSet, HashMap}};
4+
use util::RegexDef;
5+
use lazy_static::lazy_static;
6+
use radix_trie::Trie;
7+
use fancy_regex::Regex;
8+
use serde::Deserialize;
9+
use serde_with::{serde_as, DefaultOnNull};
10+
use simple_error::{self, bail, SimpleError};
11+
12+
pub enum Resolution {
13+
Specifier(String),
14+
Path(PathBuf),
15+
}
16+
17+
pub struct PnpResolutionHost {
18+
pub find_pnp_manifest: Box<dyn Fn(&Path) -> Result<Option<Manifest>, Box<dyn std::error::Error>>>,
19+
}
20+
21+
impl Default for PnpResolutionHost {
22+
fn default() -> PnpResolutionHost {
23+
PnpResolutionHost {
24+
find_pnp_manifest: Box::new(find_pnp_manifest),
25+
}
26+
}
27+
}
28+
29+
#[derive(Default)]
30+
pub struct PnpResolutionConfig {
31+
pub builtins: HashSet<String>,
32+
pub host: PnpResolutionHost,
33+
}
34+
35+
#[derive(Deserialize)]
36+
pub struct PackageLocator {
37+
name: String,
38+
reference: String,
39+
}
40+
41+
#[derive(Clone)]
42+
#[derive(Deserialize)]
43+
#[serde(untagged)]
44+
enum PackageDependency {
45+
Reference(String),
46+
Alias(String, String),
47+
}
48+
49+
#[serde_as]
50+
#[derive(Deserialize)]
51+
#[serde(rename_all = "camelCase")]
52+
pub struct PackageInformation {
53+
package_location: PathBuf,
54+
55+
#[serde(default)]
56+
discard_from_lookup: bool,
57+
58+
#[serde_as(as = "Vec<(_, Option<_>)>")]
59+
package_dependencies: HashMap<String, Option<PackageDependency>>,
60+
}
61+
62+
#[serde_as]
63+
#[derive(Deserialize)]
64+
#[serde(rename_all = "camelCase")]
65+
pub struct Manifest {
66+
#[serde(skip_deserializing)]
67+
manifest_path: PathBuf,
68+
69+
#[serde(skip_deserializing)]
70+
fallback_dependencies: HashMap<String, Option<PackageDependency>>,
71+
72+
#[serde(skip_deserializing)]
73+
location_trie: Trie<PathBuf, PackageLocator>,
74+
75+
enable_top_level_fallback: bool,
76+
ignore_pattern_data: Option<RegexDef>,
77+
78+
// fallbackPool: [[
79+
// "@app/monorepo",
80+
// "workspace:.",
81+
// ]]
82+
fallback_pool: Vec<PackageLocator>,
83+
84+
// fallbackExclusionList: [[
85+
// "@app/server",
86+
// ["workspace:sources/server"],
87+
// ]]
88+
#[serde_as(as = "Vec<(_, _)>")]
89+
fallback_exclusion_list: HashMap<String, HashSet<String>>,
90+
91+
// packageRegistryData: [
92+
// [null, [
93+
// [null, {
94+
// ...
95+
// }]
96+
// }]
97+
// ]
98+
#[serde_as(as = "Vec<(DefaultOnNull<_>, Vec<(DefaultOnNull<_>, _)>)>")]
99+
package_registry_data: HashMap<String, HashMap<String, PackageInformation>>,
100+
}
101+
102+
fn normalize_path(path: &Path) -> PathBuf {
103+
let mut components = path.components().peekable();
104+
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
105+
components.next();
106+
PathBuf::from(c.as_os_str())
107+
} else {
108+
PathBuf::new()
109+
};
110+
111+
for component in components {
112+
match component {
113+
Component::Prefix(..) => unreachable!(),
114+
Component::RootDir => {
115+
ret.push(component.as_os_str());
116+
}
117+
Component::CurDir => {}
118+
Component::ParentDir => {
119+
ret.pop();
120+
}
121+
Component::Normal(c) => {
122+
ret.push(c);
123+
}
124+
}
125+
}
126+
ret
127+
}
128+
129+
pub fn is_builtin(specifier: &str, config: &PnpResolutionConfig) -> bool {
130+
config.builtins.contains(specifier)
131+
}
132+
133+
pub fn is_path_specifier(specifier: &str) -> bool {
134+
lazy_static! {
135+
static ref RE: Regex = Regex::new("^\\.{0,2}/").unwrap();
136+
}
137+
138+
RE.is_match(specifier).unwrap()
139+
}
140+
141+
pub fn parse_bare_identifier(specifier: &str) -> Result<(String, Option<String>), SimpleError> {
142+
let mut segments = specifier.splitn(3, '/');
143+
let mut ident_option: Option<String> = None;
144+
145+
if let Some(first) = segments.next() {
146+
if first.starts_with('@') {
147+
if let Some(second) = segments.next() {
148+
ident_option = Some(format!("{}/{}", first, second));
149+
}
150+
} else {
151+
ident_option = Some(first.to_string());
152+
}
153+
}
154+
155+
if let Some(ident) = ident_option {
156+
Ok((ident, segments.next().map(|v| v.to_string())))
157+
} else {
158+
bail!("Invalid specifier")
159+
}
160+
}
161+
162+
pub fn find_closest_pnp_manifest_path(p: &Path) -> Option<PathBuf> {
163+
if let Some(directory_path) = p.parent() {
164+
let pnp_path = directory_path.join(".pnp.cjs");
165+
166+
if pnp_path.exists() {
167+
Some(pnp_path)
168+
} else {
169+
find_closest_pnp_manifest_path(directory_path)
170+
}
171+
} else {
172+
None
173+
}
174+
}
175+
176+
pub fn load_pnp_manifest(p: &Path) -> Result<Manifest, Box<dyn std::error::Error>> {
177+
let manifest_content = fs::read_to_string(p)?;
178+
179+
lazy_static! {
180+
static ref RE: Regex = Regex::new("const\\s+RAW_RUNTIME_STATE\\s*=\\s*'").unwrap();
181+
}
182+
183+
let manifest_match = RE.find(&manifest_content)?
184+
.expect("Should have been able to locate the runtime state payload offset");
185+
186+
let iter = manifest_content.chars().skip(manifest_match.end());
187+
let mut json_string = String::default();
188+
let mut escaped = false;
189+
190+
for c in iter {
191+
match c {
192+
'\'' if !escaped => {
193+
break;
194+
}
195+
'\\' if !escaped => {
196+
escaped = true;
197+
}
198+
_ => {
199+
escaped = false;
200+
json_string.push(c);
201+
}
202+
}
203+
}
204+
205+
let mut manifest: Manifest = serde_json::from_str(&json_string.to_owned())?;
206+
init_pnp_manifest(&mut manifest, p);
207+
208+
Ok(manifest)
209+
}
210+
211+
pub fn init_pnp_manifest(manifest: &mut Manifest, p: &Path) {
212+
let manifest_dir = p.parent()
213+
.expect("Should have a parent directory");
214+
215+
manifest.manifest_path = p.to_owned();
216+
217+
for locator in manifest.fallback_pool.iter() {
218+
let info = manifest.package_registry_data
219+
.get(&locator.name)
220+
.expect("Assertion failed: The locator should be registered")
221+
.get(&locator.reference)
222+
.expect("Assertion failed: The locator should be registered");
223+
224+
for (name, dependency) in info.package_dependencies.iter() {
225+
manifest.fallback_dependencies.insert(name.clone(), dependency.clone());
226+
}
227+
}
228+
229+
for (name, ranges) in manifest.package_registry_data.iter_mut() {
230+
for (reference, info) in ranges.iter_mut() {
231+
if info.discard_from_lookup {
232+
continue;
233+
}
234+
235+
info.package_location = normalize_path(manifest_dir
236+
.join(info.package_location.clone())
237+
.as_path());
238+
239+
manifest.location_trie.insert(info.package_location.clone(), PackageLocator {
240+
name: name.clone(),
241+
reference: reference.clone(),
242+
});
243+
}
244+
}
245+
}
246+
247+
pub fn find_pnp_manifest(parent: &Path) -> Result<Option<Manifest>, Box<dyn std::error::Error>> {
248+
find_closest_pnp_manifest_path(parent).map_or(Ok(None), |p| Ok(Some(load_pnp_manifest(&p)?)))
249+
}
250+
251+
pub fn find_locator<'a>(manifest: &'a Manifest, path: &Path) -> Option<&'a PackageLocator> {
252+
let relative_path = pathdiff::diff_paths(path, &manifest.manifest_path)
253+
.map_or(String::from("."), |p| p.to_string_lossy().to_string());
254+
255+
if let Some(regex) = &manifest.ignore_pattern_data {
256+
if regex.0.is_match(&relative_path).unwrap() {
257+
return None
258+
}
259+
}
260+
261+
manifest.location_trie.get_ancestor_value(path)
262+
}
263+
264+
pub fn get_package<'a>(manifest: &'a Manifest, locator: &PackageLocator) -> Result<&'a PackageInformation, Box<dyn std::error::Error>> {
265+
let references = manifest.package_registry_data.get(&locator.name)
266+
.expect("Should have an entry in the package registry");
267+
268+
let info = references.get(&locator.reference)
269+
.expect("Should have an entry in the package registry");
270+
271+
Ok(info)
272+
}
273+
274+
pub fn is_excluded_from_fallback(manifest: &Manifest, locator: &PackageLocator) -> bool {
275+
if let Some(references) = manifest.fallback_exclusion_list.get(&locator.name) {
276+
references.contains(&locator.reference)
277+
} else {
278+
false
279+
}
280+
}
281+
282+
pub fn pnp_resolve(specifier: &str, parent: &Path, config: &PnpResolutionConfig) -> Result<Resolution, Box<dyn std::error::Error>> {
283+
if is_builtin(specifier, config) {
284+
return Ok(Resolution::Specifier(specifier.to_string()))
285+
}
286+
287+
if is_path_specifier(specifier) {
288+
return Ok(Resolution::Specifier(specifier.to_string()))
289+
}
290+
291+
resolve_to_unqualified(specifier, parent, config)
292+
}
293+
294+
pub fn resolve_to_unqualified(specifier: &str, parent: &Path, config: &PnpResolutionConfig) -> Result<Resolution, Box<dyn std::error::Error>> {
295+
let (ident, module_path) = parse_bare_identifier(specifier)?;
296+
297+
if let Some(manifest) = (config.host.find_pnp_manifest)(parent)? {
298+
if let Some(parent_locator) = find_locator(&manifest, parent) {
299+
let parent_pkg = get_package(&manifest, parent_locator)?;
300+
301+
let mut reference_or_alias: Option<PackageDependency> = None;
302+
let mut is_set = false;
303+
304+
if !is_set {
305+
if let Some(binding) = parent_pkg.package_dependencies.get(&ident) {
306+
reference_or_alias = binding.clone();
307+
is_set = true;
308+
}
309+
}
310+
311+
if !is_set && manifest.enable_top_level_fallback && !is_excluded_from_fallback(&manifest, parent_locator) {
312+
if let Some(fallback_resolution) = manifest.fallback_dependencies.get(&ident) {
313+
reference_or_alias = fallback_resolution.clone();
314+
is_set = true;
315+
}
316+
}
317+
318+
if !is_set {
319+
bail!("Resolution failed");
320+
}
321+
322+
if let Some(resolution) = reference_or_alias {
323+
let dependency_pkg = match resolution {
324+
PackageDependency::Reference(reference) => get_package(&manifest, &PackageLocator { name: ident, reference }),
325+
PackageDependency::Alias(name, reference) => get_package(&manifest, &PackageLocator { name, reference }),
326+
}?;
327+
328+
let final_path = dependency_pkg.package_location
329+
.join(module_path.unwrap_or_default());
330+
331+
Ok(Resolution::Path(final_path))
332+
} else {
333+
bail!("Resolution failed: Unsatisfied peer dependency");
334+
}
335+
} else {
336+
Ok(Resolution::Specifier(specifier.to_string()))
337+
}
338+
} else {
339+
Ok(Resolution::Specifier(specifier.to_string()))
340+
}
341+
}

0 commit comments

Comments
 (0)