Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 12 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@

# Backup Script CLI (English)

A TypeScript command-line utility (CLI) to quickly create ZIP backup archives of your project, allowing granular selection of files and folders.
A TypeScript command-line utility (CLI) to quickly create and extract ZIP backup archives of your project, allowing granular selection of files and folders and secure encryption.

## 🚀 Features

- **Compression & Decompression**: Choose whether to create a new backup or extract an existing one from a simple initial menu.
- **Interactive Selection**: Choose which files and folders from the root to include in the archive.
- **Automatic Encryption**: Generates a secure 14-character password to protect the ZIP archive.
- **Secure Extraction**: Allows extracting encrypted ZIP files by providing the correct password.
- **Smart Exclusions**: Automatically excludes heavy or unnecessary folders like `node_modules`, `dist`, `.git`, and other existing backup files.
- **Optimized Compression**: Uses `archiver` with compression level 9 (Zlib).
- **Customizable Naming**: Suggests a name based on today's date (`backup_YYYY-MM-DD.zip`), but allows customization.
- **Overwrite Protection**: Asks for confirmation before overwriting an existing file.
- **Real-time Feedback**: Displays progress percentage and the size of processed data.
- **Real-time Feedback**: Displays progress percentage and the size of processed data during compression.
- **Code Quality**: Linting with ESLint and formatting with Prettier.
- **Automated Testing**: Test suite with Jest to ensure logic correctness.
- **Continuous Integration**: GitHub Actions configured for automatic lint, build, and test on every push.
Expand Down Expand Up @@ -83,12 +85,6 @@ After the build, you can run the standard version:
npm start
```

Or the minified version:

```bash
npm run start:minify
```

### Quality and Testing

To lint the code:
Expand All @@ -111,7 +107,7 @@ npm test

## 🏗️ Project Structure

- `src/main.ts`: Main entry point of the script.
- `src/main.ts`: Main entry point of the script (manages the main menu).
- `src/utils/utils.ts`: Utility functions extracted for testability.
- `tests/`: Automated test suite.
- `dist/`: Contains bundles generated after the build.
Expand All @@ -123,6 +119,7 @@ npm test

- [archiver](https://www.npmjs.com/package/archiver): ZIP archive generation.
- [archiver-zip-encryptable](https://www.npmjs.com/package/archiver-zip-encryptable): Password encryption support.
- [adm-zip](https://www.npmjs.com/package/adm-zip): Estrazione robusta di file ZIP con supporto password.
- [inquirer](https://www.npmjs.com/package/inquirer): Interactive CLI interface.
- [glob](https://www.npmjs.com/package/glob): Pattern matching for files.
- [esbuild](https://esbuild.github.io/): Ultra-fast bundler for compilation.
Expand All @@ -145,17 +142,19 @@ _Created to simplify local backup management during development._

# Backup Script CLI (Italiano)

Un'utilità a riga di comando (CLI) in TypeScript per creare rapidamente archivi ZIP di backup del progetto, permettendo la selezione granulare dei file e delle cartelle.
Un'utilità a riga di comando (CLI) in TypeScript per creare ed estrarre rapidamente archivi ZIP di backup del progetto, permettendo la selezione granulare dei file e delle cartelle e la cifratura sicura.

## 🚀 Caratteristiche

- **Compressione e Decompressione**: Scegli se creare un nuovo backup o estrarne uno esistente tramite un semplice menu iniziale.
- **Selezione Interattiva**: Scegli quali file e cartelle della root includere nell'archivio.
- **Cifratura Automatica**: Genera una password sicura a 14 caratteri per proteggere l'archivio ZIP.
- **Estrazione Sicura**: Permette di decomprimere file ZIP cifrati fornendo la password corretta.
- **Esclusioni Intelligenti**: Esclude automaticamente cartelle pesanti o inutili come `node_modules`, `dist`, `.git` e altri file di backup esistenti.
- **Compressione Ottimizzata**: Utilizza `archiver` con livello di compressione 9 (Zlib).
- **Naming Personalizzabile**: Suggerisce un nome basato sulla data odierna (`backup_YYYY-MM-DD.zip`), ma permette la personalizzazione.
- **Protezione Sovrascrittura**: Chiede conferma prima di sovrascrivere un file esistente.
- **Feedback in Tempo Reale**: Mostra la percentuale di avanzamento e la dimensione dei dati processati.
- **Feedback in Tempo Reale**: Mostra la percentuale di avanzamento e la dimensione dei dati processati durante la compressione.
- **Qualità del Codice**: Linting con ESLint e formattazione con Prettier.
- **Test Automatizzati**: Suite di test con Jest per garantire la correttezza della logica.
- **Continuous Integration**: GitHub Actions configurate per lint, build e test automatici ad ogni push.
Expand Down Expand Up @@ -220,12 +219,6 @@ Dopo la build, puoi eseguire la versione standard:
npm start
```

