@abdess76/i18nkit 1.0.3 → 1.0.5
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/CHANGELOG.md +18 -0
- package/LICENSE +21 -21
- package/README.md +174 -15
- package/bin/commands/backup.js +226 -0
- package/bin/commands/extract.js +2 -2
- package/bin/core/applier-utils.js +8 -9
- package/bin/core/applier.js +90 -14
- package/bin/core/args.js +17 -0
- package/bin/core/backup/cleanup.js +176 -0
- package/bin/core/backup/constants.js +114 -0
- package/bin/core/backup/file-ops.js +188 -0
- package/bin/core/backup/gitignore.js +173 -0
- package/bin/core/backup/index.js +189 -0
- package/bin/core/backup/manifest.js +185 -0
- package/bin/core/backup/restore.js +216 -0
- package/bin/core/backup/session.js +258 -0
- package/bin/core/backup.js +91 -41
- package/bin/core/command-interface.js +1 -0
- package/bin/core/file-walker.js +26 -12
- package/bin/core/fs-adapter.js +10 -0
- package/bin/core/help-generator.js +1 -1
- package/bin/core/index.js +0 -4
- package/bin/core/json-utils.js +10 -6
- package/bin/core/key-generator.js +12 -12
- package/bin/core/orphan-finder.js +12 -15
- package/bin/core/paths.js +4 -4
- package/bin/core/plugin-resolver-utils.js +1 -1
- package/bin/core/translator.js +17 -8
- package/bin/plugins/adapter-transloco.js +3 -2
- package/bin/plugins/parser-primeng.js +1 -1
- package/bin/plugins/provider-deepl.js +22 -8
- package/package.json +1 -1
- package/bin/core/types.js +0 -297
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
6
6
|
and this project adheres to
|
|
7
7
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [1.0.4] - 05-01-2026
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Backup system with session-linked reports
|
|
14
|
+
- Automatic report archival in backup sessions
|
|
15
|
+
- Restore/rollback capabilities from backup sessions
|
|
16
|
+
- Auto-cleanup of old backup sessions
|
|
17
|
+
- Gitignore auto-management for `.i18nkit` directory
|
|
18
|
+
- Manifest schema with `reportFile` field for audit trail
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Backup directory renamed from `.i18n` to `.i18nkit`
|
|
23
|
+
- Report files now stored alongside backup sessions
|
|
24
|
+
- Improved session status tracking (pending, ready, in_progress, completed,
|
|
25
|
+
failed)
|
|
26
|
+
|
|
9
27
|
## [1.0.3] - 05-01-2026
|
|
10
28
|
|
|
11
29
|
### Changed
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Abdessamad DERRAZ
|
|
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.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Abdessamad DERRAZ
|
|
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.md
CHANGED
|
@@ -50,7 +50,7 @@ npm install --save-dev @abdess76/i18nkit
|
|
|
50
50
|
"i18n:apply": "i18nkit --auto-apply --init-langs en,fr",
|
|
51
51
|
"i18n:check": "i18nkit --check-sync --strict",
|
|
52
52
|
"i18n:orphans": "i18nkit --find-orphans --strict",
|
|
53
|
-
"i18n:translate": "i18nkit --translate fr
|
|
53
|
+
"i18n:translate": "i18nkit --translate en:fr",
|
|
54
54
|
"i18n:watch": "i18nkit --watch",
|
|
55
55
|
"i18n:ci": "npm run i18n:check && npm run i18n:orphans"
|
|
56
56
|
}
|
|
@@ -88,7 +88,7 @@ npm run i18n:ci
|
|
|
88
88
|
<input [placeholder]="'home.forms.enter_your_name' | transloco" />
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
**Generated `
|
|
91
|
+
**Generated `en.json`:**
|
|
92
92
|
|
|
93
93
|
```json
|
|
94
94
|
{
|
|
@@ -102,13 +102,26 @@ npm run i18n:ci
|
|
|
102
102
|
|
|
103
103
|
## Commands
|
|
104
104
|
|
|
105
|
+
### Core Commands
|
|
106
|
+
|
|
107
|
+
| Command | Description |
|
|
108
|
+
| -------------- | ---------------------------------------- |
|
|
109
|
+
| `extract` | Extract i18n strings from source files |
|
|
110
|
+
| `check-sync` | Compare language files for missing keys |
|
|
111
|
+
| `find-orphans` | Find translation keys not used in source |
|
|
112
|
+
| `translate` | Translate a language file via API |
|
|
113
|
+
| `watch` | Watch for file changes and re-run |
|
|
114
|
+
| `backup` | Manage backup sessions and restore files |
|
|
115
|
+
|
|
116
|
+
### NPM Scripts Examples
|
|
117
|
+
|
|
105
118
|
| Script | Command | Description |
|
|
106
119
|
| ------------------------ | --------------------------- | ---------------------------- |
|
|
107
120
|
| `npm run i18n` | `--lang fr --merge` | Extract, merge with existing |
|
|
108
121
|
| `npm run i18n:apply` | `--auto-apply --init-langs` | Extract + replace + create |
|
|
109
122
|
| `npm run i18n:check` | `--check-sync --strict` | Validate files are in sync |
|
|
110
123
|
| `npm run i18n:orphans` | `--find-orphans --strict` | Find unused keys |
|
|
111
|
-
| `npm run i18n:translate` | `--translate fr
|
|
124
|
+
| `npm run i18n:translate` | `--translate en:fr` | Translate via API |
|
|
112
125
|
| `npm run i18n:watch` | `--watch` | Re-run on file changes |
|
|
113
126
|
|
|
114
127
|
## Configuration
|
|
@@ -188,6 +201,105 @@ jobs:
|
|
|
188
201
|
- run: npm run i18n:ci
|
|
189
202
|
```
|
|
190
203
|
|
|
204
|
+
## Backup & Restore
|
|
205
|
+
|
|
206
|
+
Source files are backed up before modification. Full session management with
|
|
207
|
+
restore, cleanup, and audit capabilities.
|
|
208
|
+
|
|
209
|
+
### Automatic Backup
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# Backup enabled by default with --auto-apply
|
|
213
|
+
npm run i18n:apply
|
|
214
|
+
|
|
215
|
+
# Disable backup if needed
|
|
216
|
+
npx i18nkit --auto-apply --no-backup
|
|
217
|
+
|
|
218
|
+
# Initialize backup structure explicitly
|
|
219
|
+
npx i18nkit --init-backups --auto-gitignore
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Session Structure
|
|
223
|
+
|
|
224
|
+
```text
|
|
225
|
+
.i18nkit/
|
|
226
|
+
├── .gitignore # Auto-managed (excludes backups/, report.json)
|
|
227
|
+
├── report.json # Current extraction report
|
|
228
|
+
└── backups/
|
|
229
|
+
└── 2026-01-05_15-39-28_apply_ae9c/
|
|
230
|
+
├── manifest.json # Session metadata with status tracking
|
|
231
|
+
├── report.json # Archived extraction report
|
|
232
|
+
└── src/ # Original source files
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Session Management
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# List all backup sessions
|
|
239
|
+
npx i18nkit --list-backups
|
|
240
|
+
|
|
241
|
+
# Show detailed info for a session
|
|
242
|
+
npx i18nkit --backup-info 2026-01-05_15-39-28_apply_ae9c
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Restore from Backup
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# Restore specific session
|
|
249
|
+
npx i18nkit --restore 2026-01-05_15-39-28_apply_ae9c
|
|
250
|
+
|
|
251
|
+
# Restore latest session
|
|
252
|
+
npx i18nkit --restore-latest
|
|
253
|
+
|
|
254
|
+
# Preview restore without applying
|
|
255
|
+
npx i18nkit --restore-latest --dry-run
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Manual Cleanup
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Clean old sessions (interactive)
|
|
262
|
+
npx i18nkit --cleanup-backups
|
|
263
|
+
|
|
264
|
+
# Keep only last 5 sessions
|
|
265
|
+
npx i18nkit --cleanup-backups --keep 5
|
|
266
|
+
|
|
267
|
+
# Remove sessions older than 7 days
|
|
268
|
+
npx i18nkit --cleanup-backups --max-age 7
|
|
269
|
+
|
|
270
|
+
# Combine options
|
|
271
|
+
npx i18nkit --cleanup-backups --keep 3 --max-age 14
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Auto-Cleanup Configuration
|
|
275
|
+
|
|
276
|
+
Old sessions are automatically cleaned up based on configuration.
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
// i18nkit.config.js
|
|
280
|
+
module.exports = {
|
|
281
|
+
backup: {
|
|
282
|
+
enabled: true,
|
|
283
|
+
maxSessions: 10, // Keep max 10 sessions
|
|
284
|
+
retentionDays: 30, // Delete sessions older than 30 days
|
|
285
|
+
autoCleanup: true, // Auto-cleanup on each run
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Session Status
|
|
291
|
+
|
|
292
|
+
Sessions track their lifecycle status:
|
|
293
|
+
|
|
294
|
+
| Status | Description |
|
|
295
|
+
| ------------- | -------------------------------- |
|
|
296
|
+
| `pending` | Session created, not started |
|
|
297
|
+
| `backing_up` | Backup in progress |
|
|
298
|
+
| `in_progress` | Apply operation running |
|
|
299
|
+
| `completed` | Successfully finished |
|
|
300
|
+
| `failed` | Error occurred (check manifest) |
|
|
301
|
+
| `restored` | Files restored from this session |
|
|
302
|
+
|
|
191
303
|
## Key Mapping
|
|
192
304
|
|
|
193
305
|
Override auto-generated keys with `.i18n-keys.json`:
|
|
@@ -251,23 +363,70 @@ implementations.
|
|
|
251
363
|
|
|
252
364
|
## Options
|
|
253
365
|
|
|
366
|
+
### Global Options
|
|
367
|
+
|
|
254
368
|
```text
|
|
369
|
+
--config <path> Path to config file (default: i18nkit.config.js)
|
|
255
370
|
--src <path> Source directory (default: src/app)
|
|
256
|
-
--
|
|
257
|
-
--lang <code>
|
|
258
|
-
--
|
|
371
|
+
--locales <path> Locales directory (default: src/assets/i18n)
|
|
372
|
+
--default-lang <code> Default language (default: fr)
|
|
373
|
+
--dry-run Preview changes without writing
|
|
374
|
+
--verbose Detailed output
|
|
375
|
+
--help, -h Show help
|
|
376
|
+
--version, -v Show version
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Extraction Options
|
|
380
|
+
|
|
381
|
+
```text
|
|
382
|
+
--lang <code> Language code for output file
|
|
383
|
+
--format <type> Output format: nested | flat (default: nested)
|
|
259
384
|
--merge Merge with existing translations
|
|
260
|
-
--auto-apply Extract
|
|
385
|
+
--auto-apply Extract AND apply pipes in one command
|
|
261
386
|
--init-langs <codes> Create language files (e.g., en,fr,es)
|
|
262
|
-
--
|
|
263
|
-
--
|
|
264
|
-
--
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
387
|
+
--include-translated Include already translated strings
|
|
388
|
+
--extract-ts-objects Extract from TypeScript object literals
|
|
389
|
+
--skip-translated Skip already translated strings (default: true)
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Validation Options
|
|
393
|
+
|
|
394
|
+
```text
|
|
395
|
+
--check-sync Compare language files for missing keys
|
|
396
|
+
--find-orphans Find unused translation keys
|
|
397
|
+
--strict Exit with error code 1 on issues
|
|
269
398
|
--ci CI mode (strict + json output)
|
|
270
|
-
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Translation Options
|
|
402
|
+
|
|
403
|
+
```text
|
|
404
|
+
--translate <src:tgt> Translate via API (e.g., fr:en)
|
|
405
|
+
--deepl Use DeepL API (requires DEEPL_API_KEY)
|
|
406
|
+
--mymemory Use MyMemory API (default)
|
|
407
|
+
--email <email> Email for MyMemory rate limits
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Backup Options
|
|
411
|
+
|
|
412
|
+
```text
|
|
413
|
+
--backup Enable backup before modifications (default)
|
|
414
|
+
--no-backup Disable backup
|
|
415
|
+
--list-backups List all backup sessions
|
|
416
|
+
--backup-info <id> Show backup session details
|
|
417
|
+
--restore [id] Restore from backup (latest if no id)
|
|
418
|
+
--restore-latest Restore from most recent backup
|
|
419
|
+
--cleanup-backups Remove old backup sessions
|
|
420
|
+
--keep <n> Keep last n sessions (default: 10)
|
|
421
|
+
--max-age <days> Max session age in days (default: 30)
|
|
422
|
+
--init-backups Initialize backup directory structure
|
|
423
|
+
--auto-gitignore Auto-add .i18nkit to .gitignore
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Watch Options
|
|
427
|
+
|
|
428
|
+
```text
|
|
429
|
+
--watch Watch mode for file changes
|
|
271
430
|
```
|
|
272
431
|
|
|
273
432
|
## Exit Codes
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview CLI commands for backup management.
|
|
5
|
+
* @module commands/backup
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const backup = require('../core/backup');
|
|
9
|
+
|
|
10
|
+
function formatDate(isoString) {
|
|
11
|
+
return new Date(isoString).toLocaleString();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getSessionAgeString(session) {
|
|
15
|
+
const preview = backup.getCleanupPreview(process.cwd());
|
|
16
|
+
const found = preview.toKeep.find(s => s.id === session.id);
|
|
17
|
+
const hasAge = found?.age !== undefined;
|
|
18
|
+
return hasAge ? `${found.age}d` : '';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function formatSessionRow(session) {
|
|
22
|
+
const status = session.status.padEnd(12);
|
|
23
|
+
const files = String(session.fileCount).padStart(3);
|
|
24
|
+
const age = getSessionAgeString(session).padStart(4);
|
|
25
|
+
return `${session.id} | ${status} | ${files} | ${age} | ${session.command || ''}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function logListHeader(log) {
|
|
29
|
+
log('\nBackup Sessions:');
|
|
30
|
+
log('-'.repeat(100));
|
|
31
|
+
log('ID | Status | Files | Age | Command');
|
|
32
|
+
log('-'.repeat(100));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function logListFooter(sessions, log) {
|
|
36
|
+
log('-'.repeat(100));
|
|
37
|
+
log(`Total: ${sessions.length} sessions\n`);
|
|
38
|
+
const latestId = backup.Session.getLatestId(process.cwd());
|
|
39
|
+
if (latestId) {
|
|
40
|
+
log(`Latest: ${latestId}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function listBackups(ctx) {
|
|
45
|
+
const { cwd, log } = ctx;
|
|
46
|
+
const sessions = backup.listBackupSessions(cwd);
|
|
47
|
+
if (sessions.length === 0) {
|
|
48
|
+
log('No backup sessions found');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
logListHeader(log);
|
|
52
|
+
sessions.forEach(session => log(formatSessionRow(session)));
|
|
53
|
+
logListFooter(sessions, log);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function logInfoHeader(manifest, log) {
|
|
57
|
+
log('\nSession Details:');
|
|
58
|
+
log('-'.repeat(60));
|
|
59
|
+
log(`ID: ${manifest.id}`);
|
|
60
|
+
log(`Status: ${manifest.status}`);
|
|
61
|
+
log(`Command: ${manifest.command}`);
|
|
62
|
+
log(`Timestamp: ${formatDate(manifest.timestamp)}`);
|
|
63
|
+
log(`Files: ${manifest.files.length}`);
|
|
64
|
+
log('-'.repeat(60));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function logInfoFiles(files, log) {
|
|
68
|
+
if (files.length > 0) {
|
|
69
|
+
log('\nBacked Up Files:');
|
|
70
|
+
files.forEach(file => log(` ${file.original} (${file.size} bytes)`));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function showBackupInfo(ctx) {
|
|
75
|
+
const { cwd, sessionId, log } = ctx;
|
|
76
|
+
const session = backup.getBackupSession(cwd, sessionId);
|
|
77
|
+
if (!session) {
|
|
78
|
+
log(`Session not found: ${sessionId}`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const { manifest } = session;
|
|
82
|
+
logInfoHeader(manifest, log);
|
|
83
|
+
logInfoFiles(manifest.files, log);
|
|
84
|
+
if (manifest.error) {
|
|
85
|
+
log(`\nError: ${manifest.error.message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function logRestoreResult(result, log) {
|
|
90
|
+
log(`\nRestored: ${result.restored} files`);
|
|
91
|
+
if (result.skipped > 0) {
|
|
92
|
+
log(`Skipped: ${result.skipped} files`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function restoreBackup(ctx) {
|
|
97
|
+
const { cwd, sessionId, dryRun, verbose, log } = ctx;
|
|
98
|
+
const targetId = sessionId || backup.Session.getLatestId(cwd);
|
|
99
|
+
if (!targetId) {
|
|
100
|
+
log('No backup sessions found');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const prefix = dryRun ? '[DRY RUN] Would restore' : 'Restoring';
|
|
104
|
+
log(`\n${prefix} from: ${targetId}`);
|
|
105
|
+
const result = backup.restoreSession(cwd, targetId, { dryRun, verbose, log });
|
|
106
|
+
logRestoreResult(result, log);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function logCleanupPreview(preview, log) {
|
|
110
|
+
log('\nCleanup Preview:');
|
|
111
|
+
log(`Would keep: ${preview.toKeep.length} sessions`);
|
|
112
|
+
log(`Would delete: ${preview.toDelete.length} sessions`);
|
|
113
|
+
if (preview.toDelete.length > 0) {
|
|
114
|
+
log('\nSessions to delete:');
|
|
115
|
+
preview.toDelete.forEach(s => log(` ${s.id} (${s.age} days old, ${s.fileCount} files)`));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function logCleanupResult(result, log) {
|
|
120
|
+
log(`\nDeleted: ${result.deleted} sessions`);
|
|
121
|
+
log(`Kept: ${result.kept} sessions`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function cleanupBackups(ctx) {
|
|
125
|
+
const { cwd, maxSessions = 10, maxAgeDays = 30, dryRun, verbose, log } = ctx;
|
|
126
|
+
if (dryRun) {
|
|
127
|
+
const preview = backup.getCleanupPreview(cwd, { maxSessions, maxAgeDays });
|
|
128
|
+
logCleanupPreview(preview, log);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const result = backup.cleanupOldSessions(cwd, { maxSessions, maxAgeDays, verbose, log });
|
|
132
|
+
logCleanupResult(result, log);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function initBackupStructure(ctx) {
|
|
136
|
+
const { cwd, autoAddGitignore = false, verbose, log } = ctx;
|
|
137
|
+
const result = backup.initializeBackupStructure(cwd, { autoAddGitignore, verbose, log });
|
|
138
|
+
log('\nBackup structure initialized');
|
|
139
|
+
if (result.suggestion) {
|
|
140
|
+
log(`\nTip: ${result.suggestion.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function parseSessionIdArg(args, flagIndex) {
|
|
145
|
+
const nextArg = args[flagIndex + 1];
|
|
146
|
+
const isValidArg = nextArg && !nextArg.startsWith('--');
|
|
147
|
+
return isValidArg ? nextArg : null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function parseIntArg(args, flag, defaultValue) {
|
|
151
|
+
const idx = args.indexOf(flag);
|
|
152
|
+
const hasFlag = idx !== -1;
|
|
153
|
+
return hasFlag ? parseInt(args[idx + 1], 10) : defaultValue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const HANDLERS = {
|
|
157
|
+
'--list-backups': (_args, ctx) => listBackups(ctx),
|
|
158
|
+
'--backup-info': (args, ctx) => {
|
|
159
|
+
const sessionId = parseSessionIdArg(args, args.indexOf('--backup-info'));
|
|
160
|
+
if (!sessionId) {
|
|
161
|
+
ctx.log('Usage: i18nkit --backup-info <session-id>');
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
return showBackupInfo({ ...ctx, sessionId });
|
|
165
|
+
},
|
|
166
|
+
'--restore': (args, ctx) => {
|
|
167
|
+
const sessionId = parseSessionIdArg(args, args.indexOf('--restore'));
|
|
168
|
+
return restoreBackup({ ...ctx, sessionId });
|
|
169
|
+
},
|
|
170
|
+
'--cleanup-backups': (args, ctx) => {
|
|
171
|
+
const maxSessions = parseIntArg(args, '--keep', 10);
|
|
172
|
+
const maxAgeDays = parseIntArg(args, '--max-age', 30);
|
|
173
|
+
return cleanupBackups({ ...ctx, maxSessions, maxAgeDays });
|
|
174
|
+
},
|
|
175
|
+
'--init-backups': (args, ctx) => {
|
|
176
|
+
const autoAddGitignore = args.includes('--auto-gitignore');
|
|
177
|
+
return initBackupStructure({ ...ctx, autoAddGitignore });
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
function handleBackupCommand(args, ctx) {
|
|
182
|
+
const cwd = ctx.cwd || process.cwd();
|
|
183
|
+
const log = ctx.log || console.log;
|
|
184
|
+
const baseCtx = {
|
|
185
|
+
cwd,
|
|
186
|
+
log,
|
|
187
|
+
dryRun: args.includes('--dry-run'),
|
|
188
|
+
verbose: args.includes('--verbose'),
|
|
189
|
+
args,
|
|
190
|
+
};
|
|
191
|
+
for (const [flag, handler] of Object.entries(HANDLERS)) {
|
|
192
|
+
if (args.includes(flag)) {
|
|
193
|
+
return handler(args, baseCtx);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function isBackupCommand(args) {
|
|
200
|
+
return Object.keys(HANDLERS).some(cmd => args.includes(cmd));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
name: 'backup',
|
|
205
|
+
aliases: ['--list-backups', '--restore', '--cleanup-backups', '--backup-info', '--init-backups'],
|
|
206
|
+
category: 'maintenance',
|
|
207
|
+
description: 'Manage backup sessions and restore files',
|
|
208
|
+
options: [
|
|
209
|
+
{ flag: '--list-backups', description: 'List all backup sessions' },
|
|
210
|
+
{ flag: '--backup-info <id>', description: 'Show backup session details' },
|
|
211
|
+
{ flag: '--restore [id]', description: 'Restore from backup (latest if no id)' },
|
|
212
|
+
{ flag: '--cleanup-backups', description: 'Remove old backup sessions' },
|
|
213
|
+
{ flag: '--keep <n>', description: 'Keep last n sessions (default: 10)' },
|
|
214
|
+
{ flag: '--max-age <days>', description: 'Max age in days (default: 30)' },
|
|
215
|
+
{ flag: '--init-backups', description: 'Initialize backup structure' },
|
|
216
|
+
{ flag: '--auto-gitignore', description: 'Auto-add to .gitignore' },
|
|
217
|
+
],
|
|
218
|
+
examples: [
|
|
219
|
+
'i18nkit --list-backups',
|
|
220
|
+
'i18nkit --restore',
|
|
221
|
+
'i18nkit --cleanup-backups --keep 5 --dry-run',
|
|
222
|
+
],
|
|
223
|
+
run: ctx => handleBackupCommand(ctx.args, ctx),
|
|
224
|
+
isBackupCommand,
|
|
225
|
+
handleBackupCommand,
|
|
226
|
+
};
|
package/bin/commands/extract.js
CHANGED
|
@@ -109,8 +109,8 @@ async function loadMergeResult(ctx) {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
function getAutoApplyOpts(ctx) {
|
|
112
|
-
const { srcDir, backupDir, adapter, backup, dryRun, verbose, interactive, log } = ctx;
|
|
113
|
-
return { srcDir, backupDir, adapter, backup, dryRun, verbose, interactive, log };
|
|
112
|
+
const { srcDir, backupDir, reportDir, adapter, backup, dryRun, verbose, interactive, log } = ctx;
|
|
113
|
+
return { srcDir, backupDir, reportDir, adapter, backup, dryRun, verbose, interactive, log };
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
async function runAutoApply(findings, ctx) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('./fs-adapter');
|
|
4
4
|
const readline = require('readline');
|
|
5
|
-
const {
|
|
5
|
+
const { getBackupFiles } = require('./backup');
|
|
6
6
|
|
|
7
7
|
const isValidFinding = f => f.file && f.text && f.key && !f.context?.startsWith('ts_');
|
|
8
8
|
|
|
@@ -68,12 +68,12 @@ function applyReplacementsToContent(content, fileFindings, adapter) {
|
|
|
68
68
|
let result = content;
|
|
69
69
|
let count = 0;
|
|
70
70
|
for (const finding of fileFindings) {
|
|
71
|
-
const transformed = adapter.transform(
|
|
72
|
-
result,
|
|
73
|
-
finding.rawText || finding.text,
|
|
74
|
-
finding.key,
|
|
75
|
-
finding.context,
|
|
76
|
-
);
|
|
71
|
+
const transformed = adapter.transform({
|
|
72
|
+
content: result,
|
|
73
|
+
rawText: finding.rawText || finding.text,
|
|
74
|
+
key: finding.key,
|
|
75
|
+
context: finding.context,
|
|
76
|
+
});
|
|
77
77
|
result = transformed.content;
|
|
78
78
|
count += transformed.replacements;
|
|
79
79
|
}
|
|
@@ -81,8 +81,7 @@ function applyReplacementsToContent(content, fileFindings, adapter) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
function tryUpdateTsImports(tsFile, opts) {
|
|
84
|
-
const {
|
|
85
|
-
createBackup(tsFile, backupDir, { enabled: backup, dryRun });
|
|
84
|
+
const { adapter, dryRun = false } = opts;
|
|
86
85
|
const tsContent = fs.readFileSync(tsFile, 'utf-8');
|
|
87
86
|
const updatedTs = adapter.updateImports(tsContent);
|
|
88
87
|
if (updatedTs === tsContent) {
|