summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/lockfile.rs148
-rw-r--r--cli/main.rs16
-rw-r--r--cli/npm/resolution.rs118
-rw-r--r--cli/npm/resolvers/common.rs3
-rw-r--r--cli/npm/resolvers/global.rs6
-rw-r--r--cli/npm/resolvers/local.rs5
-rw-r--r--cli/npm/resolvers/mod.rs44
-rw-r--r--cli/proc_state.rs8
-rw-r--r--cli/tests/integration/npm_tests.rs8
-rw-r--r--cli/tests/testdata/npm/lock_file/lock.json164
-rw-r--r--cli/tests/testdata/npm/lock_file/main.js5
-rw-r--r--cli/tests/testdata/npm/lock_file/main.out4
12 files changed, 513 insertions, 16 deletions
diff --git a/cli/lockfile.rs b/cli/lockfile.rs
index b572b9d93..53a3c1286 100644
--- a/cli/lockfile.rs
+++ b/cli/lockfile.rs
@@ -1,7 +1,5 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
-#![allow(dead_code)]
-
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
@@ -17,19 +15,68 @@ use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
+use crate::npm::NpmPackageReq;
+use crate::npm::NpmResolutionPackage;
use crate::tools::fmt::format_json;
+#[derive(Debug)]
+pub struct LockfileError(String);
+
+impl std::fmt::Display for LockfileError {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.write_str(&self.0)
+ }
+}
+
+impl std::error::Error for LockfileError {}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct NpmPackageInfo {
+ pub integrity: String,
+ pub dependencies: BTreeMap<String, String>,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize)]
+pub struct NpmContent {
+ /// Mapping between requests for npm packages and resolved specifiers, eg.
+ /// {
+ /// "chalk": "chalk@5.0.0"
+ /// "react@17": "react@17.0.1"
+ /// "foo@latest": "foo@1.0.0"
+ /// }
+ pub specifiers: BTreeMap<String, String>,
+ /// Mapping between resolved npm specifiers and their associated info, eg.
+ /// {
+ /// "chalk@5.0.0": {
+ /// "integrity": "sha512-...",
+ /// "dependencies": {
+ /// "ansi-styles": "ansi-styles@4.1.0",
+ /// }
+ /// }
+ /// }
+ pub packages: BTreeMap<String, NpmPackageInfo>,
+}
+
+impl NpmContent {
+ fn is_empty(&self) -> bool {
+ self.specifiers.is_empty() && self.packages.is_empty()
+ }
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LockfileContent {
version: String,
- // Mapping between URLs and their checksums
+ // Mapping between URLs and their checksums for "http:" and "https:" deps
remote: BTreeMap<String, String>,
+ #[serde(skip_serializing_if = "NpmContent::is_empty")]
+ #[serde(default)]
+ pub npm: NpmContent,
}
#[derive(Debug, Clone)]
pub struct Lockfile {
- write: bool,
- content: LockfileContent,
+ pub write: bool,
+ pub content: LockfileContent,
pub filename: PathBuf,
}
@@ -40,6 +87,7 @@ impl Lockfile {
LockfileContent {
version: "2".to_string(),
remote: BTreeMap::new(),
+ npm: NpmContent::default(),
}
} else {
let s = std::fs::read_to_string(&filename).with_context(|| {
@@ -60,6 +108,7 @@ impl Lockfile {
LockfileContent {
version: "2".to_string(),
remote,
+ npm: NpmContent::default(),
}
}
};
@@ -92,7 +141,13 @@ impl Lockfile {
Ok(())
}
- pub fn check_or_insert(&mut self, specifier: &str, code: &str) -> bool {
+ // TODO(bartlomieju): this function should return an error instead of a bool,
+ // but it requires changes to `deno_graph`'s `Locker`.
+ pub fn check_or_insert_remote(
+ &mut self,
+ specifier: &str,
+ code: &str,
+ ) -> bool {
if self.write {
// In case --lock-write is specified check always passes
self.insert(specifier, code);
@@ -102,6 +157,19 @@ impl Lockfile {
}
}
+ pub fn check_or_insert_npm_package(
+ &mut self,
+ package: &NpmResolutionPackage,
+ ) -> Result<(), LockfileError> {
+ if self.write {
+ // In case --lock-write is specified check always passes
+ self.insert_npm_package(package);
+ Ok(())
+ } else {
+ self.check_npm_package(package)
+ }
+ }
+
/// Checks the given module is included.
/// Returns Ok(true) if check passed.
fn check(&mut self, specifier: &str, code: &str) -> bool {
@@ -123,6 +191,64 @@ impl Lockfile {
let checksum = crate::checksum::gen(&[code.as_bytes()]);
self.content.remote.insert(specifier.to_string(), checksum);
}
+
+ fn check_npm_package(
+ &mut self,
+ package: &NpmResolutionPackage,
+ ) -> Result<(), LockfileError> {
+ let specifier = package.id.serialize_for_lock_file();
+ if let Some(package_info) = self.content.npm.packages.get(&specifier) {
+ let integrity = package
+ .dist
+ .integrity
+ .as_ref()
+ .unwrap_or(&package.dist.shasum);
+ if &package_info.integrity != integrity {
+ return Err(LockfileError(format!(
+ "Integrity check failed for npm package: \"{}\".
+ Cache has \"{}\" and lockfile has \"{}\".
+ Use \"--lock-write\" flag to update the lockfile.",
+ package.id, integrity, package_info.integrity
+ )));
+ }
+ }
+
+ Ok(())
+ }
+
+ fn insert_npm_package(&mut self, package: &NpmResolutionPackage) {
+ let dependencies = package
+ .dependencies
+ .iter()
+ .map(|(name, id)| (name.to_string(), id.serialize_for_lock_file()))
+ .collect::<BTreeMap<String, String>>();
+
+ let integrity = package
+ .dist
+ .integrity
+ .as_ref()
+ .unwrap_or(&package.dist.shasum);
+ self.content.npm.packages.insert(
+ package.id.serialize_for_lock_file(),
+ NpmPackageInfo {
+ integrity: integrity.to_string(),
+ dependencies,
+ },
+ );
+ }
+
+ pub fn insert_npm_specifier(
+ &mut self,
+ package_req: &NpmPackageReq,
+ version: String,
+ ) {
+ if self.write {
+ self.content.npm.specifiers.insert(
+ package_req.to_string(),
+ format!("{}@{}", package_req.name, version),
+ );
+ }
+ }
}
#[derive(Debug)]
@@ -136,7 +262,7 @@ impl deno_graph::source::Locker for Locker {
) -> bool {
if let Some(lock_file) = &self.0 {
let mut lock_file = lock_file.lock();
- lock_file.check_or_insert(specifier.as_str(), source)
+ lock_file.check_or_insert_remote(specifier.as_str(), source)
} else {
true
}
@@ -180,6 +306,10 @@ mod tests {
"remote": {
"https://deno.land/std@0.71.0/textproto/mod.ts": "3118d7a42c03c242c5a49c2ad91c8396110e14acca1324e7aaefd31a999b71a4",
"https://deno.land/std@0.71.0/async/delay.ts": "35957d585a6e3dd87706858fb1d6b551cb278271b03f52c5a2cb70e65e00c26a"
+ },
+ "npm": {
+ "specifiers": {},
+ "packages": {}
}
});
@@ -305,13 +435,13 @@ mod tests {
"Here is some source code",
);
- let check_true = lockfile.check_or_insert(
+ let check_true = lockfile.check_or_insert_remote(
"https://deno.land/std@0.71.0/textproto/mod.ts",
"Here is some source code",
);
assert!(check_true);
- let check_false = lockfile.check_or_insert(
+ let check_false = lockfile.check_or_insert_remote(
"https://deno.land/std@0.71.0/textproto/mod.ts",
"This is new Source code",
);
diff --git a/cli/main.rs b/cli/main.rs
index 23f073a7d..1fb942963 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -1060,16 +1060,22 @@ fn unwrap_or_exit<T>(result: Result<T, AnyError>) -> T {
match result {
Ok(value) => value,
Err(error) => {
- let error_string = match error.downcast_ref::<JsError>() {
- Some(e) => format_js_error(e),
- None => format!("{:?}", error),
- };
+ let mut error_string = format!("{:?}", error);
+ let mut error_code = 1;
+
+ if let Some(e) = error.downcast_ref::<JsError>() {
+ error_string = format_js_error(e);
+ } else if let Some(e) = error.downcast_ref::<lockfile::LockfileError>() {
+ error_string = e.to_string();
+ error_code = 10;
+ }
+
eprintln!(
"{}: {}",
colors::red_bold("error"),
error_string.trim_start_matches("error: ")
);
- std::process::exit(1);
+ std::process::exit(error_code);
}
}
}
diff --git a/cli/npm/resolution.rs b/cli/npm/resolution.rs
index 8656d8a3c..7cd4df124 100644
--- a/cli/npm/resolution.rs
+++ b/cli/npm/resolution.rs
@@ -11,9 +11,13 @@ use deno_core::anyhow::Context;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
use deno_core::futures;
+use deno_core::parking_lot::Mutex;
use deno_core::parking_lot::RwLock;
use serde::Deserialize;
use serde::Serialize;
+use std::sync::Arc;
+
+use crate::lockfile::Lockfile;
use super::cache::should_sync_download;
use super::registry::NpmPackageInfo;
@@ -172,6 +176,24 @@ impl NpmPackageId {
None
}
}
+
+ pub fn serialize_for_lock_file(&self) -> String {
+ format!("{}@{}", self.name, self.version)
+ }
+
+ pub fn deserialize_from_lock_file(id: &str) -> Result<Self, AnyError> {
+ let reference = NpmPackageReference::from_str(&format!("npm:{}", id))
+ .with_context(|| {
+ format!("Unable to deserialize npm package reference: {}", id)
+ })?;
+ let version =
+ NpmVersion::parse(&reference.req.version_req.unwrap().to_string())
+ .unwrap();
+ Ok(Self {
+ name: reference.req.name,
+ version,
+ })
+ }
}
impl std::fmt::Display for NpmPackageId {
@@ -345,6 +367,88 @@ impl NpmResolutionSnapshot {
}
maybe_best_version.cloned()
}
+
+ pub async fn from_lockfile(
+ lockfile: Arc<Mutex<Lockfile>>,
+ api: &NpmRegistryApi,
+ ) -> Result<Self, AnyError> {
+ let mut package_reqs = HashMap::new();
+ let mut packages_by_name: HashMap<String, Vec<NpmVersion>> = HashMap::new();
+ let mut packages = HashMap::new();
+
+ {
+ let lockfile = lockfile.lock();
+
+ for (key, value) in &lockfile.content.npm.specifiers {
+ let reference = NpmPackageReference::from_str(&format!("npm:{}", key))
+ .with_context(|| format!("Unable to parse npm specifier: {}", key))?;
+ let package_id = NpmPackageId::deserialize_from_lock_file(value)?;
+ package_reqs.insert(reference.req, package_id.version.clone());
+ }
+
+ for (key, value) in &lockfile.content.npm.packages {
+ let package_id = NpmPackageId::deserialize_from_lock_file(key)?;
+ let mut dependencies = HashMap::default();
+
+ for (name, specifier) in &value.dependencies {
+ dependencies.insert(
+ name.to_string(),
+ NpmPackageId::deserialize_from_lock_file(specifier)?,
+ );
+ }
+
+ for (name, id) in &dependencies {
+ packages_by_name
+ .entry(name.to_string())
+ .or_default()
+ .push(id.version.clone());
+ }
+
+ let package = NpmResolutionPackage {
+ id: package_id.clone(),
+ // temporary dummy value
+ dist: NpmPackageVersionDistInfo {
+ tarball: "foobar".to_string(),
+ shasum: "foobar".to_string(),
+ integrity: Some("foobar".to_string()),
+ },
+ dependencies,
+ };
+
+ packages.insert(package_id.clone(), package);
+ }
+ }
+
+ let mut unresolved_tasks = Vec::with_capacity(packages_by_name.len());
+
+ for package_id in packages.keys() {
+ let package_id = package_id.clone();
+ let api = api.clone();
+ unresolved_tasks.push(tokio::task::spawn(async move {
+ let info = api.package_info(&package_id.name).await?;
+ Result::<_, AnyError>::Ok((package_id, info))
+ }));
+ }
+ for result in futures::future::join_all(unresolved_tasks).await {
+ let (package_id, info) = result??;
+
+ let version_and_info = get_resolved_package_version_and_info(
+ &package_id.name,
+ &NpmVersionReq::parse(&package_id.version.to_string()).unwrap(),
+ info,
+ None,
+ )?;
+
+ let package = packages.get_mut(&package_id).unwrap();
+ package.dist = version_and_info.info.dist;
+ }
+
+ Ok(Self {
+ package_reqs,
+ packages_by_name,
+ packages,
+ })
+ }
}
pub struct NpmResolution {
@@ -628,6 +732,20 @@ impl NpmResolution {
pub fn snapshot(&self) -> NpmResolutionSnapshot {
self.snapshot.read().clone()
}
+
+ pub fn lock(
+ &self,
+ lockfile: &mut Lockfile,
+ snapshot: &NpmResolutionSnapshot,
+ ) -> Result<(), AnyError> {
+ for (package_req, version) in snapshot.package_reqs.iter() {
+ lockfile.insert_npm_specifier(package_req, version.to_string());
+ }
+ for package in self.all_packages() {
+ lockfile.check_or_insert_npm_package(&package)?;
+ }
+ Ok(())
+ }
}
#[derive(Clone)]
diff --git a/cli/npm/resolvers/common.rs b/cli/npm/resolvers/common.rs
index 4981d5613..e114f3f8a 100644
--- a/cli/npm/resolvers/common.rs
+++ b/cli/npm/resolvers/common.rs
@@ -11,6 +11,7 @@ use deno_core::futures;
use deno_core::futures::future::BoxFuture;
use deno_core::url::Url;
+use crate::lockfile::Lockfile;
use crate::npm::cache::should_sync_download;
use crate::npm::resolution::NpmResolutionSnapshot;
use crate::npm::NpmCache;
@@ -50,6 +51,8 @@ pub trait InnerNpmPackageResolver: Send + Sync {
fn ensure_read_permission(&self, path: &Path) -> Result<(), AnyError>;
fn snapshot(&self) -> NpmResolutionSnapshot;
+
+ fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError>;
}
/// Caches all the packages in parallel.
diff --git a/cli/npm/resolvers/global.rs b/cli/npm/resolvers/global.rs
index 8eafc19f4..996f55c2d 100644
--- a/cli/npm/resolvers/global.rs
+++ b/cli/npm/resolvers/global.rs
@@ -15,6 +15,7 @@ use deno_core::url::Url;
use deno_runtime::deno_node::PackageJson;
use deno_runtime::deno_node::TYPES_CONDITIONS;
+use crate::lockfile::Lockfile;
use crate::npm::resolution::NpmResolution;
use crate::npm::resolution::NpmResolutionSnapshot;
use crate::npm::resolvers::common::cache_packages;
@@ -145,6 +146,11 @@ impl InnerNpmPackageResolver for GlobalNpmPackageResolver {
fn snapshot(&self) -> NpmResolutionSnapshot {
self.resolution.snapshot()
}
+
+ fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> {
+ let snapshot = self.resolution.snapshot();
+ self.resolution.lock(lockfile, &snapshot)
+ }
}
async fn cache_packages_in_resolver(
diff --git a/cli/npm/resolvers/local.rs b/cli/npm/resolvers/local.rs
index b51593d4c..6c4c4ef6c 100644
--- a/cli/npm/resolvers/local.rs
+++ b/cli/npm/resolvers/local.rs
@@ -22,6 +22,7 @@ use deno_runtime::deno_node::TYPES_CONDITIONS;
use tokio::task::JoinHandle;
use crate::fs_util;
+use crate::lockfile::Lockfile;
use crate::npm::cache::should_sync_download;
use crate::npm::resolution::NpmResolution;
use crate::npm::resolution::NpmResolutionSnapshot;
@@ -213,6 +214,10 @@ impl InnerNpmPackageResolver for LocalNpmPackageResolver {
fn snapshot(&self) -> NpmResolutionSnapshot {
self.resolution.snapshot()
}
+
+ fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> {
+ self.resolution.lock(lockfile, &self.snapshot())
+ }
}
async fn sync_resolver_with_fs(
diff --git a/cli/npm/resolvers/mod.rs b/cli/npm/resolvers/mod.rs
index 5498bbf75..3d55170ac 100644
--- a/cli/npm/resolvers/mod.rs
+++ b/cli/npm/resolvers/mod.rs
@@ -8,6 +8,7 @@ use deno_ast::ModuleSpecifier;
use deno_core::anyhow::bail;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
+use deno_core::parking_lot::Mutex;
use deno_core::serde_json;
use deno_runtime::deno_node::PathClean;
use deno_runtime::deno_node::RequireNpmResolver;
@@ -21,6 +22,7 @@ use std::path::PathBuf;
use std::sync::Arc;
use crate::fs_util;
+use crate::lockfile::Lockfile;
use self::common::InnerNpmPackageResolver;
use self::local::LocalNpmPackageResolver;
@@ -70,6 +72,7 @@ pub struct NpmPackageResolver {
local_node_modules_path: Option<PathBuf>,
api: NpmRegistryApi,
cache: NpmCache,
+ maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
}
impl std::fmt::Debug for NpmPackageResolver {
@@ -101,6 +104,32 @@ impl NpmPackageResolver {
)
}
+ /// This function will replace current resolver with a new one built from a
+ /// snapshot created out of the lockfile.
+ pub async fn add_lockfile(
+ &mut self,
+ lockfile: Arc<Mutex<Lockfile>>,
+ ) -> Result<(), AnyError> {
+ let snapshot =
+ NpmResolutionSnapshot::from_lockfile(lockfile.clone(), &self.api).await?;
+ self.maybe_lockfile = Some(lockfile);
+ if let Some(node_modules_folder) = &self.local_node_modules_path {
+ self.inner = Arc::new(LocalNpmPackageResolver::new(
+ self.cache.clone(),
+ self.api.clone(),
+ node_modules_folder.clone(),
+ Some(snapshot),
+ ));
+ } else {
+ self.inner = Arc::new(GlobalNpmPackageResolver::new(
+ self.cache.clone(),
+ self.api.clone(),
+ Some(snapshot),
+ ));
+ }
+ Ok(())
+ }
+
fn new_with_maybe_snapshot(
cache: NpmCache,
api: NpmRegistryApi,
@@ -138,6 +167,7 @@ impl NpmPackageResolver {
local_node_modules_path,
api,
cache,
+ maybe_lockfile: None,
}
}
@@ -224,7 +254,15 @@ impl NpmPackageResolver {
));
}
- self.inner.add_package_reqs(packages).await
+ self.inner.add_package_reqs(packages).await?;
+
+ // If there's a lock file, update it with all discovered npm packages
+ if let Some(lockfile_mutex) = &self.maybe_lockfile {
+ let mut lockfile = lockfile_mutex.lock();
+ self.lock(&mut lockfile)?;
+ }
+
+ Ok(())
}
/// Sets package requirements to the resolver, removing old requirements and adding new ones.
@@ -266,6 +304,10 @@ impl NpmPackageResolver {
Some(self.inner.snapshot()),
)
}
+
+ pub fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> {
+ self.inner.lock(lockfile)
+ }
}
impl RequireNpmResolver for NpmPackageResolver {
diff --git a/cli/proc_state.rs b/cli/proc_state.rs
index ac83e9459..7fc28b553 100644
--- a/cli/proc_state.rs
+++ b/cli/proc_state.rs
@@ -235,7 +235,8 @@ impl ProcState {
cli_options.cache_setting(),
progress_bar.clone(),
);
- let npm_resolver = NpmPackageResolver::new(
+ let maybe_lockfile = lockfile.as_ref().filter(|l| !l.lock().write).cloned();
+ let mut npm_resolver = NpmPackageResolver::new(
npm_cache.clone(),
api,
cli_options.unstable()
@@ -246,6 +247,9 @@ impl ProcState {
.resolve_local_node_modules_folder()
.with_context(|| "Resolving local node_modules folder.")?,
);
+ if let Some(lockfile) = maybe_lockfile.clone() {
+ npm_resolver.add_lockfile(lockfile).await?;
+ }
let node_analysis_cache =
NodeAnalysisCache::new(Some(dir.node_analysis_db_file_path()));
@@ -464,6 +468,8 @@ impl ProcState {
}
/// Add the builtin node modules to the graph data.
+ // FIXME(bartlomieju): appears this function can be called more than once
+ // if we have npm imports
pub async fn prepare_node_std_graph(&self) -> Result<(), AnyError> {
let node_std_graph = self
.create_graph(vec![(node::MODULE_ALL_URL.clone(), ModuleKind::Esm)])
diff --git a/cli/tests/integration/npm_tests.rs b/cli/tests/integration/npm_tests.rs
index 9fc817141..8fa79d06c 100644
--- a/cli/tests/integration/npm_tests.rs
+++ b/cli/tests/integration/npm_tests.rs
@@ -158,6 +158,14 @@ itest!(import_map {
http_server: true,
});
+itest!(lock_file {
+ args: "run --allow-read --allow-env --unstable --lock npm/lock_file/lock.json npm/lock_file/main.js",
+ output: "npm/lock_file/main.out",
+ envs: env_vars(),
+ http_server: true,
+ exit_code: 10,
+});
+
itest!(sub_paths {
args: "run --unstable -A --quiet npm/sub_paths/main.jsx",
output: "npm/sub_paths/main.out",
diff --git a/cli/tests/testdata/npm/lock_file/lock.json b/cli/tests/testdata/npm/lock_file/lock.json
new file mode 100644
index 000000000..57253314e
--- /dev/null
+++ b/cli/tests/testdata/npm/lock_file/lock.json
@@ -0,0 +1,164 @@
+{
+ "version": "2",
+ "remote": {},
+ "npm": {
+ "specifiers": { "fs-extra@10.1.0": "fs-extra@10.1.0", "vue": "vue@3.2.38" },
+ "packages": {
+ "@babel/parser@7.19.0": {
+ "integrity": "sha512-foobar!",
+ "dependencies": {}
+ },
+ "@vue/compiler-core@3.2.38": {
+ "integrity": "sha512-/FsvnSu7Z+lkd/8KXMa4yYNUiqQrI22135gfsQYVGuh5tqEgOB0XqrUdb/KnCLa5+TmQLPwvyUnKMyCpu+SX3Q==",
+ "dependencies": {
+ "@babel/parser": "@babel/parser@7.19.0",
+ "@vue/shared": "@vue/shared@3.2.38",
+ "estree-walker": "estree-walker@2.0.2",
+ "source-map": "source-map@0.6.1"
+ }
+ },
+ "@vue/compiler-dom@3.2.38": {
+ "integrity": "sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g==",
+ "dependencies": {
+ "@vue/compiler-core": "@vue/compiler-core@3.2.38",
+ "@vue/shared": "@vue/shared@3.2.38"
+ }
+ },
+ "@vue/compiler-sfc@3.2.38": {
+ "integrity": "sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg==",
+ "dependencies": {
+ "@babel/parser": "@babel/parser@7.19.0",
+ "@vue/compiler-core": "@vue/compiler-core@3.2.38",
+ "@vue/compiler-dom": "@vue/compiler-dom@3.2.38",
+ "@vue/compiler-ssr": "@vue/compiler-ssr@3.2.38",
+ "@vue/reactivity-transform": "@vue/reactivity-transform@3.2.38",
+ "@vue/shared": "@vue/shared@3.2.38",
+ "estree-walker": "estree-walker@2.0.2",
+ "magic-string": "magic-string@0.25.9",
+ "postcss": "postcss@8.4.16",
+ "source-map": "source-map@0.6.1"
+ }
+ },
+ "@vue/compiler-ssr@3.2.38": {
+ "integrity": "sha512-bm9jOeyv1H3UskNm4S6IfueKjUNFmi2kRweFIGnqaGkkRePjwEcfCVqyS3roe7HvF4ugsEkhf4+kIvDhip6XzQ==",
+ "dependencies": {
+ "@vue/compiler-dom": "@vue/compiler-dom@3.2.38",
+ "@vue/shared": "@vue/shared@3.2.38"
+ }
+ },
+ "@vue/reactivity-transform@3.2.38": {
+ "integrity": "sha512-3SD3Jmi1yXrDwiNJqQ6fs1x61WsDLqVk4NyKVz78mkaIRh6d3IqtRnptgRfXn+Fzf+m6B1KxBYWq1APj6h4qeA==",
+ "dependencies": {
+ "@babel/parser": "@babel/parser@7.19.0",
+ "@vue/compiler-core": "@vue/compiler-core@3.2.38",
+ "@vue/shared": "@vue/shared@3.2.38",
+ "estree-walker": "estree-walker@2.0.2",
+ "magic-string": "magic-string@0.25.9"
+ }
+ },
+ "@vue/reactivity@3.2.38": {
+ "integrity": "sha512-6L4myYcH9HG2M25co7/BSo0skKFHpAN8PhkNPM4xRVkyGl1K5M3Jx4rp5bsYhvYze2K4+l+pioN4e6ZwFLUVtw==",
+ "dependencies": { "@vue/shared": "@vue/shared@3.2.38" }
+ },
+ "@vue/runtime-core@3.2.38": {
+ "integrity": "sha512-kk0qiSiXUU/IKxZw31824rxmFzrLr3TL6ZcbrxWTKivadoKupdlzbQM4SlGo4MU6Zzrqv4fzyUasTU1jDoEnzg==",
+ "dependencies": {
+ "@vue/reactivity": "@vue/reactivity@3.2.38",
+ "@vue/shared": "@vue/shared@3.2.38"
+ }
+ },
+ "@vue/runtime-dom@3.2.38": {
+ "integrity": "sha512-4PKAb/ck2TjxdMSzMsnHViOrrwpudk4/A56uZjhzvusoEU9xqa5dygksbzYepdZeB5NqtRw5fRhWIiQlRVK45A==",
+ "dependencies": {
+ "@vue/runtime-core": "@vue/runtime-core@3.2.38",
+ "@vue/shared": "@vue/shared@3.2.38",
+ "csstype": "csstype@2.6.20"
+ }
+ },
+ "@vue/server-renderer@3.2.38": {
+ "integrity": "sha512-pg+JanpbOZ5kEfOZzO2bt02YHd+ELhYP8zPeLU1H0e7lg079NtuuSB8fjLdn58c4Ou8UQ6C1/P+528nXnLPAhA==",
+ "dependencies": {
+ "@vue/compiler-ssr": "@vue/compiler-ssr@3.2.38",
+ "@vue/shared": "@vue/shared@3.2.38"
+ }
+ },
+ "@vue/shared@3.2.38": {
+ "integrity": "sha512-dTyhTIRmGXBjxJE+skC8tTWCGLCVc4wQgRRLt8+O9p5ewBAjoBwtCAkLPrtToSr1xltoe3st21Pv953aOZ7alg==",
+ "dependencies": {}
+ },
+ "csstype@2.6.20": {
+ "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==",
+ "dependencies": {}
+ },
+ "estree-walker@2.0.2": {
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dependencies": {}
+ },
+ "fs-extra@10.1.0": {
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dependencies": {
+ "graceful-fs": "graceful-fs@4.2.10",
+ "jsonfile": "jsonfile@6.1.0",
+ "universalify": "universalify@2.0.0"
+ }
+ },
+ "graceful-fs@4.2.10": {
+ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
+ "dependencies": {}
+ },
+ "jsonfile@6.1.0": {
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dependencies": {
+ "graceful-fs": "graceful-fs@4.2.10",
+ "universalify": "universalify@2.0.0"
+ }
+ },
+ "magic-string@0.25.9": {
+ "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "dependencies": { "sourcemap-codec": "sourcemap-codec@1.4.8" }
+ },
+ "nanoid@3.3.4": {
+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+ "dependencies": {}
+ },
+ "picocolors@1.0.0": {
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dependencies": {}
+ },
+ "postcss@8.4.16": {
+ "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
+ "dependencies": {
+ "nanoid": "nanoid@3.3.4",
+ "picocolors": "picocolors@1.0.0",
+ "source-map-js": "source-map-js@1.0.2"
+ }
+ },
+ "source-map-js@1.0.2": {
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dependencies": {}
+ },
+ "source-map@0.6.1": {
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dependencies": {}
+ },
+ "sourcemap-codec@1.4.8": {
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dependencies": {}
+ },
+ "universalify@2.0.0": {
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "dependencies": {}
+ },
+ "vue@3.2.38": {
+ "integrity": "sha512-hHrScEFSmDAWL0cwO4B6WO7D3sALZPbfuThDsGBebthrNlDxdJZpGR3WB87VbjpPh96mep1+KzukYEhpHDFa8Q==",
+ "dependencies": {
+ "@vue/compiler-dom": "@vue/compiler-dom@3.2.38",
+ "@vue/compiler-sfc": "@vue/compiler-sfc@3.2.38",
+ "@vue/runtime-dom": "@vue/runtime-dom@3.2.38",
+ "@vue/server-renderer": "@vue/server-renderer@3.2.38",
+ "@vue/shared": "@vue/shared@3.2.38"
+ }
+ }
+ }
+ }
+}
diff --git a/cli/tests/testdata/npm/lock_file/main.js b/cli/tests/testdata/npm/lock_file/main.js
new file mode 100644
index 000000000..a7b5960ca
--- /dev/null
+++ b/cli/tests/testdata/npm/lock_file/main.js
@@ -0,0 +1,5 @@
+import fsx from "npm:fs-extra@10.1.0";
+import { createApp } from "npm:vue";
+
+console.log(fsx.access);
+console.log(createApp);
diff --git a/cli/tests/testdata/npm/lock_file/main.out b/cli/tests/testdata/npm/lock_file/main.out
new file mode 100644
index 000000000..4c034d03b
--- /dev/null
+++ b/cli/tests/testdata/npm/lock_file/main.out
@@ -0,0 +1,4 @@
+Download [WILDCARD]
+error: Integrity check failed for npm package: "@babel/parser@7.19.0".
+ Cache has "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==" and lockfile has "sha512-foobar!".
+ Use "--lock-write" flag to update the lockfile.