Repository: oppia/oppia

Issue:
Move npm audit checks out of travis to only presubmit checks
Currently our npm audit checks are being run on a linter which is causing unnecessary slowdowns. We should only have our audit checks run locally and only whenever package.json is modified. 


Discussion:

Resolving patch:
diff --git a/scripts/pre_commit_linter.py b/scripts/pre_commit_linter.py
--- a/scripts/pre_commit_linter.py
+++ b/scripts/pre_commit_linter.py
@@ -2497,59 +2497,6 @@ def _check_constants_declaration(self):
 
         return summary_messages
 
-    def _check_npm_packages(self):
-        """Checks to ensure that npm packages are not identified as vulnerable
-        by npm audit.
-        """
-        parent_dir = os.path.abspath(os.path.join(os.getcwd(), os.pardir))
-
-        npm_path = os.path.join(
-            parent_dir, 'oppia_tools', 'node-10.15.3', 'bin', 'npm')
-        if self.verbose_mode_enabled:
-            print 'Starting npm package vulnerability check...'
-            print '----------------------------------------'
-        print ''
-        if not self.verbose_mode_enabled:
-            print 'Checking npm packages.'
-
-        summary_messages = []
-        failed = False
-
-        proc_args = [npm_path, 'audit', '--parseable']
-        with _redirect_stdout(_TARGET_STDOUT):
-            proc = subprocess.Popen(
-                proc_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-
-            linter_stdout, _ = proc.communicate()
-
-            errors = linter_stdout.lstrip().rstrip()
-            if errors:
-                failed = True
-                error_list = errors.split('\n')
-                for line in error_list:
-                    line_tokens = line.split('\t')
-                    print ('%s package is vulnerable. Please run "%s" to '
-                           'update.' %
-                           (line_tokens[1], line_tokens[3]))
-
-            if failed:
-                print '(%s errors found)' % len(error_list)
-                summary_message = (
-                    '%s  npm package vulnerability check failed' % (
-                        _MESSAGE_TYPE_FAILED))
-            else:
-                summary_message = (
-                    '%s  npm package vulnerability check passed' % (
-                        _MESSAGE_TYPE_SUCCESS))
-            summary_messages.append(summary_message)
-
-            print ''
-            print summary_message
-            print ''
-
-        return summary_messages
-
-
     def perform_all_lint_checks(self):
         """Perform all the lint checks and returns the messages returned by all
         the checks.
@@ -2579,7 +2526,6 @@ def perform_all_lint_checks(self):
         pattern_messages = self._check_bad_patterns()
         codeowner_messages = self._check_codeowner_file()
         constants_messages = self._check_constants_declaration()
-        npm_package_messages = self._check_npm_packages()
         all_messages = (
             extra_js_files_messages + js_and_ts_component_messages +
             directive_scope_messages + sorted_dependencies_messages +
@@ -2587,7 +2533,7 @@ def perform_all_lint_checks(self):
             mandatory_patterns_messages + docstring_messages +
             comment_messages + html_tag_and_attribute_messages +
             html_linter_messages + linter_messages + pattern_messages +
-            codeowner_messages + constants_messages + npm_package_messages)
+            codeowner_messages + constants_messages)
         return all_messages
 
 
diff --git a/scripts/pre_push_hook.py b/scripts/pre_push_hook.py
--- a/scripts/pre_push_hook.py
+++ b/scripts/pre_push_hook.py
@@ -50,6 +50,9 @@
 LINTER_SCRIPT = 'pre_commit_linter.py'
 LINTER_FILE_FLAG = '--files'
 PYTHON_CMD = 'python'
+OPPIA_PARENT_DIR = os.path.join(FILE_DIR, os.pardir, os.pardir, os.pardir)
+NPM_CMD = os.path.join(
+    OPPIA_PARENT_DIR, 'oppia_tools', 'node-10.15.3', 'bin', 'npm')
 FRONTEND_TEST_SCRIPT = 'run_frontend_tests.sh'
 GIT_IS_DIRTY_CMD = 'git status --porcelain --untracked-files=no'
 
@@ -266,6 +269,13 @@ def _start_sh_script(scriptname):
     return task.returncode
 
 
+def _start_npm_audit():
+    """Starts the npm audit checks and returns the returncode of the task."""
+    task = subprocess.Popen([NPM_CMD, 'audit'])
+    task.communicate()
+    return task.returncode
+
+
 def _has_uncommitted_files():
     """Returns true if the repo contains modified files that are uncommitted.
     Ignores untracked files.
@@ -305,7 +315,7 @@ def _install_hook():
         raise ValueError(err_chmod_cmd)
 
 
-def does_diff_include_js_or_ts_files(files_to_lint):
+def _does_diff_include_js_or_ts_files(files_to_lint):
     """Returns true if diff includes JavaScript or TypeScript files.
 
     Args:
@@ -322,6 +332,22 @@ def does_diff_include_js_or_ts_files(files_to_lint):
     return False
 
 
+def _does_diff_include_package_json(files_to_lint):
+    """Returns true if diff includes package.json or package-lock.json.
+
+    Args:
+        files_to_lint: list(str). List of files to be linted.
+
+    Returns:
+        bool. Whether the diff contains changes in package.json or
+            package-lock.json.
+    """
+    for filename in files_to_lint:
+        if filename == 'package.json' or filename == 'package-lock.json':
+            return True
+    return False
+
+
 def main():
     """Main method for pre-push hook that executes the Python/JS linters on all
     files that deviate from develop.
@@ -352,10 +378,17 @@ def main():
             if files_to_lint:
                 lint_status = _start_linter(files_to_lint)
                 if lint_status != 0:
-                    print 'Push failed, please correct the linting issues above'
+                    print ('Push failed, please correct the linting issues '
+                           'above.')
+                    sys.exit(1)
+            if _does_diff_include_package_json(files_to_lint):
+                npm_audit_status = _start_npm_audit()
+                if npm_audit_status != 0:
+                    print ('Push failed, please correct the npm audit issues '
+                           'above.')
                     sys.exit(1)
             frontend_status = 0
-            if does_diff_include_js_or_ts_files(files_to_lint):
+            if _does_diff_include_js_or_ts_files(files_to_lint):
                 frontend_status = _start_sh_script(FRONTEND_TEST_SCRIPT)
             if frontend_status != 0:
                 print 'Push aborted due to failing frontend tests.'


Test patch: