Update update_node.py
diff --git a/scripts/update_node.py b/scripts/update_node.py
index b16d3e5..5307b7d 100755
--- a/scripts/update_node.py
+++ b/scripts/update_node.py
@@ -15,14 +15,17 @@
 import subprocess
 import os
 import shutil
+import sys
+from zip import unzip_cmd, zip_cmd
 
-version = '18.20.3'
-base = 'https://nodejs.org/dist/latest-v18.x/'
+version = '20.18.0'
+base = 'https://nodejs.org/dist/v20.18.0/'
 upload_base = 'gs://webassembly/emscripten-releases-builds/deps/'
 
 suffixes = [
     '-win-x86.zip',
     '-win-x64.zip',
+    '-win-arm64.zip',
     '-darwin-x64.tar.gz',
     '-darwin-arm64.tar.gz',
     '-linux-x64.tar.xz',
@@ -37,18 +40,19 @@
     urllib.request.urlretrieve(download_url, filename)
 
     if '-win-' in suffix:
-      subprocess.check_call(['unzip', '-q', filename])
+      subprocess.check_call(unzip_cmd() + [filename])
       dirname = os.path.splitext(os.path.basename(filename))[0]
       shutil.move(dirname, 'bin')
       os.mkdir(dirname)
       shutil.move('bin', dirname)
       os.remove(filename)
-      subprocess.check_call(['zip', '-rq', filename, dirname])
+      subprocess.check_call(zip_cmd() + [filename, dirname])
       shutil.rmtree(dirname)
 
-    upload_url = upload_base + filename
-    print('Uploading: ' + upload_url)
-    cmd = ['gsutil', 'cp', '-n', filename, upload_url]
-    print(' '.join(cmd))
-    subprocess.check_call(cmd)
-    os.remove(filename)
+    if '--upload' in sys.argv:
+      upload_url = upload_base + filename
+      print('Uploading: ' + upload_url)
+      cmd = ['gsutil', 'cp', '-n', filename, upload_url]
+      print(' '.join(cmd))
+      subprocess.check_call(cmd)
+      os.remove(filename)
diff --git a/scripts/update_python.py b/scripts/update_python.py
index 432bf0b..53c2983 100755
--- a/scripts/update_python.py
+++ b/scripts/update_python.py
@@ -30,6 +30,7 @@
 import subprocess
 import sys
 from subprocess import check_call
+from zip import unzip_cmd, zip_cmd
 
 version = '3.13.0'
 major_minor_version = '.'.join(version.split('.')[:2])  # e.g. '3.9.2' -> '3.9'
@@ -42,24 +43,6 @@
 upload_base = 'gs://webassembly/emscripten-releases-builds/deps/'
 
 
-def unzip_cmd():
-    # Use 7-Zip if available (https://www.7-zip.org/)
-    sevenzip = os.path.join(os.getenv('ProgramFiles', ''), '7-Zip', '7z.exe')
-    if os.path.isfile(sevenzip):
-        return [sevenzip, 'x']
-    # Fall back to 'unzip' tool
-    return ['unzip', '-q']
-
-
-def zip_cmd():
-    # Use 7-Zip if available (https://www.7-zip.org/)
-    sevenzip = os.path.join(os.getenv('ProgramFiles', ''), '7-Zip', '7z.exe')
-    if os.path.isfile(sevenzip):
-        return [sevenzip, 'a', '-mx9']
-    # Fall back to 'zip' tool
-    return ['zip', '-rq']
-
-
 # Detects whether current python interpreter architecture is ARM64 or AMD64
 # If running AMD64 python on an ARM64 Windows, this still intentionally returns AMD64
 def find_python_arch():
diff --git a/scripts/zip.py b/scripts/zip.py
new file mode 100644
index 0000000..30bceea
--- /dev/null
+++ b/scripts/zip.py
@@ -0,0 +1,18 @@
+import os
+
+def unzip_cmd():
+    # Use 7-Zip if available (https://www.7-zip.org/)
+    sevenzip = os.path.join(os.getenv('ProgramFiles', ''), '7-Zip', '7z.exe')
+    if os.path.isfile(sevenzip):
+        return [sevenzip, 'x']
+    # Fall back to 'unzip' tool
+    return ['unzip', '-q']
+
+
+def zip_cmd():
+    # Use 7-Zip if available (https://www.7-zip.org/)
+    sevenzip = os.path.join(os.getenv('ProgramFiles', ''), '7-Zip', '7z.exe')
+    if os.path.isfile(sevenzip):
+        return [sevenzip, 'a', '-mx9']
+    # Fall back to 'zip' tool
+    return ['zip', '-rq']