Oppure la versione minificata:

```bash
npm run start:minify
```

### Qualità e Test

Per eseguire il linting del codice:
Expand All @@ -248,7 +241,7 @@ npm test

## 🏗️ Struttura del Progetto

- `src/main.ts`: Punto di ingresso principale dello script.
- `src/main.ts`: Punto di ingresso principale dello script (gestisce il menu principale).
- `src/utils/utils.ts`: Funzioni di utilità estratte per testabilità.
- `tests/`: Suite di test automatizzati.
- `dist/`: Contiene i bundle generati dopo la build.
Expand All @@ -260,6 +253,7 @@ npm test

- [archiver](https://www.npmjs.com/package/archiver): Generazione di archivi ZIP.
- [archiver-zip-encryptable](https://www.npmjs.com/package/archiver-zip-encryptable): Supporto alla cifratura con password.
- [adm-zip](https://www.npmjs.com/package/adm-zip): Estrazione robusta di file ZIP con supporto password.
- [inquirer](https://www.npmjs.com/package/inquirer): Interfaccia interattiva CLI.
- [glob](https://www.npmjs.com/package/glob): Corrispondenza di pattern per i file.
- [esbuild](https://esbuild.github.io/): Bundler ultra-veloce per la compilazione.
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@
},
"license": "ISC",
"dependencies": {
"adm-zip": "^0.5.17",
"archiver": "^7.0.1",
"archiver-zip-encryptable": "^1.0.10",
"glob": "^13.0.6",
"inquirer": "^8.2.7"
},
"devDependencies": {
"@types/adm-zip": "^0.5.8",
"@types/archiver": "^7.0.0",
"@types/glob": "^9.0.0",
"@types/inquirer": "^8.2.12",
Expand Down
19 changes: 19 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

135 changes: 101 additions & 34 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,26 @@ const path = require("path");
const archiver = require("archiver");
const { glob } = require("glob");
const inquirer = require("inquirer");
const AdmZip = require("adm-zip");
const {
formatFileName,
validateSelection,
filterRootContent,
generatePassword,
formatProgress,
prepareChoices,
filterZipFiles,
} = require("./utils/utils");

// Registra il formato zip-encryptable per archiver
archiver.registerFormat("zip-encryptable", require("archiver-zip-encryptable"));

/**
* Crea un file zip di backup di tutti i file nella directory genitore della cartella src,
* escludendo le cartelle node_modules, dist e altri file di backup.
* Crea un file zip di backup di tutti i file nella directory root del progetto.
*/
async function createBackup(): Promise<void> {
console.log("=== Backup Script CLI ===");
console.log("Questo strumento creerà un archivio ZIP del progetto.");
console.log("Puoi scegliere quali file e cartelle della root includere.\n");

// 1. Identifica la root del progetto (genitore di src)
const projectRoot: string = __dirname;
async function createBackup(projectRoot: string): Promise<void> {
console.log("\n--- Modalità Compressione ---");

// 2. Legge il contenuto della root per permettere la selezione
const rootContent = fs.readdirSync(projectRoot);
const excludeList = [
"node_modules",
Expand Down Expand Up @@ -55,11 +50,9 @@ async function createBackup(): Promise<void> {

const selectedItems = selectionAnswer.selectedItems;

// 3. Genera il nome di default del file di backup
const today: string = new Date().toISOString().slice(0, 10); // Formato YYYY-MM-DD
const today: string = new Date().toISOString().slice(0, 10);
const defaultFileName: string = `backup_${today}.zip`;

// 4. Chiede all'utente il nome del file
const nameAnswer = await inquirer.prompt([
{
type: "input",
Expand All @@ -74,7 +67,6 @@ async function createBackup(): Promise<void> {
const outputFileName: string = nameAnswer.fileName;
const outputFilePath: string = path.join(projectRoot, outputFileName);

// 5. Gestione conferme
if (fs.existsSync(outputFilePath)) {
const overwriteAnswer = await inquirer.prompt([
{
Expand All @@ -91,28 +83,14 @@ async function createBackup(): Promise<void> {
);
return;
}
} else {
const confirmAnswer = await inquirer.prompt([
{
type: "confirm",
name: "confirm",
message: `Confermi l'esecuzione del backup come '${outputFileName}'?`,
default: true,
},
]);

if (!confirmAnswer.confirm) {
console.log("Operazione annullata dall'utente.");
return;
}
}

console.log(`\nInizio del processo di backup: ${outputFileName}`);

const password = generatePassword();
const output = fs.createWriteStream(outputFilePath);
const archive = archiver("zip-encryptable", {
zlib: { level: 9 }, // Imposta il livello di compressione
zlib: { level: 9 },
password: password,
});

Expand All @@ -131,15 +109,13 @@ async function createBackup(): Promise<void> {
},
);

// 6. Trova i file basandosi sulla selezione dell'utente
console.log("Scansione dei file in corso...");

for (const item of selectedItems) {
const fullPath = path.join(projectRoot, item);
const isDirectory = fs.statSync(fullPath).isDirectory();

if (isDirectory) {
// Se è una cartella, usa glob per trovare tutti i file al suo interno
const files: string[] = await glob(`${item}/**/*`, {
cwd: projectRoot,
nodir: true,
Expand All @@ -151,7 +127,6 @@ async function createBackup(): Promise<void> {
archive.file(path.join(projectRoot, file), { name: file });
});
} else {
// Se è un file singolo
archive.file(fullPath, { name: item });
}
}
Expand Down Expand Up @@ -179,8 +154,100 @@ async function createBackup(): Promise<void> {
await closePromise;
}

/**
* Decomprime un file zip selezionato.
*/
async function extractBackup(projectRoot: string): Promise<void> {
console.log("\n--- Modalità Decompressione ---");

const rootContent = fs.readdirSync(projectRoot);
const zipFiles = filterZipFiles(rootContent);

if (zipFiles.length === 0) {
console.log("Nessun file .zip trovato nella cartella corrente.");
return;
}

const selectionAnswer = await inquirer.prompt([
{
type: "list",
name: "selectedZip",
message: "Seleziona il file .zip da decomprimere:",
choices: zipFiles,
},
]);

const selectedZip = selectionAnswer.selectedZip;
const zipPath = path.join(projectRoot, selectedZip);

const passwordAnswer = await inquirer.prompt([
{
type: "input",
name: "password",
message: "Inserisci la password per il file zip:",
},
]);

const password = passwordAnswer.password;

const folderName = `extracted_${selectedZip.replace(".zip", "")}_${Date.now()}`;
const extractPath = path.join(projectRoot, folderName);

console.log(`\nInizio estrazione in: ${folderName}...`);

try {
const zip = new AdmZip(zipPath);
// Nota: extractAllTo richiede la password se i file sono cifrati
zip.extractAllTo(extractPath, true, false, password);
console.log("Estrazione completata con successo!");
} catch (err: unknown) {
if (err instanceof Error) {
console.error(
"\nErrore durante l'estrazione:",
(err as Error).message || err,
);
} else {
console.error("\nErrore sconosciuto durante l'estrazione:", err);
}
if (
fs.existsSync(extractPath) &&
fs.readdirSync(extractPath).length === 0
) {
fs.rmdirSync(extractPath);
}
}
}

/**
* Funzione principale che gestisce il menu iniziale.
*/
async function main(): Promise<void> {
console.log("=== Backup Script CLI ===");

// Utilizziamo process.cwd() per operare nella cartella dove viene lanciato il comando
const projectRoot: string = process.cwd();

const modeAnswer = await inquirer.prompt([
{
type: "list",
name: "mode",
message: "Cosa desideri fare?",
choices: [
{ name: "Comprimere (Crea un backup ZIP)", value: "compress" },
{ name: "Decomprimere (Estrai un backup ZIP)", value: "decompress" },
],
},
]);

if (modeAnswer.mode === "compress") {
await createBackup(projectRoot);
} else {
await extractBackup(projectRoot);
}
}

if (require.main === module) {
createBackup().catch((err: Error) => {
console.error("Errore durante la creazione del backup:", err);
main().catch((err: Error) => {
console.error("Errore fatale:", err);
});
}
Loading
Loading