@1agh/maude 0.18.0 → 0.18.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.
@@ -0,0 +1,103 @@
1
+ var __create = Object.create;
2
+ var __getProtoOf = Object.getPrototypeOf;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ function __accessProp(key) {
7
+ return this[key];
8
+ }
9
+ var __toESMCache_node;
10
+ var __toESMCache_esm;
11
+ var __toESM = (mod, isNodeMode, target) => {
12
+ var canCache = mod != null && typeof mod === "object";
13
+ if (canCache) {
14
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
15
+ var cached = cache.get(mod);
16
+ if (cached)
17
+ return cached;
18
+ }
19
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
20
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
21
+ for (let key of __getOwnPropNames(mod))
22
+ if (!__hasOwnProp.call(to, key))
23
+ __defProp(to, key, {
24
+ get: __accessProp.bind(mod, key),
25
+ enumerable: true
26
+ });
27
+ if (canCache)
28
+ cache.set(mod, to);
29
+ return to;
30
+ };
31
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
32
+ var __returnValue = (v) => v;
33
+ function __exportSetter(name, newValue) {
34
+ this[name] = __returnValue.bind(null, newValue);
35
+ }
36
+ var __export = (target, all) => {
37
+ for (var name in all)
38
+ __defProp(target, name, {
39
+ get: all[name],
40
+ enumerable: true,
41
+ configurable: true,
42
+ set: __exportSetter.bind(all, name)
43
+ });
44
+ };
45
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
46
+
47
+ // ../../../node_modules/.pnpm/react@19.2.6/node_modules/react/cjs/react-jsx-runtime.production.js
48
+ var exports_react_jsx_runtime_production = {};
49
+ __export(exports_react_jsx_runtime_production, {
50
+ jsxs: () => $jsxs,
51
+ jsx: () => $jsx,
52
+ Fragment: () => $Fragment
53
+ });
54
+ function jsxProd(type, config, maybeKey) {
55
+ var key = null;
56
+ maybeKey !== undefined && (key = "" + maybeKey);
57
+ config.key !== undefined && (key = "" + config.key);
58
+ if ("key" in config) {
59
+ maybeKey = {};
60
+ for (var propName in config)
61
+ propName !== "key" && (maybeKey[propName] = config[propName]);
62
+ } else
63
+ maybeKey = config;
64
+ config = maybeKey.ref;
65
+ return {
66
+ $$typeof: REACT_ELEMENT_TYPE,
67
+ type,
68
+ key,
69
+ ref: config !== undefined ? config : null,
70
+ props: maybeKey
71
+ };
72
+ }
73
+ var REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE, $Fragment, $jsx, $jsxs;
74
+ var init_react_jsx_runtime_production = __esm(() => {
75
+ REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element");
76
+ REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
77
+ $Fragment = REACT_FRAGMENT_TYPE;
78
+ $jsx = jsxProd;
79
+ $jsxs = jsxProd;
80
+ });
81
+
82
+ // ../../../node_modules/.pnpm/react@19.2.6/node_modules/react/jsx-runtime.js
83
+ var require_jsx_runtime = __commonJS((exports, module) => {
84
+ init_react_jsx_runtime_production();
85
+ if (true) {
86
+ module.exports = exports_react_jsx_runtime_production;
87
+ }
88
+ });
89
+
90
+ // synth:/home/runner/work/maude/maude/plugins/design/dev-server/.runtime-bundle-react_jsx-runtime-entry.tsx
91
+ var __mod__ = __toESM(require_jsx_runtime(), 1);
92
+ var {
93
+ Fragment,
94
+ jsx,
95
+ jsxs
96
+ } = __mod__;
97
+ var __runtime_bundle_react_jsx_runtime_entry_default = __mod__;
98
+ export {
99
+ jsxs,
100
+ jsx,
101
+ __runtime_bundle_react_jsx_runtime_entry_default as default,
102
+ Fragment
103
+ };
@@ -5,8 +5,7 @@
5
5
  // top-level fall-through for paths Bun's `routes` field doesn't cover.
