mdenc 0.1.5 → 2.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/README.md +10 -11
- package/dist/cli.js +117 -50
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**Encrypt your Markdown. Keep your diffs.**
|
|
4
4
|
|
|
5
|
+
[**Live Demo**](https://yogh-io.github.io/mdenc/) | [npm](https://www.npmjs.com/package/mdenc) | [Specification](SPECIFICATION.md) | [Security](SECURITY.md)
|
|
6
|
+
|
|
5
7
|
mdenc lets you store encrypted Markdown in git without losing the ability to see *what changed*. Edit one paragraph, and only that paragraph changes in the encrypted output. Your `git log` stays useful. Your pull request reviews stay sane.
|
|
6
8
|
|
|
7
9
|
## What it looks like
|
|
@@ -92,29 +94,26 @@ Password is read from `MDENC_PASSWORD` env var or prompted interactively (no ech
|
|
|
92
94
|
|
|
93
95
|
## Git Integration
|
|
94
96
|
|
|
95
|
-
mdenc
|
|
97
|
+
mdenc uses git's native **smudge/clean filter** to transparently encrypt and decrypt `.md` files. You edit plaintext locally; git stores ciphertext in the repository.
|
|
96
98
|
|
|
97
99
|
```bash
|
|
98
|
-
# Set up git
|
|
100
|
+
# Set up git smudge/clean filter and textconv diff
|
|
99
101
|
mdenc init
|
|
100
102
|
|
|
101
103
|
# Generate a random password into .mdenc-password
|
|
102
|
-
mdenc genpass
|
|
104
|
+
mdenc genpass [--force]
|
|
103
105
|
|
|
104
|
-
# Mark a directory -- .md files inside will be
|
|
106
|
+
# Mark a directory -- .md files inside will be filtered
|
|
105
107
|
mdenc mark docs/private
|
|
106
108
|
|
|
107
|
-
# See which files
|
|
109
|
+
# See which files are configured for encryption
|
|
108
110
|
mdenc status
|
|
109
111
|
|
|
110
|
-
#
|
|
111
|
-
mdenc
|
|
112
|
-
|
|
113
|
-
# Remove mdenc hooks from the repository
|
|
114
|
-
mdenc remove-hooks
|
|
112
|
+
# Remove git filter configuration
|
|
113
|
+
mdenc remove-filter
|
|
115
114
|
```
|
|
116
115
|
|
|
117
|
-
After `mdenc init` and `mdenc mark`, the workflow is
|
|
116
|
+
After `mdenc init` and `mdenc mark`, the workflow is transparent: the **clean filter** encrypts `.md` files when they're staged (`git add`), and the **smudge filter** decrypts them on checkout. You always see plaintext in your working directory. The custom diff driver shows plaintext diffs of encrypted content.
|
|
118
117
|
|
|
119
118
|
## Library
|
|
120
119
|
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { readFileSync as readFileSync7, writeFileSync as
|
|
4
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
5
5
|
|
|
6
6
|
// src/crypto/encrypt.ts
|
|
7
7
|
import { hmac as hmac3 } from "@noble/hashes/hmac";
|
|
@@ -436,15 +436,70 @@ ${chunkLines.join("\n")}`;
|
|
|
436
436
|
}
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
+
// src/git/diff-driver.ts
|
|
440
|
+
import { execFileSync } from "child_process";
|
|
441
|
+
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
442
|
+
import { tmpdir } from "os";
|
|
443
|
+
import { join } from "path";
|
|
444
|
+
async function diffDriverCommand(args) {
|
|
445
|
+
const [path, oldFile, oldHex, , newFile, newHex] = args;
|
|
446
|
+
if (!path || !oldFile || !newFile) {
|
|
447
|
+
process.stderr.write("mdenc diff-driver: insufficient arguments\n");
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
const oldEnc = catBlob(oldHex);
|
|
451
|
+
const newEnc = catBlob(newHex);
|
|
452
|
+
if (oldEnc !== null || newEnc !== null) {
|
|
453
|
+
const tmp = mkdtempSync(join(tmpdir(), "mdenc-diff-"));
|
|
454
|
+
try {
|
|
455
|
+
const oldTmp = join(tmp, "old");
|
|
456
|
+
const newTmp = join(tmp, "new");
|
|
457
|
+
writeFileSync(oldTmp, oldEnc ?? "");
|
|
458
|
+
writeFileSync(newTmp, newEnc ?? "");
|
|
459
|
+
const encDiff = unifiedDiff(oldTmp, newTmp, `a/${path}`, `b/${path}`);
|
|
460
|
+
if (encDiff) process.stdout.write(encDiff);
|
|
461
|
+
} finally {
|
|
462
|
+
rmSync(tmp, { recursive: true });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const plainDiff = unifiedDiff(oldFile, newFile, `a/${path}`, `b/${path}`);
|
|
466
|
+
if (plainDiff) {
|
|
467
|
+
const annotated = plainDiff.replace(
|
|
468
|
+
/^(@@ .+ @@)(.*)/gm,
|
|
469
|
+
"$1 decrypted \u2014 not stored in repository"
|
|
470
|
+
);
|
|
471
|
+
process.stdout.write(annotated);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function catBlob(hex) {
|
|
475
|
+
if (!hex || hex === "." || /^0+$/.test(hex)) return null;
|
|
476
|
+
try {
|
|
477
|
+
return execFileSync("git", ["cat-file", "blob", hex], { encoding: "utf-8" });
|
|
478
|
+
} catch {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function unifiedDiff(oldFile, newFile, oldLabel, newLabel) {
|
|
483
|
+
try {
|
|
484
|
+
return execFileSync("diff", ["-u", "--label", oldLabel, "--label", newLabel, oldFile, newFile], {
|
|
485
|
+
encoding: "utf-8"
|
|
486
|
+
}) || null;
|
|
487
|
+
} catch (e) {
|
|
488
|
+
const err = e;
|
|
489
|
+
if (err.status === 1 && err.stdout) return err.stdout;
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
439
494
|
// src/git/password.ts
|
|
440
495
|
import { readFileSync } from "fs";
|
|
441
|
-
import { join } from "path";
|
|
496
|
+
import { join as join2 } from "path";
|
|
442
497
|
var PASSWORD_FILE = ".mdenc-password";
|
|
443
498
|
function resolvePassword(repoRoot) {
|
|
444
499
|
const envPassword = process.env["MDENC_PASSWORD"];
|
|
445
500
|
if (envPassword) return envPassword;
|
|
446
501
|
try {
|
|
447
|
-
const content = readFileSync(
|
|
502
|
+
const content = readFileSync(join2(repoRoot, PASSWORD_FILE), "utf-8").trim();
|
|
448
503
|
if (content.length > 0) return content;
|
|
449
504
|
} catch {
|
|
450
505
|
}
|
|
@@ -452,14 +507,14 @@ function resolvePassword(repoRoot) {
|
|
|
452
507
|
}
|
|
453
508
|
|
|
454
509
|
// src/git/utils.ts
|
|
455
|
-
import { execFileSync } from "child_process";
|
|
510
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
456
511
|
import { readdirSync, statSync } from "fs";
|
|
457
|
-
import { join as
|
|
512
|
+
import { join as join3 } from "path";
|
|
458
513
|
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".hg", ".svn"]);
|
|
459
514
|
var MARKER_FILE = ".mdenc.conf";
|
|
460
515
|
function findGitRoot() {
|
|
461
516
|
try {
|
|
462
|
-
return
|
|
517
|
+
return execFileSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
463
518
|
encoding: "utf-8",
|
|
464
519
|
stdio: ["pipe", "pipe", "pipe"]
|
|
465
520
|
}).trim();
|
|
@@ -469,7 +524,7 @@ function findGitRoot() {
|
|
|
469
524
|
}
|
|
470
525
|
function gitShow(repoRoot, ref, path) {
|
|
471
526
|
try {
|
|
472
|
-
return
|
|
527
|
+
return execFileSync2("git", ["show", `${ref}:${path}`], {
|
|
473
528
|
cwd: repoRoot,
|
|
474
529
|
encoding: "utf-8",
|
|
475
530
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -495,7 +550,7 @@ function walkForMarker(dir, results) {
|
|
|
495
550
|
}
|
|
496
551
|
for (const entry of entries) {
|
|
497
552
|
if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue;
|
|
498
|
-
const full =
|
|
553
|
+
const full = join3(dir, entry);
|
|
499
554
|
try {
|
|
500
555
|
if (statSync(full).isDirectory()) {
|
|
501
556
|
walkForMarker(full, results);
|
|
@@ -506,14 +561,14 @@ function walkForMarker(dir, results) {
|
|
|
506
561
|
}
|
|
507
562
|
function getMdFilesInDir(dir) {
|
|
508
563
|
try {
|
|
509
|
-
return readdirSync(dir).filter((f) => f.endsWith(".md") && statSync(
|
|
564
|
+
return readdirSync(dir).filter((f) => f.endsWith(".md") && statSync(join3(dir, f)).isFile());
|
|
510
565
|
} catch {
|
|
511
566
|
return [];
|
|
512
567
|
}
|
|
513
568
|
}
|
|
514
569
|
function gitAdd(repoRoot, files) {
|
|
515
570
|
if (files.length === 0) return;
|
|
516
|
-
|
|
571
|
+
execFileSync2("git", ["add", "--", ...files], {
|
|
517
572
|
cwd: repoRoot,
|
|
518
573
|
stdio: ["pipe", "pipe", "pipe"]
|
|
519
574
|
});
|
|
@@ -712,6 +767,7 @@ async function filterProcessMain() {
|
|
|
712
767
|
writeFlush();
|
|
713
768
|
writeBinaryPktLines(resultBuf);
|
|
714
769
|
writeFlush();
|
|
770
|
+
writeFlush();
|
|
715
771
|
} catch (err) {
|
|
716
772
|
process.stderr.write(
|
|
717
773
|
`mdenc: filter error for ${pathname}: ${err instanceof Error ? err.message : err}
|
|
@@ -725,54 +781,54 @@ async function filterProcessMain() {
|
|
|
725
781
|
}
|
|
726
782
|
|
|
727
783
|
// src/git/genpass.ts
|
|
728
|
-
import { existsSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
729
|
-
import { join as
|
|
784
|
+
import { existsSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
785
|
+
import { join as join4 } from "path";
|
|
730
786
|
import { randomBytes as randomBytes2 } from "@noble/ciphers/webcrypto";
|
|
731
787
|
var PASSWORD_FILE2 = ".mdenc-password";
|
|
732
788
|
function genpassCommand(force) {
|
|
733
789
|
const repoRoot = findGitRoot();
|
|
734
|
-
const passwordPath =
|
|
790
|
+
const passwordPath = join4(repoRoot, PASSWORD_FILE2);
|
|
735
791
|
if (existsSync(passwordPath) && !force) {
|
|
736
792
|
console.error(`${PASSWORD_FILE2} already exists. Use --force to overwrite.`);
|
|
737
793
|
process.exit(1);
|
|
738
794
|
}
|
|
739
795
|
const password = Buffer.from(randomBytes2(32)).toString("base64url");
|
|
740
|
-
|
|
796
|
+
writeFileSync2(passwordPath, `${password}
|
|
741
797
|
`, { mode: 384 });
|
|
742
798
|
console.error(`Generated password and wrote to ${PASSWORD_FILE2}`);
|
|
743
799
|
console.error(password);
|
|
744
|
-
const gitignorePath =
|
|
800
|
+
const gitignorePath = join4(repoRoot, ".gitignore");
|
|
745
801
|
const entry = PASSWORD_FILE2;
|
|
746
802
|
if (existsSync(gitignorePath)) {
|
|
747
803
|
const content = readFileSync2(gitignorePath, "utf-8");
|
|
748
804
|
const lines = content.split("\n").map((l) => l.trim());
|
|
749
805
|
if (!lines.includes(entry)) {
|
|
750
|
-
|
|
806
|
+
writeFileSync2(gitignorePath, `${content.trimEnd()}
|
|
751
807
|
${entry}
|
|
752
808
|
`);
|
|
753
809
|
console.error("Added .mdenc-password to .gitignore");
|
|
754
810
|
}
|
|
755
811
|
} else {
|
|
756
|
-
|
|
812
|
+
writeFileSync2(gitignorePath, `${entry}
|
|
757
813
|
`);
|
|
758
814
|
console.error("Created .gitignore with .mdenc-password");
|
|
759
815
|
}
|
|
760
816
|
}
|
|
761
817
|
|
|
762
818
|
// src/git/init.ts
|
|
763
|
-
import { execFileSync as
|
|
764
|
-
import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as
|
|
765
|
-
import { join as
|
|
819
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
820
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
821
|
+
import { join as join5 } from "path";
|
|
766
822
|
var FILTER_CONFIGS = [
|
|
767
823
|
["filter.mdenc.process", "mdenc filter-process"],
|
|
768
824
|
["filter.mdenc.clean", "mdenc filter-clean %f"],
|
|
769
825
|
["filter.mdenc.smudge", "mdenc filter-smudge %f"],
|
|
770
826
|
["filter.mdenc.required", "true"],
|
|
771
|
-
["diff.mdenc.
|
|
827
|
+
["diff.mdenc.command", "mdenc diff-driver"]
|
|
772
828
|
];
|
|
773
829
|
function configureGitFilter(repoRoot) {
|
|
774
830
|
for (const [key, value] of FILTER_CONFIGS) {
|
|
775
|
-
|
|
831
|
+
execFileSync3("git", ["config", "--local", key, value], {
|
|
776
832
|
cwd: repoRoot,
|
|
777
833
|
stdio: ["pipe", "pipe", "pipe"]
|
|
778
834
|
});
|
|
@@ -780,7 +836,7 @@ function configureGitFilter(repoRoot) {
|
|
|
780
836
|
}
|
|
781
837
|
function isFilterConfigured(repoRoot) {
|
|
782
838
|
try {
|
|
783
|
-
const val =
|
|
839
|
+
const val = execFileSync3("git", ["config", "--get", "filter.mdenc.process"], {
|
|
784
840
|
cwd: repoRoot,
|
|
785
841
|
encoding: "utf-8",
|
|
786
842
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -798,13 +854,13 @@ async function initCommand() {
|
|
|
798
854
|
configureGitFilter(repoRoot);
|
|
799
855
|
console.log("Configured git filter (filter.mdenc + diff.mdenc)");
|
|
800
856
|
}
|
|
801
|
-
const gitignorePath =
|
|
857
|
+
const gitignorePath = join5(repoRoot, ".gitignore");
|
|
802
858
|
const entry = ".mdenc-password";
|
|
803
859
|
if (existsSync2(gitignorePath)) {
|
|
804
860
|
const content = readFileSync3(gitignorePath, "utf-8");
|
|
805
861
|
const lines = content.split("\n").map((l) => l.trim());
|
|
806
862
|
if (!lines.includes(entry)) {
|
|
807
|
-
|
|
863
|
+
writeFileSync3(gitignorePath, `${content.trimEnd()}
|
|
808
864
|
${entry}
|
|
809
865
|
`);
|
|
810
866
|
console.log("Added .mdenc-password to .gitignore");
|
|
@@ -812,7 +868,7 @@ ${entry}
|
|
|
812
868
|
console.log(".mdenc-password already in .gitignore (skipped)");
|
|
813
869
|
}
|
|
814
870
|
} else {
|
|
815
|
-
|
|
871
|
+
writeFileSync3(gitignorePath, `${entry}
|
|
816
872
|
`);
|
|
817
873
|
console.log("Created .gitignore with .mdenc-password");
|
|
818
874
|
}
|
|
@@ -822,7 +878,7 @@ ${entry}
|
|
|
822
878
|
for (const dir of markedDirs) {
|
|
823
879
|
const relDir = relative3(repoRoot, dir) || ".";
|
|
824
880
|
try {
|
|
825
|
-
|
|
881
|
+
execFileSync3("git", ["checkout", "HEAD", "--", `${relDir}/*.md`], {
|
|
826
882
|
cwd: repoRoot,
|
|
827
883
|
stdio: ["pipe", "pipe", "pipe"]
|
|
828
884
|
});
|
|
@@ -836,7 +892,7 @@ function removeFilterCommand() {
|
|
|
836
892
|
const repoRoot = findGitRoot();
|
|
837
893
|
for (const section of ["filter.mdenc", "diff.mdenc"]) {
|
|
838
894
|
try {
|
|
839
|
-
|
|
895
|
+
execFileSync3("git", ["config", "--local", "--remove-section", section], {
|
|
840
896
|
cwd: repoRoot,
|
|
841
897
|
stdio: ["pipe", "pipe", "pipe"]
|
|
842
898
|
});
|
|
@@ -847,15 +903,15 @@ function removeFilterCommand() {
|
|
|
847
903
|
}
|
|
848
904
|
|
|
849
905
|
// src/git/mark.ts
|
|
850
|
-
import { execFileSync as
|
|
851
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as
|
|
852
|
-
import { join as
|
|
906
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
907
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
908
|
+
import { join as join6, relative, resolve } from "path";
|
|
853
909
|
var MARKER_FILE2 = ".mdenc.conf";
|
|
854
910
|
var MARKER_CONTENT = "# mdenc: .md files in this directory are automatically encrypted\n";
|
|
855
911
|
var GITATTR_PATTERN = "*.md filter=mdenc diff=mdenc";
|
|
856
912
|
function isFilterConfigured2(repoRoot) {
|
|
857
913
|
try {
|
|
858
|
-
const val =
|
|
914
|
+
const val = execFileSync4("git", ["config", "--get", "filter.mdenc.process"], {
|
|
859
915
|
cwd: repoRoot,
|
|
860
916
|
encoding: "utf-8",
|
|
861
917
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -905,26 +961,26 @@ function markCommand(dirArg) {
|
|
|
905
961
|
}
|
|
906
962
|
const relDir = rel || ".";
|
|
907
963
|
const filterReady = isFilterConfigured2(repoRoot);
|
|
908
|
-
const confPath =
|
|
964
|
+
const confPath = join6(dir, MARKER_FILE2);
|
|
909
965
|
if (!existsSync3(confPath)) {
|
|
910
|
-
|
|
966
|
+
writeFileSync4(confPath, MARKER_CONTENT);
|
|
911
967
|
console.log(`Created ${relDir}/${MARKER_FILE2}`);
|
|
912
968
|
} else {
|
|
913
969
|
console.log(`${relDir}/${MARKER_FILE2} already exists (skipped)`);
|
|
914
970
|
}
|
|
915
|
-
const gitattrsPath =
|
|
971
|
+
const gitattrsPath = join6(dir, ".gitattributes");
|
|
916
972
|
if (existsSync3(gitattrsPath)) {
|
|
917
973
|
const content = readFileSync4(gitattrsPath, "utf-8");
|
|
918
974
|
if (content.includes("filter=mdenc")) {
|
|
919
975
|
console.log(`${relDir}/.gitattributes already has filter=mdenc (skipped)`);
|
|
920
976
|
} else {
|
|
921
|
-
|
|
977
|
+
writeFileSync4(gitattrsPath, `${content.trimEnd()}
|
|
922
978
|
${GITATTR_PATTERN}
|
|
923
979
|
`);
|
|
924
980
|
console.log(`Updated ${relDir}/.gitattributes`);
|
|
925
981
|
}
|
|
926
982
|
} else {
|
|
927
|
-
|
|
983
|
+
writeFileSync4(gitattrsPath, `${GITATTR_PATTERN}
|
|
928
984
|
`);
|
|
929
985
|
console.log(`Created ${relDir}/.gitattributes`);
|
|
930
986
|
}
|
|
@@ -938,13 +994,13 @@ Warning: git filter not configured yet. Run "mdenc init" to enable encryption.`)
|
|
|
938
994
|
}
|
|
939
995
|
|
|
940
996
|
// src/git/status.ts
|
|
941
|
-
import { execFileSync as
|
|
997
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
942
998
|
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
|
|
943
|
-
import { join as
|
|
999
|
+
import { join as join7, relative as relative2 } from "path";
|
|
944
1000
|
function getFilterConfig(repoRoot) {
|
|
945
1001
|
const get = (key) => {
|
|
946
1002
|
try {
|
|
947
|
-
return
|
|
1003
|
+
return execFileSync5("git", ["config", "--get", key], {
|
|
948
1004
|
cwd: repoRoot,
|
|
949
1005
|
encoding: "utf-8",
|
|
950
1006
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -957,8 +1013,7 @@ function getFilterConfig(repoRoot) {
|
|
|
957
1013
|
process: get("filter.mdenc.process"),
|
|
958
1014
|
clean: get("filter.mdenc.clean"),
|
|
959
1015
|
smudge: get("filter.mdenc.smudge"),
|
|
960
|
-
required: get("filter.mdenc.required") === "true"
|
|
961
|
-
textconv: get("diff.mdenc.textconv")
|
|
1016
|
+
required: get("filter.mdenc.required") === "true"
|
|
962
1017
|
};
|
|
963
1018
|
}
|
|
964
1019
|
function statusCommand() {
|
|
@@ -975,7 +1030,7 @@ function statusCommand() {
|
|
|
975
1030
|
console.log(` ${relDir}/`);
|
|
976
1031
|
const mdFiles = getMdFilesInDir(dir);
|
|
977
1032
|
for (const f of mdFiles) {
|
|
978
|
-
const content = readFileSync5(
|
|
1033
|
+
const content = readFileSync5(join7(dir, f), "utf-8");
|
|
979
1034
|
if (content.startsWith("mdenc:v1")) {
|
|
980
1035
|
console.log(` ${f} [encrypted \u2014 needs smudge]`);
|
|
981
1036
|
} else {
|
|
@@ -985,7 +1040,7 @@ function statusCommand() {
|
|
|
985
1040
|
if (mdFiles.length === 0) {
|
|
986
1041
|
console.log(" (no .md files)");
|
|
987
1042
|
}
|
|
988
|
-
const gitattrsPath =
|
|
1043
|
+
const gitattrsPath = join7(dir, ".gitattributes");
|
|
989
1044
|
if (!existsSync4(gitattrsPath)) {
|
|
990
1045
|
console.log(" WARNING: no .gitattributes in this directory");
|
|
991
1046
|
} else {
|
|
@@ -1106,8 +1161,7 @@ async function getPasswordWithConfirmation() {
|
|
|
1106
1161
|
}
|
|
1107
1162
|
return password;
|
|
1108
1163
|
}
|
|
1109
|
-
|
|
1110
|
-
console.error(`Usage:
|
|
1164
|
+
var USAGE = `Usage:
|
|
1111
1165
|
mdenc encrypt <file> [-o output] Encrypt a markdown file
|
|
1112
1166
|
mdenc decrypt <file> [-o output] Decrypt an mdenc file
|
|
1113
1167
|
mdenc verify <file> Verify file integrity
|
|
@@ -1123,13 +1177,23 @@ Internal (called by git):
|
|
|
1123
1177
|
mdenc filter-process Long-running filter process
|
|
1124
1178
|
mdenc filter-clean <path> Single-file clean filter
|
|
1125
1179
|
mdenc filter-smudge <path> Single-file smudge filter
|
|
1126
|
-
mdenc textconv <file> Output plaintext for git diff
|
|
1127
|
-
|
|
1180
|
+
mdenc textconv <file> Output plaintext for git diff
|
|
1181
|
+
mdenc diff-driver <path> ... Custom diff driver (encrypted + plaintext)`;
|
|
1182
|
+
function usage(exitCode = 1) {
|
|
1183
|
+
console.error(USAGE);
|
|
1184
|
+
process.exit(exitCode);
|
|
1128
1185
|
}
|
|
1129
1186
|
async function main() {
|
|
1130
1187
|
const args = process.argv.slice(2);
|
|
1131
1188
|
if (args.length === 0) usage();
|
|
1132
1189
|
const command = args[0];
|
|
1190
|
+
if (command === "--help" || command === "-h") usage(0);
|
|
1191
|
+
if (command === "--version" || command === "-v") {
|
|
1192
|
+
const pkgPath = new URL("../package.json", import.meta.url);
|
|
1193
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
1194
|
+
console.log(pkg.version);
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1133
1197
|
try {
|
|
1134
1198
|
switch (command) {
|
|
1135
1199
|
case "encrypt": {
|
|
@@ -1141,7 +1205,7 @@ async function main() {
|
|
|
1141
1205
|
const plaintext = readFileSync7(inputFile, "utf-8");
|
|
1142
1206
|
const encrypted = await encrypt(plaintext, password);
|
|
1143
1207
|
if (outputFile) {
|
|
1144
|
-
|
|
1208
|
+
writeFileSync5(outputFile, encrypted);
|
|
1145
1209
|
} else {
|
|
1146
1210
|
process.stdout.write(encrypted);
|
|
1147
1211
|
}
|
|
@@ -1156,7 +1220,7 @@ async function main() {
|
|
|
1156
1220
|
const fileContent = readFileSync7(inputFile, "utf-8");
|
|
1157
1221
|
const decrypted = await decrypt(fileContent, password);
|
|
1158
1222
|
if (outputFile) {
|
|
1159
|
-
|
|
1223
|
+
writeFileSync5(outputFile, decrypted);
|
|
1160
1224
|
} else {
|
|
1161
1225
|
process.stdout.write(decrypted);
|
|
1162
1226
|
}
|
|
@@ -1207,6 +1271,9 @@ async function main() {
|
|
|
1207
1271
|
case "filter-smudge":
|
|
1208
1272
|
await simpleSmudgeFilter();
|
|
1209
1273
|
break;
|
|
1274
|
+
case "diff-driver":
|
|
1275
|
+
await diffDriverCommand(args.slice(1));
|
|
1276
|
+
break;
|
|
1210
1277
|
case "textconv":
|
|
1211
1278
|
if (!args[1]) {
|
|
1212
1279
|
console.error("Usage: mdenc textconv <file>");
|