jsmdcui 0.3.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/CHANGELOG.md +76 -0
- package/LICENSE +22 -0
- package/README.md +285 -0
- package/package.json +17 -0
- package/project_structure.txt +35 -0
- package/runmd.mjs +240 -0
- package/runtime/colorschemes/atom-dark.micro +33 -0
- package/runtime/colorschemes/bubblegum.micro +31 -0
- package/runtime/colorschemes/cmc-16.micro +47 -0
- package/runtime/colorschemes/cmc-tc.micro +43 -0
- package/runtime/colorschemes/darcula.micro +34 -0
- package/runtime/colorschemes/default.micro +1 -0
- package/runtime/colorschemes/dracula-tc.micro +49 -0
- package/runtime/colorschemes/dukedark-tc.micro +38 -0
- package/runtime/colorschemes/dukelight-tc.micro +38 -0
- package/runtime/colorschemes/dukeubuntu-tc.micro +38 -0
- package/runtime/colorschemes/geany.micro +29 -0
- package/runtime/colorschemes/gotham.micro +29 -0
- package/runtime/colorschemes/gruvbox-tc.micro +29 -0
- package/runtime/colorschemes/gruvbox.micro +26 -0
- package/runtime/colorschemes/material-tc.micro +36 -0
- package/runtime/colorschemes/monokai-dark.micro +28 -0
- package/runtime/colorschemes/monokai.micro +34 -0
- package/runtime/colorschemes/one-dark.micro +39 -0
- package/runtime/colorschemes/railscast.micro +37 -0
- package/runtime/colorschemes/simple.micro +33 -0
- package/runtime/colorschemes/solarized-tc.micro +31 -0
- package/runtime/colorschemes/solarized.micro +30 -0
- package/runtime/colorschemes/sunny-day.micro +29 -0
- package/runtime/colorschemes/twilight.micro +40 -0
- package/runtime/colorschemes/zenburn.micro +30 -0
- package/runtime/help/actions.md +161 -0
- package/runtime/help/cdp.md +119 -0
- package/runtime/help/colors.md +421 -0
- package/runtime/help/commands.md +161 -0
- package/runtime/help/copypaste.md +149 -0
- package/runtime/help/defaultkeys.md +148 -0
- package/runtime/help/help.md +285 -0
- package/runtime/help/keybindings.md +355 -0
- package/runtime/help/linter.md +90 -0
- package/runtime/help/options.md +709 -0
- package/runtime/help/plugins.md +544 -0
- package/runtime/help/tutorial.md +112 -0
- package/runtime/jsplugins/cdp/cdp-server.js +1161 -0
- package/runtime/jsplugins/cdp/cdp.js +192 -0
- package/runtime/jsplugins/chapter/chapter.js +108 -0
- package/runtime/jsplugins/diff/diff.js +46 -0
- package/runtime/jsplugins/example/example.js +110 -0
- package/runtime/jsplugins/linter/linter.js +281 -0
- package/runtime/plugins/autoclose/autoclose.lua +75 -0
- package/runtime/plugins/ftoptions/ftoptions.lua +17 -0
- package/runtime/plugins/literate/README.md +5 -0
- package/runtime/plugins/literate/literate.lua +55 -0
- package/runtime/plugins/status/help/status.md +21 -0
- package/runtime/plugins/status/status.lua +62 -0
- package/runtime/syntax/LICENSE +22 -0
- package/runtime/syntax/PowerShell.yaml +128 -0
- package/runtime/syntax/README.md +63 -0
- package/runtime/syntax/ada.yaml +43 -0
- package/runtime/syntax/apacheconf.yaml +59 -0
- package/runtime/syntax/arduino.yaml +101 -0
- package/runtime/syntax/asciidoc.yaml +51 -0
- package/runtime/syntax/asm.yaml +123 -0
- package/runtime/syntax/ats.yaml +99 -0
- package/runtime/syntax/awk.yaml +44 -0
- package/runtime/syntax/b.yaml +87 -0
- package/runtime/syntax/bat.yaml +57 -0
- package/runtime/syntax/c.yaml +60 -0
- package/runtime/syntax/caddyfile.yaml +23 -0
- package/runtime/syntax/cake.yaml +7 -0
- package/runtime/syntax/clojure.yaml +38 -0
- package/runtime/syntax/cmake.yaml +42 -0
- package/runtime/syntax/coffeescript.yaml +56 -0
- package/runtime/syntax/colortest.yaml +19 -0
- package/runtime/syntax/conky.yaml +17 -0
- package/runtime/syntax/cpp.yaml +91 -0
- package/runtime/syntax/crontab.yaml +36 -0
- package/runtime/syntax/crystal.yaml +72 -0
- package/runtime/syntax/csharp.yaml +52 -0
- package/runtime/syntax/css.yaml +44 -0
- package/runtime/syntax/csx.yaml +8 -0
- package/runtime/syntax/cuda.yaml +68 -0
- package/runtime/syntax/cython.yaml +52 -0
- package/runtime/syntax/d.yaml +121 -0
- package/runtime/syntax/dart.yaml +46 -0
- package/runtime/syntax/default.yaml +10 -0
- package/runtime/syntax/dockerfile.yaml +36 -0
- package/runtime/syntax/dot.yaml +29 -0
- package/runtime/syntax/elixir.yaml +30 -0
- package/runtime/syntax/elm.yaml +38 -0
- package/runtime/syntax/erb.yaml +42 -0
- package/runtime/syntax/erlang.yaml +45 -0
- package/runtime/syntax/fish.yaml +48 -0
- package/runtime/syntax/forth.yaml +34 -0
- package/runtime/syntax/fortran.yaml +64 -0
- package/runtime/syntax/freebsd-kernel.yaml +14 -0
- package/runtime/syntax/fsharp.yaml +48 -0
- package/runtime/syntax/gdscript.yaml +61 -0
- package/runtime/syntax/gemini.yaml +19 -0
- package/runtime/syntax/gentoo-ebuild.yaml +48 -0
- package/runtime/syntax/gentoo-etc-portage.yaml +23 -0
- package/runtime/syntax/git-commit.yaml +35 -0
- package/runtime/syntax/git-config.yaml +14 -0
- package/runtime/syntax/git-rebase-todo.yaml +19 -0
- package/runtime/syntax/gleam.yaml +69 -0
- package/runtime/syntax/glsl.yaml +26 -0
- package/runtime/syntax/gnuplot.yaml +15 -0
- package/runtime/syntax/go.yaml +62 -0
- package/runtime/syntax/godoc.yaml +17 -0
- package/runtime/syntax/golo.yaml +73 -0
- package/runtime/syntax/gomod.yaml +31 -0
- package/runtime/syntax/graphql.yaml +47 -0
- package/runtime/syntax/groff.yaml +30 -0
- package/runtime/syntax/groovy.yaml +111 -0
- package/runtime/syntax/haml.yaml +16 -0
- package/runtime/syntax/hare.yaml +52 -0
- package/runtime/syntax/haskell.yaml +52 -0
- package/runtime/syntax/hc.yaml +52 -0
- package/runtime/syntax/html.yaml +70 -0
- package/runtime/syntax/html4.yaml +25 -0
- package/runtime/syntax/html5.yaml +25 -0
- package/runtime/syntax/ini.yaml +23 -0
- package/runtime/syntax/inputrc.yaml +14 -0
- package/runtime/syntax/java.yaml +37 -0
- package/runtime/syntax/javascript.yaml +124 -0
- package/runtime/syntax/jinja2.yaml +19 -0
- package/runtime/syntax/json.yaml +39 -0
- package/runtime/syntax/jsonnet.yaml +92 -0
- package/runtime/syntax/julia.yaml +57 -0
- package/runtime/syntax/justfile.yaml +40 -0
- package/runtime/syntax/keymap.yaml +27 -0
- package/runtime/syntax/kickstart.yaml +16 -0
- package/runtime/syntax/kotlin.yaml +66 -0
- package/runtime/syntax/kvlang.yaml +67 -0
- package/runtime/syntax/ledger.yaml +14 -0
- package/runtime/syntax/lfe.yaml +17 -0
- package/runtime/syntax/lilypond.yaml +26 -0
- package/runtime/syntax/lisp.yaml +17 -0
- package/runtime/syntax/log.yaml +92 -0
- package/runtime/syntax/lua.yaml +111 -0
- package/runtime/syntax/mail.yaml +25 -0
- package/runtime/syntax/makefile.yaml +38 -0
- package/runtime/syntax/man.yaml +12 -0
- package/runtime/syntax/markdown.yaml +49 -0
- package/runtime/syntax/mc.yaml +23 -0
- package/runtime/syntax/meson.yaml +51 -0
- package/runtime/syntax/micro.yaml +34 -0
- package/runtime/syntax/mpdconf.yaml +13 -0
- package/runtime/syntax/msbuild.yaml +6 -0
- package/runtime/syntax/nanorc.yaml +16 -0
- package/runtime/syntax/nftables.yaml +30 -0
- package/runtime/syntax/nginx.yaml +22 -0
- package/runtime/syntax/nim.yaml +27 -0
- package/runtime/syntax/nix.yaml +32 -0
- package/runtime/syntax/nu.yaml +114 -0
- package/runtime/syntax/objc.yaml +60 -0
- package/runtime/syntax/ocaml.yaml +43 -0
- package/runtime/syntax/octave.yaml +83 -0
- package/runtime/syntax/odin.yaml +64 -0
- package/runtime/syntax/pascal.yaml +45 -0
- package/runtime/syntax/patch.yaml +14 -0
- package/runtime/syntax/peg.yaml +16 -0
- package/runtime/syntax/perl.yaml +58 -0
- package/runtime/syntax/php.yaml +60 -0
- package/runtime/syntax/pkg-config.yaml +12 -0
- package/runtime/syntax/po.yaml +12 -0
- package/runtime/syntax/pony.yaml +37 -0
- package/runtime/syntax/pov.yaml +21 -0
- package/runtime/syntax/privoxy-action.yaml +14 -0
- package/runtime/syntax/privoxy-config.yaml +10 -0
- package/runtime/syntax/privoxy-filter.yaml +12 -0
- package/runtime/syntax/proto.yaml +40 -0
- package/runtime/syntax/prql.yaml +84 -0
- package/runtime/syntax/puppet.yaml +22 -0
- package/runtime/syntax/python2.yaml +60 -0
- package/runtime/syntax/python3.yaml +80 -0
- package/runtime/syntax/r.yaml +32 -0
- package/runtime/syntax/raku.yaml +42 -0
- package/runtime/syntax/reST.yaml +18 -0
- package/runtime/syntax/renpy.yaml +15 -0
- package/runtime/syntax/rpmspec.yaml +43 -0
- package/runtime/syntax/ruby.yaml +73 -0
- package/runtime/syntax/rust.yaml +78 -0
- package/runtime/syntax/sage.yaml +60 -0
- package/runtime/syntax/scad.yaml +53 -0
- package/runtime/syntax/scala.yaml +33 -0
- package/runtime/syntax/sed.yaml +13 -0
- package/runtime/syntax/sh.yaml +69 -0
- package/runtime/syntax/sls.yaml +15 -0
- package/runtime/syntax/smalltalk.yaml +55 -0
- package/runtime/syntax/solidity.yaml +41 -0
- package/runtime/syntax/sql.yaml +35 -0
- package/runtime/syntax/stata.yaml +67 -0
- package/runtime/syntax/svelte.yaml +27 -0
- package/runtime/syntax/swift.yaml +103 -0
- package/runtime/syntax/systemd.yaml +16 -0
- package/runtime/syntax/tcl.yaml +18 -0
- package/runtime/syntax/terraform.yaml +87 -0
- package/runtime/syntax/tex.yaml +32 -0
- package/runtime/syntax/toml.yaml +56 -0
- package/runtime/syntax/twig.yaml +55 -0
- package/runtime/syntax/typescript.yaml +49 -0
- package/runtime/syntax/v.yaml +80 -0
- package/runtime/syntax/vala.yaml +26 -0
- package/runtime/syntax/verilog.yaml +60 -0
- package/runtime/syntax/vhdl.yaml +37 -0
- package/runtime/syntax/vi.yaml +31 -0
- package/runtime/syntax/vue.yaml +64 -0
- package/runtime/syntax/xml.yaml +37 -0
- package/runtime/syntax/xresources.yaml +14 -0
- package/runtime/syntax/yaml.yaml +34 -0
- package/runtime/syntax/yum.yaml +12 -0
- package/runtime/syntax/zig.yaml +52 -0
- package/runtime/syntax/zscript.yaml +72 -0
- package/runtime/syntax/zsh.yaml +52 -0
- package/single-exe/README.md +80 -0
- package/single-exe/assetsHelper.js +90 -0
- package/single-exe/assetsLoader.mjs +85 -0
- package/single-exe/compiled.js +149 -0
- package/single-exe/entry.mjs +9 -0
- package/single-exe/packAssets.sh +7 -0
- package/src/buffer/backup.js +160 -0
- package/src/buffer/buffer.js +126 -0
- package/src/buffer/fixed3-codec.js +140 -0
- package/src/buffer/loc.js +38 -0
- package/src/buffer/message.js +29 -0
- package/src/config/clean.js +172 -0
- package/src/config/colorscheme.js +99 -0
- package/src/config/config.js +122 -0
- package/src/config/defaults.js +109 -0
- package/src/cui/rpc.mjs +259 -0
- package/src/cui/server.mjs +116 -0
- package/src/display/ansi-style.js +60 -0
- package/src/highlight/highlighter.js +243 -0
- package/src/highlight/parser.js +225 -0
- package/src/index.js +8009 -0
- package/src/lua/engine.js +71 -0
- package/src/platform/archive.js +50 -0
- package/src/platform/clipboard.js +278 -0
- package/src/platform/commands.js +137 -0
- package/src/plugins/js-bridge.js +983 -0
- package/src/plugins/manager.js +674 -0
- package/src/runtime/assets.js +90 -0
- package/src/runtime/compiled.js +25 -0
- package/src/runtime/encodings.js +10 -0
- package/src/runtime/registry.js +134 -0
- package/src/screen/cell-buffer.js +81 -0
- package/src/screen/events.js +263 -0
- package/src/screen/screen.js +142 -0
- package/src/screen/vt100.js +571 -0
- package/src/shell/shell.js +70 -0
- package/testapp.md +55 -0
- package/tests/backup.test.js +133 -0
- package/tests/cmphex3b64.js +95 -0
- package/tests/pty-demo.js +492 -0
- package/tests/wv-client.js +96 -0
- package/tui +5 -0
- package/wui +5 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { run } from "../platform/commands.js";
|
|
2
|
+
|
|
3
|
+
export function shellSplit(input) {
|
|
4
|
+
const args = [];
|
|
5
|
+
let current = "";
|
|
6
|
+
let quote = null;
|
|
7
|
+
let escaped = false;
|
|
8
|
+
let started = false;
|
|
9
|
+
|
|
10
|
+
for (const ch of String(input)) {
|
|
11
|
+
if (escaped) {
|
|
12
|
+
if (ch === 'n') current += '\n';
|
|
13
|
+
else if (ch === 't') current += '\t';
|
|
14
|
+
else if (ch === 'r') current += '\r';
|
|
15
|
+
else current += ch;
|
|
16
|
+
escaped = false;
|
|
17
|
+
started = true;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (ch === "\\" && quote !== "'") {
|
|
21
|
+
escaped = true;
|
|
22
|
+
started = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (quote) {
|
|
26
|
+
if (ch === quote) { quote = null; }
|
|
27
|
+
else { current += ch; }
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (ch === "'" || ch === '"') {
|
|
31
|
+
quote = ch;
|
|
32
|
+
started = true;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (/\s/.test(ch)) {
|
|
36
|
+
if (started) {
|
|
37
|
+
args.push(current);
|
|
38
|
+
current = "";
|
|
39
|
+
started = false;
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
current += ch;
|
|
44
|
+
started = true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (escaped) current += "\\";
|
|
48
|
+
if (quote) throw new Error(`Unclosed ${quote} quote`);
|
|
49
|
+
if (started) args.push(current);
|
|
50
|
+
return args;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function execCommand(name, args = []) {
|
|
54
|
+
const result = await run([name, ...args.map(String)], { allowFailure: true });
|
|
55
|
+
const output = result.stdout + result.stderr;
|
|
56
|
+
return [output, result.ok ? null : `${name} exited with ${result.code}`];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function runCommand(input) {
|
|
60
|
+
const args = shellSplit(input);
|
|
61
|
+
if (args.length === 0) return ["", "No arguments"];
|
|
62
|
+
return execCommand(args[0], args.slice(1));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function runBackgroundShell(input) {
|
|
66
|
+
return async () => {
|
|
67
|
+
const [output, error] = await runCommand(input);
|
|
68
|
+
return error ? `${error}: ${output}` : output;
|
|
69
|
+
};
|
|
70
|
+
}
|
package/testapp.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# jsmdcui
|
|
2
|
+
# hello
|
|
3
|
+
- world
|
|
4
|
+
- 你好 世界 😅
|
|
5
|
+
- Bun JavaScript
|
|
6
|
+
- [example](https://example.com)
|
|
7
|
+
- [Hello! Click Me](javascript:alert('world'))
|
|
8
|
+
- [myfunc 請按我](javascript:myfunc())
|
|
9
|
+
- [Print process.argv](javascript:pav())
|
|
10
|
+
- [Calculator🧮計算機](javascript:calc())
|
|
11
|
+
* Use cos sin PI directly
|
|
12
|
+
## Task list
|
|
13
|
+
- [X] task1
|
|
14
|
+
- [ ] task2
|
|
15
|
+
|
|
16
|
+
```js front
|
|
17
|
+
export async function myfunc()
|
|
18
|
+
{
|
|
19
|
+
let yn=confirm('😃 Are you happy? 你開心嗎?')
|
|
20
|
+
alert(
|
|
21
|
+
yn ? 'Great 太棒了':
|
|
22
|
+
'Sorry to hear that. 很遺憾聽到你這麼說'
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function pav()
|
|
27
|
+
{
|
|
28
|
+
let r=await rpc.getArgv();
|
|
29
|
+
alert(r)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function calc()
|
|
33
|
+
{
|
|
34
|
+
let s = prompt('Enter an expression 輸入運算式:')
|
|
35
|
+
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
|
36
|
+
const result = await new AsyncFunction('rpc',...Object.getOwnPropertyNames(Math),
|
|
37
|
+
`return await (${s});`
|
|
38
|
+
)(rpc,...Object.getOwnPropertyNames(Math).map(i=>Math[i]))
|
|
39
|
+
alert(
|
|
40
|
+
|
|
41
|
+
'Result 結果:'+
|
|
42
|
+
result
|
|
43
|
+
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
```js back
|
|
50
|
+
|
|
51
|
+
export function getArgv()
|
|
52
|
+
{
|
|
53
|
+
return process.argv ;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import {
|
|
8
|
+
BACKUP_SUFFIX,
|
|
9
|
+
applyBackup,
|
|
10
|
+
determineBackupPath,
|
|
11
|
+
removeBackup,
|
|
12
|
+
writeBackup,
|
|
13
|
+
} from "../src/buffer/backup.js";
|
|
14
|
+
|
|
15
|
+
const cleanup = [];
|
|
16
|
+
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
await Promise.all(cleanup.splice(0).map((path) => rm(path, { recursive: true, force: true })));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
async function tempDir() {
|
|
22
|
+
const path = await mkdtemp(join(tmpdir(), "bunmicro-backup-"));
|
|
23
|
+
cleanup.push(path);
|
|
24
|
+
return path;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function buffer(path, text = "changed") {
|
|
28
|
+
return {
|
|
29
|
+
path,
|
|
30
|
+
AbsPath: path,
|
|
31
|
+
type: "default",
|
|
32
|
+
lines: text.split("\n"),
|
|
33
|
+
Settings: { backup: true, backupdir: "", permbackup: false },
|
|
34
|
+
_savedSerial: 0,
|
|
35
|
+
setModified(value) { this.modified = Boolean(value); },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("go-micro compatible backup paths", () => {
|
|
40
|
+
test("uses Go url.QueryEscape-compatible names", async () => {
|
|
41
|
+
const dir = await tempDir();
|
|
42
|
+
const result = determineBackupPath(dir, "/tmp/a b%~!'()*.txt");
|
|
43
|
+
expect(result).toEqual({
|
|
44
|
+
name: join(dir, "%2Ftmp%2Fa+b%25~%21%27%28%29%2A.txt"),
|
|
45
|
+
resolveName: null,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("prefers an existing legacy name", async () => {
|
|
50
|
+
const dir = await tempDir();
|
|
51
|
+
const legacy = join(dir, "%tmp%legacy.txt");
|
|
52
|
+
await writeFile(legacy, "legacy");
|
|
53
|
+
expect(determineBackupPath(dir, "/tmp/legacy.txt").name).toBe(legacy);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("uses Go's full MD5 hash and .path sidecar for long names", async () => {
|
|
57
|
+
const dir = await tempDir();
|
|
58
|
+
const path = "/" + "x".repeat(300);
|
|
59
|
+
const hash = createHash("md5").update(path).digest("hex");
|
|
60
|
+
expect(determineBackupPath(dir, path)).toEqual({
|
|
61
|
+
name: join(dir, hash),
|
|
62
|
+
resolveName: join(dir, hash + ".path"),
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("backup lifecycle", () => {
|
|
68
|
+
test("writes atomically using the Go backup suffix", async () => {
|
|
69
|
+
const root = await tempDir();
|
|
70
|
+
const backupDir = join(root, "backups");
|
|
71
|
+
const buf = buffer("/tmp/file.txt", "one\ntwo");
|
|
72
|
+
buf.Settings.backupdir = backupDir;
|
|
73
|
+
const target = determineBackupPath(backupDir, buf.AbsPath);
|
|
74
|
+
|
|
75
|
+
expect(await writeBackup(buf, root)).toBe(true);
|
|
76
|
+
expect(await readFile(target.name, "utf8")).toBe("one\ntwo");
|
|
77
|
+
expect(existsSync(target.name + BACKUP_SUFFIX)).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("recover keeps the backup and marks a distinct dirty baseline", async () => {
|
|
81
|
+
const root = await tempDir();
|
|
82
|
+
const backupDir = join(root, "backups");
|
|
83
|
+
await mkdir(backupDir);
|
|
84
|
+
const buf = buffer("/tmp/file.txt", "disk");
|
|
85
|
+
buf.Settings.backupdir = backupDir;
|
|
86
|
+
const target = determineBackupPath(backupDir, buf.AbsPath);
|
|
87
|
+
await writeFile(target.name, "recovered");
|
|
88
|
+
|
|
89
|
+
expect(await applyBackup(buf, root, async () => "recover")).toEqual({ recovered: true, abort: false });
|
|
90
|
+
expect(buf.lines).toEqual(["recovered"]);
|
|
91
|
+
expect(buf.modified).toBe(true);
|
|
92
|
+
expect(buf._savedSerial).toBe(-1);
|
|
93
|
+
expect(existsSync(target.name)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("ignore removes the backup", async () => {
|
|
97
|
+
const root = await tempDir();
|
|
98
|
+
const backupDir = join(root, "backups");
|
|
99
|
+
await mkdir(backupDir);
|
|
100
|
+
const buf = buffer("/tmp/file.txt");
|
|
101
|
+
buf.Settings.backupdir = backupDir;
|
|
102
|
+
const target = determineBackupPath(backupDir, buf.AbsPath);
|
|
103
|
+
await writeFile(target.name, "ignored");
|
|
104
|
+
|
|
105
|
+
expect(await applyBackup(buf, root, async () => "ignore")).toEqual({ recovered: false, abort: false });
|
|
106
|
+
expect(existsSync(target.name)).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("permanent backups survive removal", async () => {
|
|
110
|
+
const root = await tempDir();
|
|
111
|
+
const backupDir = join(root, "backups");
|
|
112
|
+
const buf = buffer("/tmp/file.txt");
|
|
113
|
+
buf.Settings.backupdir = backupDir;
|
|
114
|
+
buf.Settings.permbackup = true;
|
|
115
|
+
await writeBackup(buf, root);
|
|
116
|
+
const target = determineBackupPath(backupDir, buf.AbsPath);
|
|
117
|
+
|
|
118
|
+
removeBackup(buf, root);
|
|
119
|
+
expect(existsSync(target.name)).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("forced safe-write backups work when periodic backups are disabled", async () => {
|
|
123
|
+
const root = await tempDir();
|
|
124
|
+
const backupDir = join(root, "backups");
|
|
125
|
+
const buf = buffer("/tmp/file.txt");
|
|
126
|
+
buf.Settings.backupdir = backupDir;
|
|
127
|
+
buf.Settings.backup = false;
|
|
128
|
+
|
|
129
|
+
expect(await writeBackup(buf, root)).toBe(false);
|
|
130
|
+
expect(await writeBackup(buf, root, buf.AbsPath, { force: true })).toBe(true);
|
|
131
|
+
expect(existsSync(determineBackupPath(backupDir, buf.AbsPath).name)).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
decodeBinaryStrict,
|
|
4
|
+
encodeBinary,
|
|
5
|
+
} from "../src/buffer/fixed3-codec.js";
|
|
6
|
+
|
|
7
|
+
const SIZES = [1024, 64 * 1024, 1024 * 1024];
|
|
8
|
+
const ITERATIONS = {
|
|
9
|
+
1024: 20_000,
|
|
10
|
+
[64 * 1024]: 2_000,
|
|
11
|
+
[1024 * 1024]: 200,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function makeRandom(size) {
|
|
15
|
+
return randomBytes(size);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makeAscii(size) {
|
|
19
|
+
const buf = Buffer.allocUnsafe(size);
|
|
20
|
+
for (let i = 0; i < size; i++) {
|
|
21
|
+
buf[i] = 0x20 + (i % 95);
|
|
22
|
+
}
|
|
23
|
+
return buf;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function mbps(bytes, ns) {
|
|
27
|
+
return (bytes / (1024 * 1024)) / (Number(ns) / 1e9);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function bench(name, fn, iters, payloadSize) {
|
|
31
|
+
const warmup = Math.min(200, Math.max(20, Math.floor(iters / 20)));
|
|
32
|
+
for (let i = 0; i < warmup; i++) fn();
|
|
33
|
+
|
|
34
|
+
const start = process.hrtime.bigint();
|
|
35
|
+
let sink = 0;
|
|
36
|
+
for (let i = 0; i < iters; i++) {
|
|
37
|
+
sink ^= fn();
|
|
38
|
+
}
|
|
39
|
+
const elapsed = process.hrtime.bigint() - start;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
iters,
|
|
44
|
+
ms: Number(elapsed) / 1e6,
|
|
45
|
+
mbps: mbps(payloadSize * iters, elapsed),
|
|
46
|
+
sink,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function runCase(label, input) {
|
|
51
|
+
const fixedEncoded = encodeBinary(input);
|
|
52
|
+
const base64Encoded = input.toString("base64");
|
|
53
|
+
|
|
54
|
+
if (!decodeBinaryStrict(fixedEncoded).equals(input)) {
|
|
55
|
+
throw new Error(`hex3 roundtrip failed for ${label}`);
|
|
56
|
+
}
|
|
57
|
+
if (!Buffer.from(base64Encoded, "base64").equals(input)) {
|
|
58
|
+
throw new Error(`base64 roundtrip failed for ${label}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const iters = ITERATIONS[input.length] ?? Math.max(50, Math.floor(100 * 1024 * 1024 / input.length));
|
|
62
|
+
|
|
63
|
+
const results = [
|
|
64
|
+
bench(
|
|
65
|
+
"hex3 encode+decode",
|
|
66
|
+
() => {
|
|
67
|
+
const encoded = encodeBinary(input);
|
|
68
|
+
return decodeBinaryStrict(encoded).length;
|
|
69
|
+
},
|
|
70
|
+
iters,
|
|
71
|
+
input.length,
|
|
72
|
+
),
|
|
73
|
+
bench(
|
|
74
|
+
"base64 encode+decode",
|
|
75
|
+
() => {
|
|
76
|
+
const encoded = input.toString("base64");
|
|
77
|
+
return Buffer.from(encoded, "base64").length;
|
|
78
|
+
},
|
|
79
|
+
iters,
|
|
80
|
+
input.length,
|
|
81
|
+
),
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
console.log(`\n${label} (${input.length.toLocaleString()} bytes, ${iters.toLocaleString()} iterations)`);
|
|
85
|
+
for (const r of results) {
|
|
86
|
+
console.log(
|
|
87
|
+
`${r.name.padEnd(22)} ${r.ms.toFixed(1).padStart(10)} ms ${r.mbps.toFixed(1).padStart(8)} MiB/s sink=${r.sink}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const size of SIZES) {
|
|
93
|
+
runCase("random", makeRandom(size));
|
|
94
|
+
runCase("ascii", makeAscii(size));
|
|
95
|
+
}
|