6
6
 
7
7
  import { watch } from 'node:fs';
8
- import { dirname, join, posix } from 'node:path';
9
- import { fileURLToPath } from 'node:url';
8
+ import { join, posix } from 'node:path';
10
9
 
11
10
  import type { Api } from './api.ts';
12
11
  import { buildCanvasModule } from './canvas-build.ts';
@@ -17,9 +16,12 @@ import { isFormat, isScope, runExport } from './exporters/index.ts';
17
16
  import type { ActiveJsonShape } from './exporters/scope.ts';
18
17
  import type { Inspect } from './inspect.ts';
19
18
  import { canvasSlug, writeLocator } from './locator.ts';
19
+ import { DEV_SERVER_ROOT } from './paths.ts';
20
20
  import { RUNTIME_PACKAGES, getRuntimeBundle, packageForSlug, slugFor } from './runtime-bundle.ts';
21
21
 
22
- const HERE = dirname(fileURLToPath(import.meta.url));
22
+ // Real disk install root — never the virtual `/$bunfs/root` of compiled bins.
23
+ // See paths.ts for the resolution logic + Phase 19.1 / v0.18.1 rationale.
24
+ const HERE = DEV_SERVER_ROOT;
23
25
 
24
26
  export const MIME: Record<string, string> = {
25
27
  '.html': 'text/html; charset=utf-8',
@@ -0,0 +1,110 @@
1
+ // Path resolution for the dev-server, robust across THREE runtime modes:
2
+ //
3
+ // 1. Dev (`bun server.ts`): import.meta.url is a real file:// path,
4
+ // use dirname of it.
5
+ // 2. Compiled binary, npm: maude installed via `npm i -g @1agh/maude`;
6
+ // binary lives in a sub-package dir, deps in
7
+ // the parent @1agh/maude/ dir. import.meta.url
8
+ // is the virtual `/$bunfs/root` (bun --compile
9
+ // embedded fs) — NOT a real disk path.
10
+ // Walk up from process.execPath until we find
11
+ // the real plugins/design/dev-server/ dir.
12
+ // 3. Compiled binary, marketplace cache: similar to (2) but lives at
13
+ // ~/.claude/plugins/cache/maude/design/<v>/
14
+ // dev-server/. Same walk-up logic finds it.
15
+ //
16
+ // Why this matters: Phase 19 v0.18.0 used `dirname(fileURLToPath(import.meta.url))`
17
+ // universally. In the compiled binary that's `/$bunfs/root` — a virtual path —
18
+ // so `existsSync('/$bunfs/root/dist/client.bundle.js')` always returned false
19
+ // even when the file was sitting on disk at the real install path. Self-heal
20
+ // false-triggered, http.ts /_client/* fell through to /$bunfs/root/client/*.jsx
21
+ // (raw source), runtime-bundle.ts synthetic entrypoint anchored in virtual fs
22
+ // so Bun.build couldn't walk node_modules. Every observed symptom traces to
23
+ // this one bug. Phase 19.1 / v0.18.1.
24
+
25
+ import { existsSync } from 'node:fs';
26
+ import { dirname, join } from 'node:path';
27
+ import { fileURLToPath } from 'node:url';
28
+
29
+ /**
30
+ * Real disk path to the dev-server install dir
31
+ * (`plugins/design/dev-server/` inside whatever package layout we're in).
32
+ *
33
+ * Always a directory that contains `http.ts` + `dist/` + (optionally)
34
+ * `node_modules/` and `client/`. Never a virtual `/$bunfs/*` path.
35
+ */
36
+ export const DEV_SERVER_ROOT: string = resolveDevServerRoot();
37
+
38
+ /** `<DEV_SERVER_ROOT>/dist/` — committed artifacts + runtime bundles + binary. */
39
+ export const DIST_DIR: string = join(DEV_SERVER_ROOT, 'dist');
40
+
41
+ /** `<DEV_SERVER_ROOT>/client/` — raw source HTML + JSX + CSS for dev fallback. */
42
+ export const CLIENT_DIR: string = join(DEV_SERVER_ROOT, 'client');
43
+
44
+ /** `<DEV_SERVER_ROOT>/dist/runtime/` — pre-built /_canvas-runtime/*.js bundles. */
45
+ export const RUNTIME_BUNDLES_DIR: string = join(DIST_DIR, 'runtime');
46
+
47
+ /**
48
+ * Whether we are running inside a `bun --compile` standalone binary
49
+ * (true when `import.meta.url` resolves to bun's virtual filesystem).
50
+ *
51
+ * Useful for code that needs to know whether disk-relative fallback paths
52
+ * (e.g. `<DEV_SERVER_ROOT>/client/app.jsx`) are even reachable — in compiled
53
+ * mode the answer is "only if shipped via the install layout".
54
+ */
55
+ export const IS_COMPILED_BINARY: boolean = isVirtualBunfsPath(getImportMetaDir());
56
+
57
+ function getImportMetaDir(): string | null {
58
+ try {
59
+ return dirname(fileURLToPath(import.meta.url));
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ function isVirtualBunfsPath(p: string | null): boolean {
66
+ return p !== null && (p.startsWith('/$bunfs') || p.startsWith('B:/~BUN'));
67
+ }
68
+
69
+ function isDevServerDir(dir: string): boolean {
70
+ // Anchor: http.ts is the route-table file — unique enough to identify the
71
+ // dev-server install dir. We do NOT also require package.json: npm excludes
72
+ // nested workspace package.json files from tarballs by default, so checking
73
+ // for it caused walk-up to silently fall through to /$bunfs/root for every
74
+ // npm-installed user. Discovered in v0.18.1 retro. Process.execPath walk-up
75
+ // only traverses node_modules layers above the binary, so false-match risk
76
+ // from a stray http.ts file in the user's working tree is negligible.
77
+ return existsSync(join(dir, 'http.ts'));
78
+ }
79
+
80
+ function resolveDevServerRoot(): string {
81
+ // (1) Dev mode: import.meta.url is a real file:// path AND lands in the
82
+ // dev-server dir. Common case for `bun run server.ts`, tests, etc.
83
+ const importDir = getImportMetaDir();
84
+ if (importDir && !isVirtualBunfsPath(importDir) && isDevServerDir(importDir)) {
85
+ return importDir;
86
+ }
87
+
88
+ // (2 + 3) Compiled binary: walk up from process.execPath until we find a dir
89
+ // that *contains* `plugins/design/dev-server/<canonical files>`. Match both
90
+ // npm install layout (binary at @1agh/maude-<plat>/maude → walk up 4 levels
91
+ // to @1agh/maude/) AND marketplace cache layout (binary somewhere under
92
+ // ~/.claude/plugins/cache/maude/design/<v>/dev-server/dist/).
93
+ let cur = dirname(process.execPath);
94
+ for (let i = 0; i < 10; i++) {
95
+ // Check if cur itself is the dev-server root.
96
+ if (isDevServerDir(cur)) return cur;
97
+ // Check if cur contains plugins/design/dev-server/.
98
+ const nested = join(cur, 'plugins', 'design', 'dev-server');
99
+ if (isDevServerDir(nested)) return nested;
100
+ const parent = dirname(cur);
101
+ if (parent === cur) break;
102
+ cur = parent;
103
+ }
104
+
105
+ // Final fallback for unanchored test contexts (e.g. tests spawning compiled
106
+ // binary in a tmp dir without our layout). Return the import dir even if
107
+ // it's virtual — callers should expect existsSync to fail and surface a
108
+ // clear error.
109
+ return importDir ?? dirname(process.execPath);
110
+ }
@@ -18,10 +18,28 @@
18
18
  // - In dev we externalise *nothing* — the four bundles together are
19
19
  // self-contained. The importmap wires them together at runtime.
20
20
 
21
- import { dirname } from 'node:path';
22
- import { fileURLToPath } from 'node:url';
21
+ import { join } from 'node:path';
23
22
 
24
- const HERE = dirname(fileURLToPath(import.meta.url));
23
+ import { DEV_SERVER_ROOT, RUNTIME_BUNDLES_DIR } from './paths.ts';
24
+
25
+ // Real disk install root — synthetic entry points must anchor here so
26
+ // Bun.build's resolver walks UP and finds node_modules/react on disk.
27
+ // In compiled binaries, import.meta.url is the virtual `/$bunfs/root`
28
+ // where no node_modules exists (Phase 19.1 / v0.18.1).
29
+ const HERE = DEV_SERVER_ROOT;
30
+
31
+ /**
32
+ * Read a pre-built runtime bundle from `dist/runtime/<slug>.js`. Returns
33
+ * null when missing → caller falls back to dynamic Bun.build (dev mode).
34
+ */
35
+ async function loadPrebuiltRuntimeBundle(pkg: RuntimePackage): Promise<BundleCacheEntry | null> {
36
+ const path = join(RUNTIME_BUNDLES_DIR, `${slugFor(pkg)}.js`);
37
+ const file = Bun.file(path);
38
+ if (!(await file.exists())) return null;
39
+ const js = await file.text();
40
+ if (!js) return null;
41
+ return { js, etag: Bun.hash(js).toString(16) };
42
+ }
25
43
 
26
44
  export const RUNTIME_PACKAGES = [
27
45
  'react',
@@ -99,10 +117,37 @@ const cache = new Map<RuntimePackage, BundleCacheEntry>();
99
117
  * each entry includes everything it needs; the four bundles only share state
100
118
  * at the browser level (via React's module-singleton convention — multiple
101
119
  * imports of "react" resolve to the same module thanks to the importmap).
120
+ *
121
+ * Two paths:
122
+ * 1. **Pre-built on disk** (Phase 19.1 / v0.18.1). Every release ships
123
+ * `dist/runtime/<slug>.js` so npm-installed users + marketplace cache
124
+ * users never need disk node_modules/react or Bun.build at request time.
125
+ * Read from disk → return.
126
+ * 2. **Dynamic Bun.build** — only fires for dev (cd dev-server; bun
127
+ * server.ts) where `dist/runtime/` may be empty/stale.
102
128
  */
103
- export async function getRuntimeBundle(pkg: RuntimePackage): Promise<BundleCacheEntry> {
129
+ export interface GetRuntimeBundleOptions {
130
+ /** Skip the disk cache lookup — used by build.ts to force a fresh dynamic build. */
131
+ skipPrebuilt?: boolean;
132
+ /** Minify the dynamic build output — used by build.ts in release mode. */
133
+ minify?: boolean;
134
+ }
135
+
136
+ export async function getRuntimeBundle(
137
+ pkg: RuntimePackage,
138
+ opts: GetRuntimeBundleOptions = {}
139
+ ): Promise<BundleCacheEntry> {
104
140
  const hit = cache.get(pkg);
105
- if (hit) return hit;
141
+ if (hit && !opts.skipPrebuilt) return hit;
142
+
143
+ // (1) Pre-built bundle path — try disk first (unless caller opts out).
144
+ if (!opts.skipPrebuilt) {
145
+ const prebuilt = await loadPrebuiltRuntimeBundle(pkg);
146
+ if (prebuilt) {
147
+ cache.set(pkg, prebuilt);
148
+ return prebuilt;
149
+ }
150
+ }
106
151
 
107
152
  // A throwaway entrypoint that re-exports every member of the target package.
108
153
  // We use named re-exports (default + an enumerated namespace) so the bundle
@@ -144,7 +189,7 @@ export async function getRuntimeBundle(pkg: RuntimePackage): Promise<BundleCache
144
189
  entrypoints: [entryName],
145
190
  target: 'browser',
146
191
  format: 'esm',
147
- minify: false,
192
+ minify: opts.minify ?? false,
148
193
  splitting: false,
149
194
  define: {
150
195
  // Force React's production module (smaller, no dev-only `let React`
@@ -228,8 +273,8 @@ export function bunCacheRemediation(pkg: string, log: string): string | null {
228
273
  const basePkg = pkg.split('/')[0] ?? pkg;
229
274
  return [
230
275
  ` ⚠ Bun's global package cache for "${basePkg}" appears to be in a bad state`,
231
- ` (truncated install, EISDIR/ENOENT on an index file).`,
232
- ``,
276
+ ' (truncated install, EISDIR/ENOENT on an index file).',
277
+ '',
233
278
  ` Fix: run \`bun pm cache rm ${basePkg}\` then reload the page.`,
234
279
  ].join('\n');
235
280
  }
@@ -17,13 +17,13 @@
17
17
  import { spawn } from 'node:child_process';
18
18
 
19
19
  import { createApi } from './api.ts';
20
+ import { bootSelfHeal } from './boot-self-heal.ts';
20
21
  import { createContext } from './context.ts';
21
22
  import { createFsWatch } from './fs-watch.ts';
22
23
  import { createHttp } from './http.ts';
23
24
  import { createInspect } from './inspect.ts';
24
25
  import { startHeapWatch } from './mem.ts';
25
26
  import { createWs } from './ws.ts';
26
- import { bootSelfHeal } from './boot-self-heal.ts';
27
27
 
28
28
  // Phase 19 / DDR-044 — covers the marketplace-cache-install gap where
29
29
  // node_modules/ ships empty (git clone honors .gitignore). Auto-installs +
@@ -1,19 +1,21 @@
1
- // Boot self-heal logic — Phase 19 / DDR-044.
2
- // Covers the marketplace-cache-install gap: if dist/ or node_modules/ is
3
- // missing on boot, self-heal runs bun install + build. Opt out with
4
- // MAUDE_NO_AUTOBUILD=1.
1
+ // Boot artifact check — Phase 19 / DDR-044, simplified in v0.18.1.
2
+ // v0.18.0 tried to `bun install` + `bun run build.ts` when node_modules/react
3
+ // was missing; that misfired in every install scenario (see boot-self-heal.ts
4
+ // header). v0.18.1 ships pre-built runtime bundles + just verifies they're
5
+ // reachable. Missing artifact == broken install, not first-boot gap.
5
6
 
6
7
  import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
7
8
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
8
9
  import { tmpdir } from 'node:os';
9
10
  import { join } from 'node:path';
10
11
 
11
- import { bootSelfHeal, type SelfHealOptions } from '../boot-self-heal.ts';
12
+ import { type SelfHealOptions, bootSelfHeal } from '../boot-self-heal.ts';
12
13
 
13
14
  let TMP: string;
14
15
 
15
16
  beforeEach(() => {
16
17
  TMP = mkdtempSync(join(tmpdir(), 'maude-self-heal-'));
18
+ mkdirSync(join(TMP, 'dist', 'runtime'), { recursive: true });
17
19
  });
18
20
 
19
21
  afterEach(() => {
@@ -21,16 +23,10 @@ afterEach(() => {
21
23
  });
22
24
 
23
25
  function harness(extra: Partial<SelfHealOptions> = {}) {
24
- const calls: { cmd: readonly string[]; cwd: string }[] = [];
25
26
  const logs: string[] = [];
26
27
  let exited: number | null = null;
27
28
  const opts: SelfHealOptions = {
28
29
  here: TMP,
29
- optOut: false,
30
- spawn: async (cmd, cwd) => {
31
- calls.push({ cmd, cwd });
32
- return { code: 0 };
33
- },
34
30
  log: (m) => logs.push(m),
35
31
  exit: ((code: number) => {
36
32
  exited = code;
@@ -38,75 +34,54 @@ function harness(extra: Partial<SelfHealOptions> = {}) {
38
34
  }) as never,
39
35
  ...extra,
40
36
  };
41
- return { opts, calls, logs, getExited: () => exited };
37
+ return { opts, logs, getExited: () => exited };
42
38
  }
43
39
 
44
- function seedDist() {
45
- mkdirSync(join(TMP, 'dist'), { recursive: true });
40
+ function seedAll() {
46
41
  writeFileSync(join(TMP, 'dist', 'client.bundle.js'), '/* stub */');
47
- }
48
-
49
- function seedDeps() {
50
- mkdirSync(join(TMP, 'node_modules', 'react'), { recursive: true });
51
- writeFileSync(join(TMP, 'node_modules', 'react', 'package.json'), '{}');
42
+ writeFileSync(join(TMP, 'dist', 'runtime', 'react.js'), '/* stub */');
52
43
  }
53
44
 
54
45
  describe('bootSelfHeal', () => {
55
- test('skips when dist + node_modules both present', async () => {
56
- seedDist();
57
- seedDeps();
58
- const { opts, calls } = harness();
59
- const result = await bootSelfHeal(opts);
60
- expect(result.skipped).toBe('all-present');
61
- expect(result.ran).toEqual([]);
62
- expect(calls).toEqual([]);
63
- });
64
-
65
- test('runs `bun install --production` when node_modules/react is missing', async () => {
66
- seedDist();
67
- const { opts, calls } = harness();
68
- const result = await bootSelfHeal(opts);
69
- expect(result.ran).toEqual(['install']);
70
- expect(calls).toHaveLength(1);
71
- expect(calls[0]?.cmd).toEqual(['bun', 'install', '--production']);
72
- expect(calls[0]?.cwd).toBe(TMP);
73
- });
74
-
75
- test('runs `bun run build.ts` when dist/client.bundle.js is missing', async () => {
76
- seedDeps();
77
- const { opts, calls } = harness();
46
+ test('passes when both required artifacts present', async () => {
47
+ seedAll();
48
+ const { opts, logs } = harness();
78
49
  const result = await bootSelfHeal(opts);
79
- expect(result.ran).toEqual(['build']);
80
- expect(calls).toHaveLength(1);
81
- expect(calls[0]?.cmd).toEqual(['bun', 'run', 'build.ts']);
50
+ expect(result.verified).toEqual(['client.bundle.js', 'runtime/react.js']);
51
+ expect(logs).toEqual([]);
82
52
  });
83
53
 
84
- test('runs install BEFORE build when both missing (build needs deps)', async () => {
85
- const { opts, calls } = harness();
86
- const result = await bootSelfHeal(opts);
87
- expect(result.ran).toEqual(['install', 'build']);
88
- expect(calls[0]?.cmd).toEqual(['bun', 'install', '--production']);
89
- expect(calls[1]?.cmd).toEqual(['bun', 'run', 'build.ts']);
54
+ test('exits 1 with remediation when dist/client.bundle.js missing', async () => {
55
+ writeFileSync(join(TMP, 'dist', 'runtime', 'react.js'), '/* stub */');
56
+ const { opts, logs, getExited } = harness();
57
+ await expect(bootSelfHeal(opts)).rejects.toThrow('__exit:1');
58
+ expect(getExited()).toBe(1);
59
+ const msg = logs.join('\n');
60
+ expect(msg).toMatch(/dist\/client\.bundle\.js/);
61
+ expect(msg).toMatch(/npm uninstall -g @1agh\/maude/);
90
62
  });
91
63
 
92
- test('MAUDE_NO_AUTOBUILD=1: exits 1 with remediation message; no spawn', async () => {
93
- const { opts, calls, logs, getExited } = harness({ optOut: true });
64
+ test('exits 1 with remediation when dist/runtime/react.js missing', async () => {
65
+ writeFileSync(join(TMP, 'dist', 'client.bundle.js'), '/* stub */');
66
+ const { opts, logs, getExited } = harness();
94
67
  await expect(bootSelfHeal(opts)).rejects.toThrow('__exit:1');
95
68
  expect(getExited()).toBe(1);
96
- expect(calls).toEqual([]);
97
- expect(logs.join('\n')).toMatch(/MAUDE_NO_AUTOBUILD=1/);
98
- expect(logs.join('\n')).toMatch(/dist\/client\.bundle\.js/);
99
- expect(logs.join('\n')).toMatch(/node_modules\/react/);
69
+ const msg = logs.join('\n');
70
+ expect(msg).toMatch(/dist\/runtime\/react\.js/);
100
71
  });
101
72
 
102
- test('spawn failure aborts with exit 1 + remediation hint', async () => {
103
- seedDist(); // only deps missing
104
- const { opts, logs, getExited } = harness({
105
- spawn: async () => ({ code: 42 }),
106
- });
73
+ test('lists ALL missing artifacts in one message (not first-fail-only)', async () => {
74
+ // Nothing seeded — both missing.
75
+ const { opts, logs } = harness();
107
76
  await expect(bootSelfHeal(opts)).rejects.toThrow('__exit:1');
108
- expect(getExited()).toBe(1);
109
- expect(logs.join('\n')).toMatch(/exited 42/);
110
- expect(logs.join('\n')).toMatch(/MAUDE_NO_AUTOBUILD=1/);
77
+ const msg = logs.join('\n');
78
+ expect(msg).toMatch(/dist\/client\.bundle\.js/);
79
+ expect(msg).toMatch(/dist\/runtime\/react\.js/);
80
+ });
81
+
82
+ test('remediation surfaces the looked-under path so user can verify', async () => {
83
+ const { opts, logs } = harness();
84
+ await expect(bootSelfHeal(opts)).rejects.toThrow();
85
+ expect(logs.join('\n')).toContain(TMP);
111
86
  });
112
87
  });
@@ -0,0 +1,31 @@
1
+ // paths.ts — resolves real disk install root across dev mode, compiled npm
2
+ // install, and compiled marketplace install. Phase 19.1 / v0.18.1.
3
+
4
+ import { describe, expect, test } from 'bun:test';
5
+
6
+ import {
7
+ CLIENT_DIR,
8
+ DEV_SERVER_ROOT,
9
+ DIST_DIR,
10
+ IS_COMPILED_BINARY,
11
+ RUNTIME_BUNDLES_DIR,
12
+ } from '../paths.ts';
13
+
14
+ describe('paths.ts', () => {
15
+ test('DEV_SERVER_ROOT contains http.ts + package.json (canonical anchor)', () => {
16
+ expect(DEV_SERVER_ROOT).not.toMatch(/\$bunfs|~BUN/);
17
+ expect(Bun.file(`${DEV_SERVER_ROOT}/http.ts`).size).toBeGreaterThan(0);
18
+ expect(Bun.file(`${DEV_SERVER_ROOT}/package.json`).size).toBeGreaterThan(0);
19
+ });
20
+
21
+ test('DIST_DIR + CLIENT_DIR + RUNTIME_BUNDLES_DIR are descendants of DEV_SERVER_ROOT', () => {
22
+ expect(DIST_DIR).toBe(`${DEV_SERVER_ROOT}/dist`);
23
+ expect(CLIENT_DIR).toBe(`${DEV_SERVER_ROOT}/client`);
24
+ expect(RUNTIME_BUNDLES_DIR).toBe(`${DEV_SERVER_ROOT}/dist/runtime`);
25
+ });
26
+
27
+ test('IS_COMPILED_BINARY is false when running under bun directly (this test)', () => {
28
+ // bun:test invokes via `bun`, so import.meta.url is a real file:// path.
29
+ expect(IS_COMPILED_BINARY).toBe(false);
30
+ });
31
+ });