bunmicro 1.0.0 → 1.0.3

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/src/index.js CHANGED
@@ -1,5 +1,97 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
+ let mainPromise = globalThis.assetsLoaderPromise ||
4
+ Promise.resolve();
5
+
6
+ const jsStart = globalThis.Bun ? Bun.nanoseconds() : Date.now() * 1e6;
7
+ const checkpoints = [
8
+ { name: "Bun Engine Boot", time: 0 },
9
+ { name: "JS Load & Module Imports", time: jsStart }
10
+ ];
11
+ function addCheckpoint(name) {
12
+ checkpoints.push({ name, time: globalThis.Bun ? Bun.nanoseconds() : Date.now() * 1e6 });
13
+ }
14
+
15
+ let parallelTimings = null;
16
+
17
+ function printProfileReport() {
18
+ console.log("\x1b[1m\x1b[36m=== Bunmicro Startup Performance Profile ===\x1b[0m\n");
19
+
20
+ let totalMs = 0;
21
+ const rows = [];
22
+
23
+ for (let i = 0; i < checkpoints.length - 1; i++) {
24
+ const current = checkpoints[i];
25
+ const next = checkpoints[i + 1];
26
+ const durationNs = next.time - current.time;
27
+ const durationMs = durationNs / 1e6;
28
+ totalMs += durationMs;
29
+
30
+ rows.push({
31
+ phase: current.name,
32
+ durationMs: durationMs,
33
+ cumulativeMs: totalMs
34
+ });
35
+ }
36
+
37
+ const colWidths = { phase: 32, duration: 15, cumulative: 15 };
38
+
39
+ const header =
40
+ "Phase".padEnd(colWidths.phase) + " | " +
41
+ "Duration (ms)".padStart(colWidths.duration) + " | " +
42
+ "Cumulative (ms)".padStart(colWidths.cumulative);
43
+
44
+ const separator =
45
+ "-".repeat(colWidths.phase) + "-+-" +
46
+ "-".repeat(colWidths.duration) + "-+-" +
47
+ "-".repeat(colWidths.cumulative);
48
+
49
+ console.log(header);
50
+ console.log(separator);
51
+
52
+ for (const row of rows) {
53
+ const phaseStr = row.phase.padEnd(colWidths.phase);
54
+ const durStr = row.durationMs.toFixed(3).padStart(colWidths.duration);
55
+ const cumStr = row.cumulativeMs.toFixed(3).padStart(colWidths.cumulative);
56
+
57
+ let color = "";
58
+ if (row.durationMs > 50) {
59
+ color = "\x1b[31m"; // Red
60
+ } else if (row.durationMs > 10) {
61
+ color = "\x1b[33m"; // Yellow
62
+ }
63
+
64
+ const reset = color ? "\x1b[0m" : "";
65
+ console.log(`${color}${phaseStr}${reset} | ${color}${durStr}${reset} | ${cumStr}`);
66
+ }
67
+
68
+ console.log(separator);
69
+
70
+ if (parallelTimings) {
71
+ console.log("\x1b[1mParallel Tasks Breakdown:\x1b[0m");
72
+ console.log(` ├── Lua Plugins & Hooks : ${parallelTimings.lua.toFixed(3).padStart(8)} ms`);
73
+ console.log(` ├── JS Plugins Load : ${parallelTimings.js.toFixed(3).padStart(8)} ms`);
74
+ console.log(` └── Buffer & History : ${parallelTimings.buffers.toFixed(3).padStart(8)} ms`);
75
+ console.log(separator);
76
+ }
77
+
78
+ console.log(`\x1b[1mTotal Startup Time: ${totalMs.toFixed(3)} ms\x1b[0m\n`);
79
+
80
+ const slowest = [...rows].sort((a, b) => b.durationMs - a.durationMs)[0];
81
+ if (slowest) {
82
+ console.log(`\x1b[1mSlowest Phase:\x1b[0m ${slowest.phase} (${slowest.durationMs.toFixed(3)} ms)`);
83
+ if (slowest.phase.includes("Clipboard")) {
84
+ console.log("\x1b[32mTip: Clipboard probing can be slow. Setting 'clipboard' option to 'terminal' or a specific tool can bypass auto-detection.\x1b[0m");
85
+ } else if (slowest.phase.includes("Plugin")) {
86
+ console.log("\x1b[32mTip: Disable unnecessary plugins to speed up startup.\x1b[0m");
87
+ } else if (slowest.phase.includes("Syntax")) {
88
+ console.log("\x1b[32mTip: Syntax loading parses many YAML/JSON files. You can pre-compile or bundle syntax definitions to speed this up.\x1b[0m");
89
+ } else if (slowest.phase.includes("JS Load")) {
90
+ console.log("\x1b[32mTip: JS load time includes loading packages like wasmoon, which might take time due to file system lookups.\x1b[0m");
91
+ }
92
+ }
93
+ }
94
+
3
95
  import child_process from "node:child_process"
