archiver-ts 0.0.1
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/.draft/vibing.md +7 -0
- package/.prettierrc +5 -0
- package/.scripts/build.js +7 -0
- package/.scripts/e2e.js +3 -0
- package/.scripts/lines.js +53 -0
- package/.scripts/publish.js +5 -0
- package/.scripts/recover.js +154 -0
- package/README.dev.md +124 -0
- package/README.md +82 -0
- package/dist/index.js +2687 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
- package/rollup.config.mjs +46 -0
- package/src/commands/archive.ts +101 -0
- package/src/commands/cd-shell.ts +24 -0
- package/src/commands/check.ts +40 -0
- package/src/commands/command-utils.ts +50 -0
- package/src/commands/config.ts +119 -0
- package/src/commands/index.ts +26 -0
- package/src/commands/list-interactive.ts +201 -0
- package/src/commands/list.ts +118 -0
- package/src/commands/log.ts +102 -0
- package/src/commands/update.ts +78 -0
- package/src/commands/vault.ts +212 -0
- package/src/consts/defaults.ts +32 -0
- package/src/consts/enums.ts +15 -0
- package/src/consts/index.ts +7 -0
- package/src/consts/path-tree.ts +24 -0
- package/src/consts/update.ts +13 -0
- package/src/core/context.ts +281 -0
- package/src/core/initialize.ts +295 -0
- package/src/global.d.ts +76 -0
- package/src/index.ts +30 -0
- package/src/services/archive.ts +579 -0
- package/src/services/audit-logger.ts +33 -0
- package/src/services/check.ts +345 -0
- package/src/services/config.ts +71 -0
- package/src/services/context.ts +46 -0
- package/src/services/log.ts +83 -0
- package/src/services/update.ts +121 -0
- package/src/services/vault.ts +247 -0
- package/src/utils/date.ts +39 -0
- package/src/utils/fs.ts +61 -0
- package/src/utils/json.ts +70 -0
- package/src/utils/parse.ts +63 -0
- package/src/utils/prompt.ts +20 -0
- package/src/utils/terminal.ts +168 -0
- package/tests/commands/cd-marker.test.ts +13 -0
- package/tests/commands/list-interactive.test.ts +26 -0
- package/tests/core/initialize.test.ts +130 -0
- package/tests/e2e/cli.e2e.test.ts +104 -0
- package/tests/e2e/mock-cli.ts +42 -0
- package/tests/services/archive-workflow.test.ts +176 -0
- package/tests/utils/parse.test.ts +44 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +16 -0
package/.draft/vibing.md
ADDED
package/.prettierrc
ADDED
package/.scripts/e2e.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const ROOT = path.resolve('.');
|
|
5
|
+
|
|
6
|
+
const IGNORE_DIRS = new Set(['node_modules', 'dist', 'tests']);
|
|
7
|
+
|
|
8
|
+
const EXTENSIONS = new Set(['.ts', '.tsx', '.css']);
|
|
9
|
+
|
|
10
|
+
let totalLines = 0;
|
|
11
|
+
let fileCount = 0;
|
|
12
|
+
|
|
13
|
+
function countLines(filePath) {
|
|
14
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
15
|
+
return content.split(/\r\n|\n|\r/).length;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function walk(dir) {
|
|
19
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
20
|
+
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
const fullPath = path.join(dir, entry.name);
|
|
23
|
+
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
if (IGNORE_DIRS.has(entry.name)) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
walk(fullPath);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!entry.isFile()) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ext = path.extname(entry.name);
|
|
37
|
+
if (!EXTENSIONS.has(ext)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const lines = countLines(fullPath);
|
|
42
|
+
totalLines += lines;
|
|
43
|
+
fileCount++;
|
|
44
|
+
|
|
45
|
+
console.log(`${lines.toString().padStart(7)} ${path.relative(ROOT, fullPath)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// "lines": "clear && find ./packages -type f \\( -name \"*.ts\" -o -name \"*.tsx\" -o -name \"*.js\" -o -name \"*.mjs\" -o -name \"*.cjs\" \\) | xargs wc -l",
|
|
50
|
+
|
|
51
|
+
walk(ROOT);
|
|
52
|
+
console.log(`Files: ${fileCount} for ${[...EXTENSIONS].join(', ')}`);
|
|
53
|
+
console.log(`Total lines: ${totalLines}`);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
function shellQuote(value) {
|
|
7
|
+
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function hasFlag(name) {
|
|
11
|
+
return process.argv.includes(name);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readOption(name) {
|
|
15
|
+
const index = process.argv.indexOf(name);
|
|
16
|
+
if (index === -1) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
return process.argv[index + 1];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function statusIsArchived(rawStatus) {
|
|
23
|
+
return rawStatus === 'A' || rawStatus === 'Archived';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeEntry(raw) {
|
|
27
|
+
const id = Number(raw.id);
|
|
28
|
+
const vaultId = Number(raw.vid ?? raw.vaultId);
|
|
29
|
+
const item = String(raw.i ?? raw.item ?? '');
|
|
30
|
+
const directory = String(raw.d ?? raw.directory ?? '');
|
|
31
|
+
const status = String(raw.st ?? raw.status ?? '');
|
|
32
|
+
if (!Number.isInteger(id) || id <= 0) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
if (!Number.isInteger(vaultId) || vaultId < 0) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
if (!item || !directory) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
return { id, vaultId, item, directory, status };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readJsonl(filePath) {
|
|
45
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
46
|
+
if (!content.trim()) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return content
|
|
51
|
+
.split(/\r?\n/)
|
|
52
|
+
.map((line) => line.trim())
|
|
53
|
+
.filter((line) => line.length > 0)
|
|
54
|
+
.map((line, index) => {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(line);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new Error(`Invalid JSON at ${filePath}:${index + 1}: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readListTable(rootPath) {
|
|
64
|
+
const jsonlPath = path.join(rootPath, 'core', 'list.jsonl');
|
|
65
|
+
if (fs.existsSync(jsonlPath)) {
|
|
66
|
+
return readJsonl(jsonlPath);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const jsonPath = path.join(rootPath, 'core', 'list.json');
|
|
70
|
+
if (!fs.existsSync(jsonPath)) {
|
|
71
|
+
throw new Error(`Neither list.jsonl nor list.json exists under ${path.join(rootPath, 'core')}.`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const parsed = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
75
|
+
return Array.isArray(parsed) ? parsed : [parsed];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function main() {
|
|
79
|
+
const apply = hasFlag('--apply');
|
|
80
|
+
const force = hasFlag('--force');
|
|
81
|
+
|
|
82
|
+
const rootPath = path.join(os.homedir(), '.archiver-rust');
|
|
83
|
+
|
|
84
|
+
const rows = readListTable(rootPath);
|
|
85
|
+
const entries = rows
|
|
86
|
+
.map((row) => normalizeEntry(row))
|
|
87
|
+
.filter((entry) => entry !== undefined)
|
|
88
|
+
.filter((entry) => statusIsArchived(entry.status))
|
|
89
|
+
.sort((a, b) => a.id - b.id);
|
|
90
|
+
|
|
91
|
+
if (entries.length === 0) {
|
|
92
|
+
console.log(`[INFO] No archived entries found in ${rootPath}.`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let listed = 0;
|
|
97
|
+
let restored = 0;
|
|
98
|
+
let skippedMissing = 0;
|
|
99
|
+
let skippedTargetExists = 0;
|
|
100
|
+
let failed = 0;
|
|
101
|
+
|
|
102
|
+
console.log(`[INFO] root=${rootPath}`);
|
|
103
|
+
console.log(`[INFO] mode=${apply ? 'apply' : 'dry-run'}${force ? ' (force enabled)' : ''}`);
|
|
104
|
+
|
|
105
|
+
for (const entry of entries) {
|
|
106
|
+
const sourcePath = path.join(rootPath, 'vaults', String(entry.vaultId), String(entry.id));
|
|
107
|
+
const targetPath = path.join(entry.directory, entry.item);
|
|
108
|
+
|
|
109
|
+
if (!fs.existsSync(sourcePath)) {
|
|
110
|
+
skippedMissing += 1;
|
|
111
|
+
console.log(`[SKIP][${entry.id}] source missing: ${sourcePath}`);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (fs.existsSync(targetPath) && !force) {
|
|
116
|
+
skippedTargetExists += 1;
|
|
117
|
+
console.log(`[SKIP][${entry.id}] target exists: ${targetPath}`);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const mkdirCommand = `mkdir -p ${shellQuote(path.dirname(targetPath))}`;
|
|
122
|
+
const moveCommand = `mv ${shellQuote(sourcePath)} ${shellQuote(targetPath)}`;
|
|
123
|
+
|
|
124
|
+
listed += 1;
|
|
125
|
+
console.log(`[PLAN][${entry.id}] ${moveCommand}`);
|
|
126
|
+
|
|
127
|
+
if (!apply) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
execSync(mkdirCommand, { stdio: 'inherit' });
|
|
133
|
+
if (force && fs.existsSync(targetPath)) {
|
|
134
|
+
const removeCommand = `rm -rf ${shellQuote(targetPath)}`;
|
|
135
|
+
execSync(removeCommand, { stdio: 'inherit' });
|
|
136
|
+
}
|
|
137
|
+
execSync(moveCommand, { stdio: 'inherit' });
|
|
138
|
+
restored += 1;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
failed += 1;
|
|
141
|
+
console.error(`[FAIL][${entry.id}] ${error.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(
|
|
146
|
+
`[INFO] listed=${listed}, restored=${restored}, skipped_missing=${skippedMissing}, skipped_target_exists=${skippedTargetExists}, failed=${failed}`,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (apply && failed > 0) {
|
|
150
|
+
process.exitCode = 1;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
main();
|
package/README.dev.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# archiver-ts (Developer Guide)
|
|
2
|
+
|
|
3
|
+
This file is for contributors and maintainers.
|
|
4
|
+
|
|
5
|
+
If you are an end user, read `readme.md`.
|
|
6
|
+
|
|
7
|
+
## Tech stack
|
|
8
|
+
|
|
9
|
+
- Node.js >= 20
|
|
10
|
+
- TypeScript (ESM)
|
|
11
|
+
- Commander for CLI
|
|
12
|
+
- Vitest for tests
|
|
13
|
+
- Rollup for build output
|
|
14
|
+
|
|
15
|
+
## Quick start (dev)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install
|
|
19
|
+
npm run check
|
|
20
|
+
npm test
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Run CLI from source:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm run dev -- --help
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Build:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm run build
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Run built CLI:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
node dist/index.js --help
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Repo scripts
|
|
42
|
+
|
|
43
|
+
- `npm run dev`: run CLI from `src/index.ts` via `tsx`
|
|
44
|
+
- `npm run check`: TypeScript type-check (`tsc --noEmit`)
|
|
45
|
+
- `npm test`: unit/e2e tests via Vitest
|
|
46
|
+
- `npm run e2e`: helper wrapper for e2e entry script
|
|
47
|
+
- `npm run build`: clean `dist` then build bundle with Rollup
|
|
48
|
+
- `npm run arv`: build and run built CLI
|
|
49
|
+
- `npm run pub`: publish helper script
|
|
50
|
+
|
|
51
|
+
## Runtime and data root
|
|
52
|
+
|
|
53
|
+
Root resolution is implemented in `src/consts/path-tree.ts`:
|
|
54
|
+
|
|
55
|
+
- `NODE_ENV=production`: `ARCHIVER_PATH` or `~/.archiver`
|
|
56
|
+
- otherwise: `<cwd>/.archiver`
|
|
57
|
+
|
|
58
|
+
Current data layout:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
.archiver/
|
|
62
|
+
config.jsonc
|
|
63
|
+
auto-incr.jsonc
|
|
64
|
+
list.jsonl
|
|
65
|
+
vaults.jsonl
|
|
66
|
+
log.jsonl
|
|
67
|
+
vaults/
|
|
68
|
+
<vaultId>/
|
|
69
|
+
<archiveId>/
|
|
70
|
+
<originalName>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Architecture map
|
|
74
|
+
|
|
75
|
+
- `src/index.ts`: process entry, shell wrapper bootstrap, command parse
|
|
76
|
+
- `src/commands/*`: CLI command definitions and command-level flows
|
|
77
|
+
- `src/services/*`: business logic (archive, vault, config, log, update, check)
|
|
78
|
+
- `src/core/context.ts`: persistence and path helpers
|
|
79
|
+
- `src/core/initialize.ts`: shell wrapper auto-install
|
|
80
|
+
- `src/consts/*`: app constants/defaults/path tree
|
|
81
|
+
- `src/utils/*`: table rendering, parsing, fs/json/date helpers, prompt helpers
|
|
82
|
+
- `tests/*`: unit/integration/e2e tests
|
|
83
|
+
|
|
84
|
+
## Command wiring notes
|
|
85
|
+
|
|
86
|
+
- Program wiring lives in `src/commands/index.ts`
|
|
87
|
+
- Main command groups:
|
|
88
|
+
- archive actions: `put`, `restore`, `move`, `cd`
|
|
89
|
+
- vault actions: `vault use/create/remove/recover/rename/list`
|
|
90
|
+
- views and maintenance: `list`, `log`, `config`, `update`, `check`
|
|
91
|
+
|
|
92
|
+
## Important behavior notes
|
|
93
|
+
|
|
94
|
+
- Archive/restore/move use filesystem rename operations.
|
|
95
|
+
- Default vault is `@` (id `0`) and is protected.
|
|
96
|
+
- `list` plain mode prints names only; default vault entries omit vault prefix.
|
|
97
|
+
- Audit logs are appended into a single file: `log.jsonl`.
|
|
98
|
+
- Auto update check interval is 24h (`src/consts/update.ts`).
|
|
99
|
+
- Shell wrapper install is best-effort and should not block command execution.
|
|
100
|
+
|
|
101
|
+
## Recovery helper
|
|
102
|
+
|
|
103
|
+
Legacy helper script:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
node .scripts/recover.js [--apply] [--force] [--root <path>]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This script is for recovery from legacy rust data layout and is not part of normal CLI flows.
|
|
110
|
+
|
|
111
|
+
## Testing
|
|
112
|
+
|
|
113
|
+
Run all tests:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npm test
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Useful test focus:
|
|
120
|
+
|
|
121
|
+
- e2e CLI flow: `tests/e2e/cli.e2e.test.ts`
|
|
122
|
+
- archive/vault workflow: `tests/services/archive-workflow.test.ts`
|
|
123
|
+
- shell wrapper init: `tests/core/initialize.test.ts`
|
|
124
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# archiver
|
|
2
|
+
|
|
3
|
+
`archiver` (binary: `arv`) archives files/folders into a managed vault directory and lets you restore them later.
|
|
4
|
+
|
|
5
|
+
It uses filesystem move/rename semantics (no compression), keeps metadata in `.archiver`, and records audit logs.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
Install dependencies and build:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g archiver-ts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Common commands
|
|
16
|
+
|
|
17
|
+
Archive / restore:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
arv put <items...> [-v|--vault <vault>] [-m|--message <msg>] [-r|--remark <remark>]
|
|
21
|
+
arv restore <ids...>
|
|
22
|
+
arv move <ids...> --to <vault>
|
|
23
|
+
arv cd <archive-id | vault/archive-id> [--print]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Vault management:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
arv vault use <name-or-id>
|
|
30
|
+
arv vault create <name> [-r|--remark <remark>] [-a|--activate]
|
|
31
|
+
arv vault remove <name-or-id>
|
|
32
|
+
arv vault recover <name-or-id>
|
|
33
|
+
arv vault rename <old> <new>
|
|
34
|
+
arv vault list [-a|--all]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Query and maintenance:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
arv list [--restored] [--all] [--vault <vault>] [--no-interactive]
|
|
41
|
+
arv log [YYYYMM | YYYYMM-YYYYMM | all]
|
|
42
|
+
arv log --id <log-id>
|
|
43
|
+
arv config list [-c|--comment]
|
|
44
|
+
arv config alias <alias=path> [-r|--remove]
|
|
45
|
+
arv config update-check <on|off>
|
|
46
|
+
arv config vault-item-sep <separator>
|
|
47
|
+
arv update [--repo <owner/repo>] [--install]
|
|
48
|
+
arv check
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## List output behavior
|
|
52
|
+
|
|
53
|
+
`arv list` plain mode prints names only (one per line):
|
|
54
|
+
|
|
55
|
+
- default vault (`@`, id `0`): show item name only
|
|
56
|
+
- non-default vault: show `<vaultName>(<vaultId>)<sep><item>`
|
|
57
|
+
- `<sep>` comes from `config vault-item-sep` (default `::`)
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
todo.txt
|
|
63
|
+
work(1)::report.pdf
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Shell wrapper note
|
|
67
|
+
|
|
68
|
+
On interactive terminal startup, `arv` may auto-install a shell wrapper function so `cd` can move your shell to archive slot paths.
|
|
69
|
+
|
|
70
|
+
Disable this behavior if needed:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
ARV_DISABLE_SHELL_INIT=1 arv <command>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Useful aliases
|
|
77
|
+
|
|
78
|
+
- `list`: `l`, `ls`
|
|
79
|
+
- `log`: `lg`
|
|
80
|
+
- `config`: `c`, `cfg`
|
|
81
|
+
- `update`: `u`, `upd`
|
|
82
|
+
- `check`: `chk`
|