@drewpayment/mink 0.2.0 → 0.2.2

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.js CHANGED
@@ -2594,7 +2594,8 @@ function startDaemon(cwd) {
2594
2594
  if (existing) {
2595
2595
  removePidFile();
2596
2596
  }
2597
- const cliPath = resolve2(import.meta.dir, "../cli.ts");
2597
+ const __dir = dirname4(new URL(import.meta.url).pathname);
2598
+ const cliPath = resolve2(__dir, "../cli.ts");
2598
2599
  const logPath = schedulerLogPath();
2599
2600
  mkdirSync6(dirname4(logPath), { recursive: true });
2600
2601
  const logFd = openSync(logPath, "a");
@@ -4582,6 +4583,146 @@ var init_project_registry = __esm(() => {
4582
4583
  init_fs_utils();
4583
4584
  });
4584
4585
 
4586
+ // src/core/runtime.ts
4587
+ import { readFile as readFile2, stat } from "fs/promises";
4588
+ import { spawn as nodeSpawn } from "child_process";
4589
+ function runtimeFile(path) {
4590
+ if (isBun) {
4591
+ const f = Bun.file(path);
4592
+ return {
4593
+ exists: () => f.exists(),
4594
+ bytes: () => f.arrayBuffer().then((ab) => new Uint8Array(ab))
4595
+ };
4596
+ }
4597
+ return {
4598
+ async exists() {
4599
+ try {
4600
+ await stat(path);
4601
+ return true;
4602
+ } catch {
4603
+ return false;
4604
+ }
4605
+ },
4606
+ async bytes() {
4607
+ return readFile2(path);
4608
+ }
4609
+ };
4610
+ }
4611
+ function runtimeSpawn(cmd, opts = {}) {
4612
+ if (isBun) {
4613
+ const proc2 = Bun.spawn(cmd, {
4614
+ cwd: opts.cwd,
4615
+ env: opts.env,
4616
+ stdout: opts.stdout ?? "ignore",
4617
+ stderr: opts.stderr ?? "ignore",
4618
+ stdin: opts.stdin ?? "ignore"
4619
+ });
4620
+ return { unref: () => proc2.unref() };
4621
+ }
4622
+ const [bin, ...args] = cmd;
4623
+ const proc = nodeSpawn(bin, args, {
4624
+ cwd: opts.cwd,
4625
+ env: opts.env,
4626
+ stdio: [
4627
+ opts.stdin ?? "ignore",
4628
+ opts.stdout ?? "ignore",
4629
+ opts.stderr ?? "ignore"
4630
+ ],
4631
+ detached: true
4632
+ });
4633
+ proc.unref();
4634
+ return { unref: () => {} };
4635
+ }
4636
+ async function runtimeServe(opts) {
4637
+ if (isBun) {
4638
+ const server = Bun.serve({
4639
+ port: opts.port,
4640
+ hostname: opts.hostname,
4641
+ idleTimeout: opts.idleTimeout ?? 0,
4642
+ fetch: opts.fetch
4643
+ });
4644
+ return {
4645
+ port: server.port,
4646
+ stop: (close) => server.stop(close)
4647
+ };
4648
+ }
4649
+ const { createServer } = await import("node:http");
4650
+ const { Readable } = await import("node:stream");
4651
+ return new Promise((resolve3) => {
4652
+ const httpServer = createServer(async (req, res) => {
4653
+ const url = `http://${opts.hostname}:${opts.port}${req.url ?? "/"}`;
4654
+ const headers = new Headers;
4655
+ for (const [key, val] of Object.entries(req.headers)) {
4656
+ if (val)
4657
+ headers.set(key, Array.isArray(val) ? val.join(", ") : val);
4658
+ }
4659
+ let body = null;
4660
+ if (req.method !== "GET" && req.method !== "HEAD") {
4661
+ const chunks = [];
4662
+ for await (const chunk of req) {
4663
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
4664
+ }
4665
+ body = Buffer.concat(chunks);
4666
+ }
4667
+ const request = new Request(url, {
4668
+ method: req.method,
4669
+ headers,
4670
+ body,
4671
+ duplex: body ? "half" : undefined
4672
+ });
4673
+ try {
4674
+ const response = await opts.fetch(request);
4675
+ res.writeHead(response.status, Object.fromEntries(response.headers));
4676
+ if (!response.body) {
4677
+ res.end();
4678
+ return;
4679
+ }
4680
+ const reader = response.body.getReader();
4681
+ const nodeStream = new Readable({
4682
+ async read() {
4683
+ try {
4684
+ const { done, value } = await reader.read();
4685
+ if (done) {
4686
+ this.push(null);
4687
+ } else {
4688
+ this.push(Buffer.from(value));
4689
+ }
4690
+ } catch {
4691
+ this.push(null);
4692
+ }
4693
+ }
4694
+ });
4695
+ res.on("close", () => {
4696
+ reader.cancel().catch(() => {});
4697
+ nodeStream.destroy();
4698
+ });
4699
+ nodeStream.pipe(res);
4700
+ } catch (err) {
4701
+ if (!res.headersSent) {
4702
+ res.writeHead(500);
4703
+ res.end(String(err));
4704
+ }
4705
+ }
4706
+ });
4707
+ httpServer.listen(opts.port, opts.hostname, () => {
4708
+ const addr = httpServer.address();
4709
+ const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
4710
+ resolve3({
4711
+ port: boundPort,
4712
+ stop: (close) => {
4713
+ if (close)
4714
+ httpServer.closeAllConnections();
4715
+ httpServer.close();
4716
+ }
4717
+ });
4718
+ });
4719
+ });
4720
+ }
4721
+ var isBun;
4722
+ var init_runtime = __esm(() => {
4723
+ isBun = typeof globalThis.Bun !== "undefined";
4724
+ });
4725
+
4585
4726
  // src/core/dashboard-server.ts
4586
4727
  var exports_dashboard_server = {};
4587
4728
  __export(exports_dashboard_server, {
@@ -4589,7 +4730,7 @@ __export(exports_dashboard_server, {
4589
4730
  });
4590
4731
  import { watch } from "fs";
4591
4732
  import { existsSync as existsSync13 } from "fs";
4592
- import { basename as basename6, join as join14, extname as extname2 } from "path";
4733
+ import { basename as basename6, dirname as dirname5, join as join14, extname as extname2 } from "path";
4593
4734
 
4594
4735
  class SSEManager {
4595
4736
  clients = new Map;
@@ -4740,7 +4881,7 @@ function extractPathParam(pathname, prefix) {
4740
4881
  return rest || null;
4741
4882
  return rest.slice(0, slashIdx) || null;
4742
4883
  }
4743
- function startDashboardServer(cwd, options = {}) {
4884
+ async function startDashboardServer(cwd, options = {}) {
4744
4885
  const port = options.port ?? 4040;
4745
4886
  const hostname = options.hostname ?? "127.0.0.1";
4746
4887
  const sseManager = new SSEManager;
@@ -4762,13 +4903,29 @@ function startDashboardServer(cwd, options = {}) {
4762
4903
  timestamp: new Date().toISOString()
4763
4904
  });
4764
4905
  });
4765
- const dashboardOutDir = join14(import.meta.dir, "..", "..", "dashboard", "out");
4906
+ const __dir = dirname5(new URL(import.meta.url).pathname);
4907
+ let pkgRoot = __dir;
4908
+ while (pkgRoot !== dirname5(pkgRoot)) {
4909
+ if (existsSync13(join14(pkgRoot, "package.json")))
4910
+ break;
4911
+ pkgRoot = dirname5(pkgRoot);
4912
+ }
4913
+ const dashboardOutDir = join14(pkgRoot, "dashboard", "out");
4766
4914
  const dashboardBuilt = existsSync13(join14(dashboardOutDir, "index.html"));
4767
4915
  let clientIdCounter = 0;
4768
4916
  if (!dashboardBuilt) {
4769
4917
  console.warn("[mink] dashboard not built. Run: cd dashboard && bun run build");
4770
4918
  }
4771
- const server = Bun.serve({
4919
+ async function serveFile(filePath, contentType) {
4920
+ const file = runtimeFile(filePath);
4921
+ if (await file.exists()) {
4922
+ return new Response(await file.bytes(), {
4923
+ headers: { "Content-Type": contentType }
4924
+ });
4925
+ }
4926
+ return null;
4927
+ }
4928
+ const server = await runtimeServe({
4772
4929
  port,
4773
4930
  hostname,
4774
4931
  idleTimeout: 0,
@@ -4791,26 +4948,17 @@ function startDashboardServer(cwd, options = {}) {
4791
4948
  if (!filePath.startsWith(dashboardOutDir)) {
4792
4949
  return jsonResponse({ error: "Forbidden" }, 403);
4793
4950
  }
4794
- const file = Bun.file(filePath);
4795
- if (await file.exists()) {
4796
- const ext = extname2(filePath);
4797
- const contentType = MIME_TYPES[ext] || "application/octet-stream";
4798
- return new Response(file, {
4799
- headers: { "Content-Type": contentType }
4800
- });
4801
- }
4802
- const htmlFile = Bun.file(filePath + ".html");
4803
- if (await htmlFile.exists()) {
4804
- return new Response(htmlFile, {
4805
- headers: { "Content-Type": "text/html; charset=utf-8" }
4806
- });
4807
- }
4808
- const indexFile = Bun.file(join14(dashboardOutDir, "index.html"));
4809
- if (await indexFile.exists()) {
4810
- return new Response(indexFile, {
4811
- headers: { "Content-Type": "text/html; charset=utf-8" }
4812
- });
4813
- }
4951
+ const ext = extname2(filePath);
4952
+ const contentType = MIME_TYPES[ext] || "application/octet-stream";
4953
+ const served = await serveFile(filePath, contentType);
4954
+ if (served)
4955
+ return served;
4956
+ const htmlServed = await serveFile(filePath + ".html", "text/html; charset=utf-8");
4957
+ if (htmlServed)
4958
+ return htmlServed;
4959
+ const indexServed = await serveFile(join14(dashboardOutDir, "index.html"), "text/html; charset=utf-8");
4960
+ if (indexServed)
4961
+ return indexServed;
4814
4962
  }
4815
4963
  }
4816
4964
  if (method === "GET" && pathname === "/api/events") {
@@ -4869,15 +5017,11 @@ retry: 3000
4869
5017
  return jsonResponse({ error: "Invalid filename" }, 400);
4870
5018
  }
4871
5019
  const imgPath = join14(designCapturesDir(resolvedCwd), filename);
4872
- const file = Bun.file(imgPath);
4873
- if (await file.exists()) {
4874
- return new Response(file, {
4875
- headers: {
4876
- "Content-Type": "image/jpeg",
4877
- "Cache-Control": "public, max-age=60",
4878
- "Access-Control-Allow-Origin": "*"
4879
- }
4880
- });
5020
+ const served = await serveFile(imgPath, "image/jpeg");
5021
+ if (served) {
5022
+ served.headers.set("Cache-Control", "public, max-age=60");
5023
+ served.headers.set("Access-Control-Allow-Origin", "*");
5024
+ return served;
4881
5025
  }
4882
5026
  return jsonResponse({ error: "Image not found" }, 404);
4883
5027
  }
@@ -4951,12 +5095,7 @@ retry: 3000
4951
5095
  try {
4952
5096
  const platform = process.platform;
4953
5097
  const cmd = platform === "darwin" ? ["open", serverUrl] : platform === "win32" ? ["cmd", "/c", "start", serverUrl] : ["xdg-open", serverUrl];
4954
- const proc = Bun.spawn(cmd, {
4955
- stdout: "ignore",
4956
- stderr: "ignore",
4957
- stdin: "ignore"
4958
- });
4959
- proc.unref();
5098
+ runtimeSpawn(cmd).unref();
4960
5099
  } catch {}
4961
5100
  }
4962
5101
  return {
@@ -4974,6 +5113,7 @@ var init_dashboard_server = __esm(() => {
4974
5113
  init_dashboard_api();
4975
5114
  init_project_registry();
4976
5115
  init_project_id();
5116
+ init_runtime();
4977
5117
  MIME_TYPES = {
4978
5118
  ".html": "text/html; charset=utf-8",
4979
5119
  ".js": "application/javascript; charset=utf-8",
@@ -5018,7 +5158,7 @@ async function dashboard(cwd, args) {
5018
5158
  const port = portArg ? parseInt(portArg.split("=")[1], 10) : 4040;
5019
5159
  const noOpen = args.includes("--no-open");
5020
5160
  const { startDashboardServer: startDashboardServer2 } = await Promise.resolve().then(() => (init_dashboard_server(), exports_dashboard_server));
5021
- const { url } = startDashboardServer2(cwd, { port, open: !noOpen });
5161
+ const { url } = await startDashboardServer2(cwd, { port, open: !noOpen });
5022
5162
  console.log(`[mink] dashboard running at ${url}`);
5023
5163
  console.log("[mink] press Ctrl+C to stop");
5024
5164
  await new Promise(() => {});
@@ -5166,7 +5306,7 @@ var init_config2 = __esm(() => {
5166
5306
  // src/commands/init.ts
5167
5307
  import { execSync as execSync3 } from "child_process";
5168
5308
  import { mkdirSync as mkdirSync7, existsSync as existsSync16 } from "fs";
5169
- import { resolve as resolve3, dirname as dirname5, basename as basename7, join as join15 } from "path";
5309
+ import { resolve as resolve3, dirname as dirname6, basename as basename7, join as join15 } from "path";
5170
5310
  function detectRuntime2() {
5171
5311
  try {
5172
5312
  execSync3("bun --version", { stdio: "ignore" });
@@ -5207,7 +5347,7 @@ function isMinkHook2(entry) {
5207
5347
  return false;
5208
5348
  }
5209
5349
  function mergeHooksIntoSettings2(settingsPath, newHooks) {
5210
- mkdirSync7(dirname5(settingsPath), { recursive: true });
5350
+ mkdirSync7(dirname6(settingsPath), { recursive: true });
5211
5351
  const existing = safeReadJson(settingsPath) ?? {};
5212
5352
  const existingHooks = existing.hooks ?? {};
5213
5353
  for (const [event, entries] of Object.entries(newHooks)) {
@@ -5230,7 +5370,7 @@ var exports_update = {};
5230
5370
  __export(exports_update, {
5231
5371
  update: () => update
5232
5372
  });
5233
- import { resolve as resolve4, dirname as dirname6 } from "path";
5373
+ import { resolve as resolve4, dirname as dirname7 } from "path";
5234
5374
  function parseArgs(args) {
5235
5375
  let dryRun = false;
5236
5376
  let project = null;
@@ -5278,7 +5418,7 @@ async function update(cwd, args) {
5278
5418
  return;
5279
5419
  }
5280
5420
  const runtime = detectRuntime2();
5281
- const cliPath = resolve4(dirname6(new URL(import.meta.url).pathname), "../cli.ts");
5421
+ const cliPath = resolve4(dirname7(new URL(import.meta.url).pathname), "../cli.ts");
5282
5422
  const newHooks = buildHooksConfig2(runtime, cliPath);
5283
5423
  for (const target of targets) {
5284
5424
  console.log(`[mink] updating: ${target.name} (${target.id})`);
@@ -5495,8 +5635,8 @@ function findFiles(dir, pattern) {
5495
5635
  continue;
5496
5636
  const full = join17(current, entry);
5497
5637
  try {
5498
- const stat = statSync8(full);
5499
- if (stat.isDirectory()) {
5638
+ const stat2 = statSync8(full);
5639
+ if (stat2.isDirectory()) {
5500
5640
  walk(full);
5501
5641
  } else if (pattern.test(entry)) {
5502
5642
  results.push(full);
@@ -35737,18 +35877,18 @@ var __runInitializers10 = function(thisArg, initializers, value) {
35737
35877
  if (target)
35738
35878
  Object.defineProperty(target, contextIn.name, descriptor);
35739
35879
  done = true;
35740
- }, Request;
35880
+ }, Request2;
35741
35881
  var init_Request = __esm(() => {
35742
35882
  init_Errors();
35743
35883
  init_EventEmitter();
35744
35884
  init_decorators();
35745
35885
  init_disposable();
35746
- Request = (() => {
35886
+ Request2 = (() => {
35747
35887
  var _a2;
35748
35888
  let _classSuper = EventEmitter;
35749
35889
  let _instanceExtraInitializers = [];
35750
35890
  let _dispose_decorators;
35751
- return class Request2 extends _classSuper {
35891
+ return class Request3 extends _classSuper {
35752
35892
  static {
35753
35893
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : undefined;
35754
35894
  __esDecorate10(this, null, _dispose_decorators, { kind: "method", name: "dispose", static: false, private: false, access: { has: (obj) => ("dispose" in obj), get: (obj) => obj.dispose }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -35756,7 +35896,7 @@ var init_Request = __esm(() => {
35756
35896
  Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
35757
35897
  }
35758
35898
  static from(browsingContext, event) {
35759
- const request = new Request2(browsingContext, event);
35899
+ const request = new Request3(browsingContext, event);
35760
35900
  request.#initialize();
35761
35901
  return request;
35762
35902
  }
@@ -35795,7 +35935,7 @@ var init_Request = __esm(() => {
35795
35935
  if (event.redirectCount !== this.#event.redirectCount + 1 && !isAfterAuth) {
35796
35936
  return;
35797
35937
  }
35798
- this.#redirect = Request2.from(this.#browsingContext, event);
35938
+ this.#redirect = Request3.from(this.#browsingContext, event);
35799
35939
  this.emit("redirect", this.#redirect);
35800
35940
  this.dispose();
35801
35941
  });
@@ -36369,7 +36509,7 @@ var init_BrowsingContext = __esm(() => {
36369
36509
  if (this.#requests.has(event.request.request)) {
36370
36510
  return;
36371
36511
  }
36372
- const request = Request.from(this, event);
36512
+ const request = Request2.from(this, event);
36373
36513
  this.#requests.set(request.id, request);
36374
36514
  this.emit("request", { request });
36375
36515
  });
@@ -51207,8 +51347,8 @@ var require_file = __commonJS((exports) => {
51207
51347
  debug2("Normalized pathname: %o", filepath);
51208
51348
  const fdHandle = await fs_1.promises.open(filepath, flags, mode);
51209
51349
  const fd = fdHandle.fd;
51210
- const stat = await fdHandle.stat();
51211
- if (cache && cache.stat && stat && isNotModified(cache.stat, stat)) {
51350
+ const stat2 = await fdHandle.stat();
51351
+ if (cache && cache.stat && stat2 && isNotModified(cache.stat, stat2)) {
51212
51352
  await fdHandle.close();
51213
51353
  throw new notmodified_1.default;
51214
51354
  }
@@ -51217,7 +51357,7 @@ var require_file = __commonJS((exports) => {
51217
51357
  ...opts,
51218
51358
  fd
51219
51359
  });
51220
- rs.stat = stat;
51360
+ rs.stat = stat2;
51221
51361
  return rs;
51222
51362
  } catch (err) {
51223
51363
  if (err.code === "ENOENT") {
@@ -77280,7 +77420,7 @@ var require_tar_fs = __commonJS((exports) => {
77280
77420
  pack2.entry(header, onnextentry);
77281
77421
  });
77282
77422
  }
77283
- function onstat(err, filename, stat) {
77423
+ function onstat(err, filename, stat2) {
77284
77424
  if (pack2.destroyed)
77285
77425
  return;
77286
77426
  if (err)
@@ -77290,31 +77430,31 @@ var require_tar_fs = __commonJS((exports) => {
77290
77430
  pack2.finalize();
77291
77431
  return finish(pack2);
77292
77432
  }
77293
- if (stat.isSocket())
77433
+ if (stat2.isSocket())
77294
77434
  return onnextentry();
77295
77435
  let header = {
77296
77436
  name: normalize(filename),
77297
- mode: (stat.mode | (stat.isDirectory() ? dmode : fmode)) & umask,
77298
- mtime: stat.mtime,
77299
- size: stat.size,
77437
+ mode: (stat2.mode | (stat2.isDirectory() ? dmode : fmode)) & umask,
77438
+ mtime: stat2.mtime,
77439
+ size: stat2.size,
77300
77440
  type: "file",
77301
- uid: stat.uid,
77302
- gid: stat.gid
77441
+ uid: stat2.uid,
77442
+ gid: stat2.gid
77303
77443
  };
77304
- if (stat.isDirectory()) {
77444
+ if (stat2.isDirectory()) {
77305
77445
  header.size = 0;
77306
77446
  header.type = "directory";
77307
77447
  header = map2(header) || header;
77308
77448
  return pack2.entry(header, onnextentry);
77309
77449
  }
77310
- if (stat.isSymbolicLink()) {
77450
+ if (stat2.isSymbolicLink()) {
77311
77451
  header.size = 0;
77312
77452
  header.type = "symlink";
77313
77453
  header = map2(header) || header;
77314
77454
  return onsymlink(filename, header);
77315
77455
  }
77316
77456
  header = map2(header) || header;
77317
- if (!stat.isFile()) {
77457
+ if (!stat2.isFile()) {
77318
77458
  if (strict)
77319
77459
  return pack2.destroy(new Error("unsupported type for " + filename));
77320
77460
  return onnextentry();
@@ -77397,7 +77537,7 @@ var require_tar_fs = __commonJS((exports) => {
77397
77537
  uid: header.uid,
77398
77538
  gid: header.gid,
77399
77539
  mode: header.mode
77400
- }, stat);
77540
+ }, stat2);
77401
77541
  }
77402
77542
  mkdirfix(dir, {
77403
77543
  fs: xfs,
@@ -77422,7 +77562,7 @@ var require_tar_fs = __commonJS((exports) => {
77422
77562
  next();
77423
77563
  });
77424
77564
  });
77425
- function stat(err) {
77565
+ function stat2(err) {
77426
77566
  if (err)
77427
77567
  return next(err);
77428
77568
  utimes(name, header, function(err2) {
@@ -77445,7 +77585,7 @@ var require_tar_fs = __commonJS((exports) => {
77445
77585
  return next(err);
77446
77586
  if (!valid && validateSymLinks)
77447
77587
  return next(new Error(name + " is not a valid symlink"));
77448
- xfs.symlink(header.linkname, name, stat);
77588
+ xfs.symlink(header.linkname, name, stat2);
77449
77589
  });
77450
77590
  });
77451
77591
  }
@@ -77462,7 +77602,7 @@ var require_tar_fs = __commonJS((exports) => {
77462
77602
  stream = xfs.createReadStream(dst);
77463
77603
  return onfile();
77464
77604
  }
77465
- stat(err2);
77605
+ stat2(err2);
77466
77606
  });
77467
77607
  });
77468
77608
  });
@@ -77479,7 +77619,7 @@ var require_tar_fs = __commonJS((exports) => {
77479
77619
  pump(rs, ws, function(err) {
77480
77620
  if (err)
77481
77621
  return next(err);
77482
- ws.on("close", stat);
77622
+ ws.on("close", stat2);
77483
77623
  });
77484
77624
  }
77485
77625
  }
@@ -77568,7 +77708,7 @@ var require_tar_fs = __commonJS((exports) => {
77568
77708
  function normalize(name) {
77569
77709
  return win32 ? name.replace(/\\/g, "/").replace(/[:?<>|]/g, "_") : name;
77570
77710
  }
77571
- function statAll(fs4, stat, cwd, ignore, entries, sort) {
77711
+ function statAll(fs4, stat2, cwd, ignore, entries, sort) {
77572
77712
  if (!entries)
77573
77713
  entries = ["."];
77574
77714
  const queue = entries.slice(0);
@@ -77577,11 +77717,11 @@ var require_tar_fs = __commonJS((exports) => {
77577
77717
  return callback(null);
77578
77718
  const next = queue.shift();
77579
77719
  const nextAbs = path7.join(cwd, next);
77580
- stat.call(fs4, nextAbs, function(err, stat2) {
77720
+ stat2.call(fs4, nextAbs, function(err, stat3) {
77581
77721
  if (err)
77582
77722
  return callback(entries.indexOf(next) === -1 && err.code === "ENOENT" ? null : err);
77583
- if (!stat2.isDirectory())
77584
- return callback(null, next, stat2);
77723
+ if (!stat3.isDirectory())
77724
+ return callback(null, next, stat3);
77585
77725
  fs4.readdir(nextAbs, function(err2, files) {
77586
77726
  if (err2)
77587
77727
  return callback(err2);
@@ -77591,7 +77731,7 @@ var require_tar_fs = __commonJS((exports) => {
77591
77731
  if (!ignore(path7.join(cwd, next, files[i])))
77592
77732
  queue.push(path7.join(next, files[i]));
77593
77733
  }
77594
- callback(null, next, stat2);
77734
+ callback(null, next, stat3);
77595
77735
  });
77596
77736
  });
77597
77737
  };
@@ -78323,19 +78463,19 @@ var init_cliui = __esm(() => {
78323
78463
  });
78324
78464
 
78325
78465
  // node_modules/escalade/sync/index.mjs
78326
- import { dirname as dirname7, resolve as resolve6 } from "path";
78466
+ import { dirname as dirname8, resolve as resolve6 } from "path";
78327
78467
  import { readdirSync as readdirSync6, statSync as statSync9 } from "fs";
78328
78468
  function sync_default(start, callback) {
78329
78469
  let dir = resolve6(".", start);
78330
78470
  let tmp, stats = statSync9(dir);
78331
78471
  if (!stats.isDirectory()) {
78332
- dir = dirname7(dir);
78472
+ dir = dirname8(dir);
78333
78473
  }
78334
78474
  while (true) {
78335
78475
  tmp = callback(dir, readdirSync6(dir));
78336
78476
  if (tmp)
78337
78477
  return resolve6(dir, tmp);
78338
- dir = dirname7(tmp = dir);
78478
+ dir = dirname8(tmp = dir);
78339
78479
  if (tmp === dir)
78340
78480
  break;
78341
78481
  }
@@ -79549,7 +79689,7 @@ import { notStrictEqual, strictEqual } from "assert";
79549
79689
  import { inspect } from "util";
79550
79690
  import { readFileSync as readFileSync20 } from "fs";
79551
79691
  import { fileURLToPath } from "url";
79552
- import { basename as basename9, dirname as dirname8, extname as extname3, relative as relative7, resolve as resolve9 } from "path";
79692
+ import { basename as basename9, dirname as dirname9, extname as extname3, relative as relative7, resolve as resolve9 } from "path";
79553
79693
  var REQUIRE_ERROR = "require is not supported by ESM", REQUIRE_DIRECTORY_ERROR = "loading a directory of commands is not supported yet for ESM", __dirname2, mainFilename, esm_default;
79554
79694
  var init_esm = __esm(() => {
79555
79695
  init_cliui();
@@ -79582,7 +79722,7 @@ var init_esm = __esm(() => {
79582
79722
  Parser: lib_default,
79583
79723
  path: {
79584
79724
  basename: basename9,
79585
- dirname: dirname8,
79725
+ dirname: dirname9,
79586
79726
  extname: extname3,
79587
79727
  relative: relative7,
79588
79728
  resolve: resolve9
@@ -84279,7 +84419,7 @@ var init_PuppeteerNode = __esm(() => {
84279
84419
  import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
84280
84420
  import fs5 from "node:fs";
84281
84421
  import os8 from "node:os";
84282
- import { dirname as dirname9 } from "node:path";
84422
+ import { dirname as dirname10 } from "node:path";
84283
84423
  import { PassThrough } from "node:stream";
84284
84424
  var import_debug6, __runInitializers22 = function(thisArg, initializers, value) {
84285
84425
  var useValue = arguments.length > 2;
@@ -84403,7 +84543,7 @@ var init_ScreenRecorder = __esm(() => {
84403
84543
  filters.push(formatArgs.splice(vf, 2).at(-1) ?? "");
84404
84544
  }
84405
84545
  if (path11) {
84406
- fs5.mkdirSync(dirname9(path11), { recursive: overwrite });
84546
+ fs5.mkdirSync(dirname10(path11), { recursive: overwrite });
84407
84547
  }
84408
84548
  this.#process = spawn2(ffmpegPath, [
84409
84549
  ["-loglevel", "error"],
@@ -84589,8 +84729,8 @@ function findChromeInDir(dir) {
84589
84729
  for (const entry of entries) {
84590
84730
  const full = join20(dir, entry);
84591
84731
  try {
84592
- const stat = statSync12(full);
84593
- if (stat.isDirectory()) {
84732
+ const stat2 = statSync12(full);
84733
+ if (stat2.isDirectory()) {
84594
84734
  const found = findChromeInDir(full);
84595
84735
  if (found)
84596
84736
  return found;
@@ -86320,10 +86460,10 @@ function collectMarkdownFiles(dirPath, rootPath) {
86320
86460
  if (entry.isDirectory()) {
86321
86461
  files.push(...collectMarkdownFiles(fullPath, rootPath));
86322
86462
  } else if (entry.name.endsWith(".md") && !entry.name.startsWith("_")) {
86323
- const stat = statSync12(fullPath);
86463
+ const stat2 = statSync12(fullPath);
86324
86464
  const title = entry.name.replace(/\.md$/, "");
86325
86465
  const relativePath = fullPath.slice(rootPath.length + 1);
86326
- files.push({ title, relativePath, mtime: stat.mtimeMs });
86466
+ files.push({ title, relativePath, mtime: stat2.mtimeMs });
86327
86467
  }
86328
86468
  }
86329
86469
  } catch {}
@@ -86880,7 +87020,7 @@ var exports_skill = {};
86880
87020
  __export(exports_skill, {
86881
87021
  skill: () => skill
86882
87022
  });
86883
- import { join as join24, resolve as resolve12, dirname as dirname10 } from "path";
87023
+ import { join as join24, resolve as resolve12, dirname as dirname11 } from "path";
86884
87024
  import { homedir as homedir5 } from "os";
86885
87025
  import {
86886
87026
  existsSync as existsSync26,
@@ -86893,7 +87033,7 @@ import {
86893
87033
  lstatSync
86894
87034
  } from "fs";
86895
87035
  function getSkillsSourceDir() {
86896
- return resolve12(dirname10(new URL(import.meta.url).pathname), "../../skills");
87036
+ return resolve12(dirname11(new URL(import.meta.url).pathname), "../../skills");
86897
87037
  }
86898
87038
  function getAvailableSkills() {
86899
87039
  const dir = getSkillsSourceDir();
@@ -87407,8 +87547,8 @@ switch (command2) {
87407
87547
  case "version":
87408
87548
  case "--version":
87409
87549
  case "-v": {
87410
- const { resolve: resolve13, dirname: dirname11 } = await import("path");
87411
- const cliPath = resolve13(dirname11(new URL(import.meta.url).pathname));
87550
+ const { resolve: resolve13, dirname: dirname12 } = await import("path");
87551
+ const cliPath = resolve13(dirname12(new URL(import.meta.url).pathname));
87412
87552
  const { readFileSync: readFileSync25 } = await import("fs");
87413
87553
  try {
87414
87554
  const pkg = JSON.parse(readFileSync25(resolve13(cliPath, "../package.json"), "utf-8"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drewpayment/mink",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "A hidden presence that moves alongside the developer — token efficiency and cross-project wiki for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,7 +18,8 @@
18
18
  "files": [
19
19
  "src/**/*.ts",
20
20
  "dist/cli.js",
21
- "skills/**/*"
21
+ "skills/**/*",
22
+ "dashboard/out"
22
23
  ],
23
24
  "publishConfig": {
24
25
  "access": "public"
@@ -12,7 +12,7 @@ export async function dashboard(cwd: string, args: string[]): Promise<void> {
12
12
  const noOpen = args.includes("--no-open");
13
13
 
14
14
  const { startDashboardServer } = await import("../core/dashboard-server");
15
- const { url } = startDashboardServer(cwd, { port, open: !noOpen });
15
+ const { url } = await startDashboardServer(cwd, { port, open: !noOpen });
16
16
 
17
17
  console.log(`[mink] dashboard running at ${url}`);
18
18
  console.log("[mink] press Ctrl+C to stop");
@@ -64,7 +64,8 @@ export function startDaemon(cwd: string): void {
64
64
  }
65
65
 
66
66
  // Resolve the CLI entry point
67
- const cliPath = resolve(import.meta.dir, "../cli.ts");
67
+ const __dir = dirname(new URL(import.meta.url).pathname);
68
+ const cliPath = resolve(__dir, "../cli.ts");
68
69
 
69
70
  // Ensure log directory exists
70
71
  const logPath = schedulerLogPath();
@@ -1,6 +1,6 @@
1
1
  import { watch, type FSWatcher } from "fs";
2
2
  import { existsSync } from "fs";
3
- import { basename, join, extname } from "path";
3
+ import { basename, dirname, join, extname } from "path";
4
4
  import { projectDir, designCapturesDir } from "./paths";
5
5
  import {
6
6
  loadOverview,
@@ -17,6 +17,7 @@ import {
17
17
  } from "./dashboard-api";
18
18
  import { listRegisteredProjects, getProjectMeta } from "./project-registry";
19
19
  import { generateProjectId } from "./project-id";
20
+ import { runtimeFile, runtimeServe, runtimeSpawn } from "./runtime";
20
21
  import type { StateFileId, StateChangeEvent } from "../types/dashboard";
21
22
  import type { RegisteredProject } from "./project-registry";
22
23
 
@@ -262,10 +263,10 @@ export interface DashboardServer {
262
263
  close(): void;
263
264
  }
264
265
 
265
- export function startDashboardServer(
266
+ export async function startDashboardServer(
266
267
  cwd: string,
267
268
  options: { port?: number; hostname?: string; open?: boolean } = {}
268
- ): DashboardServer {
269
+ ): Promise<DashboardServer> {
269
270
  const port = options.port ?? 4040;
270
271
  const hostname = options.hostname ?? "127.0.0.1";
271
272
 
@@ -295,13 +296,15 @@ export function startDashboardServer(
295
296
  });
296
297
 
297
298
  // Resolve the Next.js static build directory
298
- const dashboardOutDir = join(
299
- import.meta.dir,
300
- "..",
301
- "..",
302
- "dashboard",
303
- "out"
304
- );
299
+ // Walk up from import.meta.url to find the package root (where package.json lives).
300
+ // From source: src/core/ → ../../ From compiled bundle: dist/ → ../
301
+ const __dir = dirname(new URL(import.meta.url).pathname);
302
+ let pkgRoot = __dir;
303
+ while (pkgRoot !== dirname(pkgRoot)) {
304
+ if (existsSync(join(pkgRoot, "package.json"))) break;
305
+ pkgRoot = dirname(pkgRoot);
306
+ }
307
+ const dashboardOutDir = join(pkgRoot, "dashboard", "out");
305
308
  const dashboardBuilt = existsSync(join(dashboardOutDir, "index.html"));
306
309
  let clientIdCounter = 0;
307
310
 
@@ -311,7 +314,20 @@ export function startDashboardServer(
311
314
  );
312
315
  }
313
316
 
314
- const server = Bun.serve({
317
+ async function serveFile(
318
+ filePath: string,
319
+ contentType: string
320
+ ): Promise<Response | null> {
321
+ const file = runtimeFile(filePath);
322
+ if (await file.exists()) {
323
+ return new Response(await file.bytes() as unknown as BodyInit, {
324
+ headers: { "Content-Type": contentType },
325
+ });
326
+ }
327
+ return null;
328
+ }
329
+
330
+ const server = await runtimeServe({
315
331
  port,
316
332
  hostname,
317
333
  idleTimeout: 0, // Disable idle timeout — SSE connections are long-lived
@@ -343,30 +359,24 @@ export function startDashboardServer(
343
359
  return jsonResponse({ error: "Forbidden" }, 403);
344
360
  }
345
361
 
346
- const file = Bun.file(filePath);
347
- if (await file.exists()) {
348
- const ext = extname(filePath);
349
- const contentType = MIME_TYPES[ext] || "application/octet-stream";
350
- return new Response(file, {
351
- headers: { "Content-Type": contentType },
352
- });
353
- }
362
+ const ext = extname(filePath);
363
+ const contentType = MIME_TYPES[ext] || "application/octet-stream";
364
+ const served = await serveFile(filePath, contentType);
365
+ if (served) return served;
354
366
 
355
367
  // Client-side routing fallback: try {pathname}.html then index.html
356
- const htmlFile = Bun.file(filePath + ".html");
357
- if (await htmlFile.exists()) {
358
- return new Response(htmlFile, {
359
- headers: { "Content-Type": "text/html; charset=utf-8" },
360
- });
361
- }
368
+ const htmlServed = await serveFile(
369
+ filePath + ".html",
370
+ "text/html; charset=utf-8"
371
+ );
372
+ if (htmlServed) return htmlServed;
362
373
 
363
374
  // SPA fallback — serve index.html for unmatched routes
364
- const indexFile = Bun.file(join(dashboardOutDir, "index.html"));
365
- if (await indexFile.exists()) {
366
- return new Response(indexFile, {
367
- headers: { "Content-Type": "text/html; charset=utf-8" },
368
- });
369
- }
375
+ const indexServed = await serveFile(
376
+ join(dashboardOutDir, "index.html"),
377
+ "text/html; charset=utf-8"
378
+ );
379
+ if (indexServed) return indexServed;
370
380
  }
371
381
  }
372
382
 
@@ -376,8 +386,6 @@ export function startDashboardServer(
376
386
  const stream = new ReadableStream<Uint8Array>({
377
387
  start(controller) {
378
388
  sseManager.addClient(clientId, controller);
379
- // Send initial comment immediately to establish the stream
380
- // and prevent Bun from treating it as idle/complete
381
389
  controller.enqueue(encoder.encode(": connected\nretry: 3000\n\n"));
382
390
  },
383
391
  cancel() {
@@ -435,15 +443,11 @@ export function startDashboardServer(
435
443
  return jsonResponse({ error: "Invalid filename" }, 400);
436
444
  }
437
445
  const imgPath = join(designCapturesDir(resolvedCwd), filename);
438
- const file = Bun.file(imgPath);
439
- if (await file.exists()) {
440
- return new Response(file, {
441
- headers: {
442
- "Content-Type": "image/jpeg",
443
- "Cache-Control": "public, max-age=60",
444
- "Access-Control-Allow-Origin": "*",
445
- },
446
- });
446
+ const served = await serveFile(imgPath, "image/jpeg");
447
+ if (served) {
448
+ served.headers.set("Cache-Control", "public, max-age=60");
449
+ served.headers.set("Access-Control-Allow-Origin", "*");
450
+ return served;
447
451
  }
448
452
  return jsonResponse({ error: "Image not found" }, 404);
449
453
  }
@@ -558,12 +562,7 @@ export function startDashboardServer(
558
562
  : platform === "win32"
559
563
  ? ["cmd", "/c", "start", serverUrl]
560
564
  : ["xdg-open", serverUrl];
561
- const proc = Bun.spawn(cmd, {
562
- stdout: "ignore",
563
- stderr: "ignore",
564
- stdin: "ignore",
565
- });
566
- proc.unref();
565
+ runtimeSpawn(cmd).unref();
567
566
  } catch {
568
567
  // Browser open is best-effort
569
568
  }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Cross-runtime utilities — Bun-first with Node.js fallbacks.
3
+ *
4
+ * Detects the runtime once at import time and exports helpers that
5
+ * abstract over Bun.serve / node:http, Bun.file / fs, and Bun.spawn / child_process.
6
+ */
7
+
8
+ import { readFile, stat } from "fs/promises";
9
+ import { spawn as nodeSpawn } from "child_process";
10
+
11
+ export const isBun = typeof globalThis.Bun !== "undefined";
12
+
13
+ // ── File helpers ──────────────────────────────────────────────────────────
14
+
15
+ export interface RuntimeFile {
16
+ exists(): Promise<boolean>;
17
+ bytes(): Promise<Uint8Array>;
18
+ }
19
+
20
+ /**
21
+ * Returns a lightweight file handle with `.exists()` and `.bytes()`.
22
+ * Uses Bun.file when available, otherwise falls back to fs.
23
+ */
24
+ export function runtimeFile(path: string): RuntimeFile {
25
+ if (isBun) {
26
+ const f = Bun.file(path);
27
+ return {
28
+ exists: () => f.exists(),
29
+ bytes: () => f.arrayBuffer().then((ab) => new Uint8Array(ab)),
30
+ };
31
+ }
32
+ return {
33
+ async exists() {
34
+ try {
35
+ await stat(path);
36
+ return true;
37
+ } catch {
38
+ return false;
39
+ }
40
+ },
41
+ async bytes() {
42
+ return readFile(path);
43
+ },
44
+ };
45
+ }
46
+
47
+ // ── Spawn helper ──────────────────────────────────────────────────────────
48
+
49
+ export interface SpawnOptions {
50
+ cwd?: string;
51
+ env?: Record<string, string | undefined>;
52
+ stdout?: "ignore" | "pipe";
53
+ stderr?: "ignore" | "pipe";
54
+ stdin?: "ignore";
55
+ }
56
+
57
+ export interface SpawnedProcess {
58
+ unref(): void;
59
+ }
60
+
61
+ /**
62
+ * Fire-and-forget process spawning. Uses Bun.spawn when available,
63
+ * otherwise child_process.spawn with detached + unref.
64
+ */
65
+ export function runtimeSpawn(
66
+ cmd: string[],
67
+ opts: SpawnOptions = {}
68
+ ): SpawnedProcess {
69
+ if (isBun) {
70
+ const proc = Bun.spawn(cmd, {
71
+ cwd: opts.cwd,
72
+ env: opts.env,
73
+ stdout: opts.stdout ?? "ignore",
74
+ stderr: opts.stderr ?? "ignore",
75
+ stdin: opts.stdin ?? "ignore",
76
+ });
77
+ return { unref: () => proc.unref() };
78
+ }
79
+
80
+ const [bin, ...args] = cmd;
81
+ const proc = nodeSpawn(bin, args, {
82
+ cwd: opts.cwd,
83
+ env: opts.env as NodeJS.ProcessEnv,
84
+ stdio: [
85
+ opts.stdin ?? "ignore",
86
+ opts.stdout ?? "ignore",
87
+ opts.stderr ?? "ignore",
88
+ ],
89
+ detached: true,
90
+ });
91
+ proc.unref();
92
+ return { unref: () => {} };
93
+ }
94
+
95
+ // ── HTTP Server ───────────────────────────────────────────────────────────
96
+
97
+ export interface RuntimeServer {
98
+ port: number;
99
+ stop(closeConnections?: boolean): void;
100
+ }
101
+
102
+ type FetchHandler = (req: Request) => Response | Promise<Response>;
103
+
104
+ interface ServeOptions {
105
+ port: number;
106
+ hostname: string;
107
+ idleTimeout?: number;
108
+ fetch: FetchHandler;
109
+ }
110
+
111
+ /**
112
+ * Start an HTTP server using Bun.serve or node:http.
113
+ * The fetch handler uses standard Web API Request/Response in both runtimes.
114
+ */
115
+ export async function runtimeServe(opts: ServeOptions): Promise<RuntimeServer> {
116
+ if (isBun) {
117
+ const server = Bun.serve({
118
+ port: opts.port,
119
+ hostname: opts.hostname,
120
+ idleTimeout: opts.idleTimeout ?? 0,
121
+ fetch: opts.fetch,
122
+ });
123
+ return {
124
+ port: server.port as number,
125
+ stop: (close) => server.stop(close),
126
+ };
127
+ }
128
+
129
+ // Node.js fallback using node:http
130
+ const { createServer } = await import("node:http");
131
+ const { Readable } = await import("node:stream");
132
+
133
+ return new Promise<RuntimeServer>((resolve) => {
134
+ const httpServer = createServer(async (req, res) => {
135
+ // Build a Web API Request from the Node IncomingMessage
136
+ const url = `http://${opts.hostname}:${opts.port}${req.url ?? "/"}`;
137
+ const headers = new Headers();
138
+ for (const [key, val] of Object.entries(req.headers)) {
139
+ if (val) headers.set(key, Array.isArray(val) ? val.join(", ") : val);
140
+ }
141
+
142
+ let body: BodyInit | null = null;
143
+ if (req.method !== "GET" && req.method !== "HEAD") {
144
+ const chunks: Buffer[] = [];
145
+ for await (const chunk of req) {
146
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
147
+ }
148
+ body = Buffer.concat(chunks);
149
+ }
150
+
151
+ const request = new Request(url, {
152
+ method: req.method,
153
+ headers,
154
+ body,
155
+ // @ts-expect-error -- Node 18+ supports duplex on Request
156
+ duplex: body ? "half" : undefined,
157
+ });
158
+
159
+ try {
160
+ const response = await opts.fetch(request);
161
+
162
+ res.writeHead(response.status, Object.fromEntries(response.headers));
163
+
164
+ if (!response.body) {
165
+ res.end();
166
+ return;
167
+ }
168
+
169
+ // Stream the response body
170
+ const reader = response.body.getReader();
171
+ const nodeStream = new Readable({
172
+ async read() {
173
+ try {
174
+ const { done, value } = await reader.read();
175
+ if (done) {
176
+ this.push(null);
177
+ } else {
178
+ this.push(Buffer.from(value));
179
+ }
180
+ } catch {
181
+ this.push(null);
182
+ }
183
+ },
184
+ });
185
+
186
+ // Clean up SSE streams when client disconnects
187
+ res.on("close", () => {
188
+ reader.cancel().catch(() => {});
189
+ nodeStream.destroy();
190
+ });
191
+
192
+ nodeStream.pipe(res);
193
+ } catch (err) {
194
+ if (!res.headersSent) {
195
+ res.writeHead(500);
196
+ res.end(String(err));
197
+ }
198
+ }
199
+ });
200
+
201
+ httpServer.listen(opts.port, opts.hostname, () => {
202
+ const addr = httpServer.address();
203
+ const boundPort =
204
+ typeof addr === "object" && addr ? addr.port : opts.port;
205
+ resolve({
206
+ port: boundPort,
207
+ stop: (close) => {
208
+ if (close) httpServer.closeAllConnections();
209
+ httpServer.close();
210
+ },
211
+ });
212
+ });
213
+ });
214
+ }