4
96
  import { accessSync, constants, existsSync, readdirSync, statSync, unlinkSync } from "node:fs";
5
97
  import { mkdir } from "node:fs/promises";
@@ -10,6 +102,7 @@ import { Config } from "./config/config.js";
10
102
  import { defaultAllSettings, OPTION_CHOICES, LOCAL_SETTINGS } from "./config/defaults.js";
11
103
  import { cleanConfig } from "./config/clean.js";
12
104
  import { RuntimeRegistry, RTColorscheme, RTHelp } from "./runtime/registry.js";
105
+ import { assetPath, hasInternalAssets, listInternalAssetDirs, readInternalAssetText } from "./runtime/assets.js";
13
106
  import { PluginManager } from "./plugins/manager.js";
14
107
  import { JsPluginManager, buildMicroGlobal, runAction, listActions } from "./plugins/js-bridge.js";
15
108
  import { Colorscheme } from "./config/colorscheme.js";
@@ -24,9 +117,11 @@ import { shellSplit } from "./shell/shell.js";
24
117
  import { styleToAnsi } from "./display/ansi-style.js";
25
118
  import { encodeBinaryToBuffer, decodeBinaryBytes } from "./buffer/fixed3-codec.js";
26
119
  import { writeBackup, removeBackup, applyBackup } from "./buffer/backup.js";
120
+ import { isHex3Encoding } from "./runtime/encodings.js";
27
121
  import { createInterface } from "node:readline/promises";
28
122
 
29
123
  import pkg from "../package.json" with { type: "json" };
124
+ import { isCompiledBinary, resolveCompiledBaseDir } from "./runtime/compiled.js";
30
125
 
31
126
 
32
127
  const __filename = fileURLToPath(import.meta.url);
@@ -70,7 +165,13 @@ Windows
70
165
  }
71
166
 
72
167
  const VERSION = pkg.version;
73
- const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
168
+ const IS_COMPILED = isCompiledBinary(process.argv);
169
+ const REPO_ROOT = IS_COMPILED
170
+ ? resolveCompiledBaseDir({ argv: process.argv })
171
+ : resolve(dirname(fileURLToPath(import.meta.url)), "..");
172
+ const SINGLE_EXE_DIR = resolve(REPO_ROOT, "single-exe");
173
+ const SINGLE_EXE_ENTRY = resolve(SINGLE_EXE_DIR, "entry.mjs");
174
+ const DEFAULT_BUILD_OUTFILE = "bmi";
74
175
  const decoder = new TextDecoder();
75
176
  let _activeTtyStream = null; // set in App.start() for use by the global error handler
76
177
 
@@ -164,13 +265,44 @@ function isHttpUrl(value) {
164
265
  }
165
266
 
