auto-organize 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.es.md +198 -0
- package/README.md +199 -0
- package/bin/auto-organize.js +9 -0
- package/package.json +50 -0
- package/src/cli/cliActions.js +20 -0
- package/src/cli/renderBanners.js +22 -0
- package/src/cli/renderEmptyFolderText.js +12 -0
- package/src/cli/renderSummary.js +31 -0
- package/src/index.js +32 -0
- package/src/organizer/organizeDirectory.js +54 -0
- package/src/rules/byType.js +29 -0
- package/src/utils/filesFilters.js +11 -0
- package/src/utils/fsHelpers.js +33 -0
- package/src/utils/helpMenu.js +28 -0
- package/src/utils/parseArgs.js +72 -0
- package/src/utils/validators.js +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Christian Mathías Rincón
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.es.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
<br>
|
|
4
|
+
|
|
5
|
+
# Auto Organize CLI
|
|
6
|
+
|
|
7
|
+
Leer en: [Inglés](README.md) | **Español**
|
|
8
|
+
|
|
9
|
+
<br>
|
|
10
|
+
|
|
11
|
+
Una herramienta de línea de comandos (CLI) para **organizar automáticamente archivos** en cualquier carpeta del sistema.
|
|
12
|
+
|
|
13
|
+
<br>
|
|
14
|
+
|
|
15
|
+
## ¿Qué problema resuelve?
|
|
16
|
+
|
|
17
|
+
Cuando trabajamos con carpetas que acumulan archivos (Descargas, Escritorio, proyectos, etc), el orden se suele alterar y vemos:
|
|
18
|
+
|
|
19
|
+
* Archivos mezclados por tipo.
|
|
20
|
+
* Pérdida de tiempo clasificando los archivos manualmente.
|
|
21
|
+
* Movemos o borramos archivos incorrectos.
|
|
22
|
+
|
|
23
|
+
<br>
|
|
24
|
+
|
|
25
|
+
> **Auto Organize CLI** automatiza y corrige ese proceso.
|
|
26
|
+
|
|
27
|
+
<br>
|
|
28
|
+
|
|
29
|
+
## Características principales
|
|
30
|
+
|
|
31
|
+
* Organización automática por tipo de archivo (clasificación por extensión).
|
|
32
|
+
* Modo simulación (`--preview`) para previsualizar.
|
|
33
|
+
* Filtros por tipo (`--only`, `--exclude`).
|
|
34
|
+
* **Solo mueve archivos — nunca los elimina.**
|
|
35
|
+
* Probado en **Windows** y **Linux** (Se espera compatibilidad con **macOS** gracias al soporte multiplataforma de **Node.js**)
|
|
36
|
+
|
|
37
|
+
<br>
|
|
38
|
+
|
|
39
|
+
## Instalación
|
|
40
|
+
|
|
41
|
+
Requiere **Node.js >= 16**
|
|
42
|
+
|
|
43
|
+
> [Descarga Node aquí](https://nodejs.org/es/download)
|
|
44
|
+
|
|
45
|
+
### Opción 1: Uso sin instalación (Para uso puntual)
|
|
46
|
+
```bash
|
|
47
|
+
npx auto-organize
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Opción 2: Instalación Global (Para uso continuo)
|
|
51
|
+
```bash
|
|
52
|
+
npm install -g auto-organize
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
<br>
|
|
56
|
+
|
|
57
|
+
## Uso básico
|
|
58
|
+
|
|
59
|
+
Ubicate en cualquier carpeta del sistema. Por ejemplo '/descargas'
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cd /users/descargas
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
y ejecuta:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
auto-organize
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Esto, dependiendo del tipo de archivos presentes, creará carpetas como:
|
|
72
|
+
|
|
73
|
+
```txt
|
|
74
|
+
Images/
|
|
75
|
+
Documents/
|
|
76
|
+
Audio/
|
|
77
|
+
Video/
|
|
78
|
+
Archives/
|
|
79
|
+
Others/
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
y clasificará los archivos en la carpeta correspondiente.
|
|
83
|
+
|
|
84
|
+
```txt
|
|
85
|
+
foto.jpg -> Images/
|
|
86
|
+
|
|
87
|
+
documento.pdf -> Documents/
|
|
88
|
+
|
|
89
|
+
canción.mp3 -> Audio/
|
|
90
|
+
|
|
91
|
+
video.mp4 -> Video/
|
|
92
|
+
|
|
93
|
+
archivo.rar -> Archives/
|
|
94
|
+
|
|
95
|
+
config.json -> Others/
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
<br>
|
|
99
|
+
|
|
100
|
+
## Modo simulación (preview)
|
|
101
|
+
|
|
102
|
+
Antes de ejecutar cambios reales, se puede previsualizar si la clasificación es correcta:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
auto-organize --preview
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Salida de ejemplo:
|
|
109
|
+
|
|
110
|
+
```txt
|
|
111
|
+
Images/
|
|
112
|
+
foto.jpg
|
|
113
|
+
|
|
114
|
+
Documents/
|
|
115
|
+
contrato.pdf
|
|
116
|
+
|
|
117
|
+
Audio/
|
|
118
|
+
cancion.mp3
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
<br>
|
|
122
|
+
|
|
123
|
+
## Configuraciones disponibles (flags)
|
|
124
|
+
|
|
125
|
+
### `--only <type>`
|
|
126
|
+
|
|
127
|
+
Organiza **solo** un tipo específico de archivo.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
auto-organize --only images
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### `--exclude <type>`
|
|
136
|
+
|
|
137
|
+
Excluye un tipo de archivo de la organización.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
auto-organize --exclude archives
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
### `--help`
|
|
146
|
+
|
|
147
|
+
Muestra una guía completa y los tipos disponibles.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
auto-organize --help
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
<br>
|
|
154
|
+
|
|
155
|
+
## Tipos soportados
|
|
156
|
+
|
|
157
|
+
* images
|
|
158
|
+
* documents
|
|
159
|
+
* spreadsheets
|
|
160
|
+
* presentations
|
|
161
|
+
* archives
|
|
162
|
+
* audio
|
|
163
|
+
* video
|
|
164
|
+
* others
|
|
165
|
+
|
|
166
|
+
<br>
|
|
167
|
+
|
|
168
|
+
## Casos típicos de uso
|
|
169
|
+
|
|
170
|
+
* Organizar la carpeta de Descargas.
|
|
171
|
+
* Limpieza rápida del Escritorio.
|
|
172
|
+
* Automatizar la clasificación de archivos de un proyecto (por ejemplo la carpeta '/public').
|
|
173
|
+
|
|
174
|
+
<br>
|
|
175
|
+
|
|
176
|
+
## Contribuciones
|
|
177
|
+
|
|
178
|
+
Las contribuciones son bienvenidas. Por favor:
|
|
179
|
+
1. Realiza un fork del proyecto.
|
|
180
|
+
2. Crea una rama con tu feature: `git checkout -b feature/{tu-feature}`
|
|
181
|
+
3. Haz commit de tus cambios: `git commit -m 'Add some feature'`
|
|
182
|
+
4. Haz push a la rama: `git push origin feature/{tu-feature}`
|
|
183
|
+
5. Abre un Pull Request.
|
|
184
|
+
|
|
185
|
+
<br>
|
|
186
|
+
|
|
187
|
+
## Licencia
|
|
188
|
+
|
|
189
|
+
* MIT
|
|
190
|
+
|
|
191
|
+
<br>
|
|
192
|
+
|
|
193
|
+
## Autor
|
|
194
|
+
|
|
195
|
+
* LinkedIn: https://www.linkedin.com/in/christian-math%C3%ADas-rinc%C3%B3n-037a90297/
|
|
196
|
+
|
|
197
|
+
* GitHub: https://github.com/ChristianRincon
|
|
198
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
<br>
|
|
4
|
+
|
|
5
|
+
# Auto Organize CLI
|
|
6
|
+
|
|
7
|
+
Read this in: **English** | [Spanish](README.es.md)
|
|
8
|
+
|
|
9
|
+
<br>
|
|
10
|
+
|
|
11
|
+
A command-line tool (CLI) to **automatically organize files** in any directory on your system.
|
|
12
|
+
|
|
13
|
+
<br>
|
|
14
|
+
|
|
15
|
+
## What problem does it solve?
|
|
16
|
+
|
|
17
|
+
When working with folders that accumulate files (Downloads, Desktop, projects, etc.), things usually get messy:
|
|
18
|
+
|
|
19
|
+
* Files mixed by type.
|
|
20
|
+
* Time wasted manually sorting files.
|
|
21
|
+
* Risk of moving or deleting the wrong files.
|
|
22
|
+
|
|
23
|
+
<br>
|
|
24
|
+
|
|
25
|
+
> **Auto Organize CLI** automates and fixes this process.
|
|
26
|
+
|
|
27
|
+
<br>
|
|
28
|
+
|
|
29
|
+
## Key Features
|
|
30
|
+
|
|
31
|
+
* Automatic file organization by type (based on file extensions).
|
|
32
|
+
* Simulation mode (`--preview`) to preview changes.
|
|
33
|
+
* Type filters (`--only`, `--exclude`).
|
|
34
|
+
* **Only moves files — never deletes them.**
|
|
35
|
+
* Tested on **Windows** and **Linux** (**macOS** compatibility is expected due to **Node.js** cross-platform support).
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
<br>
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
Requires **Node.js >= 16**
|
|
43
|
+
|
|
44
|
+
> [Download Node here](https://nodejs.org/en/download)
|
|
45
|
+
|
|
46
|
+
### Option 1: Without installation (For one-time use)
|
|
47
|
+
```bash
|
|
48
|
+
npx auto-organize
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Option 2: Global Installation (For regular use)
|
|
52
|
+
```bash
|
|
53
|
+
npm install -g auto-organize
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
<br>
|
|
57
|
+
|
|
58
|
+
## Basic Usage
|
|
59
|
+
|
|
60
|
+
Navigate to any directory on your system. For example:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cd /users/downloads
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Then run:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
auto-organize
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Depending on the files present, it will create folders such as:
|
|
73
|
+
|
|
74
|
+
```txt
|
|
75
|
+
Images/
|
|
76
|
+
Documents/
|
|
77
|
+
Audio/
|
|
78
|
+
Video/
|
|
79
|
+
Archives/
|
|
80
|
+
Others/
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
And move files into their corresponding folders.
|
|
84
|
+
|
|
85
|
+
```txt
|
|
86
|
+
photo.jpg -> Images/
|
|
87
|
+
|
|
88
|
+
document.pdf -> Documents/
|
|
89
|
+
|
|
90
|
+
song.mp3 -> Audio/
|
|
91
|
+
|
|
92
|
+
video.mp4 -> Video/
|
|
93
|
+
|
|
94
|
+
archive.rar -> Archives/
|
|
95
|
+
|
|
96
|
+
config.json -> Others/
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
<br>
|
|
100
|
+
|
|
101
|
+
## Simulation Mode (preview)
|
|
102
|
+
|
|
103
|
+
Preview the organization before applying real changes:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
auto-organize --preview
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Example output:
|
|
110
|
+
|
|
111
|
+
```txt
|
|
112
|
+
Images/
|
|
113
|
+
photo.jpg
|
|
114
|
+
|
|
115
|
+
Documents/
|
|
116
|
+
contract.pdf
|
|
117
|
+
|
|
118
|
+
Audio/
|
|
119
|
+
song.mp3
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
<br>
|
|
123
|
+
|
|
124
|
+
## Available Flags
|
|
125
|
+
|
|
126
|
+
### `--only <type>`
|
|
127
|
+
|
|
128
|
+
Organize **only** a specific file type.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
auto-organize --only images
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### `--exclude <type>`
|
|
137
|
+
|
|
138
|
+
Exclude a specific file type from organization.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
auto-organize --exclude archives
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### `--help`
|
|
147
|
+
|
|
148
|
+
Display the help guide and available types.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
auto-organize --help
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
<br>
|
|
155
|
+
|
|
156
|
+
## Supported Types
|
|
157
|
+
|
|
158
|
+
* images
|
|
159
|
+
* documents
|
|
160
|
+
* spreadsheets
|
|
161
|
+
* presentations
|
|
162
|
+
* archives
|
|
163
|
+
* audio
|
|
164
|
+
* video
|
|
165
|
+
* others
|
|
166
|
+
|
|
167
|
+
<br>
|
|
168
|
+
|
|
169
|
+
## Common Use Cases
|
|
170
|
+
|
|
171
|
+
* Organizing the Downloads folder
|
|
172
|
+
* Quick Desktop cleanup
|
|
173
|
+
* Automatically classifying project files (e.g. a `/public` directory)
|
|
174
|
+
|
|
175
|
+
<br>
|
|
176
|
+
|
|
177
|
+
## Contributing
|
|
178
|
+
|
|
179
|
+
Contributions are welcome:
|
|
180
|
+
|
|
181
|
+
1. Fork the project.
|
|
182
|
+
2. Create a feature branch: `git checkout -b feature/{your-feature}`
|
|
183
|
+
3. Commit your changes: `git commit -m 'Add your feature'`
|
|
184
|
+
4. Push the branch: `git push origin feature/{your-feature}`
|
|
185
|
+
5. Open a pull request.
|
|
186
|
+
|
|
187
|
+
<br>
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
* MIT
|
|
192
|
+
|
|
193
|
+
<br>
|
|
194
|
+
|
|
195
|
+
## Author
|
|
196
|
+
|
|
197
|
+
* LinkedIn: https://www.linkedin.com/in/christian-math%C3%ADas-rinc%C3%B3n-037a90297/
|
|
198
|
+
|
|
199
|
+
* GitHub: https://github.com/ChristianRincon
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "auto-organize",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool to organize files in a directory based on their file types.",
|
|
5
|
+
"main": "./bin/auto-organize.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"auto-organize": "./bin/auto-organize.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"cli",
|
|
16
|
+
"automation",
|
|
17
|
+
"file-organizer",
|
|
18
|
+
"file-management",
|
|
19
|
+
"organize",
|
|
20
|
+
"sort",
|
|
21
|
+
"cleanup",
|
|
22
|
+
"files",
|
|
23
|
+
"folders",
|
|
24
|
+
"directories",
|
|
25
|
+
"command-line",
|
|
26
|
+
"terminal",
|
|
27
|
+
"nodejs",
|
|
28
|
+
"filesystem",
|
|
29
|
+
"productivity"
|
|
30
|
+
],
|
|
31
|
+
"author": {
|
|
32
|
+
"name": "Christian Mathías Rincón",
|
|
33
|
+
"url": "https://www.linkedin.com/in/christian-math%C3%ADas-rinc%C3%B3n-037a90297/"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/ChristianRincon/auto-organize.git"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/ChristianRincon/auto-organize/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/ChristianRincon/auto-organize",
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"chalk": "^5.6.2"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { showHelp } from '../utils/helpMenu.js';
|
|
2
|
+
import { validateFlags, validateTypes, validateFlagsAndTypes } from '../utils/validators.js';
|
|
3
|
+
import { renderBanners } from './renderBanners.js';
|
|
4
|
+
|
|
5
|
+
function cliActions(cliFlags, availableTypes) {
|
|
6
|
+
validateFlags(cliFlags);
|
|
7
|
+
validateTypes(availableTypes);
|
|
8
|
+
validateFlagsAndTypes(cliFlags, availableTypes);
|
|
9
|
+
|
|
10
|
+
if (cliFlags.help) {
|
|
11
|
+
showHelp(availableTypes);
|
|
12
|
+
return { exit: true, code: 0 };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
renderBanners(cliFlags);
|
|
16
|
+
|
|
17
|
+
return { exit: false };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { cliActions };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
function renderBanners(cliFlags) {
|
|
4
|
+
const currentDir = process.cwd();
|
|
5
|
+
|
|
6
|
+
const scannedDirectoryText = 'SCANNED DIRECTORY';
|
|
7
|
+
let textMargin = 0;
|
|
8
|
+
|
|
9
|
+
if(currentDir.length > scannedDirectoryText.length){
|
|
10
|
+
textMargin = (currentDir.length - scannedDirectoryText.length) / 2 + 2;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
console.log(chalk.yellow("\n" + " ".repeat(textMargin) + scannedDirectoryText + "\n"));
|
|
14
|
+
console.log(chalk.yellow('* ') + currentDir + chalk.yellow(' *'));
|
|
15
|
+
|
|
16
|
+
if (cliFlags.preview) {
|
|
17
|
+
const previewModeText = 'PREVIEW MODE';
|
|
18
|
+
console.log("\n" + " ".repeat(textMargin) + chalk.blueBright(previewModeText));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { renderBanners };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
function renderEmptyFolderText() {
|
|
4
|
+
const emptyDirectoryText = 'NO FILES TO ORGANIZE';
|
|
5
|
+
const emptyDirectoryTextMargin = 10;
|
|
6
|
+
|
|
7
|
+
console.log("\n" + " ".repeat(emptyDirectoryTextMargin) + chalk.bgRed(" ".repeat(emptyDirectoryText.length + 2)));
|
|
8
|
+
console.log(" ".repeat(emptyDirectoryTextMargin) + chalk.bgRed.whiteBright(" " +emptyDirectoryText+ " "));
|
|
9
|
+
console.log(" ".repeat(emptyDirectoryTextMargin) + chalk.bgRed(" ".repeat(emptyDirectoryText.length + 2)) + "\n");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { renderEmptyFolderText };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
function renderFoldersSummary(summary) {
|
|
4
|
+
const { foldersByExtensionType, folderWasCreated, previewMode } = summary;
|
|
5
|
+
|
|
6
|
+
Object.entries(foldersByExtensionType).forEach(([folder, files]) => {
|
|
7
|
+
if (previewMode) {
|
|
8
|
+
console.log(`\n${chalk.blueBright('o ')}${folder}/`);
|
|
9
|
+
}else{
|
|
10
|
+
console.log(`\n${chalk.green('o ')}${folder}/`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
files.forEach(file => {
|
|
14
|
+
const FILE_TAB = 4;
|
|
15
|
+
const line = `${' '.repeat(FILE_TAB)}• ${file}`;
|
|
16
|
+
|
|
17
|
+
if (previewMode) {
|
|
18
|
+
console.log(chalk.blueBright(line));
|
|
19
|
+
} else {
|
|
20
|
+
console.log(chalk.greenBright(line));
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (folderWasCreated && !previewMode) {
|
|
26
|
+
const foldersCreatedTab = 1;
|
|
27
|
+
console.log(`\n${' '.repeat(foldersCreatedTab)} ${Object.keys(foldersByExtensionType).length} Folder(s) created\n`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { renderFoldersSummary };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { organizeDirectory } from './organizer/organizeDirectory.js';
|
|
2
|
+
import { renderFoldersSummary } from './cli/renderSummary.js';
|
|
3
|
+
import { parseArgs } from './utils/parseArgs.js';
|
|
4
|
+
import { getAvailableTypes } from './rules/byType.js';
|
|
5
|
+
import { cliActions } from './cli/cliActions.js';
|
|
6
|
+
|
|
7
|
+
function main() {
|
|
8
|
+
try {
|
|
9
|
+
const cliArguments = process.argv.slice(2);
|
|
10
|
+
const cliFlags = parseArgs(cliArguments);
|
|
11
|
+
const availableTypes = getAvailableTypes();
|
|
12
|
+
const currentDir = process.cwd();
|
|
13
|
+
|
|
14
|
+
const cliResult = cliActions(cliFlags, availableTypes);
|
|
15
|
+
|
|
16
|
+
if (cliResult.exit) {
|
|
17
|
+
process.exit(cliResult.code);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const summary = organizeDirectory(currentDir, cliFlags);
|
|
21
|
+
|
|
22
|
+
if(!summary) return;
|
|
23
|
+
|
|
24
|
+
renderFoldersSummary(summary);
|
|
25
|
+
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(`\nError: ${error.message}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
main();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { getFilesFromDirectory, ensureDirectoryExists, moveFile } from '../utils/fsHelpers.js';
|
|
3
|
+
import { getFolderNameByExtensionType } from '../rules/byType.js';
|
|
4
|
+
import { shouldSkipFile } from '../utils/filesFilters.js';
|
|
5
|
+
import { renderEmptyFolderText } from '../cli/renderEmptyFolderText.js';
|
|
6
|
+
|
|
7
|
+
function organizeDirectory(baseDir, cliFlags = {}) {
|
|
8
|
+
try{
|
|
9
|
+
const files = getFilesFromDirectory(baseDir);
|
|
10
|
+
const fileIsEmpty = files.length === 0;
|
|
11
|
+
|
|
12
|
+
if(fileIsEmpty){
|
|
13
|
+
renderEmptyFolderText();
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const outputFoldersSummary = {};
|
|
18
|
+
let folderByExtensionTypeCreated = false;
|
|
19
|
+
|
|
20
|
+
files.forEach(file => {
|
|
21
|
+
const fileExtension = path.extname(file.name);
|
|
22
|
+
const folderNameByExtensionType = getFolderNameByExtensionType(fileExtension);
|
|
23
|
+
|
|
24
|
+
if (shouldSkipFile(folderNameByExtensionType, cliFlags)) return;
|
|
25
|
+
|
|
26
|
+
if (!outputFoldersSummary[folderNameByExtensionType]) {
|
|
27
|
+
outputFoldersSummary[folderNameByExtensionType] = [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
outputFoldersSummary[folderNameByExtensionType].push(file.name);
|
|
31
|
+
|
|
32
|
+
if (cliFlags.preview) return;
|
|
33
|
+
|
|
34
|
+
const folderPathByExtensionType = path.join(baseDir, folderNameByExtensionType);
|
|
35
|
+
const filePathByExtension = path.join(folderPathByExtensionType, file.name);
|
|
36
|
+
const folderByExtensionTypeWasCreated = ensureDirectoryExists(folderPathByExtensionType);
|
|
37
|
+
|
|
38
|
+
if (folderByExtensionTypeWasCreated) folderByExtensionTypeCreated = true;
|
|
39
|
+
|
|
40
|
+
moveFile(file.path, filePathByExtension);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
foldersByExtensionType: outputFoldersSummary,
|
|
45
|
+
folderWasCreated: folderByExtensionTypeCreated,
|
|
46
|
+
previewMode: cliFlags.preview,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch(error){
|
|
50
|
+
throw new Error(`Error organizing directory: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { organizeDirectory };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const RULES_BY_TYPE = {
|
|
2
|
+
Images: ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp', '.tiff', '.ico', '.heic', '.psd', '.eps'],
|
|
3
|
+
Documents: ['.pdf', '.doc', '.docx', '.txt', '.md', '.rtf', '.odt', '.tex', '.wpd', '.epub', '.fb2', '.djvu', '.xps', '.pages'],
|
|
4
|
+
Spreadsheets: ['.xls', '.xlsx', '.csv', '.ods', '.tsv', '.xlsm', '.xltx', '.xltm', '.xlsb', '.xlam', '.xla', '.xlw', '.xlc', '.xlt'],
|
|
5
|
+
Presentations: ['.ppt', '.pptx', '.odp', '.key', '.ppsx', '.potx', '.pptm', '.ppsm', '.potm', '.pps', '.pot', '.ppa', '.thmx', '.sldx', '.sldm'],
|
|
6
|
+
Archives: ['.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.xz', '.tgz', '.tbz2'],
|
|
7
|
+
Audio: ['.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a'],
|
|
8
|
+
Video: ['.mp4', '.mov', '.avi', '.mkv', '.webm', '.wmv', '.flv']
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const DEFAULT_FOLDER_NAME = 'Others';
|
|
12
|
+
|
|
13
|
+
function getFolderNameByExtensionType(extension) {
|
|
14
|
+
const extensionToLowerCase = extension.toLowerCase();
|
|
15
|
+
|
|
16
|
+
for (const [folderName, extensionsList] of Object.entries(RULES_BY_TYPE)) {
|
|
17
|
+
if (extensionsList.includes(extensionToLowerCase)) {
|
|
18
|
+
return folderName;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return DEFAULT_FOLDER_NAME;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getAvailableTypes() {
|
|
26
|
+
return Object.keys(RULES_BY_TYPE).map(availableType => availableType.toLowerCase());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { getFolderNameByExtensionType, getAvailableTypes };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function shouldSkipFile(folder, cliFlags) {
|
|
2
|
+
const { only, exclude } = cliFlags;
|
|
3
|
+
const normalizedFolder = folder.toLowerCase();
|
|
4
|
+
|
|
5
|
+
if (only && normalizedFolder !== only.toLowerCase()) return true;
|
|
6
|
+
if (exclude && normalizedFolder === exclude.toLowerCase()) return true;
|
|
7
|
+
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export { shouldSkipFile };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
function getFilesFromDirectory(dirPath) {
|
|
5
|
+
const dirPathList = fs.readdirSync(dirPath);
|
|
6
|
+
|
|
7
|
+
return dirPathList
|
|
8
|
+
.map(dirPathItemName => {
|
|
9
|
+
const fullPath = path.join(dirPath, dirPathItemName);
|
|
10
|
+
const stats = fs.statSync(fullPath);
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
name: dirPathItemName,
|
|
14
|
+
path: fullPath,
|
|
15
|
+
isFile: stats.isFile()
|
|
16
|
+
};
|
|
17
|
+
})
|
|
18
|
+
.filter(dirPathItemName => dirPathItemName.isFile);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ensureDirectoryExists(dirPath) {
|
|
22
|
+
if (!fs.existsSync(dirPath)) {
|
|
23
|
+
fs.mkdirSync(dirPath);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function moveFile(sourcePath, targetPath) {
|
|
30
|
+
fs.renameSync(sourcePath, targetPath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { getFilesFromDirectory, ensureDirectoryExists, moveFile };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
|
|
3
|
+
function showHelp(availableTypes) {
|
|
4
|
+
console.log(`
|
|
5
|
+
${chalk.blueBright('Auto Organize CLI Help Menu')}
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
auto-organize [${chalk.green('options')}]
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
${chalk.green('--preview, -p')} Show a preview without making changes
|
|
12
|
+
${chalk.green('--only, -o <type>')} Organize only a specific file type
|
|
13
|
+
${chalk.green('--exclude, -e <type>')} Exclude a file type from organization
|
|
14
|
+
${chalk.green('--help, -h')} Show this help message
|
|
15
|
+
|
|
16
|
+
Available types:
|
|
17
|
+
${chalk.yellow(availableTypes.join(', '))}
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
auto-organize ${chalk.green('--preview')} || auto-organize ${chalk.green('-p')}
|
|
21
|
+
|
|
22
|
+
auto-organize ${chalk.green('--only')} ${chalk.yellow('images')} || auto-organize ${chalk.green('-o')} ${chalk.yellow('images')}
|
|
23
|
+
|
|
24
|
+
auto-organize ${chalk.green('--exclude')} ${chalk.yellow('archives')} || auto-organize ${chalk.green('-e')} ${chalk.yellow('archives')}
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { showHelp };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
|
|
3
|
+
function parseArgs(cliArguments) {
|
|
4
|
+
const cliFlags = {
|
|
5
|
+
preview: false,
|
|
6
|
+
only: null,
|
|
7
|
+
exclude: null,
|
|
8
|
+
help: false
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
if (cliArguments.length === 0) {
|
|
12
|
+
return cliFlags;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const allowedFlags = [
|
|
16
|
+
'--preview', '-p',
|
|
17
|
+
'--only', '-o',
|
|
18
|
+
'--exclude', '-e',
|
|
19
|
+
'--help', '-h'
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < cliArguments.length; i++) {
|
|
23
|
+
const cliArgument = cliArguments[i];
|
|
24
|
+
|
|
25
|
+
if (!cliArgument.startsWith('-')) {
|
|
26
|
+
throw new Error(`\n'${chalk.red(cliArgument)}' is invalid. Run ${chalk.green('auto-organize --help')} to see available options.`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!allowedFlags.includes(cliArgument)) {
|
|
30
|
+
throw new Error(`\nUnknown flag '${chalk.red(cliArgument)}'. Run ${chalk.green('auto-organize --help')} to see available flags.`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (cliArgument === '--preview' || cliArgument === '-p') {
|
|
34
|
+
cliFlags.preview = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (cliArgument === '--help' || cliArgument === '-h') {
|
|
39
|
+
cliFlags.help = true;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if (cliArgument === '--only' || cliArgument === '-o') {
|
|
45
|
+
const onlyFlagArgument = cliArguments[i + 1];
|
|
46
|
+
|
|
47
|
+
if (!onlyFlagArgument || onlyFlagArgument.startsWith('-')) {
|
|
48
|
+
throw new Error(chalk.red(`\n--only requires a valid type.`));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
cliFlags.only = onlyFlagArgument.toLowerCase();
|
|
52
|
+
i++;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (cliArgument === '--exclude' || cliArgument === '-e') {
|
|
57
|
+
const excludeFlagArgument = cliArguments[i + 1];
|
|
58
|
+
|
|
59
|
+
if (!excludeFlagArgument || excludeFlagArgument.startsWith('-')) {
|
|
60
|
+
throw new Error(chalk.red(`\n--exclude requires a valid type.`));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
cliFlags.exclude = excludeFlagArgument.toLowerCase();
|
|
64
|
+
i++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return cliFlags;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { parseArgs };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
function validateFlags(cliFlags) {
|
|
4
|
+
if (!cliFlags || typeof cliFlags !== 'object') {
|
|
5
|
+
throw new Error("Missing or invalid 'cliFlags'");
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function validateTypes(availableTypes) {
|
|
10
|
+
if (!Array.isArray(availableTypes) || availableTypes.length === 0) {
|
|
11
|
+
throw new Error("Missing or invalid 'availableTypes'");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function validateFlagsAndTypes(cliFlags, availableTypes) {
|
|
16
|
+
const { only, exclude } = cliFlags;
|
|
17
|
+
|
|
18
|
+
if (only && !availableTypes.includes(only)) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`'${chalk.red(only)}' is not a valid type for ${chalk.green('--only')}\n` +
|
|
21
|
+
`Valid types: ${chalk.yellow(availableTypes.join(', '))}`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (exclude && !availableTypes.includes(exclude)) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`'${chalk.red(exclude)}' is not a valid type for ${chalk.green('--exclude')}\n` +
|
|
28
|
+
`Valid types: ${chalk.yellow(availableTypes.join(', '))}`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { validateFlags, validateTypes, validateFlagsAndTypes };
|