1+ import fs from "fs-extra" ;
2+ import path from "path" ;
3+ import type { Dirent } from "fs-extra" ;
4+ import { execSync } from "node:child_process" ;
5+
6+ /**
7+ * Example usage with node >= v22:
8+ * node --experimental-strip-types bin/cleanupNodeModules.ts /path/to/build/folder [--skip-prune-dev-deps]
9+ * Example usage with tsx:
10+ * tsx bin/cleanupNodeModules.ts /path/to/build/folder [--skip-prune-dev-deps]
11+ */
12+ function main ( ) {
13+
14+ if ( process . argv . length > 4 || process . argv . length < 3 ) {
15+ console . error ( "Usage: cleanupNodeModules.ts [path-to-build-folder] [--skip-prune-dev-deps]" ) ;
16+ process . exit ( 1 ) ;
17+ }
18+
19+ const basePath = process . argv [ 2 ] ;
20+ const pruneDevDeps = process . argv [ 3 ] !== "--skip-prune-dev-deps" ;
21+
22+ if ( ! fs . existsSync ( basePath ) ) {
23+ console . error ( `Supplied path '${ basePath } ' does not exist. Aborting.` ) ;
24+ process . exit ( 1 ) ;
25+ }
26+
27+ console . log ( `Starting pruning of node_modules ${ ! pruneDevDeps ? '(skipping npm pruning)' : '' } in '${ basePath } '...` ) ;
28+ cleanupNodeModules ( basePath , pruneDevDeps ) ;
29+ console . log ( "Successfully pruned node_modules." ) ;
30+ }
31+
32+ function cleanupNodeModules ( basePath : string , pruneDevDeps : boolean = true ) {
33+
34+ // This needs to run for the server and Docker build,
35+ // but needs to be skipped for electron-forge: its
36+ // built-in pruning takes care of it already
37+ if ( pruneDevDeps ) {
38+ execSync ( `npm ci --omit=dev --prefix ${ basePath } ` ) ;
39+ }
40+
41+ const nodeModulesDirPath = path . join ( basePath , "node_modules" ) ;
42+ const nodeModulesContent = fs . readdirSync ( nodeModulesDirPath , { recursive : true , withFileTypes : true } ) ;
43+ //const libDir = fs.readdirSync(path.join(basePath, "./libraries"), { recursive: true, withFileTypes: true });
44+
45+ /**
46+ * Delete unnecessary folders
47+ */
48+ const filterableDirs = new Set ( [
49+ "demo" ,
50+ "demos" ,
51+ "doc" ,
52+ "docs" ,
53+ "example" ,
54+ "examples" ,
55+ "test" ,
56+ "tests"
57+ ] ) ;
58+
59+ nodeModulesContent
60+ . filter ( el => el . isDirectory ( ) && filterableDirs . has ( el . name ) )
61+ . forEach ( dir => removeDirent ( dir ) ) ;
62+
63+ /**
64+ * Delete unnecessary files based on file extension
65+ * TODO filter out useless (README).md files
66+ */
67+ const filterableFileExt = new Set ( [
68+ "ts" ,
69+ "map"
70+ ] ) ;
71+
72+ nodeModulesContent
73+ // TriliumNextTODO: check if we can improve this naive file ext matching, without introducing any additional dependency
74+ . filter ( el => el . isFile ( ) && filterableFileExt . has ( el . name . split ( "." ) . at ( - 1 ) || "" ) )
75+ . forEach ( dir => removeDirent ( dir ) ) ;
76+
77+
78+ /**
79+ * Delete specific unnecessary folders
80+ * TODO: check if we want removeSync to throw an error, if path does not exist anymore -> currently it will silently fail
81+ */
82+ const extraFoldersDelete = new Set ( [
83+ path . join ( nodeModulesDirPath , ".bin" ) ,
84+ path . join ( nodeModulesDirPath , "@excalidraw" , "excalidraw" , "dist" , "dev" ) ,
85+ path . join ( nodeModulesDirPath , "boxicons" , "svg" ) ,
86+ path . join ( nodeModulesDirPath , "boxicons" , "node_modules" ) ,
87+ path . join ( nodeModulesDirPath , "boxicons" , "src" ) ,
88+ path . join ( nodeModulesDirPath , "boxicons" , "iconjar" ) ,
89+ path . join ( nodeModulesDirPath , "@jimp" , "plugin-print" , "fonts" ) ,
90+ path . join ( nodeModulesDirPath , "jimp" , "dist" , "browser" ) // missing "@" in front of jimp is not a typo here
91+ ] ) ;
92+
93+ nodeModulesContent
94+ . filter ( el => el . isDirectory ( ) && extraFoldersDelete . has ( path . join ( el . parentPath , el . name ) ) )
95+ . forEach ( dir => removeDirent ( dir ) )
96+ }
97+
98+
99+ function removeDirent ( el : Dirent ) {
100+ const elementToDelete = path . join ( el . parentPath , el . name ) ;
101+ fs . removeSync ( elementToDelete ) ;
102+
103+ if ( process . env . VERBOSE ) {
104+ console . log ( `Deleted ${ elementToDelete } ` ) ;
105+ }
106+
107+ }
108+
109+ main ( )
0 commit comments