166
267
  function decodeTextBytesWithEncoding(bytes, encoding = "utf-8") {
167
- if (normalizeEncodingLabel(encoding) === "hex3") {
168
- return { text: encodeBinaryToBuffer(bytes).toString("latin1"), encoding: "hex3" };
268
+ const normalized = normalizeEncodingLabel(encoding);
269
+ if (normalized === "hex3gz") {
270
+ const decoded = decodeHex3Bytes(Bun.gunzipSync(bytes));
271
+ return { ...decoded, encoding: "hex3gz" };
272
+ }
273
+ if (normalized === "hex3zst") {
274
+ const decoded = decodeHex3Bytes(Bun.zstdDecompressSync(bytes));
275
+ return { ...decoded, encoding: "hex3zst" };
276
+ }
277
+ if (normalized === "hex3") {
278
+ return decodeHex3Bytes(bytes);
169
279
  }
170
- const decoder = new TextDecoder(normalizeEncodingLabel(encoding));
280
+ const decoder = new TextDecoder(normalized);
171
281
  return { text: decoder.decode(bytes), encoding: decoder.encoding };
172
282
  }
173
283
 
284
+ function encodeTextBytesWithEncoding(text, encoding = "utf-8") {
285
+ const normalized = normalizeEncodingLabel(encoding);
286
+ if (normalized === "hex3gz") {
287
+ return Bun.gzipSync(encodeHex3Text(text));
288
+ }
289
+ if (normalized === "hex3zst") {
290
+ return Bun.zstdCompressSync(encodeHex3Text(text));
291
+ }
292
+ if (normalized === "hex3") {
293
+ return encodeHex3Text(text);
294
+ }
295
+ return new TextEncoder().encode(String(text));
296
+ }
297
+
298
+ function decodeHex3Bytes(bytes) {
299
+ return { text: encodeBinaryToBuffer(bytes).toString("latin1"), encoding: "hex3" };
300
+ }
301
+
302
+ function encodeHex3Text(text) {
303
+ return decodeBinaryBytes(Buffer.from(text, "latin1"));
304
+ }
305
+
174
306
  async function readTextFileWithEncoding(path, encoding = "utf-8") {
175
307
  const bytes = new Uint8Array(await Bun.file(path).arrayBuffer());
176
308
  return decodeTextBytesWithEncoding(bytes, encoding);
@@ -183,7 +315,7 @@ async function fetchTextWithEncoding(url, encoding = "utf-8") {
183
315
 
184
316
  function normalizeEncodingLabel(encoding = "utf-8") {
185
317
  const s = String(encoding || "utf-8");
186
- if (s === "hex3") return "hex3";
318
+ if (isHex3Encoding(s)) return s.toLowerCase();
187
319
  return new TextDecoder(s).encoding;
188
320
  }
189
321
 
@@ -512,6 +644,9 @@ function parseArgs(argv) {
512
644
  clean: false,
513
645
  cat: false,
514
646
  docs: false,
647
+ changelog: false,
648
+ buildExe: false,
649
+ buildFor: "",
515
650
  configDir: "",
516
651
  debug: false,
517
652
  profile: false,
@@ -529,9 +664,25 @@ function parseArgs(argv) {
529
664
  else if (arg === "-help" || arg === "--help" || arg === "-h") flags.help = true;
530
665
  else if (arg === "-clean") flags.clean = true;
531
666
  else if (arg === "--cat" || arg === "-cat" || arg === "--ccat" || arg === "-ccat" || arg === "--bat" || arg === "-bat" || arg === "--glow" || arg === "-glow") flags.cat = true;
667
+ else if (arg === "--xxd" || arg === "--hexdump") {
668
+ flags.cat = true;
669
+ flags.settings.set("encoding", "hex3");
670
+ }
671
+ else if (arg === "--hex3") {
672
+ flags.settings.set("encoding", "hex3");
673
+ }
674
+ else if (arg === "--hex3gz") {
675
+ flags.settings.set("encoding", "hex3gz");
676
+ }
677
+ else if (arg === "--hex3zst") {
678
+ flags.settings.set("encoding", "hex3zst");
679
+ }
532
680
  else if (arg === "--docs" || arg === "--readme") flags.docs = true;
681
+ else if (arg === "--changelog") flags.changelog = true;
682
+ else if (arg === "--build-exe") flags.buildExe = true;
683
+ else if (arg === "--build-for") flags.buildFor = argv[++i] ?? "";
533
684
  else if (arg === "-debug") flags.debug = true;
534
- else if (arg === "-profile") flags.profile = true;
685
+ else if (arg === "-profile" || arg === "--profile") flags.profile = true;
535
686
  else if (arg === "-config-dir") flags.configDir = argv[++i] ?? "";
536
687
  else if (arg === "-plugin") flags.plugin = argv[++i] ?? "";
537
688
  else if (arg.startsWith("--remote-debugging-port=")) {
@@ -578,14 +729,23 @@ function usage() {
578
729
  " Set an option for this session",
579
730
  "-options",
580
731
  " Show option help and exit\n",
732
+ "-profile, --profile",
733
+ " Print startup performance profile and exit\n",
581
734
  "--cat, --ccat, --bat, --glow",
582
735
  " Syntax-highlight file(s) and write to stdout, then exit (.md uses Bun.markdown.ansi)\n",
736
+ "--xxd, --hexdump",
737
+ " Hex3 dump file(s) and write to stdout (same as --cat -encoding hex3)\n",
738
+ "--hex3, --hex3gz, --hex3zst",
739
+ " Set -encoding hex3, hex3gz, or hex3zst for this session",
740
+ " hex3 shows raw bytes; gz/zst variants compress the same hex3 view\n",
583
741
  "-help, -h, --help",
584
742
  " Show this help & exit",
585
743
  "-version, -V, --version",
586
744
  " Show version+backend info & exit",
587
745
  "--docs, --readme",
588
746
  ` Show ${pkg.name}'s README.md & exit`,
747
+ "--changelog",
748
+ " Show CHANGELOG.md & exit",
589
749
  "",
590
750
  "--remote-debugging-port=PORT",
591
751
  " Start CDP (Chrome DevTools Protocol) server on PORT at launch",
@@ -595,6 +755,50 @@ function usage() {
595
755
  ].join("\n");
596
756
  }
597
757
 
758
+ async function buildExecutable(target = "") {
759
+ const outfile = resolve(process.cwd(), DEFAULT_BUILD_OUTFILE);
760
+ const normalizedTarget = String(target || "").trim();
761
+
762
+ const steps = [
763
+ {
764
+ label: "Pack assets",
765
+ cwd: SINGLE_EXE_DIR,
766
+ cmd: "bun",
767
+ args: ["./packAssets.sh"],
768
+ },
769
+ {
770
+ label: "Compile executable",
771
+ cwd: process.cwd(),
772
+ cmd: "bun",
773
+ args: [
774
+ "build",
775
+ "--compile",
776
+ "--bytecode",
777
+ "--minify",
778
+ SINGLE_EXE_ENTRY,
779
+ `--outfile=${DEFAULT_BUILD_OUTFILE}`,
780
+ ...(normalizedTarget ? [`--target=${normalizedTarget}`] : []),
781
+ ],
782
+ },
783
+ ];
784
+
785
+ for (const step of steps) {
786
+ console.log('');
787
+ console.log(Bun.markdown.ansi('## '+step.label));
788
+
789
+ const result = child_process.spawnSync(step.cmd, step.args, {
790
+ cwd: step.cwd,
791
+ stdio: "inherit",
792
+ env: process.env,
793
+ });
794
+ if (result.status !== 0) {
795
+ process.exit(result.status ?? 1);
796
+ }
797
+ }
798
+
799
+ console.log(`Built executable: ${outfile}`);
800
+ }
801
+
598
802
  function parseInput(args) {
599
803
  const files = [];
600
804
  const command = {
@@ -1177,9 +1381,9 @@ class BufferModel {
1177
1381
  if (this._backupRevision === backupRevision) this._backupRequested = false;
1178
1382
  this._forceKeepBackup = true;
1179
1383
  }
1180
- if (this.encoding === "hex3") {
1384
+ if (isHex3Encoding(this.encoding)) {
1181
1385
  try {
1182
- await Bun.write(targetPath, decodeBinaryBytes(Buffer.from(text, "latin1")));
1386
+ await Bun.write(targetPath, encodeTextBytesWithEncoding(text, this.encoding));
1183
1387
  } finally {
1184
1388
  this._forceKeepBackup = false;
1185
1389
  }
@@ -4037,7 +4241,7 @@ class App {
4037
4241
  if (!force && this.buffer?.readonly) { this.message = "Can't save under readonly mode"; return; }
4038
4242
  try {
4039
4243
  const enc = normalizeEncodingLabel(this.buffer?.encoding);
4040
- if (enc !== "utf-8" && enc !== "hex3") {
4244
+ if (enc !== "utf-8" && !isHex3Encoding(enc)) {
4041
4245
  this.openYNPrompt("Save in UTF-8?(y,n)", async (answer) => {
4042
4246
  if (answer === "y") await this.saveUtf8();
4043
4247
  });
@@ -4454,7 +4658,8 @@ class App {
4454
4658
  const saveArgs = [...cmdArgs];
4455
4659
  const saveForce = saveArgs[0] === "-f" && (saveArgs.shift(), true);
4456
4660
  if (!saveForce && buf?.readonly) { this.message = "Can't save under readonly mode"; break; }
4457
- if (saveArgs.length > 0 && normalizeEncodingLabel(buf?.encoding) !== "utf-8" && normalizeEncodingLabel(buf?.encoding) !== "hex3") {
4661
+ const bufferEncoding = normalizeEncodingLabel(buf?.encoding);
4662
+ if (saveArgs.length > 0 && bufferEncoding !== "utf-8" && !isHex3Encoding(bufferEncoding)) {
4458
4663
  const target = resolve(expandHome(saveArgs[0]));
4459
4664
  this.openYNPrompt("Save in UTF-8?(y,n)", async (answer) => {
4460
4665
  if (answer === "y") {
@@ -5329,14 +5534,14 @@ const COMMAND_NAMES = [
5329
5534
  ];
5330
5535
 
5331
5536
  const SUPPORTED_ENCODING_LABELS = [
5332
- "hex3",
5537
+ "hex3", "hex3gz", "hex3zst",
5333
5538
  "utf-8", "utf-16le", "utf-16be",
5334
5539
  "windows-1252", "iso-8859-1", "latin1",
5335
5540
  "big5", "gbk", "gb18030",
5336
5541
  "shift_jis", "sjis", "euc-jp", "iso-2022-jp",
5337
5542
  "euc-kr", "ks_c_5601-1987",
5338
5543
  ].filter((encoding) => {
5339
- if (encoding === "hex3") return true;
5544
+ if (isHex3Encoding(encoding)) return true;
5340
5545
  try { new TextDecoder(encoding); return true; }
5341
5546
  catch { return false; }
5342
5547
  });
@@ -6573,8 +6778,16 @@ async function loadBuffers(files, command) {
6573
6778
  } else if (!process.stdin.isTTY) {
6574
6779
  const chunks = [];
6575
6780
  for await (const chunk of process.stdin) chunks.push(chunk);
6576
- const stdinText = Buffer.concat(chunks).toString("utf8");
6577
- const stdinBuf = new BufferModel({ text: stdinText, type: process.stdout.isTTY ? "default" : "stdout", command });
6781
+ const context = loadBuffers.context ?? {};
6782
+ const encoding = context.config?.globalSettings?.encoding ?? DEFAULT_SETTINGS.encoding;
6783
+ const decoded = decodeTextBytesWithEncoding(Buffer.concat(chunks), encoding);
6784
+ const stdinText = decoded.text;
6785
+ const stdinBuf = new BufferModel({
6786
+ text: stdinText,
6787
+ type: process.stdout.isTTY ? "default" : "stdout",
6788
+ command,
6789
+ encoding: decoded.encoding,
6790
+ });
6578
6791
  if (loadBuffers.context) attachSyntax(stdinBuf, loadBuffers.context, "", stdinText);
6579
6792
  buffers.push(stdinBuf);
6580
6793
  } else {
@@ -6589,12 +6802,38 @@ async function loadBuffers(files, command) {
6589
6802
  }
6590
6803
 
6591
6804
  async function printReadmeDocs() {
6592
- const readme = await Bun.file(join(REPO_ROOT, "README.md")).text();
6805
+ const readme = readInternalAssetText("README.md") ?? await Bun.file(join(REPO_ROOT, "README.md")).text();
6593
6806
  process.stdout.write(Bun.markdown.ansi(readme, { hyperlinks: true }));
6594
6807
  }
6595
6808
 
6809
+ async function printChangelogDocs() {
6810
+ const changelog = readInternalAssetText("CHANGELOG.md") ?? await Bun.file(join(REPO_ROOT, "CHANGELOG.md")).text();
6811
+ process.stdout.write(Bun.markdown.ansi(changelog, { hyperlinks: true }));
6812
+ }
6813
+
6596
6814
  async function main() {
6815
+ addCheckpoint("Argument Parsing");
6597
6816
  const { flags, files: rawFiles } = parseArgs(process.argv.slice(2));
6817
+ if (flags.buildExe && flags.buildFor) {
6818
+ console.error("--build-exe and --build-for are separate paths; use only one");
6819
+ process.exit(1);
6820
+ }
6821
+ if (flags.buildExe) {
6822
+ if (IS_COMPILED) {
6823
+ console.error("--build-exe is only available in the source tree");
6824
+ process.exit(1);
6825
+ }
6826
+ await buildExecutable();
6827
+ return;
6828
+ }
6829
+ if (flags.buildFor) {
6830
+ if (IS_COMPILED) {
6831
+ console.error("--build-for is only available in the source tree");
6832
+ process.exit(1);
6833
+ }
6834
+ await buildExecutable(flags.buildFor);
6835
+ return;
6836
+ }
6598
6837
  if (flags.help) {
6599
6838
  console.log(usage());
6600
6839
  return;
@@ -6628,6 +6867,10 @@ async function main() {
6628
6867
  await printReadmeDocs();
6629
6868
  return;
6630
6869
  }
6870
+ if (flags.changelog) {
6871
+ await printChangelogDocs();
6872
+ return;
6873
+ }
6631
6874
  if (flags.options) {
6632
6875
  for (const [key, value] of Object.entries(defaultAllSettings()).sort(([a], [b]) => a.localeCompare(b))) {
6633
6876
  console.log(`-${key} value`);
@@ -6635,13 +6878,16 @@ async function main() {
6635
6878
  }
6636
6879
  return;
6637
6880
  }
6881
+ addCheckpoint("Config Initialization");
6638
6882
  const config = await new Config({ configDir: flags.configDir }).init();
6639
6883
  config.applyCliSettings(flags.settings);
6640
6884
  syncEditorSettings(config);
6641
6885
 
6886
+ addCheckpoint("Runtime Registry Init");
6642
6887
  const runtime = new RuntimeRegistry({ repoRoot: REPO_ROOT, configDir: config.configDir });
6643
6888
  await runtime.init({ user: true });
6644
6889
 
6890
+ addCheckpoint("Colorscheme & Syntax Load");
6645
6891
  const colorscheme = await new Colorscheme(runtime).load(config.getGlobalOption("colorscheme") || "default");
6646
6892
  const syntaxDefinitions = await loadSyntaxDefinitions(runtime);
6647
6893
 
@@ -6650,22 +6896,36 @@ async function main() {
6650
6896
  return;
6651
6897
  }
6652
6898
 
6899
+ addCheckpoint("Lua Plugin Manager Init");
6653
6900
  const plugins = new PluginManager({ config, runtime, repoRoot: REPO_ROOT });
6654
6901
  await plugins.init();
6655
6902
 
6656
6903
  if (flags.plugin === "list") {
6657
6904
  const luaList = plugins.list();
6658
- const jsDirs = [
6659
- { dir: join(REPO_ROOT, "runtime", "jsplugins"), builtin: true },
6660
- { dir: join(config.configDir, "jsplug"), builtin: false },
6661
- ];
6662
6905
  const jsItems = [];
6663
- for (const { dir, builtin } of jsDirs) {
6664
- if (!existsSync(dir)) continue;
6665
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
6906
+ const builtinJsPluginNames = hasInternalAssets()
6907
+ ? listInternalAssetDirs(assetPath("runtime", "jsplugins"))
6908
+ : [];
6909
+ if (builtinJsPluginNames.length > 0) {
6910
+ for (const name of builtinJsPluginNames) {
6911
+ jsItems.push({ name, builtin: true });
6912
+ }
6913
+ } else {
6914
+ const builtinJsDir = join(REPO_ROOT, "runtime", "jsplugins");
6915
+ if (existsSync(builtinJsDir)) {
6916
+ for (const entry of readdirSync(builtinJsDir, { withFileTypes: true })) {
6917
+ if (!entry.isDirectory()) continue;
6918
+ if (existsSync(join(builtinJsDir, entry.name, `${entry.name}.js`)))
6919
+ jsItems.push({ name: entry.name, builtin: true });
6920
+ }
6921
+ }
6922
+ }
6923
+ const userJsDir = join(config.configDir, "jsplug");
6924
+ if (existsSync(userJsDir)) {
6925
+ for (const entry of readdirSync(userJsDir, { withFileTypes: true })) {
6666
6926
  if (!entry.isDirectory()) continue;
6667
- if (existsSync(join(dir, entry.name, `${entry.name}.js`)))
6668
- jsItems.push({ name: entry.name, builtin });
6927
+ if (existsSync(join(userJsDir, entry.name, `${entry.name}.js`)))
6928
+ jsItems.push({ name: entry.name, builtin: false });
6669
6929
  }
6670
6930
  }
6671
6931
  const fmtTag = (p) => p.builtin ? " *(built-in)*" : "";
@@ -6688,50 +6948,114 @@ async function main() {
6688
6948
  return;
6689
6949
  }
6690
6950
 
6691
- const pluginErr = await plugins.loadAll();
6692
- if (pluginErr) console.error(`Plugin runtime disabled: ${pluginErr.message}`);
6693
- if (!pluginErr) {
6694
- await plugins.run("preinit");
6695
- await plugins.run("init");
6696
- await plugins.run("postinit");
6697
- }
6698
-
6699
- // ── JS plugin system ──────────────────────────────────────────────────────
6700
- const jsPlugins = new JsPluginManager();
6701
6951
  const { files, command } = parseInput(rawFiles);
6952
+ const jsPlugins = new JsPluginManager();
6702
6953
  const context = { colorscheme, syntaxDefinitions, plugins, config, runtime, jsPlugins };
6703
6954
  jsPlugins.setContext(context);
6704
6955
  buildMicroGlobal(jsPlugins); // sets globalThis.micro
6705
6956
 
6706
- const jsDirs = [
6707
- { dir: join(REPO_ROOT, "runtime", "jsplugins"), builtin: true },
6708
- { dir: join(config.configDir, "jsplug"), builtin: false },
6709
- ];
6710
- await jsPlugins.loadFrom(jsDirs);
6711
- // ─────────────────────────────────────────────────────────────────────────
6957
+ addCheckpoint("Parallel Initialization Start");
6958
+
6959
+ const luaPromise = (async () => {
6960
+ // return 0;
6961
+ const start = Bun.nanoseconds();
6962
+ const pluginErr = await plugins.loadAll();
6963
+ if (pluginErr) console.error(`Plugin runtime disabled: ${pluginErr.message}`);
6964
+ if (!pluginErr) {
6965
+ await plugins.run("preinit");
6966
+ await plugins.run("init");
6967
+ await plugins.run("postinit");
6968
+ }
6969
+ const end = Bun.nanoseconds();
6970
+ return { pluginErr, duration: end - start };
6971
+ })();
6972
+
6973
+ const jsPromise = (async () => {
6974
+ const start = Bun.nanoseconds();
6975
+ const jsDirs = [
6976
+ { dir: join(REPO_ROOT, "runtime", "jsplugins"), builtin: true },
6977
+ { dir: join(config.configDir, "jsplug"), builtin: false },
6978
+ ];
6979
+ await jsPlugins.loadFrom(jsDirs);
6980
+ const end = Bun.nanoseconds();
6981
+ return { duration: end - start };
6982
+ })();
6983
+
6984
+ const buffersPromise = (async () => {
6985
+ const start = Bun.nanoseconds();
6986
+ let cursorStates = {};
6987
+ if (DEFAULT_SETTINGS.savecursor) {
6988
+ cursorStates = await loadCursorStates(config.configDir);
6989
+ }
6990
+ // Mix in context properties needed for buffer loading:
6991
+ context.cursorStates = cursorStates;
6992
+ context._openBuffers = new Map();
6993
+ context._termPrompt = process.stdout.isTTY ? termPromptLine : null;
6994
+
6995
+ loadBuffers.context = context;
6996
+ const buffers = await loadBuffers(files.map((file) =>
6997
+ isHttpUrl(file) ? file : resolve(file)
6998
+ ), command);
6712
6999
 
6713
- if (DEFAULT_SETTINGS.savecursor) {
6714
- context.cursorStates = await loadCursorStates(config.configDir);
7000
+ let historyPromise = Promise.resolve();
7001
+ if (config.getGlobalOption("savehistory") !== false) {
7002
+ historyPromise = loadHistory(config.configDir);
7003
+ }
7004
+ await historyPromise;
7005
+ const end = Bun.nanoseconds();
7006
+ return { buffers, duration: end - start };
7007
+ })();
7008
+
7009
+ const [luaSettled, jsSettled, buffersSettled] = await Promise.allSettled([
7010
+ luaPromise,
7011
+ jsPromise,
7012
+ buffersPromise
7013
+ ]);
7014
+
7015
+ const luaResult = luaSettled.status === "fulfilled"
7016
+ ? luaSettled.value
7017
+ : { pluginErr: luaSettled.reason, duration: 0 };
7018
+ if (luaSettled.status === "rejected") {
7019
+ console.error(`Lua plugin runtime disabled: ${luaSettled.reason?.message || luaSettled.reason}`);
7020
+ }
7021
+
7022
+ const jsResult = jsSettled.status === "fulfilled"
7023
+ ? jsSettled.value
7024
+ : { duration: 0 };
7025
+ if (jsSettled.status === "rejected") {
7026
+ console.error(`JS plugin runtime disabled: ${jsSettled.reason?.message || jsSettled.reason}`);
7027
+ }
7028
+
7029
+ const buffersResult = buffersSettled.status === "fulfilled"
7030
+ ? buffersSettled.value
7031
+ : { buffers: [new BufferModel({ command })], duration: 0 };
7032
+ if (buffersSettled.status === "rejected") {
7033
+ console.error(`Buffer load failed: ${buffersSettled.reason?.message || buffersSettled.reason}`);
6715
7034
  }
6716
- // Backup prompt available before App starts (stdin still in cooked mode).
6717
- context._termPrompt = process.stdout.isTTY ? termPromptLine : null;
6718
- loadBuffers.context = context;
6719
- const buffers = await loadBuffers(files.map((file) =>
6720
- isHttpUrl(file) ? file : resolve(file)
6721
- ), command);
6722
7035
 
6723
- if (!process.stdout.isTTY) {
7036
+ addCheckpoint("Parallel Initialization End");
7037
+
7038
+ parallelTimings = {
7039
+ lua: luaResult.duration / 1e6,
7040
+ js: jsResult.duration / 1e6,
7041
+ buffers: buffersResult.duration / 1e6
7042
+ };
7043
+
7044
+ const { pluginErr } = luaResult;
7045
+ const { buffers } = buffersResult;
7046
+
7047
+ if (!process.stdout.isTTY && !flags.profile) {
6724
7048
  console.log(buffers[0].lines.join("\n"));
6725
7049
  return;
6726
7050
  }
6727
- if (config.getGlobalOption("savehistory") !== false) {
6728
- await loadHistory(config.configDir);
6729
- }
7051
+
7052
+ addCheckpoint("App Instantiation");
6730
7053
  const app = new App(buffers, context);
6731
7054
  jsPlugins.setApp(app);
6732
7055
  if (plugins && !pluginErr && app.buffer) plugins.curPaneAdapter = makePaneAdapter(app.buffer, app);
6733
7056
  // Dispatch all JS plugin lifecycle hooks after setApp so TermMessage,
6734
7057
  // CurPane, cmd/action proxies, and buffer APIs all work correctly.
7058
+ addCheckpoint("JS Lifecycle Hooks");
6735
7059
  await jsPlugins.run("preinit");
6736
7060
  await jsPlugins.run("init");
6737
7061
  await jsPlugins.run("postinit");
@@ -6744,6 +7068,26 @@ async function main() {
6744
7068
  if (flags.cdpAddress) cdpArgs.push(`--address=${flags.cdpAddress}`);
6745
7069
  await app.handleCommand(`cdp ${cdpArgs.join(" ")}`);
6746
7070
  }
7071
+
7072
+ if (flags.profile) {
7073
+ addCheckpoint("Clipboard Probing");
7074
+ const clipSetting = config.getGlobalOption("clipboard") ?? "external";
7075
+ const clipboard = new ClipboardManager();
7076
+ if (process.stdin.isTTY && process.stdout.isTTY) {
7077
+ process.stdin.setRawMode?.(true);
7078
+ process.stdin.resume();
7079
+ await clipboard.initFromSetting(clipSetting, process.stdin, process.stdout, 150);
7080
+ process.stdin.setRawMode?.(false);
7081
+ process.stdin.pause();
7082
+ } else {
7083
+ await clipboard.initFromSetting(clipSetting, process.stdin, process.stdout, 150);
7084
+ }
7085
+
7086
+ addCheckpoint("Profile Done");
7087
+ printProfileReport();
7088
+ process.exit(0);
7089
+ }
7090
+
6747
7091
  await app.start();
6748
7092
  }
6749
7093
 
@@ -7168,6 +7512,10 @@ async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAUL
7168
7512
  }
7169
7513
  }
7170
7514
 
7515
+
7516
+ mainPromise.then(r=>{
7517
+
7518
+
7171
7519
  main().catch((error) => {
7172
7520
  try {
7173
7521
  (_activeTtyStream ?? process.stdin).setRawMode?.(false);
@@ -7177,3 +7525,6 @@ main().catch((error) => {
7177
7525
  process.exit(1);
7178
7526
  }
7179
7527
  });
7528
+
7529
+
7530
+ })