wasm_pack/manifest/
mod.rs

1//! Reading and writing Cargo.toml and package.json manifests.
2
3#![allow(
4    clippy::new_ret_no_self,
5    clippy::needless_pass_by_value,
6    clippy::redundant_closure
7)]
8
9use anyhow::{anyhow, bail, Context, Result};
10mod npm;
11
12use std::path::Path;
13use std::{collections::HashMap, fs};
14
15use self::npm::{
16    repository::Repository, CommonJSPackage, ESModulesPackage, NoModulesPackage, NpmPackage,
17};
18use crate::command::build::{BuildProfile, Target};
19use crate::PBAR;
20use cargo_metadata::Metadata;
21use chrono::offset;
22use chrono::DateTime;
23use serde::{self, Deserialize};
24use serde_json;
25use std::collections::BTreeSet;
26use std::env;
27use std::io::Write;
28use strsim::levenshtein;
29use toml;
30
31const WASM_PACK_METADATA_KEY: &str = "package.metadata.wasm-pack";
32const WASM_PACK_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
33const WASM_PACK_REPO_URL: &str = "https://github.com/rustwasm/wasm-pack";
34
35/// Store for metadata learned about a crate
36pub struct CrateData {
37    data: Metadata,
38    current_idx: usize,
39    manifest: CargoManifest,
40    out_name: Option<String>,
41}
42
43#[doc(hidden)]
44#[derive(Deserialize)]
45pub struct CargoManifest {
46    package: CargoPackage,
47}
48
49#[derive(Deserialize)]
50struct CargoPackage {
51    name: String,
52
53    #[serde(default)]
54    metadata: CargoMetadata,
55}
56
57#[derive(Default, Deserialize)]
58struct CargoMetadata {
59    #[serde(default, rename = "wasm-pack")]
60    wasm_pack: CargoWasmPack,
61}
62
63#[derive(Default, Deserialize)]
64struct CargoWasmPack {
65    #[serde(default)]
66    profile: CargoWasmPackProfiles,
67}
68
69#[derive(Deserialize)]
70struct CargoWasmPackProfiles {
71    #[serde(
72        default = "CargoWasmPackProfile::default_dev",
73        deserialize_with = "CargoWasmPackProfile::deserialize_dev"
74    )]
75    dev: CargoWasmPackProfile,
76
77    #[serde(
78        default = "CargoWasmPackProfile::default_release",
79        deserialize_with = "CargoWasmPackProfile::deserialize_release"
80    )]
81    release: CargoWasmPackProfile,
82
83    #[serde(
84        default = "CargoWasmPackProfile::default_profiling",
85        deserialize_with = "CargoWasmPackProfile::deserialize_profiling"
86    )]
87    profiling: CargoWasmPackProfile,
88}
89
90impl Default for CargoWasmPackProfiles {
91    fn default() -> CargoWasmPackProfiles {
92        CargoWasmPackProfiles {
93            dev: CargoWasmPackProfile::default_dev(),
94            release: CargoWasmPackProfile::default_release(),
95            profiling: CargoWasmPackProfile::default_profiling(),
96        }
97    }
98}
99
100/// This is where configuration goes for wasm-bindgen, wasm-opt, wasm-snip, or
101/// anything else that wasm-pack runs.
102#[derive(Default, Deserialize)]
103pub struct CargoWasmPackProfile {
104    #[serde(default, rename = "wasm-bindgen")]
105    wasm_bindgen: CargoWasmPackProfileWasmBindgen,
106    #[serde(default, rename = "wasm-opt")]
107    wasm_opt: Option<CargoWasmPackProfileWasmOpt>,
108}
109
110#[derive(Default, Deserialize)]
111struct CargoWasmPackProfileWasmBindgen {
112    #[serde(default, rename = "debug-js-glue")]
113    debug_js_glue: Option<bool>,
114
115    #[serde(default, rename = "demangle-name-section")]
116    demangle_name_section: Option<bool>,
117
118    #[serde(default, rename = "dwarf-debug-info")]
119    dwarf_debug_info: Option<bool>,
120
121    #[serde(default, rename = "omit-default-module-path")]
122    omit_default_module_path: Option<bool>,
123}
124
125/// Struct for storing information received from crates.io
126#[derive(Deserialize, Debug)]
127pub struct Crate {
128    #[serde(rename = "crate")]
129    crt: CrateInformation,
130}
131
132#[derive(Deserialize, Debug)]
133struct CrateInformation {
134    max_version: String,
135}
136
137impl Crate {
138    /// Returns latest wasm-pack version
139    pub fn return_wasm_pack_latest_version() -> Result<Option<String>> {
140        let current_time = chrono::offset::Local::now();
141        let old_metadata_file = Self::return_wasm_pack_file();
142
143        match old_metadata_file {
144            Some(ref file_contents) => {
145                let last_updated = Self::return_stamp_file_value(&file_contents, "created")
146                    .and_then(|t| DateTime::parse_from_str(t.as_str(), "%+").ok());
147
148                last_updated
149                    .map(|last_updated| {
150                        if current_time.signed_duration_since(last_updated).num_hours() > 24 {
151                            Self::return_api_call_result(current_time).map(Some)
152                        } else {
153                            Ok(Self::return_stamp_file_value(&file_contents, "version"))
154                        }
155                    })
156                    .unwrap_or_else(|| Ok(None))
157            }
158            None => Self::return_api_call_result(current_time).map(Some),
159        }
160    }
161
162    fn return_api_call_result(current_time: DateTime<offset::Local>) -> Result<String> {
163        let version = Self::return_latest_wasm_pack_version();
164
165        // We always override the stamp file with the current time because we don't
166        // want to hit the API all the time if it fails. It should follow the same
167        // "policy" as the success. This means that the 24 hours rate limiting
168        // will be active regardless if the check succeeded or failed.
169        match version {
170            Ok(ref version) => Self::override_stamp_file(current_time, Some(&version)).ok(),
171            Err(_) => Self::override_stamp_file(current_time, None).ok(),
172        };
173
174        version
175    }
176
177    fn override_stamp_file(
178        current_time: DateTime<offset::Local>,
179        version: Option<&str>,
180    ) -> Result<()> {
181        let path = env::current_exe()?;
182
183        let mut file = fs::OpenOptions::new()
184            .read(true)
185            .write(true)
186            .append(true)
187            .create(true)
188            .open(path.with_extension("stamp"))?;
189
190        file.set_len(0)?;
191
192        write!(file, "created {:?}", current_time)?;
193
194        if let Some(version) = version {
195            write!(file, "\nversion {}", version)?;
196        }
197
198        Ok(())
199    }
200
201    /// Return stamp file where metadata is stored.
202    fn return_wasm_pack_file() -> Option<String> {
203        if let Ok(path) = env::current_exe() {
204            if let Ok(file) = fs::read_to_string(path.with_extension("stamp")) {
205                return Some(file);
206            }
207        }
208        None
209    }
210
211    /// Returns wasm-pack latest version (if it's received) by executing check_wasm_pack_latest_version function.
212    fn return_latest_wasm_pack_version() -> Result<String> {
213        Self::check_wasm_pack_latest_version().map(|crt| crt.crt.max_version)
214    }
215
216    /// Read the stamp file and return value assigned to a certain key.
217    fn return_stamp_file_value(file: &str, word: &str) -> Option<String> {
218        let created = file
219            .lines()
220            .find(|line| line.starts_with(word))
221            .and_then(|l| l.split_whitespace().nth(1));
222
223        created.map(|s| s.to_string())
224    }
225
226    /// Call to the crates.io api and return the latest version of `wasm-pack`
227    fn check_wasm_pack_latest_version() -> Result<Crate> {
228        let url = "https://crates.io/api/v1/crates/wasm-pack";
229        let agent = ureq::builder()
230            .try_proxy_from_env(true)
231            .user_agent(&format!(
232                "wasm-pack/{} ({})",
233                WASM_PACK_VERSION.unwrap_or_else(|| "unknown"),
234                WASM_PACK_REPO_URL
235            ))
236            .build();
237        let resp = agent
238            .get(url)
239            .call()
240            .context("failed to get wasm-pack version")?;
241
242        let status_code = resp.status();
243
244        if 200 <= status_code && status_code < 300 {
245            let json = resp.into_json()?;
246
247            Ok(json)
248        } else {
249            bail!(
250                "Received a bad HTTP status code ({}) when checking for newer wasm-pack version at: {}",
251                status_code,
252                url
253            )
254        }
255    }
256}
257
258#[derive(Clone, Deserialize)]
259#[serde(untagged)]
260enum CargoWasmPackProfileWasmOpt {
261    Enabled(bool),
262    ExplicitArgs(Vec<String>),
263}
264
265impl Default for CargoWasmPackProfileWasmOpt {
266    fn default() -> Self {
267        CargoWasmPackProfileWasmOpt::Enabled(false)
268    }
269}
270
271impl CargoWasmPackProfile {
272    fn default_dev() -> Self {
273        CargoWasmPackProfile {
274            wasm_bindgen: CargoWasmPackProfileWasmBindgen {
275                debug_js_glue: Some(true),
276                demangle_name_section: Some(true),
277                dwarf_debug_info: Some(false),
278                omit_default_module_path: Some(false),
279            },
280            wasm_opt: None,
281        }
282    }
283
284    fn default_release() -> Self {
285        CargoWasmPackProfile {
286            wasm_bindgen: CargoWasmPackProfileWasmBindgen {
287                debug_js_glue: Some(false),
288                demangle_name_section: Some(true),
289                dwarf_debug_info: Some(false),
290                omit_default_module_path: Some(false),
291            },
292            wasm_opt: Some(CargoWasmPackProfileWasmOpt::Enabled(true)),
293        }
294    }
295
296    fn default_profiling() -> Self {
297        CargoWasmPackProfile {
298            wasm_bindgen: CargoWasmPackProfileWasmBindgen {
299                debug_js_glue: Some(false),
300                demangle_name_section: Some(true),
301                dwarf_debug_info: Some(false),
302                omit_default_module_path: Some(false),
303            },
304            wasm_opt: Some(CargoWasmPackProfileWasmOpt::Enabled(true)),
305        }
306    }
307
308    fn deserialize_dev<'de, D>(deserializer: D) -> Result<Self, D::Error>
309    where
310        D: serde::Deserializer<'de>,
311    {
312        let mut profile = <Option<Self>>::deserialize(deserializer)?.unwrap_or_default();
313        profile.update_with_defaults(&Self::default_dev());
314        Ok(profile)
315    }
316
317    fn deserialize_release<'de, D>(deserializer: D) -> Result<Self, D::Error>
318    where
319        D: serde::Deserializer<'de>,
320    {
321        let mut profile = <Option<Self>>::deserialize(deserializer)?.unwrap_or_default();
322        profile.update_with_defaults(&Self::default_release());
323        Ok(profile)
324    }
325
326    fn deserialize_profiling<'de, D>(deserializer: D) -> Result<Self, D::Error>
327    where
328        D: serde::Deserializer<'de>,
329    {
330        let mut profile = <Option<Self>>::deserialize(deserializer)?.unwrap_or_default();
331        profile.update_with_defaults(&Self::default_profiling());
332        Ok(profile)
333    }
334
335    fn update_with_defaults(&mut self, defaults: &Self) {
336        macro_rules! d {
337            ( $( $path:ident ).* ) => {
338                self. $( $path ).* .get_or_insert(defaults. $( $path ).* .unwrap());
339            }
340        }
341        d!(wasm_bindgen.debug_js_glue);
342        d!(wasm_bindgen.demangle_name_section);
343        d!(wasm_bindgen.dwarf_debug_info);
344        d!(wasm_bindgen.omit_default_module_path);
345
346        if self.wasm_opt.is_none() {
347            self.wasm_opt = defaults.wasm_opt.clone();
348        }
349    }
350
351    /// Get this profile's configured `[wasm-bindgen.debug-js-glue]` value.
352    pub fn wasm_bindgen_debug_js_glue(&self) -> bool {
353        self.wasm_bindgen.debug_js_glue.unwrap()
354    }
355
356    /// Get this profile's configured `[wasm-bindgen.demangle-name-section]` value.
357    pub fn wasm_bindgen_demangle_name_section(&self) -> bool {
358        self.wasm_bindgen.demangle_name_section.unwrap()
359    }
360
361    /// Get this profile's configured `[wasm-bindgen.dwarf-debug-info]` value.
362    pub fn wasm_bindgen_dwarf_debug_info(&self) -> bool {
363        self.wasm_bindgen.dwarf_debug_info.unwrap()
364    }
365
366    /// Get this profile's configured `[wasm-bindgen.omit-default-module-path]` value.
367    pub fn wasm_bindgen_omit_default_module_path(&self) -> bool {
368        self.wasm_bindgen.omit_default_module_path.unwrap()
369    }
370
371    /// Get this profile's configured arguments for `wasm-opt`, if enabled.
372    pub fn wasm_opt_args(&self) -> Option<Vec<String>> {
373        match self.wasm_opt.as_ref()? {
374            CargoWasmPackProfileWasmOpt::Enabled(false) => None,
375            CargoWasmPackProfileWasmOpt::Enabled(true) => Some(vec!["-O".to_string()]),
376            CargoWasmPackProfileWasmOpt::ExplicitArgs(s) => Some(s.clone()),
377        }
378    }
379}
380
381struct NpmData {
382    name: String,
383    files: Vec<String>,
384    dts_file: Option<String>,
385    main: String,
386    homepage: Option<String>, // https://docs.npmjs.com/files/package.json#homepage,
387    keywords: Option<Vec<String>>, // https://docs.npmjs.com/files/package.json#keywords
388}
389
390#[doc(hidden)]
391pub struct ManifestAndUnsedKeys {
392    pub manifest: CargoManifest,
393    pub unused_keys: BTreeSet<String>,
394}
395
396impl CrateData {
397    /// Reads all metadata for the crate whose manifest is inside the directory
398    /// specified by `path`.
399    pub fn new(crate_path: &Path, out_name: Option<String>) -> Result<CrateData> {
400        let manifest_path = crate_path.join("Cargo.toml");
401        if !manifest_path.is_file() {
402            bail!(
403                "crate directory is missing a `Cargo.toml` file; is `{}` the \
404                 wrong directory?",
405                crate_path.display()
406            )
407        }
408
409        let data = cargo_metadata::MetadataCommand::new()
410            .manifest_path(&manifest_path)
411            .exec()?;
412
413        let manifest_and_keys = CrateData::parse_crate_data(&manifest_path)?;
414        CrateData::warn_for_unused_keys(&manifest_and_keys);
415
416        let manifest = manifest_and_keys.manifest;
417        let current_idx = data
418            .packages
419            .iter()
420            .position(|pkg| {
421                pkg.name == manifest.package.name
422                    && CrateData::is_same_path(pkg.manifest_path.as_std_path(), &manifest_path)
423            })
424            .ok_or_else(|| anyhow!("failed to find package in metadata"))?;
425
426        Ok(CrateData {
427            data,
428            manifest,
429            current_idx,
430            out_name,
431        })
432    }
433
434    fn is_same_path(path1: &Path, path2: &Path) -> bool {
435        if let Ok(path1) = fs::canonicalize(&path1) {
436            if let Ok(path2) = fs::canonicalize(&path2) {
437                return path1 == path2;
438            }
439        }
440        path1 == path2
441    }
442
443    /// Read the `manifest_path` file and deserializes it using the toml Deserializer.
444    /// Returns a Result containing `ManifestAndUnsedKeys` which contains `CargoManifest`
445    /// and a `BTreeSet<String>` containing the unused keys from the parsed file.
446    ///
447    /// # Errors
448    /// Will return Err if the file (manifest_path) couldn't be read or
449    /// if deserialize to `CargoManifest` fails.
450    pub fn parse_crate_data(manifest_path: &Path) -> Result<ManifestAndUnsedKeys> {
451        let manifest = fs::read_to_string(&manifest_path)
452            .with_context(|| anyhow!("failed to read: {}", manifest_path.display()))?;
453        let manifest = toml::Deserializer::new(&manifest);
454
455        let mut unused_keys = BTreeSet::new();
456        let levenshtein_threshold = 1;
457
458        let manifest: CargoManifest = serde_ignored::deserialize(manifest, |path| {
459            let path_string = path.to_string();
460
461            if path_string.starts_with("package.metadata")
462                && (path_string.contains("wasm-pack")
463                    || levenshtein(WASM_PACK_METADATA_KEY, &path_string) <= levenshtein_threshold)
464            {
465                unused_keys.insert(path_string);
466            }
467        })
468        .with_context(|| anyhow!("failed to parse manifest: {}", manifest_path.display()))?;
469
470        Ok(ManifestAndUnsedKeys {
471            manifest,
472            unused_keys,
473        })
474    }
475
476    /// Iterating through all the passed `unused_keys` and output
477    /// a warning for each unknown key.
478    pub fn warn_for_unused_keys(manifest_and_keys: &ManifestAndUnsedKeys) {
479        manifest_and_keys.unused_keys.iter().for_each(|path| {
480            PBAR.warn(&format!(
481                "\"{}\" is an unknown key and will be ignored. Please check your Cargo.toml.",
482                path
483            ));
484        });
485    }
486
487    /// Get the configured profile.
488    pub fn configured_profile(&self, profile: BuildProfile) -> &CargoWasmPackProfile {
489        match profile {
490            BuildProfile::Dev => &self.manifest.package.metadata.wasm_pack.profile.dev,
491            BuildProfile::Profiling => &self.manifest.package.metadata.wasm_pack.profile.profiling,
492            BuildProfile::Release => &self.manifest.package.metadata.wasm_pack.profile.release,
493        }
494    }
495
496    /// Check that the crate the given path is properly configured.
497    pub fn check_crate_config(&self) -> Result<()> {
498        self.check_crate_type()?;
499        Ok(())
500    }
501
502    fn check_crate_type(&self) -> Result<()> {
503        let pkg = &self.data.packages[self.current_idx];
504        let any_cdylib = pkg
505            .targets
506            .iter()
507            .filter(|target| target.kind.iter().any(|k| k == "cdylib"))
508            .any(|target| target.crate_types.iter().any(|s| s == "cdylib"));
509        if any_cdylib {
510            return Ok(());
511        }
512        bail!(
513            "crate-type must be cdylib to compile to wasm32-unknown-unknown. Add the following to your \
514             Cargo.toml file:\n\n\
515             [lib]\n\
516             crate-type = [\"cdylib\", \"rlib\"]"
517        )
518    }
519
520    fn pkg(&self) -> &cargo_metadata::Package {
521        &self.data.packages[self.current_idx]
522    }
523
524    /// Get the crate name for the crate at the given path.
525    pub fn crate_name(&self) -> String {
526        let pkg = self.pkg();
527        match pkg
528            .targets
529            .iter()
530            .find(|t| t.kind.iter().any(|k| k == "cdylib"))
531        {
532            Some(lib) => lib.name.replace("-", "_"),
533            None => pkg.name.replace("-", "_"),
534        }
535    }
536
537    /// Get the prefix for output file names
538    pub fn name_prefix(&self) -> String {
539        match &self.out_name {
540            Some(value) => value.clone(),
541            None => self.crate_name(),
542        }
543    }
544
545    /// Gets the optional path to the readme, or None if disabled.
546    pub fn crate_readme(&self) -> Option<String> {
547        self.pkg()
548            .readme
549            .clone()
550            .map(|readme_file| readme_file.into_string())
551    }
552
553    /// Get the license for the crate at the given path.
554    pub fn crate_license(&self) -> &Option<String> {
555        &self.pkg().license
556    }
557
558    /// Get the license file path for the crate at the given path.
559    pub fn crate_license_file(&self) -> Option<String> {
560        self.pkg()
561            .license_file
562            .clone()
563            .map(|license_file| license_file.into_string())
564    }
565
566    /// Returns the path to this project's target directory where artifacts are
567    /// located after a cargo build.
568    pub fn target_directory(&self) -> &Path {
569        Path::new(&self.data.target_directory)
570    }
571
572    /// Returns the path to this project's root cargo workspace directory
573    pub fn workspace_root(&self) -> &Path {
574        Path::new(&self.data.workspace_root)
575    }
576
577    /// Generate a package.json file inside in `./pkg`.
578    pub fn write_package_json(
579        &self,
580        out_dir: &Path,
581        scope: &Option<String>,
582        disable_dts: bool,
583        target: Target,
584    ) -> Result<()> {
585        let pkg_file_path = out_dir.join("package.json");
586        // Check if a `package.json` was already generated by wasm-bindgen, if so
587        // we merge the NPM dependencies already specified in it.
588        let existing_deps = if pkg_file_path.exists() {
589            // It's just a map of dependency names to versions
590            Some(serde_json::from_str::<HashMap<String, String>>(
591                &fs::read_to_string(&pkg_file_path)?,
592            )?)
593        } else {
594            None
595        };
596        let npm_data = match target {
597            Target::Nodejs => self.to_commonjs(scope, disable_dts, existing_deps, out_dir),
598            Target::NoModules => self.to_nomodules(scope, disable_dts, existing_deps, out_dir),
599            Target::Bundler => self.to_esmodules(scope, disable_dts, existing_deps, out_dir),
600            Target::Web => self.to_web(scope, disable_dts, existing_deps, out_dir),
601            // Deno does not need package.json
602            Target::Deno => return Ok(()),
603        };
604
605        let npm_json = serde_json::to_string_pretty(&npm_data)?;
606
607        fs::write(&pkg_file_path, npm_json)
608            .with_context(|| anyhow!("failed to write: {}", pkg_file_path.display()))?;
609        Ok(())
610    }
611
612    fn npm_data(
613        &self,
614        scope: &Option<String>,
615        add_js_bg_to_package_json: bool,
616        disable_dts: bool,
617        out_dir: &Path,
618    ) -> NpmData {
619        let name_prefix = self.name_prefix();
620        let wasm_file = format!("{}_bg.wasm", name_prefix);
621        let js_file = format!("{}.js", name_prefix);
622        let mut files = vec![wasm_file];
623
624        files.push(js_file.clone());
625        if add_js_bg_to_package_json {
626            let js_bg_file = format!("{}_bg.js", name_prefix);
627            files.push(js_bg_file);
628        }
629
630        let pkg = &self.data.packages[self.current_idx];
631        let npm_name = match scope {
632            Some(s) => format!("@{}/{}", s, pkg.name),
633            None => pkg.name.clone(),
634        };
635
636        let dts_file = if !disable_dts {
637            let file = format!("{}.d.ts", name_prefix);
638            files.push(file.to_string());
639            Some(file)
640        } else {
641            None
642        };
643
644        let keywords = if pkg.keywords.len() > 0 {
645            Some(pkg.keywords.clone())
646        } else {
647            None
648        };
649
650        if let Ok(entries) = fs::read_dir(out_dir) {
651            let file_names = entries
652                .filter_map(|e| e.ok())
653                .filter(|e| e.metadata().map(|m| m.is_file()).unwrap_or(false))
654                .filter_map(|e| e.file_name().into_string().ok())
655                .filter(|f| f.starts_with("LICENSE"))
656                .filter(|f| f != "LICENSE");
657            for file_name in file_names {
658                files.push(file_name);
659            }
660        }
661
662        NpmData {
663            name: npm_name,
664            dts_file,
665            files,
666            main: js_file,
667            homepage: self.pkg().homepage.clone(),
668            keywords,
669        }
670    }
671
672    fn license(&self) -> Option<String> {
673        self.crate_license().clone().or_else(|| {
674            self.crate_license_file().clone().map(|file| {
675                // When license is written in file: https://docs.npmjs.com/files/package.json#license
676                format!("SEE LICENSE IN {}", file)
677            })
678        })
679    }
680
681    fn to_commonjs(
682        &self,
683        scope: &Option<String>,
684        disable_dts: bool,
685        dependencies: Option<HashMap<String, String>>,
686        out_dir: &Path,
687    ) -> NpmPackage {
688        let data = self.npm_data(scope, false, disable_dts, out_dir);
689        let pkg = &self.data.packages[self.current_idx];
690
691        self.check_optional_fields();
692
693        NpmPackage::CommonJSPackage(CommonJSPackage {
694            name: data.name,
695            collaborators: pkg.authors.clone(),
696            description: self.pkg().description.clone(),
697            version: pkg.version.to_string(),
698            license: self.license(),
699            repository: self.pkg().repository.clone().map(|repo_url| Repository {
700                ty: "git".to_string(),
701                url: repo_url,
702            }),
703            files: data.files,
704            main: data.main,
705            homepage: data.homepage,
706            types: data.dts_file,
707            keywords: data.keywords,
708            dependencies,
709        })
710    }
711
712    fn to_esmodules(
713        &self,
714        scope: &Option<String>,
715        disable_dts: bool,
716        dependencies: Option<HashMap<String, String>>,
717        out_dir: &Path,
718    ) -> NpmPackage {
719        let data = self.npm_data(scope, true, disable_dts, out_dir);
720        let pkg = &self.data.packages[self.current_idx];
721
722        self.check_optional_fields();
723
724        NpmPackage::ESModulesPackage(ESModulesPackage {
725            name: data.name,
726            ty: "module".into(),
727            collaborators: pkg.authors.clone(),
728            description: self.pkg().description.clone(),
729            version: pkg.version.to_string(),
730            license: self.license(),
731            repository: self.pkg().repository.clone().map(|repo_url| Repository {
732                ty: "git".to_string(),
733                url: repo_url,
734            }),
735            files: data.files,
736            main: data.main.clone(),
737            homepage: data.homepage,
738            types: data.dts_file,
739            side_effects: vec![format!("./{}", data.main), "./snippets/*".to_owned()],
740            keywords: data.keywords,
741            dependencies,
742        })
743    }
744
745    fn to_web(
746        &self,
747        scope: &Option<String>,
748        disable_dts: bool,
749        dependencies: Option<HashMap<String, String>>,
750        out_dir: &Path,
751    ) -> NpmPackage {
752        let data = self.npm_data(scope, false, disable_dts, out_dir);
753        let pkg = &self.data.packages[self.current_idx];
754
755        self.check_optional_fields();
756
757        NpmPackage::ESModulesPackage(ESModulesPackage {
758            name: data.name,
759            ty: "module".into(),
760            collaborators: pkg.authors.clone(),
761            description: self.pkg().description.clone(),
762            version: pkg.version.to_string(),
763            license: self.license(),
764            repository: self.pkg().repository.clone().map(|repo_url| Repository {
765                ty: "git".to_string(),
766                url: repo_url,
767            }),
768            files: data.files,
769            main: data.main,
770            homepage: data.homepage,
771            types: data.dts_file,
772            side_effects: vec!["./snippets/*".to_owned()],
773            keywords: data.keywords,
774            dependencies,
775        })
776    }
777
778    fn to_nomodules(
779        &self,
780        scope: &Option<String>,
781        disable_dts: bool,
782        dependencies: Option<HashMap<String, String>>,
783        out_dir: &Path,
784    ) -> NpmPackage {
785        let data = self.npm_data(scope, false, disable_dts, out_dir);
786        let pkg = &self.data.packages[self.current_idx];
787
788        self.check_optional_fields();
789
790        NpmPackage::NoModulesPackage(NoModulesPackage {
791            name: data.name,
792            collaborators: pkg.authors.clone(),
793            description: self.pkg().description.clone(),
794            version: pkg.version.to_string(),
795            license: self.license(),
796            repository: self.pkg().repository.clone().map(|repo_url| Repository {
797                ty: "git".to_string(),
798                url: repo_url,
799            }),
800            files: data.files,
801            browser: data.main,
802            homepage: data.homepage,
803            types: data.dts_file,
804            keywords: data.keywords,
805            dependencies,
806        })
807    }
808
809    fn check_optional_fields(&self) {
810        let mut messages = vec![];
811        if self.pkg().description.is_none() {
812            messages.push("description");
813        }
814        if self.pkg().repository.is_none() {
815            messages.push("repository");
816        }
817        if self.pkg().license.is_none() && self.pkg().license_file.is_none() {
818            messages.push("license");
819        }
820
821        match messages.len() {
822            1 => PBAR.info(&format!("Optional field missing from Cargo.toml: '{}'. This is not necessary, but recommended", messages[0])),
823            2 => PBAR.info(&format!("Optional fields missing from Cargo.toml: '{}', '{}'. These are not necessary, but recommended", messages[0], messages[1])),
824            3 => PBAR.info(&format!("Optional fields missing from Cargo.toml: '{}', '{}', and '{}'. These are not necessary, but recommended", messages[0], messages[1], messages[2])),
825            _ => ()
826        };
827    }
828}