@dbarjs/dead-drop 0.1.0 → 0.2.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/dist/cli.mjs +63 -27
- package/package.json +4 -2
package/dist/cli.mjs
CHANGED
|
@@ -3,8 +3,10 @@ import { fileURLToPath } from 'node:url';
|
|
|
3
3
|
import { defineCommand, runMain } from 'citty';
|
|
4
4
|
import { readPackageJSON } from 'pkg-types';
|
|
5
5
|
import { consola } from 'consola';
|
|
6
|
-
import { join,
|
|
7
|
-
import { stat,
|
|
6
|
+
import { join, dirname, resolve, sep } from 'pathe';
|
|
7
|
+
import { stat, readFile, mkdir, writeFile } from 'node:fs/promises';
|
|
8
|
+
import { glob } from 'tinyglobby';
|
|
9
|
+
import ignore from 'ignore';
|
|
8
10
|
import { ofetch } from 'ofetch';
|
|
9
11
|
|
|
10
12
|
const SALT_BYTES = 16;
|
|
@@ -57,34 +59,60 @@ function fromBase64(b64) {
|
|
|
57
59
|
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
function isUnderGitDir(relPath) {
|
|
63
|
+
return relPath === ".git" || relPath.startsWith(".git/");
|
|
64
|
+
}
|
|
65
|
+
function isIgnored(relPath, cascade) {
|
|
66
|
+
let ignored = false;
|
|
67
|
+
for (const { dir, ig } of cascade) {
|
|
68
|
+
if (dir !== "" && relPath !== dir && !relPath.startsWith(`${dir}/`)) continue;
|
|
69
|
+
const sub = dir === "" ? relPath : relPath.slice(dir.length + 1);
|
|
70
|
+
if (sub === "") continue;
|
|
71
|
+
const { ignored: hit, unignored: rescued } = ig.test(sub);
|
|
72
|
+
if (hit) ignored = true;
|
|
73
|
+
else if (rescued) ignored = false;
|
|
74
|
+
}
|
|
75
|
+
return ignored;
|
|
76
|
+
}
|
|
77
|
+
async function buildCascade(rootDir, gitignorePaths) {
|
|
78
|
+
const ordered = [...gitignorePaths].sort(
|
|
79
|
+
(a, b) => a.split("/").length - b.split("/").length
|
|
80
|
+
);
|
|
81
|
+
const cascade = [];
|
|
82
|
+
for (const relPath of ordered) {
|
|
83
|
+
if (isIgnored(relPath, cascade)) continue;
|
|
84
|
+
const text = await readFile(join(rootDir, relPath), "utf8");
|
|
85
|
+
const parent = dirname(relPath);
|
|
86
|
+
cascade.push({ dir: parent === "." ? "" : parent, ig: ignore().add(text) });
|
|
87
|
+
}
|
|
88
|
+
return cascade;
|
|
89
|
+
}
|
|
90
|
+
async function* walkDirectory(rootDir, options = {}) {
|
|
70
91
|
const root = await stat(rootDir);
|
|
71
92
|
if (!root.isDirectory()) {
|
|
72
93
|
throw new Error(`${rootDir} is not a directory`);
|
|
73
94
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
const respectGitignore = options.respectGitignore ?? true;
|
|
96
|
+
let relPaths = await glob("**/*", {
|
|
97
|
+
cwd: rootDir,
|
|
98
|
+
dot: true,
|
|
99
|
+
onlyFiles: true,
|
|
100
|
+
expandDirectories: false,
|
|
101
|
+
ignore: [".git", ".git/**"]
|
|
102
|
+
});
|
|
103
|
+
relPaths = relPaths.filter((relPath) => !isUnderGitDir(relPath));
|
|
104
|
+
if (respectGitignore) {
|
|
105
|
+
const gitignorePaths = relPaths.filter(
|
|
106
|
+
(relPath) => relPath === ".gitignore" || relPath.endsWith("/.gitignore")
|
|
107
|
+
);
|
|
108
|
+
const cascade = await buildCascade(rootDir, gitignorePaths);
|
|
109
|
+
relPaths = relPaths.filter((relPath) => !isIgnored(relPath, cascade));
|
|
110
|
+
}
|
|
111
|
+
relPaths.sort();
|
|
112
|
+
for (const relPath of relPaths) {
|
|
113
|
+
const absPath = join(rootDir, relPath);
|
|
114
|
+
const bytes = new Uint8Array(await readFile(absPath));
|
|
115
|
+
yield { absPath, relPath, bytes };
|
|
88
116
|
}
|
|
89
117
|
}
|
|
90
118
|
|
|
@@ -214,12 +242,20 @@ const copy = defineCommand({
|
|
|
214
242
|
api: {
|
|
215
243
|
type: "string",
|
|
216
244
|
description: "API base URL (overrides $DEAD_DROP_API_URL, default http://localhost:3000)."
|
|
245
|
+
},
|
|
246
|
+
gitignore: {
|
|
247
|
+
type: "boolean",
|
|
248
|
+
default: true,
|
|
249
|
+
description: "Copy every file, even those matched by .gitignore."
|
|
217
250
|
}
|
|
218
251
|
},
|
|
219
252
|
async run({ args }) {
|
|
220
253
|
const rootDir = resolve(args.directory);
|
|
221
254
|
const baseUrl = resolveBaseUrl(args.api);
|
|
222
255
|
const api = createApi(baseUrl);
|
|
256
|
+
if (args.gitignore) {
|
|
257
|
+
consola.log("Respecting .gitignore (use --no-gitignore to include all files).");
|
|
258
|
+
}
|
|
223
259
|
const passphrase = await promptPassphrase();
|
|
224
260
|
if (!passphrase) {
|
|
225
261
|
consola.error("Passphrase required.");
|
|
@@ -234,7 +270,7 @@ const copy = defineCommand({
|
|
|
234
270
|
};
|
|
235
271
|
let dropId = null;
|
|
236
272
|
let count = 0;
|
|
237
|
-
for await (const file of walkDirectory(rootDir)) {
|
|
273
|
+
for await (const file of walkDirectory(rootDir, { respectGitignore: args.gitignore })) {
|
|
238
274
|
const { ciphertext, nonce } = await encryptBytes(file.bytes, key);
|
|
239
275
|
const contentHash = await sha256Hex(file.bytes);
|
|
240
276
|
const entry = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbarjs/dead-drop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Privacy-first dead-drop CLI: copy a project directory; the recipient cuts it once.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"citty": "^0.1.6",
|
|
20
20
|
"consola": "^3.4.2",
|
|
21
|
+
"ignore": "^7.0.5",
|
|
21
22
|
"ofetch": "^1.5.1",
|
|
22
23
|
"pathe": "^1.1.2",
|
|
23
|
-
"pkg-types": "^1.3.1"
|
|
24
|
+
"pkg-types": "^1.3.1",
|
|
25
|
+
"tinyglobby": "^0.2.16"
|
|
24
26
|
},
|
|
25
27
|
"devDependencies": {
|
|
26
28
|
"@types/node": "^25.8.0",
|