diff --git a/.changeset/fruity-taxes-shake.md b/.changeset/fruity-taxes-shake.md new file mode 100644 index 0000000000..4a0d93afae --- /dev/null +++ b/.changeset/fruity-taxes-shake.md @@ -0,0 +1,9 @@ +--- +"@pnpm/deps.compliance.commands": minor +"@pnpm/config.reader": minor +"@pnpm/types": minor +"pnpm": minor +"pacquet": minor +--- + +Added the `audit.ignorePrune` setting. When set to `true`, `pnpm audit --fix` removes ignored GHSA entries that no longer appear in the audit report. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c2d80cca6..7976ab9648 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2940,6 +2940,9 @@ importers: '@pnpm/store.path': specifier: workspace:* version: link:../../../store/path + '@pnpm/text.sanitize': + specifier: workspace:* + version: link:../../../text/sanitize '@pnpm/types': specifier: workspace:* version: link:../../../core/types diff --git a/pnpm/crates/cli/src/cli_args/audit.rs b/pnpm/crates/cli/src/cli_args/audit.rs index fa9358008a..85683f4117 100644 --- a/pnpm/crates/cli/src/cli_args/audit.rs +++ b/pnpm/crates/cli/src/cli_args/audit.rs @@ -1,4 +1,7 @@ -use crate::{State, cli_args::install::resolve_bool_override}; +use crate::{ + State, + cli_args::{install::resolve_bool_override, sanitize::sanitize_inline}, +}; use chrono::{DateTime, Utc}; use clap::{Args, ValueEnum}; use derive_more::{Display, Error}; @@ -38,7 +41,7 @@ mod version_ranges; pub(crate) use fix::{ AuditFixObserver, PackumentPublishInfo, VulnerabilityGuard, fetch_publish_times, filter_advisories_for_fix, fix_override, fix_with_update, format_fix_with_update_output, - ignore_vulnerabilities, interactive_select, + ignore_vulnerabilities, interactive_select, prune_ignored_ghsas, }; pub(crate) use paths::{AuditPathIndex, PathInfo, build_audit_path_index, package_version}; pub(crate) use render::{ @@ -260,6 +263,45 @@ impl AuditArgs { .await; if let Some(fix_method) = fix_method { + // Remove ignored GHSAs that no longer appear in the report before + // filtering. Mirrors pnpm's `audit.ignorePrune` handling in the + // `audit` command handler. + if state.config.audit_ignore_prune.unwrap_or(false) + && !state.config.audit_config.ignore_ghsas.is_empty() + { + let configured_ghsas = &state.config.audit_config.ignore_ghsas; + let prune = prune_ignored_ghsas(configured_ghsas, &report); + if !prune.pruned.is_empty() { + // The pruned ids keep their original spelling from the + // repository-controlled workspace manifest, so strip + // control characters before they reach the terminal. + println!( + "Removed {} unused ignored GHSA{}: {}", + prune.pruned.len(), + if prune.pruned.len() == 1 { "" } else { "s" }, + prune + .pruned + .iter() + .map(|ghsa| sanitize_inline(ghsa)) + .collect::>() + .join(", "), + ); + } + // Persist even when nothing was removed: `retained` may + // still differ from the configured list (deduplicated or + // case-normalized), and the file should always reflect the + // canonical form. + if &prune.retained != configured_ghsas { + pnpm_workspace_manifest_writer::set_audit_ignore_ghsas( + &settings_dir, + &prune.retained, + ) + .map_err(|err| { + miette::Report::new(err) + .wrap_err("write auditConfig.ignoreGhsas to pnpm-workspace.yaml") + })?; + } + } // Pre-filter by audit-level and ignored GHSAs so the interactive // prompt and both fix methods see the same advisory set the // override path's fixable filter would. diff --git a/pnpm/crates/cli/src/cli_args/audit/fix.rs b/pnpm/crates/cli/src/cli_args/audit/fix.rs index db49e9c30c..7662793434 100644 --- a/pnpm/crates/cli/src/cli_args/audit/fix.rs +++ b/pnpm/crates/cli/src/cli_args/audit/fix.rs @@ -40,6 +40,44 @@ pub(crate) fn filter_advisories_for_fix( .collect() } +/// `auditConfig.ignoreGhsas` entries split by whether their GHSA id still +/// appears in the audit report. +pub(crate) struct PruneIgnoredGhsasResult { + pub(crate) pruned: Vec, + pub(crate) retained: Vec, +} + +/// Split `ignored_ghsas` into those still present in `report` — normalized +/// to their canonical spelling and deduplicated (`retained`) — and those +/// that aren't, in their original spelling (`pruned`). Mirrors pnpm's +/// `pruneIgnoredGhsas`. +pub(crate) fn prune_ignored_ghsas( + ignored_ghsas: &[String], + report: &AuditReport, +) -> PruneIgnoredGhsasResult { + let advisory_ghsa_ids = report + .advisories + .values() + .filter(|advisory| !advisory.github_advisory_id.is_empty()) + .map(|advisory| normalize_ghsa_id(&advisory.github_advisory_id)) + .collect::>(); + + let mut retained_seen = HashSet::new(); + let mut retained = Vec::new(); + let mut pruned = Vec::new(); + for ghsa in ignored_ghsas { + let normalized = normalize_ghsa_id(ghsa); + if advisory_ghsa_ids.contains(&normalized) { + if retained_seen.insert(normalized.clone()) { + retained.push(normalized); + } + } else { + pruned.push(ghsa.clone()); + } + } + PruneIgnoredGhsasResult { pruned, retained } +} + /// Build the `name@vulnerable_versions → ^patched` override map from the /// fixable advisories (those with an inferred patched range). Keyed by a /// `BTreeMap` so the output is sorted, mirroring pnpm's `sortDirectKeys`. diff --git a/pnpm/crates/cli/tests/suite/audit.rs b/pnpm/crates/cli/tests/suite/audit.rs index af0e1efdb3..c1870751e8 100644 --- a/pnpm/crates/cli/tests/suite/audit.rs +++ b/pnpm/crates/cli/tests/suite/audit.rs @@ -951,6 +951,260 @@ fn audit_fix_override_with_no_fixable_vulnerabilities_makes_no_changes() { mock.assert(); } +#[test] +fn audit_fix_ignore_prune_removes_unused_ignored_ghsas() { + let CommandTempCwd { mut pacquet, workspace, root: _root, .. } = CommandTempCwd::init(); + let mut registry = mockito::Server::new(); + // GHSA-test-1111-2222 exists in the report; GHSA-test-9999-9999 doesn't. + let mock = audit_mock( + &mut registry, + &advisory_response("vulnerable", 123, "high", "<2.0.0", "test", "GHSA-test-1111-2222"), + ) + .create(); + write_audit_workspace( + &workspace, + ®istry.url(), + "audit:\n ignorePrune: true\nauditConfig:\n ignoreGhsas:\n - GHSA-test-1111-2222\n - GHSA-test-9999-9999\n", + ); + + let output = pacquet.arg("audit").arg("--fix").output().expect("run pacquet audit --fix"); + + assert_success(&output); + assert!( + stdout(&output).contains("Removed 1 unused ignored GHSA: GHSA-test-9999-9999"), + "stdout should report the removed GHSA:\n{}", + stdout(&output), + ); + let manifest = + fs::read_to_string(workspace.join("pnpm-workspace.yaml")).expect("read workspace manifest"); + assert!( + manifest.contains("GHSA-test-1111-2222") && !manifest.contains("GHSA-test-9999-9999"), + "manifest should retain the still-relevant GHSA and drop the unused one:\n{manifest}", + ); + mock.assert(); +} + +#[test] +fn audit_fix_ignore_prune_disabled_by_default_keeps_all_ignored_ghsas() { + let CommandTempCwd { mut pacquet, workspace, root: _root, .. } = CommandTempCwd::init(); + let mut registry = mockito::Server::new(); + let mock = audit_mock( + &mut registry, + &advisory_response("vulnerable", 123, "high", "<2.0.0", "test", "GHSA-test-1111-2222"), + ) + .create(); + write_audit_workspace( + &workspace, + ®istry.url(), + "auditConfig:\n ignoreGhsas:\n - GHSA-test-1111-2222\n - GHSA-test-9999-9999\n", + ); + + let output = pacquet.arg("audit").arg("--fix").output().expect("run pacquet audit --fix"); + + assert_success(&output); + assert!(!stdout(&output).contains("unused ignored GHSA")); + let manifest = + fs::read_to_string(workspace.join("pnpm-workspace.yaml")).expect("read workspace manifest"); + assert!( + manifest.contains("GHSA-test-9999-9999"), + "manifest should keep the unused GHSA since pruning is disabled:\n{manifest}", + ); + mock.assert(); +} + +#[test] +fn audit_fix_ignore_prune_normalizes_ghsa_casing() { + let CommandTempCwd { mut pacquet, workspace, root: _root, .. } = CommandTempCwd::init(); + let mut registry = mockito::Server::new(); + let mock = audit_mock( + &mut registry, + &advisory_response("vulnerable", 123, "high", "<2.0.0", "test", "GHSA-test-1111-2222"), + ) + .create(); + write_audit_workspace( + &workspace, + ®istry.url(), + "audit:\n ignorePrune: true\nauditConfig:\n ignoreGhsas:\n - ghsa-test-1111-2222\n - GHSA-TEST-9999-9999\n", + ); + + let output = pacquet.arg("audit").arg("--fix").output().expect("run pacquet audit --fix"); + + assert_success(&output); + // Retained entries are rewritten to their canonical spelling regardless + // of the casing the user originally ignored them with, and deduplicated + // — the exact list must be just the one canonical, still-relevant id. + assert_eq!(audit_config_ignore_ghsas(&workspace), vec!["GHSA-test-1111-2222".to_string()]); + mock.assert(); +} + +#[test] +fn audit_fix_ignore_prune_persists_canonical_form_even_when_nothing_is_removed() { + let CommandTempCwd { mut pacquet, workspace, root: _root, .. } = CommandTempCwd::init(); + let mut registry = mockito::Server::new(); + let mock = audit_mock( + &mut registry, + &advisory_response("vulnerable", 123, "high", "<2.0.0", "test", "GHSA-test-1111-2222"), + ) + .create(); + // Both entries match the same advisory (a differently-cased duplicate) + // — nothing gets removed, but the stored list should still collapse to + // the single canonical entry. + write_audit_workspace( + &workspace, + ®istry.url(), + "audit:\n ignorePrune: true\nauditConfig:\n ignoreGhsas:\n - ghsa-test-1111-2222\n - GHSA-TEST-1111-2222\n", + ); + + let output = pacquet.arg("audit").arg("--fix").output().expect("run pacquet audit --fix"); + + assert_success(&output); + assert!(!stdout(&output).contains("unused ignored GHSA")); + assert_eq!(audit_config_ignore_ghsas(&workspace), vec!["GHSA-test-1111-2222".to_string()]); + mock.assert(); +} + +#[test] +fn audit_fix_ignore_prune_removes_a_comment_attached_to_the_removed_entry() { + let CommandTempCwd { mut pacquet, workspace, root: _root, .. } = CommandTempCwd::init(); + let mut registry = mockito::Server::new(); + let mock = audit_mock( + &mut registry, + &advisory_response("vulnerable", 123, "high", "<2.0.0", "test", "GHSA-test-1111-2222"), + ) + .create(); + write_audit_workspace( + &workspace, + ®istry.url(), + "audit:\n ignorePrune: true\nauditConfig:\n ignoreGhsas:\n - GHSA-test-1111-2222\n # Expired GHSA, should not be ignored\n - GHSA-test-9999-9999 # trailing comment, should also go\n", + ); + + let output = pacquet.arg("audit").arg("--fix").output().expect("run pacquet audit --fix"); + + assert_success(&output); + let manifest = + fs::read_to_string(workspace.join("pnpm-workspace.yaml")).expect("read workspace manifest"); + // Both the preceding comment and the trailing same-line comment attached + // to the removed entry must go with it. + assert!( + !manifest.contains("Expired GHSA") && !manifest.contains("trailing comment"), + "manifest should drop the comments along with the entry they were attached to:\n{manifest}", + ); + mock.assert(); +} + +#[test] +fn audit_fix_ignore_prune_removes_all_when_none_are_relevant() { + let CommandTempCwd { mut pacquet, workspace, root: _root, .. } = CommandTempCwd::init(); + let mut registry = mockito::Server::new(); + let mock = audit_mock( + &mut registry, + &advisory_response("vulnerable", 123, "high", "<2.0.0", "test", "GHSA-test-1111-2222"), + ) + .create(); + write_audit_workspace( + &workspace, + ®istry.url(), + "audit:\n ignorePrune: true\nauditConfig:\n ignoreGhsas:\n - GHSA-test-9999-0001\n - GHSA-test-9999-0002\n", + ); + + let output = pacquet.arg("audit").arg("--fix").output().expect("run pacquet audit --fix"); + + assert_success(&output); + let manifest = + fs::read_to_string(workspace.join("pnpm-workspace.yaml")).expect("read workspace manifest"); + assert!( + !manifest.contains("ignoreGhsas:"), + "manifest should drop ignoreGhsas once every entry is pruned:\n{manifest}", + ); + mock.assert(); +} + +#[test] +fn audit_fix_ignore_prune_edits_an_inline_audit_config_in_place() { + let CommandTempCwd { mut pacquet, workspace, root: _root, .. } = CommandTempCwd::init(); + let mut registry = mockito::Server::new(); + let mock = audit_mock( + &mut registry, + &advisory_response("vulnerable", 123, "high", "<2.0.0", "test", "GHSA-test-1111-2222"), + ) + .create(); + write_audit_workspace( + &workspace, + ®istry.url(), + "audit:\n ignorePrune: true\nauditConfig: { ignoreGhsas: [GHSA-test-1111-2222, GHSA-test-9999-9999] }\n", + ); + + let output = pacquet.arg("audit").arg("--fix").output().expect("run pacquet audit --fix"); + + assert_success(&output); + let actual_manifest = + fs::read_to_string(workspace.join("pnpm-workspace.yaml")).expect("read workspace manifest"); + let expected_manifest = "fetchRetries: 0\naudit:\n ignorePrune: true\nauditConfig: { ignoreGhsas: [ GHSA-test-1111-2222 ] }\n"; + eprintln!("actual manifest:\n{actual_manifest}\nexpected manifest:\n{expected_manifest}"); + assert_eq!(actual_manifest, expected_manifest); + mock.assert(); +} + +#[test] +fn audit_fix_ignore_prune_updates_the_canonical_audit_ignore_list() { + let CommandTempCwd { mut pacquet, workspace, root: _root, .. } = CommandTempCwd::init(); + let mut registry = mockito::Server::new(); + let mock = audit_mock( + &mut registry, + &advisory_response("vulnerable", 123, "high", "<2.0.0", "test", "GHSA-test-1111-2222"), + ) + .create(); + write_audit_workspace( + &workspace, + ®istry.url(), + "audit:\n ignorePrune: true\n ignore:\n - GHSA-test-1111-2222\n - GHSA-test-9999-9999\n", + ); + + let output = pacquet.arg("audit").arg("--fix").output().expect("run pacquet audit --fix"); + + assert_success(&output); + let actual_manifest = + fs::read_to_string(workspace.join("pnpm-workspace.yaml")).expect("read workspace manifest"); + // The retained list must land back on the canonical `audit.ignore` that + // supplied it — writing the deprecated `auditConfig.ignoreGhsas` instead + // would let the unchanged canonical list shadow the prune on the next + // read and restore the stale id. + let expected_manifest = + "fetchRetries: 0\naudit:\n ignorePrune: true\n ignore:\n - GHSA-test-1111-2222\n"; + eprintln!("actual manifest:\n{actual_manifest}\nexpected manifest:\n{expected_manifest}"); + assert_eq!(actual_manifest, expected_manifest); + mock.assert(); +} + +#[test] +fn audit_fix_ignore_prune_sanitizes_the_removed_ids_in_output() { + let CommandTempCwd { mut pacquet, workspace, root: _root, .. } = CommandTempCwd::init(); + let mut registry = mockito::Server::new(); + let mock = audit_mock( + &mut registry, + &advisory_response("vulnerable", 123, "high", "<2.0.0", "test", "GHSA-test-1111-2222"), + ) + .create(); + // The stale entry carries an ANSI escape from the repository-controlled + // manifest; the removal message must strip it before the terminal. + write_audit_workspace( + &workspace, + ®istry.url(), + "audit:\n ignorePrune: true\nauditConfig:\n ignoreGhsas:\n - GHSA-test-1111-2222\n - \"GHSA-test-9999-9999\\e[31m\"\n", + ); + + let output = pacquet.arg("audit").arg("--fix").output().expect("run pacquet audit --fix"); + + assert_success(&output); + let out = stdout(&output); + assert!( + out.contains("Removed 1 unused ignored GHSA: GHSA-test-9999-9999[31m"), + "stdout should report the removed GHSA with its control characters stripped:\n{out}", + ); + assert!(!out.contains('\u{1b}'), "stdout must not carry the escape character:\n{out}"); + mock.assert(); +} + #[test] fn audit_fix_rejects_invalid_method() { let CommandTempCwd { mut pacquet, workspace, root: _root, .. } = CommandTempCwd::init(); @@ -1219,6 +1473,28 @@ fn advisory_response( ) } +/// Parse `workspace`'s `pnpm-workspace.yaml` and return the exact +/// `auditConfig.ignoreGhsas` list (empty when the key is absent). +fn audit_config_ignore_ghsas(workspace: &Path) -> Vec { + #[derive(Default, serde::Deserialize)] + #[serde(rename_all = "camelCase", default)] + struct OnlyAuditConfig { + audit_config: AuditConfig, + } + #[derive(Default, serde::Deserialize)] + #[serde(rename_all = "camelCase", default)] + struct AuditConfig { + ignore_ghsas: Vec, + } + + let text = + fs::read_to_string(workspace.join("pnpm-workspace.yaml")).expect("read workspace manifest"); + serde_saphyr::from_str::(&text) + .expect("parse pnpm-workspace.yaml") + .audit_config + .ignore_ghsas +} + fn write_audit_workspace(workspace: &Path, registry_url: &str, workspace_yaml: &str) { write_audit_workspace_with_npmrc( workspace, diff --git a/pnpm/crates/config/src/known_settings.rs b/pnpm/crates/config/src/known_settings.rs index fc7e190e4d..2c0d5cdf19 100644 --- a/pnpm/crates/config/src/known_settings.rs +++ b/pnpm/crates/config/src/known_settings.rs @@ -56,6 +56,7 @@ const TYPED_WORKSPACE_MANIFEST_KEYS: &[&str] = &[ /// fields. const CONFIG_ONLY_SETTING_KEYS: &[&str] = &[ "allowNew", + "auditIgnorePrune", "authConfig", "autoConfirmAllPrompts", "bin", diff --git a/pnpm/crates/config/src/lib.rs b/pnpm/crates/config/src/lib.rs index 0a241d07ac..53311ca61a 100644 --- a/pnpm/crates/config/src/lib.rs +++ b/pnpm/crates/config/src/lib.rs @@ -2249,6 +2249,10 @@ pub struct Config { /// `auditConfig` config for `pnpm audit`. pub audit_config: AuditConfig, + /// `audit.ignorePrune` from `pnpm-workspace.yaml`. See + /// [`AuditSettings::ignore_prune`]. + pub audit_ignore_prune: Option, + /// `versioning` from `pnpm-workspace.yaml`: native workspace release /// management, consumed by `pnpm change` and the bare `pnpm version -r`. pub versioning: pnpm_versioning::VersioningSettings, @@ -2521,6 +2525,7 @@ impl Config { level: self.audit_level, ignore: (!self.audit_config.ignore_ghsas.is_empty()) .then(|| self.audit_config.ignore_ghsas.clone()), + ignore_prune: self.audit_ignore_prune, }; (audit != AuditSettings::default()).then_some(audit) } diff --git a/pnpm/crates/config/src/workspace_yaml.rs b/pnpm/crates/config/src/workspace_yaml.rs index ab5a0b9e81..cfc7a8a7ab 100644 --- a/pnpm/crates/config/src/workspace_yaml.rs +++ b/pnpm/crates/config/src/workspace_yaml.rs @@ -832,6 +832,13 @@ pub struct AuditSettings { /// [`AuditConfig::ignore_ghsas`]. #[serde(skip_serializing_if = "Option::is_none")] pub ignore: Option>, + + /// When `true`, `pnpm audit --fix` removes entries from the ignore + /// list that no longer appear in the audit report, so a re-introduced + /// vulnerability under the same GHSA ID gets re-evaluated instead of + /// staying silently suppressed. + #[serde(skip_serializing_if = "Option::is_none")] + pub ignore_prune: Option, } /// `update` entry: settings that tune `pnpm update` (and `pnpm @@ -1877,6 +1884,9 @@ impl WorkspaceSettings { } config.audit_config.ignore_ghsas = ignore; } + if let Some(prune) = audit.ignore_prune { + config.audit_ignore_prune = Some(prune); + } } if let Some(v) = self.versioning { config.versioning = v; diff --git a/pnpm/crates/deps-restorer/src/install_package_from_registry/tests.rs b/pnpm/crates/deps-restorer/src/install_package_from_registry/tests.rs index 6296d0779d..fac8eb8d92 100644 --- a/pnpm/crates/deps-restorer/src/install_package_from_registry/tests.rs +++ b/pnpm/crates/deps-restorer/src/install_package_from_registry/tests.rs @@ -200,6 +200,7 @@ fn create_config( verify_deps_before_run: Default::default(), audit_level: None, audit_config: Default::default(), + audit_ignore_prune: None, trust_policy_exclude: None, trust_policy_ignore_after: None, resolution_mode: Default::default(), diff --git a/pnpm/crates/workspace-manifest-writer/src/edit.rs b/pnpm/crates/workspace-manifest-writer/src/edit.rs index 9d55dde7f8..4144d24ccf 100644 --- a/pnpm/crates/workspace-manifest-writer/src/edit.rs +++ b/pnpm/crates/workspace-manifest-writer/src/edit.rs @@ -423,15 +423,41 @@ fn override_keys_in_text(text: &str) -> Vec { .unwrap_or_default() } -/// Set `auditConfig.ignoreGhsas:` to `ghsas` (the complete desired list), -/// creating the `auditConfig:` block or the nested `ignoreGhsas:` key when -/// absent. An empty `ghsas` removes the `auditConfig:` block. `pnpm audit -/// --ignore` calls this with the merged ignore list. Returns whether anything +/// Set the ignore list to `ghsas` (the complete desired list) in whichever +/// spelling the manifest uses — the canonical `audit.ignore` wins over the +/// deprecated `auditConfig.ignoreGhsas`, matching the reader's precedence, +/// so a stale canonical list can't shadow the update on the next read. When +/// both spellings are present, the shadowed deprecated list is removed as +/// part of the write. `auditConfig.ignoreGhsas` is created when neither is +/// present. An empty `ghsas` removes the list, dropping its block when +/// nothing else remains in it. `pnpm audit --ignore` and `audit.ignorePrune` +/// call this with the complete desired list. Returns whether anything /// changed. pub(crate) fn set_audit_ignore_ghsas( manifest: &mut Manifest, ghsas: &[String], ) -> Result> { + if manifest.audit_ignore.is_some() { + let mut changed = if ghsas.is_empty() { + remove_block_list_key(manifest, "audit", "ignore"); + manifest.audit_ignore = None; + true + } else if manifest.audit_ignore.as_deref() == Some(ghsas) { + false + } else { + let new_text = upsert_sequence_entry(manifest.text(), "audit", "ignore", ghsas); + manifest.set_text(new_text); + manifest.audit_ignore = Some(ghsas.to_vec()); + true + }; + if manifest.audit_ignore_ghsas.is_some() { + remove_block_list_key(manifest, "auditConfig", "ignoreGhsas"); + manifest.audit_ignore_ghsas = None; + changed = true; + } + return Ok(changed); + } + const BLOCK: &str = "auditConfig"; let current = manifest.audit_ignore_ghsas.as_deref().unwrap_or_default(); @@ -440,18 +466,12 @@ pub(crate) fn set_audit_ignore_ghsas( if locate(text, &[BLOCK]).is_none() { return Ok(false); } - let keys = mapping_keys(text, &[BLOCK]); // Nothing to remove if `ignoreGhsas` isn't present — and crucially, // don't touch sibling `auditConfig` keys. - if !keys.iter().any(|key| key == "ignoreGhsas") { + if !mapping_keys(text, &[BLOCK]).iter().any(|key| key == "ignoreGhsas") { return Ok(false); } - if keys.iter().all(|key| key == "ignoreGhsas") { - manifest.set_text(remove_top_level_block(text, BLOCK)); - manifest.top_level_keys.retain(|key| key != BLOCK); - } else { - manifest.set_text(remove_mapping_entries(text, &[BLOCK], &["ignoreGhsas".to_string()])); - } + remove_block_list_key(manifest, BLOCK, "ignoreGhsas"); manifest.audit_ignore_ghsas = None; return Ok(true); } @@ -475,6 +495,28 @@ pub(crate) fn set_audit_ignore_ghsas( Ok(true) } +/// Remove `block.key` from the document — the whole `block:` when the key is +/// its only entry, so no empty mapping is left behind. Sibling keys of +/// `block` are never touched. A missing block or key is a no-op. +fn remove_block_list_key(manifest: &mut Manifest, block: &str, key: &str) { + let text = manifest.text(); + if locate(text, &[block]).is_none() { + return; + } + let keys = mapping_keys(text, &[block]); + if !keys.iter().any(|k| k == key) { + return; + } + if keys.iter().all(|k| k == key) { + let new_text = remove_top_level_block(text, block); + manifest.set_text(new_text); + manifest.top_level_keys.retain(|k| k != block); + } else { + let new_text = remove_mapping_entries(text, &[block], &[key.to_string()]); + manifest.set_text(new_text); + } +} + /// Set the top-level `minimumReleaseAgeExclude:` block to `items` (the /// complete desired list), creating or replacing it, and removing it when /// `items` is empty. The caller is responsible for merging with the existing diff --git a/pnpm/crates/workspace-manifest-writer/src/lib.rs b/pnpm/crates/workspace-manifest-writer/src/lib.rs index c9fe072d1e..6c471cbb52 100644 --- a/pnpm/crates/workspace-manifest-writer/src/lib.rs +++ b/pnpm/crates/workspace-manifest-writer/src/lib.rs @@ -552,12 +552,16 @@ where write_or_remove_manifest(&path, manifest) } -/// Set `dir`'s `pnpm-workspace.yaml` `auditConfig.ignoreGhsas:` to `ghsas` -/// (the complete desired list), creating the file/block if absent and -/// removing the `auditConfig:` block when `ghsas` is empty. Preserves the -/// rest of the document's formatting and writes the file back only when -/// something actually changed. Used by `pnpm audit --ignore` / -/// `--ignore-unfixable` to persist suppressed advisories. +/// Set `dir`'s `pnpm-workspace.yaml` audit ignore list to `ghsas` (the +/// complete desired list), targeting whichever spelling the manifest uses — +/// the canonical `audit.ignore` wins over the deprecated +/// `auditConfig.ignoreGhsas`, matching the reader's precedence, and the +/// shadowed deprecated list is removed when both are present — creating the +/// file plus an `auditConfig:` block when neither is present. +/// Preserves the rest of the document's formatting and writes the file back +/// only when something actually changed. Used by `pnpm audit --ignore` / +/// `--ignore-unfixable` and the `audit.ignorePrune` cleanup to persist +/// suppressed advisories. pub fn set_audit_ignore_ghsas( dir: &Path, ghsas: &[String], @@ -582,7 +586,7 @@ pub fn set_audit_ignore_ghsas( if let Some(key) = unsupported_inline_key( manifest.text(), - &[&["auditConfig"], &["auditConfig", "ignoreGhsas"]], + &[&["auditConfig"], &["auditConfig", "ignoreGhsas"], &["audit"], &["audit", "ignore"]], ) { return Err(UpdateWorkspaceManifestError::UnsupportedInlineBlock { path, key }); } diff --git a/pnpm/crates/workspace-manifest-writer/src/model.rs b/pnpm/crates/workspace-manifest-writer/src/model.rs index b6be1d6c45..ec7014494e 100644 --- a/pnpm/crates/workspace-manifest-writer/src/model.rs +++ b/pnpm/crates/workspace-manifest-writer/src/model.rs @@ -39,6 +39,9 @@ pub(crate) struct Manifest { /// `auditConfig.ignoreGhsas:` list. Consulted to detect a no-op write /// of an already-present list. pub(crate) audit_ignore_ghsas: Option>, + /// `audit.ignore:` list — the canonical spelling, which wins over + /// `auditConfig.ignoreGhsas` when both are present. + pub(crate) audit_ignore: Option>, /// `minimumReleaseAgeExclude:` list. Consulted to detect a no-op write /// of an already-present list. pub(crate) minimum_release_age_exclude: Option>, @@ -60,6 +63,8 @@ struct CatalogData { overrides: Option>, #[serde(default, rename = "auditConfig")] audit_config: Option, + #[serde(default)] + audit: Option, #[serde(default, rename = "minimumReleaseAgeExclude")] minimum_release_age_exclude: Option>, } @@ -71,6 +76,13 @@ struct AuditConfigData { ignore_ghsas: Option>, } +/// The `audit` slice consulted for no-op detection and target selection. +#[derive(Default, Deserialize)] +struct AuditData { + #[serde(default)] + ignore: Option>, +} + /// An `allowBuilds` value, tolerant of the string form pnpm also accepts /// (a version spec) so decoding a manifest that uses it doesn't fail. Only /// the boolean shape is retained — the only shape `pnpm approve-builds` @@ -123,6 +135,7 @@ impl Manifest { overrides: None, non_scalar_overrides: HashSet::new(), audit_ignore_ghsas: None, + audit_ignore: None, minimum_release_age_exclude: None, }); } @@ -165,6 +178,7 @@ impl Manifest { .collect() }); let audit_ignore_ghsas = data.audit_config.and_then(|config| config.ignore_ghsas); + let audit_ignore = data.audit.and_then(|audit| audit.ignore); Ok(Manifest { text, @@ -177,6 +191,7 @@ impl Manifest { overrides, non_scalar_overrides, audit_ignore_ghsas, + audit_ignore, minimum_release_age_exclude: data.minimum_release_age_exclude, }) } diff --git a/pnpm/crates/workspace-manifest-writer/src/tests.rs b/pnpm/crates/workspace-manifest-writer/src/tests.rs index 640e46c962..0ab332a62f 100644 --- a/pnpm/crates/workspace-manifest-writer/src/tests.rs +++ b/pnpm/crates/workspace-manifest-writer/src/tests.rs @@ -860,24 +860,46 @@ fn ignore_ghsas_empty_with_sibling_only_is_a_noop() { assert_eq!(out, original); } +#[test] +fn ignore_ghsas_targets_the_canonical_audit_ignore_list() { + let original = "audit:\n ignorePrune: true\n ignore:\n - GHSA-aaaa-bbbb-cccc\n"; + let out = run_ignore_ghsas(Some(original), &["GHSA-dddd-eeee-ffff"]).expect("written"); + assert_eq!(out, "audit:\n ignorePrune: true\n ignore:\n - GHSA-dddd-eeee-ffff\n"); +} + +#[test] +fn ignore_ghsas_removes_the_shadowed_deprecated_list_when_both_are_present() { + let original = "audit:\n ignore:\n - GHSA-aaaa-bbbb-cccc\nauditConfig:\n ignoreGhsas:\n - GHSA-1111-2222-3333\n"; + let out = run_ignore_ghsas(Some(original), &["GHSA-dddd-eeee-ffff"]).expect("written"); + assert_eq!(out, "audit:\n ignore:\n - GHSA-dddd-eeee-ffff\n"); +} + +#[test] +fn ignore_ghsas_empty_removes_audit_ignore_and_keeps_siblings() { + let original = "audit:\n ignorePrune: true\n ignore:\n - GHSA-aaaa-bbbb-cccc\n"; + let out = run_ignore_ghsas(Some(original), &[]).expect("written"); + assert_eq!(out, "audit:\n ignorePrune: true\n"); +} + +#[test] +fn ignore_ghsas_empty_removes_the_audit_block_when_ignore_is_its_only_key() { + let original = "packages:\n - '.'\naudit:\n ignore:\n - GHSA-aaaa-bbbb-cccc\n"; + let out = run_ignore_ghsas(Some(original), &[]).expect("written"); + assert_eq!(out, "packages:\n - '.'\n"); +} + #[test] fn ignore_ghsas_edits_an_inline_flow_audit_config() { let dir = TempDir::new().expect("temp dir"); let path = dir.path().join(WORKSPACE_MANIFEST_FILENAME); - fs::write( - &path, - "auditConfig: { cleanupUnusedIgnoredGhsas: true, ignoreGhsas: [GHSA-aaaa-bbbb-cccc] }\n", - ) - .expect("seed"); + fs::write(&path, "auditConfig: { other: keep, ignoreGhsas: [GHSA-aaaa-bbbb-cccc] }\n") + .expect("seed"); crate::set_audit_ignore_ghsas(dir.path(), &["GHSA-dddd-eeee-ffff".to_string()]) .expect("set_audit_ignore_ghsas succeeds"); let after = fs::read_to_string(&path).expect("read manifest"); - assert_eq!( - after, - "auditConfig: { cleanupUnusedIgnoredGhsas: true, ignoreGhsas: [ GHSA-dddd-eeee-ffff ] }\n", - ); + assert_eq!(after, "auditConfig: { other: keep, ignoreGhsas: [ GHSA-dddd-eeee-ffff ] }\n"); } #[test] diff --git a/pnpm11/config/reader/src/getOptionsFromRootManifest.ts b/pnpm11/config/reader/src/getOptionsFromRootManifest.ts index 58dc957577..f052ad5e0f 100644 --- a/pnpm11/config/reader/src/getOptionsFromRootManifest.ts +++ b/pnpm11/config/reader/src/getOptionsFromRootManifest.ts @@ -39,6 +39,7 @@ export type OptionsFromRootManifest = { registriesByScope?: Record registriesByPrefix?: Record registryOptionsByUrl?: Record + auditIgnorePrune?: boolean } & Pick interface GetOptionsFromPnpmSettingsOptions { @@ -332,6 +333,9 @@ function translateUpdateSettings (pnpmSettings: PnpmSettings, settings: OptionsF */ function translateAuditSettings (pnpmSettings: PnpmSettings, settings: OptionsFromRootManifest): void { delete (settings as { audit?: unknown }).audit + // `auditIgnorePrune` is derived from `audit.ignorePrune` below; a raw + // top-level key of that name is not a setting in either CLI. + delete settings.auditIgnorePrune const audit = pnpmSettings.audit if (audit == null) return assertObjectSetting(audit, 'audit') @@ -351,6 +355,10 @@ function translateAuditSettings (pnpmSettings: PnpmSettings, settings: OptionsFr } ;(settings as { auditLevel?: string }).auditLevel = audit.level } + if (audit.ignorePrune != null) { + assertBoolean(audit.ignorePrune, 'audit.ignorePrune') + settings.auditIgnorePrune = audit.ignorePrune + } } /** @@ -372,15 +380,17 @@ export function toUpdateSettings (updateConfig: OptionsFromRootManifest['updateC /** * The `audit` settings the CLI acts on, re-joined from the internal - * `auditConfig` / `auditLevel` pair {@link translateAuditSettings} splits the - * section into — the view `pnpm config get audit` prints. An empty ignore - * list reads as unset. `undefined` when nothing is set. + * `auditConfig` / `auditLevel` / `auditIgnorePrune` settings + * {@link translateAuditSettings} splits the section into — the view + * `pnpm config get audit` prints. An empty ignore list reads as unset. + * `undefined` when nothing is set. */ -export function toAuditSettings ({ auditConfig, auditLevel }: { auditConfig?: AuditConfig, auditLevel?: AuditLevel }): AuditSettings | undefined { +export function toAuditSettings ({ auditConfig, auditLevel, auditIgnorePrune }: { auditConfig?: AuditConfig, auditLevel?: AuditLevel, auditIgnorePrune?: boolean }): AuditSettings | undefined { const ignore = auditConfig?.ignoreGhsas const audit: AuditSettings = { ...(auditLevel != null ? { level: auditLevel } : {}), ...(ignore != null && ignore.length > 0 ? { ignore } : {}), + ...(auditIgnorePrune != null ? { ignorePrune: auditIgnorePrune } : {}), } return Object.keys(audit).length > 0 ? audit : undefined } diff --git a/pnpm11/config/reader/src/unknownSettings.ts b/pnpm11/config/reader/src/unknownSettings.ts index b258cbb505..4dba3ef83d 100644 --- a/pnpm11/config/reader/src/unknownSettings.ts +++ b/pnpm11/config/reader/src/unknownSettings.ts @@ -57,6 +57,7 @@ const _proofTypedWorkspaceManifestKeysAreExhaustive: ProofTypedWorkspaceManifest */ const CONFIG_ONLY_SETTING_KEYS = [ 'allowNew', + 'auditIgnorePrune', 'authConfig', 'autoConfirmAllPrompts', 'bin', diff --git a/pnpm11/config/reader/test/updateSettings.test.ts b/pnpm11/config/reader/test/updateSettings.test.ts index a1df4fba17..a3afea92ba 100644 --- a/pnpm11/config/reader/test/updateSettings.test.ts +++ b/pnpm11/config/reader/test/updateSettings.test.ts @@ -132,6 +132,17 @@ test('getOptionsFromPnpmSettings() maps the "audit" settings section to auditCon expect(globalWarn).not.toHaveBeenCalled() }) +test('getOptionsFromPnpmSettings() maps "audit.ignorePrune" to auditIgnorePrune', () => { + const options = getOptionsFromPnpmSettings(process.cwd(), { + audit: { + ignore: ['GHSA-1'], + ignorePrune: true, + }, + }) + expect(options.auditConfig).toStrictEqual({ ignoreGhsas: ['GHSA-1'] }) + expect(options.auditIgnorePrune).toBe(true) +}) + test('getOptionsFromPnpmSettings() never leaks the raw "audit" key into the options', () => { const options = getOptionsFromPnpmSettings(process.cwd(), { audit: { @@ -179,6 +190,12 @@ test('getOptionsFromPnpmSettings() throws when "audit.ignore" is not a string ar } as any)).toThrow(/audit\.ignore/) // eslint-disable-line }) +test('getOptionsFromPnpmSettings() throws when "audit.ignorePrune" is not a boolean', () => { + expect(() => getOptionsFromPnpmSettings(process.cwd(), { + audit: { ignorePrune: 'yes' }, + } as any)).toThrow(/audit\.ignorePrune/) // eslint-disable-line +}) + test('getOptionsFromPnpmSettings() throws on an invalid "audit.level"', () => { expect(() => getOptionsFromPnpmSettings(process.cwd(), { audit: { level: 'severe' }, diff --git a/pnpm11/config/writer/src/index.ts b/pnpm11/config/writer/src/index.ts index e8da0b5de7..bc0fc37f4f 100644 --- a/pnpm11/config/writer/src/index.ts +++ b/pnpm11/config/writer/src/index.ts @@ -4,6 +4,7 @@ import { updateWorkspaceManifest } from '@pnpm/workspace.workspace-manifest-writ export interface WriteSettingsOptions { updatedSettings?: PnpmSettings updatedOverrides?: Record + updatedAuditIgnoreGhsas?: string[] addedMinimumReleaseAgeExcludes?: string[] deletedLegacyKeys?: string[] rootProjectManifest?: ProjectManifest @@ -15,6 +16,7 @@ export async function writeSettings (opts: WriteSettingsOptions): Promise await updateWorkspaceManifest(opts.workspaceDir, { updatedFields: opts.updatedSettings, updatedOverrides: opts.updatedOverrides, + updatedAuditIgnoreGhsas: opts.updatedAuditIgnoreGhsas, addedMinimumReleaseAgeExcludes: opts.addedMinimumReleaseAgeExcludes, deletedLegacyKeys: opts.deletedLegacyKeys, }) diff --git a/pnpm11/core/types/src/package.ts b/pnpm11/core/types/src/package.ts index 75ae1e68a3..e2e1651bf5 100644 --- a/pnpm11/core/types/src/package.ts +++ b/pnpm11/core/types/src/package.ts @@ -202,6 +202,13 @@ export interface AuditSettings { * `auditConfig.ignoreGhsas`. */ ignore?: string[] + /** + * When `true`, `pnpm audit --fix` removes entries from the ignore list that + * no longer appear in the audit report, so a re-introduced vulnerability + * under the same GHSA ID gets re-evaluated instead of staying silently + * suppressed. + */ + ignorePrune?: boolean } export interface UpdateSettings { diff --git a/pnpm11/deps/compliance/commands/package.json b/pnpm11/deps/compliance/commands/package.json index a1060935c7..80b75d1c1c 100644 --- a/pnpm11/deps/compliance/commands/package.json +++ b/pnpm11/deps/compliance/commands/package.json @@ -61,6 +61,7 @@ "@pnpm/npm-package-arg": "catalog:", "@pnpm/object.key-sorting": "workspace:*", "@pnpm/store.path": "workspace:*", + "@pnpm/text.sanitize": "workspace:*", "@pnpm/types": "workspace:*", "@pnpm/workspace.project-manifest-reader": "workspace:*", "@zkochan/table": "catalog:", diff --git a/pnpm11/deps/compliance/commands/src/audit/audit.ts b/pnpm11/deps/compliance/commands/src/audit/audit.ts index 34cdb9ce7f..e216fd7b2d 100644 --- a/pnpm11/deps/compliance/commands/src/audit/audit.ts +++ b/pnpm11/deps/compliance/commands/src/audit/audit.ts @@ -1,11 +1,13 @@ import { checkbox, Separator } from '@inquirer/prompts' import { docsUrl, interactivePromptPageSize, TABLE_OPTIONS } from '@pnpm/cli.utils' import { type Config, type ConfigContext, types as allTypes, type UniversalOptions } from '@pnpm/config.reader' +import { writeSettings } from '@pnpm/config.writer' import { audit, type AuditAdvisory, type AuditLevelNumber, type AuditLevelString, type AuditReport, type AuditVulnerabilityCounts, type IgnoredAuditVulnerabilityCounts, normalizeGhsaId } from '@pnpm/deps.compliance.audit' import { PnpmError } from '@pnpm/error' import { type InstallCommandOptions, update } from '@pnpm/installing.commands' import { globalInfo } from '@pnpm/logger' import { createGetAuthHeaderByURI } from '@pnpm/network.auth-header' +import { sanitizeInline } from '@pnpm/text.sanitize' import type { RegistriesByScope } from '@pnpm/types' import { table } from '@zkochan/table' import chalk, { type ChalkInstance } from 'chalk' @@ -17,6 +19,7 @@ import { fix } from './fix.js' import { fixWithUpdate, type FixWithUpdateResult } from './fixWithUpdate.js' import { getAuditFixChoices } from './getAuditFixChoices.js' import { ignore } from './ignore.js' +import { pruneIgnoredGhsas } from './pruneIgnoredGhsas.js' import { correctInferredPatchedVersions, createPublishTimesFetcher, type PublishTimesFetcher } from './publishTimes.js' import { auditSignatures } from './signatures.js' @@ -177,6 +180,7 @@ export type AuditOptions = Pick & { */ getPublishTimes?: PublishTimesFetcher } & Pick 0) { + // The pruned ids keep their original spelling from the + // repository-controlled workspace manifest, so strip control + // characters before they reach the terminal. + globalInfo(`Removed ${pruned.length} unused ignored GHSA${pruned.length === 1 ? '' : 's'}: ${pruned.map(sanitizeInline).join(', ')}`) + } + // Persist even when nothing was removed: `retained` may still differ + // from the configured list (deduplicated or case-normalized), and the + // file should always reflect the canonical form. + const retainedDiffers = retained.length !== configuredGhsas.length || + retained.some((ghsa, index) => ghsa !== configuredGhsas[index]) + if (retainedDiffers) { + // Written through the dedicated ignore-list update so the retained + // list lands on whichever spelling the manifest uses — replacing + // only `auditConfig` would let a canonical `audit.ignore` list + // shadow the pruned result on the next read. + await writeSettings({ + ...opts, + workspaceDir: opts.workspaceDir ?? opts.rootProjectManifestDir, + updatedAuditIgnoreGhsas: retained, + }) + // Update opts for subsequent operations + opts.auditConfig = { + ...opts.auditConfig, + ignoreGhsas: retained.length > 0 ? retained : undefined, + } + } + } // Pre-filter by auditLevel and ignoreGhsas so the interactive prompt // and the update-method path see the same set of advisories that // fix.ts's getFixableAdvisories filters for the override path. diff --git a/pnpm11/deps/compliance/commands/src/audit/ignore.ts b/pnpm11/deps/compliance/commands/src/audit/ignore.ts index 57b1192400..1c2b2ef3fd 100644 --- a/pnpm11/deps/compliance/commands/src/audit/ignore.ts +++ b/pnpm11/deps/compliance/commands/src/audit/ignore.ts @@ -39,16 +39,15 @@ export async function ignore (opts: IgnoreVulnerabilitiesOptions): Promise 0 ? Array.from(currentUniqueGhsas) : undefined - const diffGhsas = difference(newIgnoreGhsas ?? [], currentGhsas) + const newIgnoreGhsas = Array.from(currentUniqueGhsas) + const diffGhsas = difference(newIgnoreGhsas, currentGhsas) + // Written through the dedicated ignore-list update so the merged list + // lands on whichever spelling the manifest uses — replacing only + // `auditConfig` would let a canonical `audit.ignore` list shadow the + // added ids on the next read. await writeSettings({ ...opts, - updatedSettings: { - auditConfig: { - ...opts.auditConfig, - ignoreGhsas: newIgnoreGhsas, - }, - }, + updatedAuditIgnoreGhsas: newIgnoreGhsas, }) return [...diffGhsas] } diff --git a/pnpm11/deps/compliance/commands/src/audit/pruneIgnoredGhsas.ts b/pnpm11/deps/compliance/commands/src/audit/pruneIgnoredGhsas.ts new file mode 100644 index 0000000000..6fd27e5ed0 --- /dev/null +++ b/pnpm11/deps/compliance/commands/src/audit/pruneIgnoredGhsas.ts @@ -0,0 +1,35 @@ +import type { AuditReport } from '@pnpm/deps.compliance.audit' +import { normalizeGhsaId } from '@pnpm/deps.compliance.audit' + +export interface PruneIgnoredGhsasResult { + pruned: string[] + retained: string[] +} + +export function pruneIgnoredGhsas ( + ignoredGhsas: string[], + auditReport: AuditReport +): PruneIgnoredGhsasResult { + if (!ignoredGhsas?.length) { + return { pruned: [], retained: [] } + } + + const advisoryGhsaIds = new Set( + Object.values(auditReport.advisories) + .filter(({ github_advisory_id: ghsaId }) => ghsaId) + .map(({ github_advisory_id: ghsaId }) => normalizeGhsaId(ghsaId)) + ) + + const retainedGhsas = new Set() + const pruned: string[] = [] + for (const ghsa of ignoredGhsas) { + const normalized = normalizeGhsaId(ghsa) + if (advisoryGhsaIds.has(normalized)) { + retainedGhsas.add(normalized) + } else { + pruned.push(ghsa) + } + } + + return { pruned, retained: Array.from(retainedGhsas) } +} diff --git a/pnpm11/deps/compliance/commands/test/audit/fix.ts b/pnpm11/deps/compliance/commands/test/audit/fix.ts index 77c7d1a0fd..74ea2501b9 100644 --- a/pnpm11/deps/compliance/commands/test/audit/fix.ts +++ b/pnpm11/deps/compliance/commands/test/audit/fix.ts @@ -1,9 +1,10 @@ -import { existsSync as fsExistsSync } from 'node:fs' +import fs, { existsSync as fsExistsSync } from 'node:fs' import path from 'node:path' import { afterEach, beforeEach, describe, expect, test } from '@jest/globals' import type { AuditAdvisory } from '@pnpm/deps.compliance.audit' import { audit } from '@pnpm/deps.compliance.commands' +import { type LogBase, streamParser } from '@pnpm/logger' import { fixtures } from '@pnpm/test-fixtures' import { getMockAgent, setupMockAgent, teardownMockAgent } from '@pnpm/testing.mock-agent' import { readYamlFileSync } from 'read-yaml-file' @@ -14,14 +15,25 @@ import * as responses from './utils/responses/index.js' const f = fixtures(import.meta.dirname) +const collectedInfos: string[] = [] + beforeEach(async () => { + collectedInfos.length = 0 + streamParser.on('data', collectInfos as (msg: LogBase) => void) await setupMockAgent() }) afterEach(async () => { + streamParser.removeListener('data', collectInfos as (msg: LogBase) => void) await teardownMockAgent() }) +function collectInfos (msg: LogBase & { message?: string }): void { + if (msg.level === 'info' && typeof msg.message === 'string') { + collectedInfos.push(msg.message) + } +} + test('overrides are added for vulnerable dependencies', async () => { const tmp = f.prepare('has-vulnerabilities') @@ -250,6 +262,273 @@ test('audit --fix respects auditLevel and only fixes matching severities', async expect(manifest.overrides?.['url-parse@<1.5.6']).toBeFalsy() }) +test('audit.ignorePrune removes ignored GHSAs that are no longer in the report', async () => { + const tmp = f.prepare('has-vulnerabilities-with-ignored-ghsas') + + getMockAgent().get(AUDIT_REGISTRY.replace(/\/$/, '')) + .intercept({ path: '/-/npm/v1/security/advisories/bulk', method: 'POST' }) + .reply(200, responses.ALL_VULN_RESP) + + // GHSA-42xw-2xvc-qx8m exists in the report (axios <=0.18.0) + // GHSA-xxxx-xxxx-xxxx does NOT exist in the report - should be removed + const { exitCode } = await audit.handler({ + ...AUDIT_REGISTRY_OPTS, + auditLevel: 'moderate', + auditConfig: { + ignoreGhsas: [ + 'GHSA-42xw-2xvc-qx8m', + 'GHSA-xxxx-xxxx-xxxx', + ], + }, + auditIgnorePrune: true, + dir: tmp, + rootProjectManifestDir: tmp, + fix: true, + }) + + expect(exitCode).toBe(0) + + const manifest = readYamlFileSync<{ auditConfig?: { ignoreGhsas?: string[] } }>(path.join(tmp, 'pnpm-workspace.yaml')) + expect(manifest.auditConfig?.ignoreGhsas).toContain('GHSA-42xw-2xvc-qx8m') + expect(manifest.auditConfig?.ignoreGhsas).not.toContain('GHSA-xxxx-xxxx-xxxx') + + // The preceding comment and the trailing same-line comment attached to + // the removed entry must both go with it. + const rawContent = fs.readFileSync(path.join(tmp, 'pnpm-workspace.yaml'), 'utf8') + expect(rawContent).not.toContain('Expired GHSA') + expect(rawContent).not.toContain('trailing comment') + + expect(collectedInfos).toContain('Removed 1 unused ignored GHSA: GHSA-xxxx-xxxx-xxxx') +}) + +test('audit.ignorePrune is disabled by default - no pruning', async () => { + const tmp = f.prepare('has-vulnerabilities-with-ignored-ghsas') + + getMockAgent().get(AUDIT_REGISTRY.replace(/\/$/, '')) + .intercept({ path: '/-/npm/v1/security/advisories/bulk', method: 'POST' }) + .reply(200, responses.ALL_VULN_RESP) + + // Without audit.ignorePrune: true, pruning should NOT run + const { exitCode } = await audit.handler({ + ...AUDIT_REGISTRY_OPTS, + auditLevel: 'moderate', + auditConfig: { + ignoreGhsas: [ + 'GHSA-42xw-2xvc-qx8m', + 'GHSA-xxxx-xxxx-xxxx', + ], + }, + dir: tmp, + rootProjectManifestDir: tmp, + fix: true, + }) + + expect(exitCode).toBe(0) + + // When pruning doesn't run, the auditConfig stays unchanged + const manifest = readYamlFileSync<{ auditConfig?: { ignoreGhsas?: string[] } }>(path.join(tmp, 'pnpm-workspace.yaml')) + expect(manifest.auditConfig?.ignoreGhsas).toContain('GHSA-xxxx-xxxx-xxxx') +}) + +// GHSA ids are case-insensitive; lowercase version should match uppercase in report +test('audit.ignorePrune handles case normalization', async () => { + const tmp = f.prepare('has-vulnerabilities-with-ignored-ghsas') + + getMockAgent().get(AUDIT_REGISTRY.replace(/\/$/, '')) + .intercept({ path: '/-/npm/v1/security/advisories/bulk', method: 'POST' }) + .reply(200, responses.ALL_VULN_RESP) + + const { exitCode } = await audit.handler({ + ...AUDIT_REGISTRY_OPTS, + auditLevel: 'moderate', + auditConfig: { + ignoreGhsas: [ + 'ghsa-42xw-2xvc-qx8m', // lowercase, should be retained + 'GHSA-XXXX-XXXX-XXXX', // uppercase, NOT in report - should be removed + ], + }, + auditIgnorePrune: true, + dir: tmp, + rootProjectManifestDir: tmp, + fix: true, + }) + + expect(exitCode).toBe(0) + + const manifest = readYamlFileSync<{ auditConfig?: { ignoreGhsas?: string[] } }>(path.join(tmp, 'pnpm-workspace.yaml')) + // Retained entries are written in their canonical form regardless of the + // casing the user originally ignored them with. + expect(manifest.auditConfig?.ignoreGhsas).toEqual(['GHSA-42xw-2xvc-qx8m']) +}) + +test('audit.ignorePrune persists the canonical form even when nothing is removed', async () => { + const tmp = f.prepare('has-vulnerabilities-with-ignored-ghsas') + + getMockAgent().get(AUDIT_REGISTRY.replace(/\/$/, '')) + .intercept({ path: '/-/npm/v1/security/advisories/bulk', method: 'POST' }) + .reply(200, responses.ALL_VULN_RESP) + + // Both entries match the same advisory (a differently-cased duplicate) — + // nothing gets removed, but the stored list should still collapse to the + // single canonical entry. + const { exitCode } = await audit.handler({ + ...AUDIT_REGISTRY_OPTS, + auditLevel: 'moderate', + auditConfig: { + ignoreGhsas: [ + 'ghsa-42xw-2xvc-qx8m', + 'GHSA-42XW-2XVC-QX8M', + ], + }, + auditIgnorePrune: true, + dir: tmp, + rootProjectManifestDir: tmp, + fix: true, + }) + + expect(exitCode).toBe(0) + expect(collectedInfos.some((message) => message.includes('unused ignored GHSA'))).toBe(false) + + const manifest = readYamlFileSync<{ auditConfig?: { ignoreGhsas?: string[] } }>(path.join(tmp, 'pnpm-workspace.yaml')) + expect(manifest.auditConfig?.ignoreGhsas).toEqual(['GHSA-42xw-2xvc-qx8m']) +}) + +test('audit.ignorePrune removes all entries when none are relevant', async () => { + const tmp = f.prepare('has-vulnerabilities-with-ignored-ghsas') + + getMockAgent().get(AUDIT_REGISTRY.replace(/\/$/, '')) + .intercept({ path: '/-/npm/v1/security/advisories/bulk', method: 'POST' }) + .reply(200, responses.ALL_VULN_RESP) + + // Only GHSAs that don't exist in the report - all should be pruned + const { exitCode } = await audit.handler({ + ...AUDIT_REGISTRY_OPTS, + auditLevel: 'moderate', + auditConfig: { + ignoreGhsas: [ + 'GHSA-xxxx-0000-0001', + 'GHSA-xxxx-0000-0002', + ], + }, + auditIgnorePrune: true, + dir: tmp, + rootProjectManifestDir: tmp, + fix: true, + }) + + expect(exitCode).toBe(0) + + const manifest = readYamlFileSync<{ auditConfig?: { ignoreGhsas?: string[] } }>(path.join(tmp, 'pnpm-workspace.yaml')) + expect(manifest.auditConfig?.ignoreGhsas).toBeUndefined() +}) + +test('audit.ignorePrune edits an inline (flow-style) auditConfig in place', async () => { + const tmp = f.prepare('has-vulnerabilities-with-ignored-ghsas') + fs.writeFileSync( + path.join(tmp, 'pnpm-workspace.yaml'), + 'packages:\n - \'.\'\nsharedWorkspaceLockfile: false\nauditConfig: { ignoreGhsas: [GHSA-42xw-2xvc-qx8m, GHSA-xxxx-xxxx-xxxx] }\n' + ) + + getMockAgent().get(AUDIT_REGISTRY.replace(/\/$/, '')) + .intercept({ path: '/-/npm/v1/security/advisories/bulk', method: 'POST' }) + .reply(200, responses.ALL_VULN_RESP) + + const { exitCode } = await audit.handler({ + ...AUDIT_REGISTRY_OPTS, + auditLevel: 'moderate', + auditConfig: { + ignoreGhsas: [ + 'GHSA-42xw-2xvc-qx8m', + 'GHSA-xxxx-xxxx-xxxx', + ], + }, + auditIgnorePrune: true, + dir: tmp, + rootProjectManifestDir: tmp, + fix: true, + }) + + expect(exitCode).toBe(0) + + // The retained GHSA is edited in place inside the flow-style block, rather + // than the whole auditConfig being reformatted into block style. + const rawContent = fs.readFileSync(path.join(tmp, 'pnpm-workspace.yaml'), 'utf8') + expect(rawContent).toContain( + 'auditConfig: { ignoreGhsas: [ GHSA-42xw-2xvc-qx8m ] }' + ) + + const manifest = readYamlFileSync<{ auditConfig?: { ignoreGhsas?: string[] } }>(path.join(tmp, 'pnpm-workspace.yaml')) + expect(manifest.auditConfig?.ignoreGhsas).toEqual(['GHSA-42xw-2xvc-qx8m']) +}) + +test('audit.ignorePrune updates the canonical audit.ignore list', async () => { + const tmp = f.prepare('has-vulnerabilities-with-ignored-ghsas') + fs.writeFileSync( + path.join(tmp, 'pnpm-workspace.yaml'), + 'packages:\n - \'.\'\nsharedWorkspaceLockfile: false\naudit:\n ignorePrune: true\n ignore:\n - GHSA-42xw-2xvc-qx8m\n - GHSA-xxxx-xxxx-xxxx\n' + ) + + getMockAgent().get(AUDIT_REGISTRY.replace(/\/$/, '')) + .intercept({ path: '/-/npm/v1/security/advisories/bulk', method: 'POST' }) + .reply(200, responses.ALL_VULN_RESP) + + // `auditConfig`/`auditIgnorePrune` are the internal fields the config + // reader derives from the manifest's `audit` section. + const { exitCode } = await audit.handler({ + ...AUDIT_REGISTRY_OPTS, + auditLevel: 'moderate', + auditConfig: { + ignoreGhsas: [ + 'GHSA-42xw-2xvc-qx8m', + 'GHSA-xxxx-xxxx-xxxx', + ], + }, + auditIgnorePrune: true, + dir: tmp, + rootProjectManifestDir: tmp, + fix: true, + }) + + expect(exitCode).toBe(0) + + // The retained list must land back on the canonical `audit.ignore` that + // supplied it — writing the deprecated `auditConfig.ignoreGhsas` instead + // would let the unchanged canonical list shadow the prune on the next + // read and restore the stale id. + const manifest = readYamlFileSync<{ audit?: { ignorePrune?: boolean, ignore?: string[] }, auditConfig?: unknown }>(path.join(tmp, 'pnpm-workspace.yaml')) + expect(manifest.audit).toStrictEqual({ ignorePrune: true, ignore: ['GHSA-42xw-2xvc-qx8m'] }) + expect(manifest.auditConfig).toBeUndefined() +}) + +test('audit.ignorePrune sanitizes the removed ids in the log message', async () => { + const tmp = f.prepare('has-vulnerabilities-with-ignored-ghsas') + + getMockAgent().get(AUDIT_REGISTRY.replace(/\/$/, '')) + .intercept({ path: '/-/npm/v1/security/advisories/bulk', method: 'POST' }) + .reply(200, responses.ALL_VULN_RESP) + + // The stale entry carries an ANSI escape from the repository-controlled + // manifest; the removal message must strip it before the terminal. + const { exitCode } = await audit.handler({ + ...AUDIT_REGISTRY_OPTS, + auditLevel: 'moderate', + auditConfig: { + ignoreGhsas: [ + 'GHSA-42xw-2xvc-qx8m', + 'GHSA-xxxx-xxxx-xxxx\u001b[31m', + ], + }, + auditIgnorePrune: true, + dir: tmp, + rootProjectManifestDir: tmp, + fix: true, + }) + + expect(exitCode).toBe(0) + expect(collectedInfos).toContain('Removed 1 unused ignored GHSA: GHSA-xxxx-xxxx-xxxx[31m') + expect(collectedInfos.every((message) => !message.includes('\u001b'))).toBe(true) +}) + function advisory (moduleName: string, vulnerableVersions: string, patchedVersions?: string): AuditAdvisory { return { findings: [], diff --git a/pnpm11/deps/compliance/commands/test/audit/fixtures/has-vulnerabilities-with-ignored-ghsas/package.json b/pnpm11/deps/compliance/commands/test/audit/fixtures/has-vulnerabilities-with-ignored-ghsas/package.json new file mode 100644 index 0000000000..ae4895c281 --- /dev/null +++ b/pnpm11/deps/compliance/commands/test/audit/fixtures/has-vulnerabilities-with-ignored-ghsas/package.json @@ -0,0 +1,13 @@ +{ + "name": "has-vulnerabilities", + "version": "1.0.0", + "dependencies": { + "karma": "~2.0.0" + }, + "devDependencies": { + "axios": "0.15" + }, + "optionalDependencies": { + "sync-exec": "0.6.2" + } +} diff --git a/pnpm11/deps/compliance/commands/test/audit/fixtures/has-vulnerabilities-with-ignored-ghsas/pnpm-lock.yaml b/pnpm11/deps/compliance/commands/test/audit/fixtures/has-vulnerabilities-with-ignored-ghsas/pnpm-lock.yaml new file mode 100644 index 0000000000..4ed9575406 --- /dev/null +++ b/pnpm11/deps/compliance/commands/test/audit/fixtures/has-vulnerabilities-with-ignored-ghsas/pnpm-lock.yaml @@ -0,0 +1,3665 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + karma: + specifier: ~2.0.0 + version: 2.0.5 + optionalDependencies: + sync-exec: + specifier: 0.6.2 + version: 0.6.2 + devDependencies: + axios: + specifier: '0.15' + version: 0.15.3 + +packages: + + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + + accepts@1.3.7: + resolution: {integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==} + engines: {node: '>= 0.6'} + + addressparser@1.0.1: + resolution: {integrity: sha1-R6++GiqSYhkdtoOOT9HTm0CCF0Y=} + + after@0.8.2: + resolution: {integrity: sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=} + + agent-base@4.2.1: + resolution: {integrity: sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==} + engines: {node: '>= 4.0.0'} + + agent-base@4.3.0: + resolution: {integrity: sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==} + engines: {node: '>= 4.0.0'} + + ajv@6.10.2: + resolution: {integrity: sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==} + + amqplib@0.5.5: + resolution: {integrity: sha512-sWx1hbfHbyKMw6bXOK2k6+lHL8TESWxjAx5hG8fBtT7wcxoXNIsFxZMnFyBjxt3yL14vn7WqBDe5U6BGOadtLg==} + engines: {node: '>=0.8 <=12'} + + ansi-regex@2.1.1: + resolution: {integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8=} + engines: {node: '>=0.10.0'} + + ansi-styles@2.2.1: + resolution: {integrity: sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=} + engines: {node: '>=0.10.0'} + + anymatch@2.0.0: + resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==} + + aproba@1.2.0: + resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==} + + are-we-there-yet@1.1.5: + resolution: {integrity: sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==} + + arr-diff@4.0.0: + resolution: {integrity: sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=} + engines: {node: '>=0.10.0'} + + arr-flatten@1.1.0: + resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==} + engines: {node: '>=0.10.0'} + + arr-union@3.1.0: + resolution: {integrity: sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=} + engines: {node: '>=0.10.0'} + + array-slice@0.2.3: + resolution: {integrity: sha1-3Tz7gO15c6dRF82sabC5nshhhvU=} + engines: {node: '>=0.10.0'} + + array-unique@0.2.1: + resolution: {integrity: sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=} + engines: {node: '>=0.10.0'} + + array-unique@0.3.2: + resolution: {integrity: sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=} + engines: {node: '>=0.10.0'} + + arraybuffer.slice@0.0.7: + resolution: {integrity: sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==} + + asn1@0.2.4: + resolution: {integrity: sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==} + + assert-plus@0.2.0: + resolution: {integrity: sha512-u1L0ZLywRziOVjUhRxI0Qg9G+4RnFB9H/Rq40YWn0dieDgO7vAYeJz6jKAO6t/aruzlDFLAPkQTT87e+f8Imaw==} + engines: {node: '>=0.8'} + + assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + + assign-symbols@1.0.0: + resolution: {integrity: sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=} + engines: {node: '>=0.10.0'} + + ast-types@0.13.2: + resolution: {integrity: sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA==} + engines: {node: '>=4'} + + async-each@1.0.3: + resolution: {integrity: sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==} + + async-limiter@1.0.1: + resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} + + async@2.6.3: + resolution: {integrity: sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + atob@2.1.2: + resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} + engines: {node: '>= 4.5.0'} + hasBin: true + + aws-sign2@0.6.0: + resolution: {integrity: sha512-JnJpAS0p9RmixkOvW2XwDxxzs1bd4/VAGIl6Q0EC5YOo+p+hqIhtDhn/nmFnB/xUNXbLkpE2mOjgVIBRKD4xYw==} + + aws-sign2@0.7.0: + resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} + + aws4@1.8.0: + resolution: {integrity: sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==} + + axios@0.15.3: + resolution: {integrity: sha1-LJ1jiy4ZGgjqHWzJiOrda6W9wFM=} + + backo2@1.0.2: + resolution: {integrity: sha1-MasayLEpNjRj41s+u2n038+6eUc=} + + balanced-match@1.0.0: + resolution: {integrity: sha1-ibTRmasr7kneFk6gK4nORi1xt2c=} + + base64-arraybuffer@0.1.5: + resolution: {integrity: sha1-c5JncZI7Whl0etZmqlzUv5xunOg=} + engines: {node: '>= 0.6.0'} + + base64id@1.0.0: + resolution: {integrity: sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=} + engines: {node: '>= 0.4.0'} + + base@0.11.2: + resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} + engines: {node: '>=0.10.0'} + + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + + better-assert@1.0.2: + resolution: {integrity: sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=} + + binary-extensions@1.13.1: + resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==} + engines: {node: '>=0.10.0'} + + bitsyntax@0.1.0: + resolution: {integrity: sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==} + engines: {node: '>=0.8'} + + bl@1.1.2: + resolution: {integrity: sha1-/cqHGplxOqANGeO7ukHER4emU5g=} + + blob@0.0.5: + resolution: {integrity: sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==} + + bluebird@3.7.1: + resolution: {integrity: sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==} + + body-parser@1.19.0: + resolution: {integrity: sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==} + engines: {node: '>= 0.8'} + + boom@2.10.1: + resolution: {integrity: sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=} + engines: {node: '>=0.10.40'} + deprecated: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial). + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + braces@0.1.5: + resolution: {integrity: sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=} + engines: {node: '>=0.10.0'} + + braces@2.3.2: + resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} + engines: {node: '>=0.10.0'} + + buffer-alloc-unsafe@1.1.0: + resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} + + buffer-alloc@1.2.0: + resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} + + buffer-fill@1.0.0: + resolution: {integrity: sha1-+PeLdniYiO858gXNY39o5wISKyw=} + + buffer-more-ints@1.0.0: + resolution: {integrity: sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==} + + buildmail@4.0.1: + resolution: {integrity: sha1-h393OLeHKYccmhBeO4N9K+EaenI=} + deprecated: This project is unmaintained + + bytes@3.1.0: + resolution: {integrity: sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==} + engines: {node: '>= 0.8'} + + cache-base@1.0.1: + resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} + engines: {node: '>=0.10.0'} + + callsite@1.0.0: + resolution: {integrity: sha1-KAOY5dZkvXQDi28JBRU+borxvCA=} + + caseless@0.11.0: + resolution: {integrity: sha512-ODLXH644w9C2fMPAm7bMDQ3GRvipZWZfKc+8As6hIadRIelE0n0xZuN38NS6kiK3KPEVrpymmQD8bvncAHWQkQ==} + + caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + + chalk@1.1.3: + resolution: {integrity: sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=} + engines: {node: '>=0.10.0'} + + chokidar@2.1.8: + resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + circular-json@0.5.9: + resolution: {integrity: sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==} + deprecated: CircularJSON is in maintenance only, flatted is its successor. + + class-utils@0.3.6: + resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} + engines: {node: '>=0.10.0'} + + co@4.6.0: + resolution: {integrity: sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + code-point-at@1.1.0: + resolution: {integrity: sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=} + engines: {node: '>=0.10.0'} + + collection-visit@1.0.0: + resolution: {integrity: sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=} + engines: {node: '>=0.10.0'} + + colors@1.4.0: + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} + + combine-lists@1.0.1: + resolution: {integrity: sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + component-bind@1.0.0: + resolution: {integrity: sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=} + + component-emitter@1.2.1: + resolution: {integrity: sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=} + + component-emitter@1.3.0: + resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} + + component-inherit@0.0.3: + resolution: {integrity: sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=} + + concat-map@0.0.1: + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + + connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + + console-control-strings@1.1.0: + resolution: {integrity: sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=} + + content-type@1.0.4: + resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} + engines: {node: '>= 0.6'} + + cookie@0.3.1: + resolution: {integrity: sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=} + engines: {node: '>= 0.6'} + + copy-descriptor@0.1.1: + resolution: {integrity: sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=} + engines: {node: '>=0.10.0'} + + core-js@2.6.10: + resolution: {integrity: sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==} + + core-util-is@1.0.2: + resolution: {integrity: sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=} + + cryptiles@2.0.5: + resolution: {integrity: sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=} + engines: {node: '>=0.10.40'} + deprecated: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial). + + custom-event@1.0.1: + resolution: {integrity: sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=} + + dashdash@1.14.1: + resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} + engines: {node: '>=0.10'} + + data-uri-to-buffer@1.2.0: + resolution: {integrity: sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==} + + date-format@1.2.0: + resolution: {integrity: sha1-YV6CjiM90aubua4JUODOzPpuytg=} + engines: {node: '>=4.0'} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.1.0: + resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.6: + resolution: {integrity: sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.1.1: + resolution: {integrity: sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-uri-component@0.2.0: + resolution: {integrity: sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=} + engines: {node: '>=0.10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deep-is@0.1.3: + resolution: {integrity: sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=} + + define-property@0.2.5: + resolution: {integrity: sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=} + engines: {node: '>=0.10.0'} + + define-property@1.0.0: + resolution: {integrity: sha1-dp66rz9KY6rTr56NMEybvnm/sOY=} + engines: {node: '>=0.10.0'} + + define-property@2.0.2: + resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==} + engines: {node: '>=0.10.0'} + + degenerator@1.0.4: + resolution: {integrity: sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + delegates@1.0.0: + resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=} + + depd@1.1.2: + resolution: {integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=} + engines: {node: '>= 0.6'} + + detect-libc@1.0.3: + resolution: {integrity: sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=} + engines: {node: '>=0.10'} + hasBin: true + + di@0.0.1: + resolution: {integrity: sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=} + + dom-serialize@2.2.1: + resolution: {integrity: sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=} + + double-ended-queue@2.1.0-0: + resolution: {integrity: sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=} + + ecc-jsbn@0.1.2: + resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} + + ee-first@1.1.1: + resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=} + + encodeurl@1.0.2: + resolution: {integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=} + engines: {node: '>= 0.8'} + + engine.io-client@3.1.6: + resolution: {integrity: sha512-hnuHsFluXnsKOndS4Hv6SvUrgdYx1pk2NqfaDMW+GWdgfU3+/V25Cj7I8a0x92idSpa5PIhJRKxPvp9mnoLsfg==} + + engine.io-parser@2.1.3: + resolution: {integrity: sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==} + + engine.io@3.1.5: + resolution: {integrity: sha512-D06ivJkYxyRrcEe0bTpNnBQNgP9d3xog+qZlLbui8EsMr/DouQpf5o9FzJnWYHEYE0YsFHllUv2R1dkgYZXHcA==} + + ent@2.2.0: + resolution: {integrity: sha1-6WQhkyWiHQX0RGai9obtbOX13R0=} + + es6-promise@4.2.8: + resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} + + es6-promisify@5.0.0: + resolution: {integrity: sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=} + + escape-html@1.0.3: + resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} + engines: {node: '>=0.8.0'} + + escodegen@1.12.0: + resolution: {integrity: sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg==} + engines: {node: '>=4.0'} + hasBin: true + + esprima@3.1.3: + resolution: {integrity: sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=} + engines: {node: '>=4'} + hasBin: true + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@4.0.0: + resolution: {integrity: sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==} + + expand-braces@0.1.2: + resolution: {integrity: sha1-SIsdHSRRyz06axks/AMPRMWFX+o=} + engines: {node: '>=0.10.0'} + + expand-brackets@2.1.4: + resolution: {integrity: sha1-t3c14xXOMPa27/D4OwQVGiJEliI=} + engines: {node: '>=0.10.0'} + + expand-range@0.1.1: + resolution: {integrity: sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=} + engines: {node: '>=0.10.0'} + + extend-shallow@2.0.1: + resolution: {integrity: sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=} + engines: {node: '>=0.10.0'} + + extend-shallow@3.0.2: + resolution: {integrity: sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=} + engines: {node: '>=0.10.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + extglob@2.0.4: + resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==} + engines: {node: '>=0.10.0'} + + extsprintf@1.3.0: + resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} + engines: {'0': node >=0.6.0} + + fast-deep-equal@2.0.1: + resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} + + fast-json-stable-stringify@2.0.0: + resolution: {integrity: sha512-eIgZvM9C3P05kg0qxfqaVU6Tma4QedCPIByQOcemV0vju8ot3cS2DpHi4m2G2JvbSMI152rjfLX0p1pkSdyPlQ==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fill-range@4.0.0: + resolution: {integrity: sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=} + engines: {node: '>=0.10.0'} + + finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} + + follow-redirects@1.0.0: + resolution: {integrity: sha1-jjQpjL0uF28lTv/sdaHHjMhJ/Tc=} + + follow-redirects@1.9.0: + resolution: {integrity: sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==} + engines: {node: '>=4.0'} + + for-in@1.0.2: + resolution: {integrity: sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=} + engines: {node: '>=0.10.0'} + + forever-agent@0.6.1: + resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} + + form-data@2.0.0: + resolution: {integrity: sha512-BWUNep0UvjzlIJgDsi0SFD3MvnLlwiRaVpfr82Hj2xgc9MJJcl1tSQj01CJDMG+w/kzm+vkZMmXwRM2XrkBuaA==} + engines: {node: '>= 0.12'} + + form-data@2.3.3: + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} + + fragment-cache@0.2.1: + resolution: {integrity: sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=} + engines: {node: '>=0.10.0'} + + fs-minipass@1.2.7: + resolution: {integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==} + + fs.realpath@1.0.0: + resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} + + fsevents@1.2.9: + resolution: {integrity: sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==} + engines: {node: '>=4.0'} + os: [darwin] + deprecated: fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2. + bundledDependencies: + - node-pre-gyp + + ftp@0.3.10: + resolution: {integrity: sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=} + engines: {node: '>=0.8.0'} + + gauge@2.7.4: + resolution: {integrity: sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=} + + generate-function@2.3.1: + resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} + + generate-object-property@1.2.0: + resolution: {integrity: sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=} + + get-uri@2.0.4: + resolution: {integrity: sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q==} + + get-value@2.0.6: + resolution: {integrity: sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=} + engines: {node: '>=0.10.0'} + + getpass@0.1.7: + resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + + glob-parent@3.1.0: + resolution: {integrity: sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=} + + glob@7.1.6: + resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} + + graceful-fs@4.2.3: + resolution: {integrity: sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==} + + har-schema@2.0.0: + resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} + engines: {node: '>=4'} + + har-validator@2.0.6: + resolution: {integrity: sha512-P6tFV+wCcUL3nbyTDAvveDySfbhy0XkDtAIfZP6HITjM2WUsiPna/Eg1Yy93SFXvahqoX+kt0n+6xlXKDXYowA==} + engines: {node: '>=0.10'} + deprecated: this library is no longer supported + hasBin: true + + har-validator@5.1.3: + resolution: {integrity: sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==} + engines: {node: '>=6'} + deprecated: this library is no longer supported + + has-ansi@2.0.0: + resolution: {integrity: sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=} + engines: {node: '>=0.10.0'} + + has-binary2@1.0.3: + resolution: {integrity: sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==} + + has-cors@1.1.0: + resolution: {integrity: sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=} + + has-unicode@2.0.1: + resolution: {integrity: sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=} + + has-value@0.3.1: + resolution: {integrity: sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=} + engines: {node: '>=0.10.0'} + + has-value@1.0.0: + resolution: {integrity: sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=} + engines: {node: '>=0.10.0'} + + has-values@0.1.4: + resolution: {integrity: sha1-bWHeldkd/Km5oCCJrThL/49it3E=} + engines: {node: '>=0.10.0'} + + has-values@1.0.0: + resolution: {integrity: sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=} + engines: {node: '>=0.10.0'} + + hawk@3.1.3: + resolution: {integrity: sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=} + engines: {node: '>=0.10.32'} + deprecated: This module moved to @hapi/hawk. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues. + + hipchat-notifier@1.1.0: + resolution: {integrity: sha512-L9ws+WOz7Kaco+qhNpWmCvPmAqEYcOMi3Vyhr9bRn6g6uvdvNpd2HjgttUpuLCZ7CW7sPc8R8y/ge3XErZChFw==} + + hoek@2.16.3: + resolution: {integrity: sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=} + engines: {node: '>=0.10.40'} + deprecated: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial). + + http-errors@1.7.2: + resolution: {integrity: sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==} + engines: {node: '>= 0.6'} + + http-errors@1.7.3: + resolution: {integrity: sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==} + engines: {node: '>= 0.6'} + + http-proxy-agent@2.1.0: + resolution: {integrity: sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==} + engines: {node: '>= 4.5.0'} + + http-proxy@1.18.0: + resolution: {integrity: sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==} + engines: {node: '>=6.0.0'} + + http-signature@1.1.1: + resolution: {integrity: sha512-iUn0NcRULlDGtqNLN1Jxmzayk8ogm7NToldASyZBpM2qggbphjXzNOiw3piN8tgz+e/DRs6X5gAzFwTI6BCRcg==} + engines: {node: '>=0.8', npm: '>=1.3.7'} + + http-signature@1.2.0: + resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} + engines: {node: '>=0.8', npm: '>=1.3.7'} + + httpntlm@1.6.1: + resolution: {integrity: sha1-rQFScUOi6Hc8+uapb1hla7UqNLI=} + engines: {node: '>=0.8.0'} + + httpreq@0.4.24: + resolution: {integrity: sha1-QzX/2CzZaWaKOUZckprGHWOTYn8=} + engines: {node: '>= 0.8.0'} + + https-proxy-agent@2.2.4: + resolution: {integrity: sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==} + engines: {node: '>= 4.5.0'} + + https-proxy-agent@3.0.1: + resolution: {integrity: sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==} + engines: {node: '>= 4.5.0'} + + iconv-lite@0.4.15: + resolution: {integrity: sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=} + engines: {node: '>=0.10.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ignore-walk@3.0.4: + resolution: {integrity: sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==} + + indexof@0.0.1: + resolution: {integrity: sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=} + + inflection@1.12.0: + resolution: {integrity: sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=} + engines: {'0': node >= 0.4.0} + + inflection@1.3.8: + resolution: {integrity: sha1-y9Fg2p91sUw8xjV41POWeEvzAU4=} + engines: {'0': node >= 0.4.0} + + inflight@1.0.6: + resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} + + inherits@2.0.3: + resolution: {integrity: sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ip@1.1.5: + resolution: {integrity: sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=} + + is-accessor-descriptor@0.1.6: + resolution: {integrity: sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=} + engines: {node: '>=0.10.0'} + + is-accessor-descriptor@1.0.0: + resolution: {integrity: sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==} + engines: {node: '>=0.10.0'} + + is-binary-path@1.0.1: + resolution: {integrity: sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=} + engines: {node: '>=0.10.0'} + + is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + + is-data-descriptor@0.1.4: + resolution: {integrity: sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=} + engines: {node: '>=0.10.0'} + + is-data-descriptor@1.0.0: + resolution: {integrity: sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==} + engines: {node: '>=0.10.0'} + + is-descriptor@0.1.6: + resolution: {integrity: sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==} + engines: {node: '>=0.10.0'} + + is-descriptor@1.0.2: + resolution: {integrity: sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==} + engines: {node: '>=0.10.0'} + + is-extendable@0.1.1: + resolution: {integrity: sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=} + engines: {node: '>=0.10.0'} + + is-extendable@1.0.1: + resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} + engines: {node: '>=0.10.0'} + + is-extglob@2.1.1: + resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@1.0.0: + resolution: {integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs=} + engines: {node: '>=0.10.0'} + + is-glob@3.1.0: + resolution: {integrity: sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=} + engines: {node: '>=0.10.0'} + + is-glob@4.0.1: + resolution: {integrity: sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==} + engines: {node: '>=0.10.0'} + + is-my-ip-valid@1.0.0: + resolution: {integrity: sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==} + + is-my-json-valid@2.20.0: + resolution: {integrity: sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==} + + is-number@0.1.1: + resolution: {integrity: sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=} + engines: {node: '>=0.10.0'} + + is-number@3.0.0: + resolution: {integrity: sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=} + engines: {node: '>=0.10.0'} + + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-property@1.0.2: + resolution: {integrity: sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=} + + is-stream@1.1.0: + resolution: {integrity: sha1-EtSj3U5o4Lec6428hBc66A2RykQ=} + engines: {node: '>=0.10.0'} + + is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + + isarray@1.0.0: + resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=} + + isarray@2.0.1: + resolution: {integrity: sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=} + + isbinaryfile@3.0.3: + resolution: {integrity: sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==} + engines: {node: '>=0.6.0'} + + isobject@2.1.0: + resolution: {integrity: sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=} + engines: {node: '>=0.10.0'} + + isobject@3.0.1: + resolution: {integrity: sha1-TkMekrEalzFjaqH5yNHMvP2reN8=} + engines: {node: '>=0.10.0'} + + isstream@0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + + jsbn@0.1.1: + resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema@0.2.3: + resolution: {integrity: sha512-a3xHnILGMtk+hDOqNwHzF6e2fNbiMrXZvxKQiEv2MlgQP+pjIOzqAmKYD2mDpXYE/44M7g+n9p2bKkYWDUcXCQ==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + jsonpointer@4.0.1: + resolution: {integrity: sha1-T9kss04OnbPInIYi7PUfm5eMbLk=} + engines: {node: '>=0.10.0'} + + jsprim@1.4.1: + resolution: {integrity: sha512-4Dj8Rf+fQ+/Pn7C5qeEX02op1WfOss3PKTE9Nsop3Dx+6UPxlm1dr/og7o2cRa5hNN07CACr4NFzRLtj/rjWog==} + engines: {'0': node >=0.6.0} + + karma@2.0.5: + resolution: {integrity: sha512-rECezBeY7mjzGUWhFlB7CvPHgkHJLXyUmWg+6vHCEsdWNUTnmiS6jRrIMcJEWgU2DUGZzGWG0bTRVky8fsDTOA==} + engines: {node: '>= 4'} + hasBin: true + + kind-of@3.2.2: + resolution: {integrity: sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=} + engines: {node: '>=0.10.0'} + + kind-of@4.0.0: + resolution: {integrity: sha1-IIE989cSkosgc3hpGkUGb65y3Vc=} + engines: {node: '>=0.10.0'} + + kind-of@5.1.0: + resolution: {integrity: sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==} + engines: {node: '>=0.10.0'} + + kind-of@6.0.2: + resolution: {integrity: sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==} + engines: {node: '>=0.10.0'} + + levn@0.3.0: + resolution: {integrity: sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=} + engines: {node: '>= 0.8.0'} + + libbase64@0.1.0: + resolution: {integrity: sha1-YjUag5VjrF/1vSbxL2Dpgwu3UeY=} + + libmime@3.0.0: + resolution: {integrity: sha1-UaGp50SOy9Ms2lRCFnW7IbwJPaY=} + + libqp@1.1.0: + resolution: {integrity: sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g=} + + lodash@4.17.15: + resolution: {integrity: sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==} + + log4js@2.11.0: + resolution: {integrity: sha512-z1XdwyGFg8/WGkOyF6DPJjivCWNLKrklGdViywdYnSKOvgtEBo2UyEMZS5sD2mZrQlU3TvO8wDWLc8mzE1ncBQ==} + engines: {node: '>=4.0'} + + loggly@1.1.1: + resolution: {integrity: sha512-0laURFVaaDk5jhU4KL9UWDIb799LJEWY0VVP9OWueTzFElyNTd9uSUWt2VoAmc6T+3+tpjXtUg+OWNz52fXlOA==} + engines: {node: '>= 0.8.0'} + + lru-cache@2.2.4: + resolution: {integrity: sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=} + + lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + + mailcomposer@4.0.1: + resolution: {integrity: sha1-DhxEsqB890DuF9wUm6AJ8Zyt/rQ=} + deprecated: This project is unmaintained + + mailgun-js@0.18.1: + resolution: {integrity: sha512-lvuMP14u24HS2uBsJEnzSyPMxzU2b99tQsIx1o6QNjqxjk8b3WvR+vq5oG1mjqz/IBYo+5gF+uSoDS0RkMVHmg==} + engines: {node: '>=6.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + map-cache@0.2.2: + resolution: {integrity: sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=} + engines: {node: '>=0.10.0'} + + map-visit@1.0.0: + resolution: {integrity: sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=} + engines: {node: '>=0.10.0'} + + media-typer@0.3.0: + resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=} + engines: {node: '>= 0.6'} + + micromatch@3.1.10: + resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==} + engines: {node: '>=0.10.0'} + + mime-db@1.42.0: + resolution: {integrity: sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.25: + resolution: {integrity: sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + minimatch@3.0.4: + resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} + + minimist@0.0.10: + resolution: {integrity: sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=} + + minimist@0.0.8: + resolution: {integrity: sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=} + + minimist@1.2.5: + resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} + + minipass@2.9.0: + resolution: {integrity: sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==} + + minizlib@1.3.3: + resolution: {integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==} + + mixin-deep@1.3.2: + resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} + engines: {node: '>=0.10.0'} + + mkdirp@0.5.1: + resolution: {integrity: sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + nan@2.14.0: + resolution: {integrity: sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==} + + nanomatch@1.2.13: + resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} + engines: {node: '>=0.10.0'} + + needle@2.8.0: + resolution: {integrity: sha512-ZTq6WYkN/3782H1393me3utVYdq2XyqNUFBsprEE3VMAT0+hP/cItpnITpqsY6ep2yeFE4Tqtqwc74VqUlUYtw==} + engines: {node: '>= 4.4.x'} + hasBin: true + + negotiator@0.6.2: + resolution: {integrity: sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==} + engines: {node: '>= 0.6'} + + netmask@1.0.6: + resolution: {integrity: sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=} + engines: {node: '>= 0.4.0'} + + node-pre-gyp@0.12.0: + resolution: {integrity: sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==} + deprecated: 'Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future' + hasBin: true + + node-uuid@1.4.8: + resolution: {integrity: sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=} + deprecated: Use uuid module instead + hasBin: true + + nodemailer-direct-transport@3.3.2: + resolution: {integrity: sha1-6W+vuQNYVglH5WkBfZfmBzilCoY=} + + nodemailer-fetch@1.6.0: + resolution: {integrity: sha1-ecSQihwPXzdbc/6IjamCj23JY6Q=} + + nodemailer-shared@1.1.0: + resolution: {integrity: sha1-z1mU4v0mjQD1zw+nZ6CBae2wfsA=} + + nodemailer-smtp-pool@2.8.2: + resolution: {integrity: sha1-LrlNbPhXgLG0clzoU7nL1ejajHI=} + + nodemailer-smtp-transport@2.7.2: + resolution: {integrity: sha1-A9ccdjFPFKx9vHvwM6am0W1n+3c=} + + nodemailer-wellknown@0.1.10: + resolution: {integrity: sha1-WG24EB2zDLRDjrVGc3pBqtDPE9U=} + + nodemailer@2.7.2: + resolution: {integrity: sha512-Jb4iapCeJ9nXmDurMyzg262u/wIVGRVkwr36oU0o8hL7U4w9n9FibMZGtPU2NN8GeBEAk0BvJCD/vJaCXF6+7A==} + engines: {node: '>=0.10.0'} + deprecated: All versions below 4.0.1 of Nodemailer are deprecated. See https://nodemailer.com/status/ + + nopt@4.0.3: + resolution: {integrity: sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==} + hasBin: true + + normalize-path@2.1.1: + resolution: {integrity: sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=} + engines: {node: '>=0.10.0'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-bundled@1.1.2: + resolution: {integrity: sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==} + + npm-normalize-package-bin@1.0.1: + resolution: {integrity: sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==} + + npm-packlist@1.4.8: + resolution: {integrity: sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==} + + npmlog@4.1.2: + resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==} + + number-is-nan@1.0.1: + resolution: {integrity: sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=} + engines: {node: '>=0.10.0'} + + oauth-sign@0.8.2: + resolution: {integrity: sha512-VlF07iu3VV3+BTXj43Nmp6Irt/G7j/NgEctUS6IweH1RGhURjjCc2NWtzXFPXXWWfc7hgbXQdtiQu2LGp6MxUg==} + + oauth-sign@0.9.0: + resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} + + object-assign@4.1.1: + resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} + engines: {node: '>=0.10.0'} + + object-component@0.0.3: + resolution: {integrity: sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=} + + object-copy@0.1.0: + resolution: {integrity: sha1-fn2Fi3gb18mRpBupde04EnVOmYw=} + engines: {node: '>=0.10.0'} + + object-visit@1.0.1: + resolution: {integrity: sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=} + engines: {node: '>=0.10.0'} + + object.pick@1.3.0: + resolution: {integrity: sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=} + engines: {node: '>=0.10.0'} + + on-finished@2.3.0: + resolution: {integrity: sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} + + optimist@0.6.1: + resolution: {integrity: sha1-2j6nRob6IaGaERwybpDrFaAZZoY=} + + optionator@0.8.3: + resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} + engines: {node: '>= 0.8.0'} + + os-homedir@1.0.2: + resolution: {integrity: sha1-/7xJiDNuDoM94MFox+8VISGqf7M=} + engines: {node: '>=0.10.0'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=} + engines: {node: '>=0.10.0'} + + osenv@0.1.5: + resolution: {integrity: sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==} + + pac-proxy-agent@3.0.1: + resolution: {integrity: sha512-44DUg21G/liUZ48dJpUSjZnFfZro/0K5JTyFYLBcmh9+T6Ooi4/i4efwUiEy0+4oQusCBqWdhv16XohIj1GqnQ==} + + pac-resolver@3.0.0: + resolution: {integrity: sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==} + + parseqs@0.0.5: + resolution: {integrity: sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=} + + parseuri@0.0.5: + resolution: {integrity: sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + pascalcase@0.1.1: + resolution: {integrity: sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=} + engines: {node: '>=0.10.0'} + + path-dirname@1.0.2: + resolution: {integrity: sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=} + + path-is-absolute@1.0.1: + resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} + engines: {node: '>=0.10.0'} + + path-proxy@1.0.0: + resolution: {integrity: sha1-GOijaFn8nS8aU7SN7hOFQ8Ag3l4=} + + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + + pinkie-promise@2.0.1: + resolution: {integrity: sha1-ITXW36ejWMBprJsXh3YogihFD/o=} + engines: {node: '>=0.10.0'} + + pinkie@2.0.4: + resolution: {integrity: sha1-clVrgM+g1IqXToDnckjoDtT3+HA=} + engines: {node: '>=0.10.0'} + + posix-character-classes@0.1.1: + resolution: {integrity: sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=} + engines: {node: '>=0.10.0'} + + prelude-ls@1.1.2: + resolution: {integrity: sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=} + engines: {node: '>= 0.8.0'} + + process-nextick-args@1.0.7: + resolution: {integrity: sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + promisify-call@2.0.4: + resolution: {integrity: sha1-1IwtRWUszM1SgB3ey9UzptS9X7o=} + engines: {node: '>=4.0'} + + proxy-agent@3.0.3: + resolution: {integrity: sha512-PXVVVuH9tiQuxQltFJVSnXWuDtNr+8aNBP6XVDDCDiUuDN8eRCm+ii4/mFWmXWEA0w8jjJSlePa4LXlM4jIzNA==} + engines: {node: '>=6'} + + proxy-from-env@1.0.0: + resolution: {integrity: sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=} + + pseudomap@1.0.2: + resolution: {integrity: sha1-8FKijacOYYkX7wqKw0wa5aaChrM=} + + psl@1.4.0: + resolution: {integrity: sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==} + + punycode@1.4.1: + resolution: {integrity: sha1-wNWmOycYgArY4esPpSachN1BhF4=} + + punycode@2.1.1: + resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + engines: {node: '>=6'} + + qjobs@1.2.0: + resolution: {integrity: sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==} + engines: {node: '>=0.9'} + + qs@6.2.3: + resolution: {integrity: sha512-AY4g8t3LMboim0t6XWFdz6J5OuJ1ZNYu54SXihS/OMpgyCqYmcAJnWqkNSOjSjWmq3xxy+GF9uWQI2lI/7tKIA==} + engines: {node: '>=0.6'} + + qs@6.5.2: + resolution: {integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==} + engines: {node: '>=0.6'} + + qs@6.7.0: + resolution: {integrity: sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==} + engines: {node: '>=0.6'} + + querystringify@2.1.1: + resolution: {integrity: sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.4.0: + resolution: {integrity: sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==} + engines: {node: '>= 0.8'} + + raw-body@2.4.1: + resolution: {integrity: sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==} + engines: {node: '>= 0.8'} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@1.1.14: + resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} + + readable-stream@2.0.6: + resolution: {integrity: sha1-j5A0HmilPMySh4jaz80Rs265t44=} + + readable-stream@2.3.6: + resolution: {integrity: sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==} + + readdirp@2.2.1: + resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==} + engines: {node: '>=0.10'} + + redis-commands@1.5.0: + resolution: {integrity: sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==} + + redis-parser@2.6.0: + resolution: {integrity: sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=} + engines: {node: '>=0.10.0'} + + redis@2.8.0: + resolution: {integrity: sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==} + engines: {node: '>=0.10.0'} + + regex-not@1.0.2: + resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} + engines: {node: '>=0.10.0'} + + remove-trailing-separator@1.1.0: + resolution: {integrity: sha1-wkvOKig62tW8P1jg1IJJuSN52O8=} + + repeat-element@1.1.3: + resolution: {integrity: sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==} + engines: {node: '>=0.10.0'} + + repeat-string@0.2.2: + resolution: {integrity: sha1-x6jTI2BoNiBZp+RlH8aITosftK4=} + engines: {node: '>=0.10'} + + repeat-string@1.6.1: + resolution: {integrity: sha1-jcrkcOHIirwtYA//Sndihtp15jc=} + engines: {node: '>=0.10'} + + request@2.75.0: + resolution: {integrity: sha512-uNXre8CefDRFBhfB1bL0CkKBD+5E1xmx69KMjl7p+bBc0vesXLQMS+iwsI2pKRlYZOOtLzkeBfz7jItKA3XlKQ==} + engines: {node: '>=0.8.0'} + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + + request@2.88.0: + resolution: {integrity: sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==} + engines: {node: '>= 4'} + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + + requestretry@1.13.0: + resolution: {integrity: sha512-Lmh9qMvnQXADGAQxsXHP4rbgO6pffCfuR8XUBdP9aitJcLQJxhp7YZK4xAVYXnPJ5E52mwrfiKQtKonPL8xsmg==} + + requires-port@1.0.0: + resolution: {integrity: sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=} + + resolve-url@0.2.1: + resolution: {integrity: sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=} + + ret@0.1.15: + resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} + engines: {node: '>=0.12'} + + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + hasBin: true + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.0: + resolution: {integrity: sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==} + + safe-regex@1.1.0: + resolution: {integrity: sha1-QKNmnzsHfR6UPURinhV91IAjvy4=} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sax@1.2.4: + resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} + + semver@5.5.1: + resolution: {integrity: sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==} + hasBin: true + + semver@5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + + set-blocking@2.0.0: + resolution: {integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc=} + + set-value@2.0.1: + resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} + engines: {node: '>=0.10.0'} + + setprototypeof@1.1.1: + resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==} + + signal-exit@3.0.3: + resolution: {integrity: sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==} + + slack-node@0.2.0: + resolution: {integrity: sha512-78HdL2e5ywYk76xyWk8L6bni6i7ZnHz4eVu7EP8nAxsMb9O0zuSCNw76Cfw5TDVLm/Qq7Fy+5AAreU8BZBEpuw==} + + smart-buffer@1.1.15: + resolution: {integrity: sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=} + engines: {node: '>= 0.10.15', npm: '>= 1.3.5'} + + smart-buffer@4.1.0: + resolution: {integrity: sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + smtp-connection@2.12.0: + resolution: {integrity: sha1-1275EnyyPCJZ7bHoNJwujV4tdME=} + + snapdragon-node@2.1.1: + resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} + engines: {node: '>=0.10.0'} + + snapdragon-util@3.0.1: + resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==} + engines: {node: '>=0.10.0'} + + snapdragon@0.8.2: + resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} + engines: {node: '>=0.10.0'} + + sntp@1.0.9: + resolution: {integrity: sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=} + engines: {node: '>=0.8.0'} + deprecated: This module moved to @hapi/sntp. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues. + + socket.io-adapter@1.1.1: + resolution: {integrity: sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=} + + socket.io-client@2.0.4: + resolution: {integrity: sha1-CRilUkBtxeVAs4Dc2Xr8SmQzL44=} + + socket.io-parser@3.1.3: + resolution: {integrity: sha512-g0a2HPqLguqAczs3dMECuA1RgoGFPyvDqcbaDEdCWY9g59kdUAz3YRmaJBNKXflrHNwB7Q12Gkf/0CZXfdHR7g==} + + socket.io@2.0.4: + resolution: {integrity: sha1-waRZDO/4fs8TxyZS8Eb3FrKeYBQ=} + + socks-proxy-agent@4.0.2: + resolution: {integrity: sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==} + engines: {node: '>= 6'} + + socks@1.1.9: + resolution: {integrity: sha1-Yo1+TQSRJDVEWsC25Fk3bLPm1pE=} + engines: {node: '>= 0.10.0', npm: '>= 1.3.5'} + deprecated: If using 2.x branch, please upgrade to at least 2.1.6 to avoid a serious bug with socket data flow and an import issue introduced in 2.1.0 + + socks@2.3.3: + resolution: {integrity: sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + source-map-resolve@0.5.2: + resolution: {integrity: sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==} + + source-map-url@0.4.0: + resolution: {integrity: sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=} + + source-map@0.5.7: + resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + split-string@3.1.0: + resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} + engines: {node: '>=0.10.0'} + + sshpk@1.16.1: + resolution: {integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==} + engines: {node: '>=0.10.0'} + hasBin: true + + static-extend@0.1.2: + resolution: {integrity: sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=} + engines: {node: '>=0.10.0'} + + statuses@1.5.0: + resolution: {integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=} + engines: {node: '>= 0.6'} + + streamroller@0.7.0: + resolution: {integrity: sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==} + engines: {node: '>=0.12.0'} + + string-width@1.0.2: + resolution: {integrity: sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=} + engines: {node: '>=0.10.0'} + + string_decoder@0.10.31: + resolution: {integrity: sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + stringstream@0.0.6: + resolution: {integrity: sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==} + + strip-ansi@3.0.1: + resolution: {integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=} + engines: {node: '>=0.10.0'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo=} + engines: {node: '>=0.10.0'} + + supports-color@2.0.0: + resolution: {integrity: sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=} + engines: {node: '>=0.8.0'} + + sync-exec@0.6.2: + resolution: {integrity: sha512-FHup6L3hMWn+2asiIC/7kj/3CaMM8aAAKPx62DRk42hQkz4H2yBADR0OnnY8Eh5Bxrzb371aPUfnW4WzAUYItQ==} + + tar@4.4.15: + resolution: {integrity: sha512-ItbufpujXkry7bHH9NpQyTXPbJ72iTlXgkBAYsAjDXk3Ds8t/3NfO5P4xZGy7u+sYuQUbimgzswX4uQIEeNVOA==} + engines: {node: '>=4.5'} + + thunkify@2.1.2: + resolution: {integrity: sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=} + + timespan@2.3.0: + resolution: {integrity: sha1-SQLOBAvRPYRcj1myfp1ZutbzmSk=} + engines: {node: '>= 0.2.0'} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + to-array@0.1.4: + resolution: {integrity: sha1-F+bBH3PdTz10zaek/zI46a2b+JA=} + + to-object-path@0.3.0: + resolution: {integrity: sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=} + engines: {node: '>=0.10.0'} + + to-regex-range@2.1.1: + resolution: {integrity: sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=} + engines: {node: '>=0.10.0'} + + to-regex@3.0.2: + resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} + engines: {node: '>=0.10.0'} + + toidentifier@1.0.0: + resolution: {integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==} + engines: {node: '>=0.6'} + + tough-cookie@2.3.4: + resolution: {integrity: sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==} + engines: {node: '>=0.8'} + + tough-cookie@2.4.3: + resolution: {integrity: sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==} + engines: {node: '>=0.8'} + + tsscmp@1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + + tunnel-agent@0.4.3: + resolution: {integrity: sha512-e0IoVDWx8SDHc/hwFTqJDQ7CCDTEeGhmcT9jkWJjoGQSpgBz20nAMr80E3Tpk7PatJ1b37DQDgJR3CNSzcMOZQ==} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + + type-check@0.3.2: + resolution: {integrity: sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=} + engines: {node: '>= 0.8.0'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + ultron@1.1.1: + resolution: {integrity: sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==} + + underscore@1.7.0: + resolution: {integrity: sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=} + + union-value@1.0.1: + resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} + engines: {node: '>=0.10.0'} + + unpipe@1.0.0: + resolution: {integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=} + engines: {node: '>= 0.8'} + + unset-value@1.0.0: + resolution: {integrity: sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=} + engines: {node: '>=0.10.0'} + + upath@1.2.0: + resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} + engines: {node: '>=4'} + + uri-js@4.2.2: + resolution: {integrity: sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==} + + urix@0.1.0: + resolution: {integrity: sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=} + + url-parse@1.4.7: + resolution: {integrity: sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==} + + use@3.1.1: + resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} + engines: {node: '>=0.10.0'} + + useragent@2.2.1: + resolution: {integrity: sha1-z1k+9PLRdYdei7ZY6pLhik/QbY4=} + + util-deprecate@1.0.2: + resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} + + utils-merge@1.0.1: + resolution: {integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=} + engines: {node: '>= 0.4.0'} + + uuid@3.3.3: + resolution: {integrity: sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + + uws@9.14.0: + resolution: {integrity: sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg==} + engines: {node: '>=4'} + deprecated: New code is available at github.com/uNetworking/uWebSockets.js + + verror@1.10.0: + resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} + engines: {'0': node >=0.6.0} + + void-elements@2.0.1: + resolution: {integrity: sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=} + engines: {node: '>=0.10.0'} + + when@3.7.8: + resolution: {integrity: sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=} + + wide-align@1.1.3: + resolution: {integrity: sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==} + + with-callback@1.0.2: + resolution: {integrity: sha1-oJYpuakgAo1yFAT7Q1vc/1yRvCE=} + engines: {node: '>=4'} + + word-wrap@1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} + + wordwrap@0.0.3: + resolution: {integrity: sha1-o9XabNXAvAAI03I0u68b7WMFkQc=} + engines: {node: '>=0.4.0'} + + wrappy@1.0.2: + resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} + + ws@3.3.3: + resolution: {integrity: sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xmlhttprequest-ssl@1.5.5: + resolution: {integrity: sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=} + engines: {node: '>=0.4.0'} + + xregexp@2.0.0: + resolution: {integrity: sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + yallist@2.1.2: + resolution: {integrity: sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yamlparser@0.0.2: + resolution: {integrity: sha512-Cou9FCGblEENtn1/8La5wkDM/ISMh2bzu5Wh7dYzCzA0o9jD4YGyLkUJxe84oPBGoB92f+Oy4ZjVhA8S0C2wlQ==} + + yeast@0.1.2: + resolution: {integrity: sha1-AI4G2AlDIMNy28L47XagymyKxBk=} + +snapshots: + + abbrev@1.1.1: + optional: true + + accepts@1.3.7: + dependencies: + mime-types: 2.1.25 + negotiator: 0.6.2 + + addressparser@1.0.1: + optional: true + + after@0.8.2: {} + + agent-base@4.2.1: + dependencies: + es6-promisify: 5.0.0 + optional: true + + agent-base@4.3.0: + dependencies: + es6-promisify: 5.0.0 + optional: true + + ajv@6.10.2: + dependencies: + fast-deep-equal: 2.0.1 + fast-json-stable-stringify: 2.0.0 + json-schema-traverse: 0.4.1 + uri-js: 4.2.2 + + amqplib@0.5.5: + dependencies: + bitsyntax: 0.1.0 + bluebird: 3.7.1 + buffer-more-ints: 1.0.0 + readable-stream: 1.1.14 + safe-buffer: 5.1.2 + url-parse: 1.4.7 + transitivePeerDependencies: + - supports-color + optional: true + + ansi-regex@2.1.1: + optional: true + + ansi-styles@2.2.1: + optional: true + + anymatch@2.0.0: + dependencies: + micromatch: 3.1.10 + normalize-path: 2.1.1 + transitivePeerDependencies: + - supports-color + + aproba@1.2.0: + optional: true + + are-we-there-yet@1.1.5: + dependencies: + delegates: 1.0.0 + readable-stream: 2.3.6 + optional: true + + arr-diff@4.0.0: {} + + arr-flatten@1.1.0: {} + + arr-union@3.1.0: {} + + array-slice@0.2.3: {} + + array-unique@0.2.1: {} + + array-unique@0.3.2: {} + + arraybuffer.slice@0.0.7: {} + + asn1@0.2.4: + dependencies: + safer-buffer: 2.1.2 + + assert-plus@0.2.0: + optional: true + + assert-plus@1.0.0: {} + + assign-symbols@1.0.0: {} + + ast-types@0.13.2: + optional: true + + async-each@1.0.3: {} + + async-limiter@1.0.1: {} + + async@2.6.3: + dependencies: + lodash: 4.17.15 + optional: true + + asynckit@0.4.0: {} + + atob@2.1.2: {} + + aws-sign2@0.6.0: + optional: true + + aws-sign2@0.7.0: {} + + aws4@1.8.0: {} + + axios@0.15.3: + dependencies: + follow-redirects: 1.0.0 + transitivePeerDependencies: + - supports-color + + backo2@1.0.2: {} + + balanced-match@1.0.0: {} + + base64-arraybuffer@0.1.5: {} + + base64id@1.0.0: {} + + base@0.11.2: + dependencies: + cache-base: 1.0.1 + class-utils: 0.3.6 + component-emitter: 1.3.0 + define-property: 1.0.0 + isobject: 3.0.1 + mixin-deep: 1.3.2 + pascalcase: 0.1.1 + + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + + better-assert@1.0.2: + dependencies: + callsite: 1.0.0 + + binary-extensions@1.13.1: {} + + bitsyntax@0.1.0: + dependencies: + buffer-more-ints: 1.0.0 + debug: 2.6.9 + safe-buffer: 5.1.2 + transitivePeerDependencies: + - supports-color + optional: true + + bl@1.1.2: + dependencies: + readable-stream: 2.0.6 + optional: true + + blob@0.0.5: {} + + bluebird@3.7.1: {} + + body-parser@1.19.0: + dependencies: + bytes: 3.1.0 + content-type: 1.0.4 + debug: 2.6.9 + depd: 1.1.2 + http-errors: 1.7.2 + iconv-lite: 0.4.24 + on-finished: 2.3.0 + qs: 6.7.0 + raw-body: 2.4.0 + type-is: 1.6.18 + transitivePeerDependencies: + - supports-color + + boom@2.10.1: + dependencies: + hoek: 2.16.3 + optional: true + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.0 + concat-map: 0.0.1 + + braces@0.1.5: + dependencies: + expand-range: 0.1.1 + + braces@2.3.2: + dependencies: + arr-flatten: 1.1.0 + array-unique: 0.3.2 + extend-shallow: 2.0.1 + fill-range: 4.0.0 + isobject: 3.0.1 + repeat-element: 1.1.3 + snapdragon: 0.8.2 + snapdragon-node: 2.1.1 + split-string: 3.1.0 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + buffer-alloc-unsafe@1.1.0: {} + + buffer-alloc@1.2.0: + dependencies: + buffer-alloc-unsafe: 1.1.0 + buffer-fill: 1.0.0 + + buffer-fill@1.0.0: {} + + buffer-more-ints@1.0.0: + optional: true + + buildmail@4.0.1: + dependencies: + addressparser: 1.0.1 + libbase64: 0.1.0 + libmime: 3.0.0 + libqp: 1.1.0 + nodemailer-fetch: 1.6.0 + nodemailer-shared: 1.1.0 + punycode: 1.4.1 + optional: true + + bytes@3.1.0: {} + + cache-base@1.0.1: + dependencies: + collection-visit: 1.0.0 + component-emitter: 1.3.0 + get-value: 2.0.6 + has-value: 1.0.0 + isobject: 3.0.1 + set-value: 2.0.1 + to-object-path: 0.3.0 + union-value: 1.0.1 + unset-value: 1.0.0 + + callsite@1.0.0: {} + + caseless@0.11.0: + optional: true + + caseless@0.12.0: {} + + chalk@1.1.3: + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 2.0.0 + strip-ansi: 3.0.1 + supports-color: 2.0.0 + optional: true + + chokidar@2.1.8: + dependencies: + anymatch: 2.0.0 + async-each: 1.0.3 + braces: 2.3.2 + glob-parent: 3.1.0 + inherits: 2.0.4 + is-binary-path: 1.0.1 + is-glob: 4.0.1 + normalize-path: 3.0.0 + path-is-absolute: 1.0.1 + readdirp: 2.2.1 + upath: 1.2.0 + optionalDependencies: + fsevents: 1.2.9 + transitivePeerDependencies: + - supports-color + + chownr@1.1.4: + optional: true + + circular-json@0.5.9: {} + + class-utils@0.3.6: + dependencies: + arr-union: 3.1.0 + define-property: 0.2.5 + isobject: 3.0.1 + static-extend: 0.1.2 + + co@4.6.0: + optional: true + + code-point-at@1.1.0: + optional: true + + collection-visit@1.0.0: + dependencies: + map-visit: 1.0.0 + object-visit: 1.0.1 + + colors@1.4.0: {} + + combine-lists@1.0.1: + dependencies: + lodash: 4.17.15 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@2.20.3: + optional: true + + component-bind@1.0.0: {} + + component-emitter@1.2.1: {} + + component-emitter@1.3.0: {} + + component-inherit@0.0.3: {} + + concat-map@0.0.1: {} + + connect@3.7.0: + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + + console-control-strings@1.1.0: + optional: true + + content-type@1.0.4: {} + + cookie@0.3.1: {} + + copy-descriptor@0.1.1: {} + + core-js@2.6.10: {} + + core-util-is@1.0.2: {} + + cryptiles@2.0.5: + dependencies: + boom: 2.10.1 + optional: true + + custom-event@1.0.1: {} + + dashdash@1.14.1: + dependencies: + assert-plus: 1.0.0 + + data-uri-to-buffer@1.2.0: + optional: true + + date-format@1.2.0: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@3.1.0: + dependencies: + ms: 2.0.0 + + debug@3.2.6: + dependencies: + ms: 2.1.2 + + debug@4.1.1: + dependencies: + ms: 2.1.2 + optional: true + + decode-uri-component@0.2.0: {} + + deep-extend@0.6.0: + optional: true + + deep-is@0.1.3: + optional: true + + define-property@0.2.5: + dependencies: + is-descriptor: 0.1.6 + + define-property@1.0.0: + dependencies: + is-descriptor: 1.0.2 + + define-property@2.0.2: + dependencies: + is-descriptor: 1.0.2 + isobject: 3.0.1 + + degenerator@1.0.4: + dependencies: + ast-types: 0.13.2 + escodegen: 1.12.0 + esprima: 3.1.3 + optional: true + + delayed-stream@1.0.0: {} + + delegates@1.0.0: + optional: true + + depd@1.1.2: {} + + detect-libc@1.0.3: + optional: true + + di@0.0.1: {} + + dom-serialize@2.2.1: + dependencies: + custom-event: 1.0.1 + ent: 2.2.0 + extend: 3.0.2 + void-elements: 2.0.1 + + double-ended-queue@2.1.0-0: + optional: true + + ecc-jsbn@0.1.2: + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + + ee-first@1.1.1: {} + + encodeurl@1.0.2: {} + + engine.io-client@3.1.6: + dependencies: + component-emitter: 1.2.1 + component-inherit: 0.0.3 + debug: 3.1.0 + engine.io-parser: 2.1.3 + has-cors: 1.1.0 + indexof: 0.0.1 + parseqs: 0.0.5 + parseuri: 0.0.5 + ws: 3.3.3 + xmlhttprequest-ssl: 1.5.5 + yeast: 0.1.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + engine.io-parser@2.1.3: + dependencies: + after: 0.8.2 + arraybuffer.slice: 0.0.7 + base64-arraybuffer: 0.1.5 + blob: 0.0.5 + has-binary2: 1.0.3 + + engine.io@3.1.5: + dependencies: + accepts: 1.3.7 + base64id: 1.0.0 + cookie: 0.3.1 + debug: 3.1.0 + engine.io-parser: 2.1.3 + ws: 3.3.3 + optionalDependencies: + uws: 9.14.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + ent@2.2.0: {} + + es6-promise@4.2.8: + optional: true + + es6-promisify@5.0.0: + dependencies: + es6-promise: 4.2.8 + optional: true + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: + optional: true + + escodegen@1.12.0: + dependencies: + esprima: 3.1.3 + estraverse: 4.3.0 + esutils: 2.0.3 + optionator: 0.8.3 + optionalDependencies: + source-map: 0.6.1 + optional: true + + esprima@3.1.3: + optional: true + + estraverse@4.3.0: + optional: true + + esutils@2.0.3: + optional: true + + eventemitter3@4.0.0: {} + + expand-braces@0.1.2: + dependencies: + array-slice: 0.2.3 + array-unique: 0.2.1 + braces: 0.1.5 + + expand-brackets@2.1.4: + dependencies: + debug: 2.6.9 + define-property: 0.2.5 + extend-shallow: 2.0.1 + posix-character-classes: 0.1.1 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + expand-range@0.1.1: + dependencies: + is-number: 0.1.1 + repeat-string: 0.2.2 + + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + + extend-shallow@3.0.2: + dependencies: + assign-symbols: 1.0.0 + is-extendable: 1.0.1 + + extend@3.0.2: {} + + extglob@2.0.4: + dependencies: + array-unique: 0.3.2 + define-property: 1.0.0 + expand-brackets: 2.1.4 + extend-shallow: 2.0.1 + fragment-cache: 0.2.1 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + extsprintf@1.3.0: {} + + fast-deep-equal@2.0.1: {} + + fast-json-stable-stringify@2.0.0: {} + + fast-levenshtein@2.0.6: + optional: true + + file-uri-to-path@1.0.0: + optional: true + + fill-range@4.0.0: + dependencies: + extend-shallow: 2.0.1 + is-number: 3.0.0 + repeat-string: 1.6.1 + to-regex-range: 2.1.1 + + finalhandler@1.1.2: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + follow-redirects@1.0.0: + dependencies: + debug: 2.6.9 + transitivePeerDependencies: + - supports-color + + follow-redirects@1.9.0: + dependencies: + debug: 3.2.6 + transitivePeerDependencies: + - supports-color + + for-in@1.0.2: {} + + forever-agent@0.6.1: {} + + form-data@2.0.0: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.25 + optional: true + + form-data@2.3.3: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.25 + + fragment-cache@0.2.1: + dependencies: + map-cache: 0.2.2 + + fs-minipass@1.2.7: + dependencies: + minipass: 2.9.0 + optional: true + + fs.realpath@1.0.0: {} + + fsevents@1.2.9: + dependencies: + nan: 2.14.0 + node-pre-gyp: 0.12.0 + optional: true + + ftp@0.3.10: + dependencies: + readable-stream: 1.1.14 + xregexp: 2.0.0 + optional: true + + gauge@2.7.4: + dependencies: + aproba: 1.2.0 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.3 + string-width: 1.0.2 + strip-ansi: 3.0.1 + wide-align: 1.1.3 + optional: true + + generate-function@2.3.1: + dependencies: + is-property: 1.0.2 + optional: true + + generate-object-property@1.2.0: + dependencies: + is-property: 1.0.2 + optional: true + + get-uri@2.0.4: + dependencies: + data-uri-to-buffer: 1.2.0 + debug: 2.6.9 + extend: 3.0.2 + file-uri-to-path: 1.0.0 + ftp: 0.3.10 + readable-stream: 2.3.6 + transitivePeerDependencies: + - supports-color + optional: true + + get-value@2.0.6: {} + + getpass@0.1.7: + dependencies: + assert-plus: 1.0.0 + + glob-parent@3.1.0: + dependencies: + is-glob: 3.1.0 + path-dirname: 1.0.2 + + glob@7.1.6: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.0.4 + once: 1.4.0 + path-is-absolute: 1.0.1 + + graceful-fs@4.2.3: {} + + har-schema@2.0.0: {} + + har-validator@2.0.6: + dependencies: + chalk: 1.1.3 + commander: 2.20.3 + is-my-json-valid: 2.20.0 + pinkie-promise: 2.0.1 + optional: true + + har-validator@5.1.3: + dependencies: + ajv: 6.10.2 + har-schema: 2.0.0 + + has-ansi@2.0.0: + dependencies: + ansi-regex: 2.1.1 + optional: true + + has-binary2@1.0.3: + dependencies: + isarray: 2.0.1 + + has-cors@1.1.0: {} + + has-unicode@2.0.1: + optional: true + + has-value@0.3.1: + dependencies: + get-value: 2.0.6 + has-values: 0.1.4 + isobject: 2.1.0 + + has-value@1.0.0: + dependencies: + get-value: 2.0.6 + has-values: 1.0.0 + isobject: 3.0.1 + + has-values@0.1.4: {} + + has-values@1.0.0: + dependencies: + is-number: 3.0.0 + kind-of: 4.0.0 + + hawk@3.1.3: + dependencies: + boom: 2.10.1 + cryptiles: 2.0.5 + hoek: 2.16.3 + sntp: 1.0.9 + optional: true + + hipchat-notifier@1.1.0: + dependencies: + lodash: 4.17.15 + request: 2.88.0 + optional: true + + hoek@2.16.3: + optional: true + + http-errors@1.7.2: + dependencies: + depd: 1.1.2 + inherits: 2.0.3 + setprototypeof: 1.1.1 + statuses: 1.5.0 + toidentifier: 1.0.0 + + http-errors@1.7.3: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.1.1 + statuses: 1.5.0 + toidentifier: 1.0.0 + optional: true + + http-proxy-agent@2.1.0: + dependencies: + agent-base: 4.3.0 + debug: 3.1.0 + transitivePeerDependencies: + - supports-color + optional: true + + http-proxy@1.18.0: + dependencies: + eventemitter3: 4.0.0 + follow-redirects: 1.9.0 + requires-port: 1.0.0 + transitivePeerDependencies: + - supports-color + + http-signature@1.1.1: + dependencies: + assert-plus: 0.2.0 + jsprim: 1.4.1 + sshpk: 1.16.1 + optional: true + + http-signature@1.2.0: + dependencies: + assert-plus: 1.0.0 + jsprim: 1.4.1 + sshpk: 1.16.1 + + httpntlm@1.6.1: + dependencies: + httpreq: 0.4.24 + underscore: 1.7.0 + optional: true + + httpreq@0.4.24: + optional: true + + https-proxy-agent@2.2.4: + dependencies: + agent-base: 4.3.0 + debug: 3.2.6 + transitivePeerDependencies: + - supports-color + optional: true + + https-proxy-agent@3.0.1: + dependencies: + agent-base: 4.3.0 + debug: 3.2.6 + transitivePeerDependencies: + - supports-color + optional: true + + iconv-lite@0.4.15: + optional: true + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ignore-walk@3.0.4: + dependencies: + minimatch: 3.0.4 + optional: true + + indexof@0.0.1: {} + + inflection@1.12.0: + optional: true + + inflection@1.3.8: + optional: true + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.3: {} + + inherits@2.0.4: {} + + ini@1.3.8: + optional: true + + ip@1.1.5: + optional: true + + is-accessor-descriptor@0.1.6: + dependencies: + kind-of: 3.2.2 + + is-accessor-descriptor@1.0.0: + dependencies: + kind-of: 6.0.2 + + is-binary-path@1.0.1: + dependencies: + binary-extensions: 1.13.1 + + is-buffer@1.1.6: {} + + is-data-descriptor@0.1.4: + dependencies: + kind-of: 3.2.2 + + is-data-descriptor@1.0.0: + dependencies: + kind-of: 6.0.2 + + is-descriptor@0.1.6: + dependencies: + is-accessor-descriptor: 0.1.6 + is-data-descriptor: 0.1.4 + kind-of: 5.1.0 + + is-descriptor@1.0.2: + dependencies: + is-accessor-descriptor: 1.0.0 + is-data-descriptor: 1.0.0 + kind-of: 6.0.2 + + is-extendable@0.1.1: {} + + is-extendable@1.0.1: + dependencies: + is-plain-object: 2.0.4 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@1.0.0: + dependencies: + number-is-nan: 1.0.1 + optional: true + + is-glob@3.1.0: + dependencies: + is-extglob: 2.1.1 + + is-glob@4.0.1: + dependencies: + is-extglob: 2.1.1 + + is-my-ip-valid@1.0.0: + optional: true + + is-my-json-valid@2.20.0: + dependencies: + generate-function: 2.3.1 + generate-object-property: 1.2.0 + is-my-ip-valid: 1.0.0 + jsonpointer: 4.0.1 + xtend: 4.0.2 + optional: true + + is-number@0.1.1: {} + + is-number@3.0.0: + dependencies: + kind-of: 3.2.2 + + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-property@1.0.2: + optional: true + + is-stream@1.1.0: + optional: true + + is-typedarray@1.0.0: {} + + is-windows@1.0.2: {} + + isarray@0.0.1: + optional: true + + isarray@1.0.0: {} + + isarray@2.0.1: {} + + isbinaryfile@3.0.3: + dependencies: + buffer-alloc: 1.2.0 + + isobject@2.1.0: + dependencies: + isarray: 1.0.0 + + isobject@3.0.1: {} + + isstream@0.1.2: {} + + jsbn@0.1.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema@0.2.3: {} + + json-stringify-safe@5.0.1: {} + + jsonpointer@4.0.1: + optional: true + + jsprim@1.4.1: + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.2.3 + verror: 1.10.0 + + karma@2.0.5: + dependencies: + bluebird: 3.7.1 + body-parser: 1.19.0 + chokidar: 2.1.8 + colors: 1.4.0 + combine-lists: 1.0.1 + connect: 3.7.0 + core-js: 2.6.10 + di: 0.0.1 + dom-serialize: 2.2.1 + expand-braces: 0.1.2 + glob: 7.1.6 + graceful-fs: 4.2.3 + http-proxy: 1.18.0 + isbinaryfile: 3.0.3 + lodash: 4.17.15 + log4js: 2.11.0 + mime: 1.6.0 + minimatch: 3.0.4 + optimist: 0.6.1 + qjobs: 1.2.0 + range-parser: 1.2.1 + rimraf: 2.7.1 + safe-buffer: 5.2.0 + socket.io: 2.0.4 + source-map: 0.6.1 + tmp: 0.0.33 + useragent: 2.2.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + kind-of@3.2.2: + dependencies: + is-buffer: 1.1.6 + + kind-of@4.0.0: + dependencies: + is-buffer: 1.1.6 + + kind-of@5.1.0: {} + + kind-of@6.0.2: {} + + levn@0.3.0: + dependencies: + prelude-ls: 1.1.2 + type-check: 0.3.2 + optional: true + + libbase64@0.1.0: + optional: true + + libmime@3.0.0: + dependencies: + iconv-lite: 0.4.15 + libbase64: 0.1.0 + libqp: 1.1.0 + optional: true + + libqp@1.1.0: + optional: true + + lodash@4.17.15: {} + + log4js@2.11.0: + dependencies: + circular-json: 0.5.9 + date-format: 1.2.0 + debug: 3.2.6 + semver: 5.7.1 + streamroller: 0.7.0 + optionalDependencies: + amqplib: 0.5.5 + axios: 0.15.3 + hipchat-notifier: 1.1.0 + loggly: 1.1.1 + mailgun-js: 0.18.1 + nodemailer: 2.7.2 + redis: 2.8.0 + slack-node: 0.2.0 + transitivePeerDependencies: + - supports-color + + loggly@1.1.1: + dependencies: + json-stringify-safe: 5.0.1 + request: 2.75.0 + timespan: 2.3.0 + optional: true + + lru-cache@2.2.4: {} + + lru-cache@4.1.5: + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + optional: true + + mailcomposer@4.0.1: + dependencies: + buildmail: 4.0.1 + libmime: 3.0.0 + optional: true + + mailgun-js@0.18.1: + dependencies: + async: 2.6.3 + debug: 3.1.0 + form-data: 2.3.3 + inflection: 1.12.0 + is-stream: 1.1.0 + path-proxy: 1.0.0 + promisify-call: 2.0.4 + proxy-agent: 3.0.3 + tsscmp: 1.0.6 + transitivePeerDependencies: + - supports-color + optional: true + + map-cache@0.2.2: {} + + map-visit@1.0.0: + dependencies: + object-visit: 1.0.1 + + media-typer@0.3.0: {} + + micromatch@3.1.10: + dependencies: + arr-diff: 4.0.0 + array-unique: 0.3.2 + braces: 2.3.2 + define-property: 2.0.2 + extend-shallow: 3.0.2 + extglob: 2.0.4 + fragment-cache: 0.2.1 + kind-of: 6.0.2 + nanomatch: 1.2.13 + object.pick: 1.3.0 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + mime-db@1.42.0: {} + + mime-types@2.1.25: + dependencies: + mime-db: 1.42.0 + + mime@1.6.0: {} + + minimatch@3.0.4: + dependencies: + brace-expansion: 1.1.11 + + minimist@0.0.10: {} + + minimist@0.0.8: {} + + minimist@1.2.5: + optional: true + + minipass@2.9.0: + dependencies: + safe-buffer: 5.2.0 + yallist: 3.1.1 + optional: true + + minizlib@1.3.3: + dependencies: + minipass: 2.9.0 + optional: true + + mixin-deep@1.3.2: + dependencies: + for-in: 1.0.2 + is-extendable: 1.0.1 + + mkdirp@0.5.1: + dependencies: + minimist: 0.0.8 + + ms@2.0.0: {} + + ms@2.1.2: {} + + nan@2.14.0: + optional: true + + nanomatch@1.2.13: + dependencies: + arr-diff: 4.0.0 + array-unique: 0.3.2 + define-property: 2.0.2 + extend-shallow: 3.0.2 + fragment-cache: 0.2.1 + is-windows: 1.0.2 + kind-of: 6.0.2 + object.pick: 1.3.0 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + needle@2.8.0: + dependencies: + debug: 3.2.6 + iconv-lite: 0.4.24 + sax: 1.2.4 + optional: true + + negotiator@0.6.2: {} + + netmask@1.0.6: + optional: true + + node-pre-gyp@0.12.0: + dependencies: + detect-libc: 1.0.3 + mkdirp: 0.5.1 + needle: 2.8.0 + nopt: 4.0.3 + npm-packlist: 1.4.8 + npmlog: 4.1.2 + rc: 1.2.8 + rimraf: 2.7.1 + semver: 5.7.1 + tar: 4.4.15 + optional: true + + node-uuid@1.4.8: + optional: true + + nodemailer-direct-transport@3.3.2: + dependencies: + nodemailer-shared: 1.1.0 + smtp-connection: 2.12.0 + optional: true + + nodemailer-fetch@1.6.0: + optional: true + + nodemailer-shared@1.1.0: + dependencies: + nodemailer-fetch: 1.6.0 + optional: true + + nodemailer-smtp-pool@2.8.2: + dependencies: + nodemailer-shared: 1.1.0 + nodemailer-wellknown: 0.1.10 + smtp-connection: 2.12.0 + optional: true + + nodemailer-smtp-transport@2.7.2: + dependencies: + nodemailer-shared: 1.1.0 + nodemailer-wellknown: 0.1.10 + smtp-connection: 2.12.0 + optional: true + + nodemailer-wellknown@0.1.10: + optional: true + + nodemailer@2.7.2: + dependencies: + libmime: 3.0.0 + mailcomposer: 4.0.1 + nodemailer-direct-transport: 3.3.2 + nodemailer-shared: 1.1.0 + nodemailer-smtp-pool: 2.8.2 + nodemailer-smtp-transport: 2.7.2 + socks: 1.1.9 + optional: true + + nopt@4.0.3: + dependencies: + abbrev: 1.1.1 + osenv: 0.1.5 + optional: true + + normalize-path@2.1.1: + dependencies: + remove-trailing-separator: 1.1.0 + + normalize-path@3.0.0: {} + + npm-bundled@1.1.2: + dependencies: + npm-normalize-package-bin: 1.0.1 + optional: true + + npm-normalize-package-bin@1.0.1: + optional: true + + npm-packlist@1.4.8: + dependencies: + ignore-walk: 3.0.4 + npm-bundled: 1.1.2 + npm-normalize-package-bin: 1.0.1 + optional: true + + npmlog@4.1.2: + dependencies: + are-we-there-yet: 1.1.5 + console-control-strings: 1.1.0 + gauge: 2.7.4 + set-blocking: 2.0.0 + optional: true + + number-is-nan@1.0.1: + optional: true + + oauth-sign@0.8.2: + optional: true + + oauth-sign@0.9.0: {} + + object-assign@4.1.1: + optional: true + + object-component@0.0.3: {} + + object-copy@0.1.0: + dependencies: + copy-descriptor: 0.1.1 + define-property: 0.2.5 + kind-of: 3.2.2 + + object-visit@1.0.1: + dependencies: + isobject: 3.0.1 + + object.pick@1.3.0: + dependencies: + isobject: 3.0.1 + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + optimist@0.6.1: + dependencies: + minimist: 0.0.10 + wordwrap: 0.0.3 + + optionator@0.8.3: + dependencies: + deep-is: 0.1.3 + fast-levenshtein: 2.0.6 + levn: 0.3.0 + prelude-ls: 1.1.2 + type-check: 0.3.2 + word-wrap: 1.2.3 + optional: true + + os-homedir@1.0.2: + optional: true + + os-tmpdir@1.0.2: {} + + osenv@0.1.5: + dependencies: + os-homedir: 1.0.2 + os-tmpdir: 1.0.2 + optional: true + + pac-proxy-agent@3.0.1: + dependencies: + agent-base: 4.3.0 + debug: 4.1.1 + get-uri: 2.0.4 + http-proxy-agent: 2.1.0 + https-proxy-agent: 3.0.1 + pac-resolver: 3.0.0 + raw-body: 2.4.1 + socks-proxy-agent: 4.0.2 + transitivePeerDependencies: + - supports-color + optional: true + + pac-resolver@3.0.0: + dependencies: + co: 4.6.0 + degenerator: 1.0.4 + ip: 1.1.5 + netmask: 1.0.6 + thunkify: 2.1.2 + optional: true + + parseqs@0.0.5: + dependencies: + better-assert: 1.0.2 + + parseuri@0.0.5: + dependencies: + better-assert: 1.0.2 + + parseurl@1.3.3: {} + + pascalcase@0.1.1: {} + + path-dirname@1.0.2: {} + + path-is-absolute@1.0.1: {} + + path-proxy@1.0.0: + dependencies: + inflection: 1.3.8 + optional: true + + performance-now@2.1.0: {} + + pinkie-promise@2.0.1: + dependencies: + pinkie: 2.0.4 + optional: true + + pinkie@2.0.4: + optional: true + + posix-character-classes@0.1.1: {} + + prelude-ls@1.1.2: + optional: true + + process-nextick-args@1.0.7: + optional: true + + process-nextick-args@2.0.1: {} + + promisify-call@2.0.4: + dependencies: + with-callback: 1.0.2 + optional: true + + proxy-agent@3.0.3: + dependencies: + agent-base: 4.3.0 + debug: 3.1.0 + http-proxy-agent: 2.1.0 + https-proxy-agent: 2.2.4 + lru-cache: 4.1.5 + pac-proxy-agent: 3.0.1 + proxy-from-env: 1.0.0 + socks-proxy-agent: 4.0.2 + transitivePeerDependencies: + - supports-color + optional: true + + proxy-from-env@1.0.0: + optional: true + + pseudomap@1.0.2: + optional: true + + psl@1.4.0: {} + + punycode@1.4.1: {} + + punycode@2.1.1: {} + + qjobs@1.2.0: {} + + qs@6.2.3: + optional: true + + qs@6.5.2: {} + + qs@6.7.0: {} + + querystringify@2.1.1: + optional: true + + range-parser@1.2.1: {} + + raw-body@2.4.0: + dependencies: + bytes: 3.1.0 + http-errors: 1.7.2 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + raw-body@2.4.1: + dependencies: + bytes: 3.1.0 + http-errors: 1.7.3 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + optional: true + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.5 + strip-json-comments: 2.0.1 + optional: true + + readable-stream@1.1.14: + dependencies: + core-util-is: 1.0.2 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + optional: true + + readable-stream@2.0.6: + dependencies: + core-util-is: 1.0.2 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 1.0.7 + string_decoder: 0.10.31 + util-deprecate: 1.0.2 + optional: true + + readable-stream@2.3.6: + dependencies: + core-util-is: 1.0.2 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readdirp@2.2.1: + dependencies: + graceful-fs: 4.2.3 + micromatch: 3.1.10 + readable-stream: 2.3.6 + transitivePeerDependencies: + - supports-color + + redis-commands@1.5.0: + optional: true + + redis-parser@2.6.0: + optional: true + + redis@2.8.0: + dependencies: + double-ended-queue: 2.1.0-0 + redis-commands: 1.5.0 + redis-parser: 2.6.0 + optional: true + + regex-not@1.0.2: + dependencies: + extend-shallow: 3.0.2 + safe-regex: 1.1.0 + + remove-trailing-separator@1.1.0: {} + + repeat-element@1.1.3: {} + + repeat-string@0.2.2: {} + + repeat-string@1.6.1: {} + + request@2.75.0: + dependencies: + aws-sign2: 0.6.0 + aws4: 1.8.0 + bl: 1.1.2 + caseless: 0.11.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.0.0 + har-validator: 2.0.6 + hawk: 3.1.3 + http-signature: 1.1.1 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.25 + node-uuid: 1.4.8 + oauth-sign: 0.8.2 + qs: 6.2.3 + stringstream: 0.0.6 + tough-cookie: 2.3.4 + tunnel-agent: 0.4.3 + optional: true + + request@2.88.0: + dependencies: + aws-sign2: 0.7.0 + aws4: 1.8.0 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + har-validator: 5.1.3 + http-signature: 1.2.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.25 + oauth-sign: 0.9.0 + performance-now: 2.1.0 + qs: 6.5.2 + safe-buffer: 5.2.0 + tough-cookie: 2.4.3 + tunnel-agent: 0.6.0 + uuid: 3.3.3 + + requestretry@1.13.0: + dependencies: + extend: 3.0.2 + lodash: 4.17.15 + request: 2.88.0 + when: 3.7.8 + optional: true + + requires-port@1.0.0: {} + + resolve-url@0.2.1: {} + + ret@0.1.15: {} + + rimraf@2.7.1: + dependencies: + glob: 7.1.6 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.0: {} + + safe-regex@1.1.0: + dependencies: + ret: 0.1.15 + + safer-buffer@2.1.2: {} + + sax@1.2.4: + optional: true + + semver@5.5.1: {} + + semver@5.7.1: {} + + set-blocking@2.0.0: + optional: true + + set-value@2.0.1: + dependencies: + extend-shallow: 2.0.1 + is-extendable: 0.1.1 + is-plain-object: 2.0.4 + split-string: 3.1.0 + + setprototypeof@1.1.1: {} + + signal-exit@3.0.3: + optional: true + + slack-node@0.2.0: + dependencies: + requestretry: 1.13.0 + optional: true + + smart-buffer@1.1.15: + optional: true + + smart-buffer@4.1.0: + optional: true + + smtp-connection@2.12.0: + dependencies: + httpntlm: 1.6.1 + nodemailer-shared: 1.1.0 + optional: true + + snapdragon-node@2.1.1: + dependencies: + define-property: 1.0.0 + isobject: 3.0.1 + snapdragon-util: 3.0.1 + + snapdragon-util@3.0.1: + dependencies: + kind-of: 3.2.2 + + snapdragon@0.8.2: + dependencies: + base: 0.11.2 + debug: 2.6.9 + define-property: 0.2.5 + extend-shallow: 2.0.1 + map-cache: 0.2.2 + source-map: 0.5.7 + source-map-resolve: 0.5.2 + use: 3.1.1 + transitivePeerDependencies: + - supports-color + + sntp@1.0.9: + dependencies: + hoek: 2.16.3 + optional: true + + socket.io-adapter@1.1.1: {} + + socket.io-client@2.0.4: + dependencies: + backo2: 1.0.2 + base64-arraybuffer: 0.1.5 + component-bind: 1.0.0 + component-emitter: 1.2.1 + debug: 2.6.9 + engine.io-client: 3.1.6 + has-cors: 1.1.0 + indexof: 0.0.1 + object-component: 0.0.3 + parseqs: 0.0.5 + parseuri: 0.0.5 + socket.io-parser: 3.1.3 + to-array: 0.1.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@3.1.3: + dependencies: + component-emitter: 1.2.1 + debug: 3.1.0 + has-binary2: 1.0.3 + isarray: 2.0.1 + transitivePeerDependencies: + - supports-color + + socket.io@2.0.4: + dependencies: + debug: 2.6.9 + engine.io: 3.1.5 + socket.io-adapter: 1.1.1 + socket.io-client: 2.0.4 + socket.io-parser: 3.1.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socks-proxy-agent@4.0.2: + dependencies: + agent-base: 4.2.1 + socks: 2.3.3 + optional: true + + socks@1.1.9: + dependencies: + ip: 1.1.5 + smart-buffer: 1.1.15 + optional: true + + socks@2.3.3: + dependencies: + ip: 1.1.5 + smart-buffer: 4.1.0 + optional: true + + source-map-resolve@0.5.2: + dependencies: + atob: 2.1.2 + decode-uri-component: 0.2.0 + resolve-url: 0.2.1 + source-map-url: 0.4.0 + urix: 0.1.0 + + source-map-url@0.4.0: {} + + source-map@0.5.7: {} + + source-map@0.6.1: {} + + split-string@3.1.0: + dependencies: + extend-shallow: 3.0.2 + + sshpk@1.16.1: + dependencies: + asn1: 0.2.4 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + + static-extend@0.1.2: + dependencies: + define-property: 0.2.5 + object-copy: 0.1.0 + + statuses@1.5.0: {} + + streamroller@0.7.0: + dependencies: + date-format: 1.2.0 + debug: 3.2.6 + mkdirp: 0.5.1 + readable-stream: 2.3.6 + transitivePeerDependencies: + - supports-color + + string-width@1.0.2: + dependencies: + code-point-at: 1.1.0 + is-fullwidth-code-point: 1.0.0 + strip-ansi: 3.0.1 + optional: true + + string_decoder@0.10.31: + optional: true + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + stringstream@0.0.6: + optional: true + + strip-ansi@3.0.1: + dependencies: + ansi-regex: 2.1.1 + optional: true + + strip-json-comments@2.0.1: + optional: true + + supports-color@2.0.0: + optional: true + + sync-exec@0.6.2: + optional: true + + tar@4.4.15: + dependencies: + chownr: 1.1.4 + fs-minipass: 1.2.7 + minipass: 2.9.0 + minizlib: 1.3.3 + mkdirp: 0.5.1 + safe-buffer: 5.2.0 + yallist: 3.1.1 + optional: true + + thunkify@2.1.2: + optional: true + + timespan@2.3.0: + optional: true + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + to-array@0.1.4: {} + + to-object-path@0.3.0: + dependencies: + kind-of: 3.2.2 + + to-regex-range@2.1.1: + dependencies: + is-number: 3.0.0 + repeat-string: 1.6.1 + + to-regex@3.0.2: + dependencies: + define-property: 2.0.2 + extend-shallow: 3.0.2 + regex-not: 1.0.2 + safe-regex: 1.1.0 + + toidentifier@1.0.0: {} + + tough-cookie@2.3.4: + dependencies: + punycode: 1.4.1 + optional: true + + tough-cookie@2.4.3: + dependencies: + psl: 1.4.0 + punycode: 1.4.1 + + tsscmp@1.0.6: + optional: true + + tunnel-agent@0.4.3: + optional: true + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.0 + + tweetnacl@0.14.5: {} + + type-check@0.3.2: + dependencies: + prelude-ls: 1.1.2 + optional: true + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.25 + + ultron@1.1.1: {} + + underscore@1.7.0: + optional: true + + union-value@1.0.1: + dependencies: + arr-union: 3.1.0 + get-value: 2.0.6 + is-extendable: 0.1.1 + set-value: 2.0.1 + + unpipe@1.0.0: {} + + unset-value@1.0.0: + dependencies: + has-value: 0.3.1 + isobject: 3.0.1 + + upath@1.2.0: {} + + uri-js@4.2.2: + dependencies: + punycode: 2.1.1 + + urix@0.1.0: {} + + url-parse@1.4.7: + dependencies: + querystringify: 2.1.1 + requires-port: 1.0.0 + optional: true + + use@3.1.1: {} + + useragent@2.2.1: + dependencies: + lru-cache: 2.2.4 + request: 2.88.0 + semver: 5.5.1 + tmp: 0.0.33 + yamlparser: 0.0.2 + + util-deprecate@1.0.2: {} + + utils-merge@1.0.1: {} + + uuid@3.3.3: {} + + uws@9.14.0: + optional: true + + verror@1.10.0: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + + void-elements@2.0.1: {} + + when@3.7.8: + optional: true + + wide-align@1.1.3: + dependencies: + string-width: 1.0.2 + optional: true + + with-callback@1.0.2: + optional: true + + word-wrap@1.2.3: + optional: true + + wordwrap@0.0.3: {} + + wrappy@1.0.2: {} + + ws@3.3.3: + dependencies: + async-limiter: 1.0.1 + safe-buffer: 5.1.2 + ultron: 1.1.1 + + xmlhttprequest-ssl@1.5.5: {} + + xregexp@2.0.0: + optional: true + + xtend@4.0.2: + optional: true + + yallist@2.1.2: + optional: true + + yallist@3.1.1: + optional: true + + yamlparser@0.0.2: {} + + yeast@0.1.2: {} diff --git a/pnpm11/deps/compliance/commands/test/audit/fixtures/has-vulnerabilities-with-ignored-ghsas/pnpm-workspace.yaml b/pnpm11/deps/compliance/commands/test/audit/fixtures/has-vulnerabilities-with-ignored-ghsas/pnpm-workspace.yaml new file mode 100644 index 0000000000..3c12abcbab --- /dev/null +++ b/pnpm11/deps/compliance/commands/test/audit/fixtures/has-vulnerabilities-with-ignored-ghsas/pnpm-workspace.yaml @@ -0,0 +1,8 @@ +packages: + - '.' +sharedWorkspaceLockfile: false +auditConfig: + ignoreGhsas: + - GHSA-42xw-2xvc-qx8m + # Expired GHSA, should not be ignored + - GHSA-xxxx-xxxx-xxxx # trailing comment, should also go diff --git a/pnpm11/deps/compliance/commands/tsconfig.json b/pnpm11/deps/compliance/commands/tsconfig.json index 9bd54bcabf..cfe4c18d35 100644 --- a/pnpm11/deps/compliance/commands/tsconfig.json +++ b/pnpm11/deps/compliance/commands/tsconfig.json @@ -91,6 +91,9 @@ { "path": "../../../testing/registry-mock" }, + { + "path": "../../../text/sanitize" + }, { "path": "../../../workspace/project-manifest-reader" }, diff --git a/pnpm11/workspace/workspace-manifest-writer/src/index.ts b/pnpm11/workspace/workspace-manifest-writer/src/index.ts index 56f6f18e3d..ea2b3a9599 100644 --- a/pnpm11/workspace/workspace-manifest-writer/src/index.ts +++ b/pnpm11/workspace/workspace-manifest-writer/src/index.ts @@ -48,6 +48,12 @@ export async function updateWorkspaceManifest (dir: string, opts: { updatedFields?: Partial updatedCatalogs?: Catalogs updatedOverrides?: Record + /** + * The complete desired audit ignore list, written to whichever spelling + * the manifest uses — see {@link setAuditIgnoreGhsas}. An empty array + * removes the list. + */ + updatedAuditIgnoreGhsas?: string[] addedMinimumReleaseAgeExcludes?: string[] deletedLegacyKeys?: string[] fileName?: FileName @@ -113,6 +119,9 @@ export async function updateWorkspaceManifest (dir: string, opts: { } } } + if (opts.updatedAuditIgnoreGhsas != null) { + shouldBeUpdated = setAuditIgnoreGhsas(manifest, opts.updatedAuditIgnoreGhsas) || shouldBeUpdated + } if (opts.resolvedPackageVersions != null) { if (opts.minimumReleaseAgeExcludePrune) { shouldBeUpdated = pruneMinimumReleaseAgeExcludes(manifest, opts.resolvedPackageVersions) || shouldBeUpdated @@ -291,6 +300,55 @@ function addPackageReference (packageReferences: Record>, pk // patterns always stay — they are forward-looking and can't be proven stale. // Entries that fail to parse stay untouched so cleanup never breaks an // install. +/** + * Set the audit ignore list to `ghsas` (the complete desired list) in + * whichever spelling the manifest uses — the canonical `audit.ignore` wins + * over the deprecated `auditConfig.ignoreGhsas`, matching the reader's + * precedence, so a stale canonical list can't shadow the update on the next + * read. When both spellings are present, the shadowed deprecated list is + * removed as part of the write. `auditConfig.ignoreGhsas` is created when + * neither is present. An empty `ghsas` removes the list, dropping its parent + * block when nothing else remains in it. Returns whether anything changed. + */ +function setAuditIgnoreGhsas (manifest: Partial, ghsas: string[]): boolean { + let changed = false + if (manifest.audit?.ignore != null) { + if (ghsas.length === 0) { + delete manifest.audit.ignore + if (Object.keys(manifest.audit).length === 0) { + delete manifest.audit + } + changed = true + } else if (!equals(manifest.audit.ignore, ghsas)) { + manifest.audit.ignore = ghsas + changed = true + } + if (manifest.auditConfig?.ignoreGhsas != null) { + changed = removeAuditConfigIgnoreGhsas(manifest) || changed + } + return changed + } + if (ghsas.length === 0) { + return removeAuditConfigIgnoreGhsas(manifest) + } + if (equals(manifest.auditConfig?.ignoreGhsas, ghsas)) { + return false + } + manifest.auditConfig = { ...manifest.auditConfig, ignoreGhsas: ghsas } + return true +} + +function removeAuditConfigIgnoreGhsas (manifest: Partial): boolean { + if (manifest.auditConfig?.ignoreGhsas == null) { + return false + } + delete manifest.auditConfig.ignoreGhsas + if (Object.keys(manifest.auditConfig).length === 0) { + delete manifest.auditConfig + } + return true +} + function pruneMinimumReleaseAgeExcludes ( manifest: Partial & { minimumReleaseAgeExclude?: string[] }, resolvedPackageVersions: ReadonlyMap> diff --git a/pnpm11/workspace/workspace-manifest-writer/test/auditIgnoreGhsas.test.ts b/pnpm11/workspace/workspace-manifest-writer/test/auditIgnoreGhsas.test.ts new file mode 100644 index 0000000000..7523f319a9 --- /dev/null +++ b/pnpm11/workspace/workspace-manifest-writer/test/auditIgnoreGhsas.test.ts @@ -0,0 +1,95 @@ +import path from 'node:path' + +import { expect, test } from '@jest/globals' +import { WORKSPACE_MANIFEST_FILENAME } from '@pnpm/constants' +import { tempDir } from '@pnpm/prepare-temp-dir' +import { updateWorkspaceManifest } from '@pnpm/workspace.workspace-manifest-writer' +import { readYamlFileSync } from 'read-yaml-file' +import { writeYamlFileSync } from 'write-yaml-file' + +test('write the list to the deprecated auditConfig.ignoreGhsas when that is where it lives', async () => { + const dir = tempDir(false) + const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME) + writeYamlFileSync(filePath, { + auditConfig: { ignoreGhsas: ['GHSA-aaaa-bbbb-cccc'] }, + }) + await updateWorkspaceManifest(dir, { + updatedAuditIgnoreGhsas: ['GHSA-dddd-eeee-ffff'], + }) + expect(readYamlFileSync(filePath)).toStrictEqual({ + auditConfig: { ignoreGhsas: ['GHSA-dddd-eeee-ffff'] }, + }) +}) + +test('create auditConfig.ignoreGhsas when neither spelling is present', async () => { + const dir = tempDir(false) + const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME) + writeYamlFileSync(filePath, { + packages: ['.'], + }) + await updateWorkspaceManifest(dir, { + updatedAuditIgnoreGhsas: ['GHSA-dddd-eeee-ffff'], + }) + expect(readYamlFileSync(filePath)).toStrictEqual({ + packages: ['.'], + auditConfig: { ignoreGhsas: ['GHSA-dddd-eeee-ffff'] }, + }) +}) + +test('write the list to the canonical audit.ignore when that is where it lives', async () => { + const dir = tempDir(false) + const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME) + writeYamlFileSync(filePath, { + audit: { ignorePrune: true, ignore: ['GHSA-aaaa-bbbb-cccc'] }, + }) + await updateWorkspaceManifest(dir, { + updatedAuditIgnoreGhsas: ['GHSA-dddd-eeee-ffff'], + }) + expect(readYamlFileSync(filePath)).toStrictEqual({ + audit: { ignorePrune: true, ignore: ['GHSA-dddd-eeee-ffff'] }, + }) +}) + +test('remove the shadowed deprecated list when both spellings are present', async () => { + const dir = tempDir(false) + const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME) + writeYamlFileSync(filePath, { + audit: { ignore: ['GHSA-aaaa-bbbb-cccc'] }, + auditConfig: { ignoreGhsas: ['GHSA-1111-2222-3333'] }, + }) + await updateWorkspaceManifest(dir, { + updatedAuditIgnoreGhsas: ['GHSA-dddd-eeee-ffff'], + }) + expect(readYamlFileSync(filePath)).toStrictEqual({ + audit: { ignore: ['GHSA-dddd-eeee-ffff'] }, + }) +}) + +test('an empty list removes audit.ignore and keeps its siblings', async () => { + const dir = tempDir(false) + const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME) + writeYamlFileSync(filePath, { + audit: { ignorePrune: true, ignore: ['GHSA-aaaa-bbbb-cccc'] }, + }) + await updateWorkspaceManifest(dir, { + updatedAuditIgnoreGhsas: [], + }) + expect(readYamlFileSync(filePath)).toStrictEqual({ + audit: { ignorePrune: true }, + }) +}) + +test('an empty list removes the audit block when ignore is its only key', async () => { + const dir = tempDir(false) + const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME) + writeYamlFileSync(filePath, { + packages: ['.'], + audit: { ignore: ['GHSA-aaaa-bbbb-cccc'] }, + }) + await updateWorkspaceManifest(dir, { + updatedAuditIgnoreGhsas: [], + }) + expect(readYamlFileSync(filePath)).toStrictEqual({ + packages: ['.'], + }) +})