@vesk/adapter 0.2.10 → 0.2.12

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,91 @@
1
+ const RUNTIME_AUTOIMPORTS = 'effect, derived, untrack, peek, tick, flushSync, on_destroy, createContext';
2
+ const SYNTAX_KINDS = ['Unexpected token', 'Parse error', 'SyntaxError'];
3
+ const MODULE_KINDS = ['Cannot find module', 'Cannot load module', 'Module not found'];
4
+ const NULL_DEREF_KINDS = ['Cannot read properties of', 'Cannot read property'];
5
+ const rules = [
6
+ {
7
+ match: (m) => m.includes('is not defined'),
8
+ build: () => ({
9
+ tips: [
10
+ 'The name was referenced before it was imported or declared in this file, so the compiler treated it as undeclared.',
11
+ ],
12
+ suggestions: [
13
+ `Vesk auto-imports the reactive helpers from @vesk/runtime (${RUNTIME_AUTOIMPORTS}); note that \`batch\` does NOT exist — never import it.`,
14
+ ],
15
+ nextSteps: [
16
+ 'Check the spelling, add an explicit import for the name, or declare it as a binding before use.',
17
+ ],
18
+ }),
19
+ },
20
+ {
21
+ match: (m) => SYNTAX_KINDS.some((k) => m.includes(k)),
22
+ build: () => ({
23
+ tips: ['The parser hit a token it could not place in the current context.'],
24
+ suggestions: [
25
+ 'Statement mode accepts bare JSX plus if/for/while/switch/try — no return statement needed; expression mode requires `return <jsx>;`.',
26
+ ],
27
+ nextSteps: [
28
+ 'Check the line under the ^ marker for missing brackets, quotes, or unclosed JSX tags, then fix and re-save.',
29
+ ],
30
+ }),
31
+ },
32
+ {
33
+ match: (m) => MODULE_KINDS.some((k) => m.includes(k)),
34
+ build: () => ({
35
+ tips: ['An import path could not be resolved from this file.'],
36
+ suggestions: ['Check the specifier spelling and the relative path (./ vs ../) in the import statement.'],
37
+ nextSteps: [
38
+ 'Install the missing dependency or create the file at the expected path, then restart the dev server.',
39
+ ],
40
+ }),
41
+ },
42
+ {
43
+ match: (m) => m.toLowerCase().includes('unterminated'),
44
+ build: () => ({
45
+ tips: ['A string or template literal was opened earlier in the file and never closed.'],
46
+ suggestions: ['Look for a missing closing quote or backtick on the line just above the marker.'],
47
+ nextSteps: [
48
+ 'Close the literal; to span lines, open it with a backtick and use ${expr} for interpolation.',
49
+ ],
50
+ }),
51
+ },
52
+ {
53
+ match: (m) => m.includes('is not a function') || m.includes('undefined is not'),
54
+ build: () => ({
55
+ tips: ['The value being called is not callable — often undefined, null, or the wrong import.'],
56
+ suggestions: ['Check that you imported the function itself and used the correct export name.'],
57
+ nextSteps: [
58
+ 'Guard the call (typeof x === \'function\' && x()) or trace where the value is assigned before invoking it.',
59
+ ],
60
+ }),
61
+ },
62
+ {
63
+ match: (m) => NULL_DEREF_KINDS.some((k) => m.includes(k)) || m.includes(' of null') || m.includes(' of undefined'),
64
+ build: () => ({
65
+ tips: ['A property was read on null or undefined at the marked line.'],
66
+ suggestions: ['Guard the access with optional chaining (obj?.prop) or a default value (const x = data ?? {}).'],
67
+ nextSteps: [
68
+ 'Trace where the value is set — async data may not have arrived when the component first renders.',
69
+ ],
70
+ }),
71
+ },
72
+ ];
73
+ const FALLBACK = {
74
+ tips: ['The compiler reported an error in this file but no rule matched its message text.'],
75
+ suggestions: [
76
+ 'Verify imports and that every referenced name is declared — and check the body in both statement mode (bare JSX, if/for/while/switch/try) and expression mode (return <jsx>).',
77
+ ],
78
+ nextSteps: [
79
+ 'Look for a fuller error message printed above this one.',
80
+ 'Restart the dev server if the error persists after the fix.',
81
+ ],
82
+ };
83
+ export function suggestFor(message) {
84
+ if (typeof message !== 'string')
85
+ return FALLBACK;
86
+ for (const rule of rules) {
87
+ if (rule.match(message))
88
+ return rule.build(message);
89
+ }
90
+ return FALLBACK;
91
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shareable HMR source-text helpers factored out of `hmr.ts` so the .vsk hot
3
+ * path can be reused (and benchmarked) independently of the WebSocket server.
4
+ * Adapter text processing is tooling, not compiler syntax analysis — regex is
5
+ * permitted here.
6
+ */
7
+ export interface ComponentAssignment {
8
+ name: string;
9
+ raw: string;
10
+ }
11
+ export declare function extractComponentAssignments(code: string): ComponentAssignment[];
12
+ export declare function extractSourceDir(filename: string): string | null;
13
+ export declare function escapeSource(src: string): string;
14
+ //# sourceMappingURL=hmr-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hmr-utils.d.ts","sourceRoot":"","sources":["../src/hmr-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,EAAE,CA8B/E;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMhE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhD"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Shareable HMR source-text helpers factored out of `hmr.ts` so the .vsk hot
3
+ * path can be reused (and benchmarked) independently of the WebSocket server.
4
+ * Adapter text processing is tooling, not compiler syntax analysis — regex is
5
+ * permitted here.
6
+ */
7
+ export function extractComponentAssignments(code) {
8
+ const assignments = [];
9
+ const startRegex = /__components\["(\w+)"\]\s*=\s*/;
10
+ const lines = code.split('\n');
11
+ let i = 0;
12
+ while (i < lines.length) {
13
+ const m = lines[i].match(startRegex);
14
+ if (m) {
15
+ const name = m[1];
16
+ const startIdx = i;
17
+ let braceDepth = 0;
18
+ for (let j = 0; j < lines[i].length; j++) {
19
+ if (lines[i][j] === '{')
20
+ braceDepth++;
21
+ if (lines[i][j] === '}')
22
+ braceDepth--;
23
+ }
24
+ i++;
25
+ while (i < lines.length && braceDepth > 0) {
26
+ for (let j = 0; j < lines[i].length; j++) {
27
+ if (lines[i][j] === '{')
28
+ braceDepth++;
29
+ if (lines[i][j] === '}')
30
+ braceDepth--;
31
+ }
32
+ i++;
33
+ }
34
+ const fullAssignment = lines.slice(startIdx, i).join('\n');
35
+ assignments.push({ name, raw: fullAssignment });
36
+ }
37
+ else {
38
+ i++;
39
+ }
40
+ }
41
+ return assignments;
42
+ }
43
+ export function extractSourceDir(filename) {
44
+ if (filename === 'page.vsk')
45
+ return '';
46
+ if (filename.endsWith('/page.vsk'))
47
+ return filename.slice(0, -'/page.vsk'.length);
48
+ if (filename === 'layout.vsk')
49
+ return '';
50
+ if (filename.endsWith('/layout.vsk'))
51
+ return filename.slice(0, -'/layout.vsk'.length);
52
+ return null;
53
+ }
54
+ export function escapeSource(src) {
55
+ return src.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
56
+ }
package/dist/hmr.d.ts CHANGED
@@ -1,7 +1,47 @@
1
1
  import type { Server } from 'node:http';
2
+ import { type Codeframe } from '@vesk/adapter/src/error-codeframe';
2
3
  import type { RouteNode } from '@vesk/adapter/src/types';
4
+ /**
5
+ * Canonical HMR error payload emitted on `'error'` broadcasts and exposed via
6
+ * `getHmrState()` (served at `/__vesk/hmr/state`). This is the wire contract
7
+ * between the HMR server and the client's `HmrErrorPayload` renderer.
8
+ */
9
+ export interface HmrErrorPayload {
10
+ file: string;
11
+ filePath?: string;
12
+ line: number | null;
13
+ column: number | null;
14
+ message: string;
15
+ codeframe?: Codeframe;
16
+ tips?: string[];
17
+ suggestions?: string[];
18
+ nextSteps?: string[];
19
+ stack?: string;
20
+ }
21
+ export declare function getHmrState(): {
22
+ status: 'up' | 'closed';
23
+ lastCompileMs: number | null;
24
+ error: HmrErrorPayload | null;
25
+ hasError: boolean;
26
+ componentCount?: number;
27
+ };
28
+ export interface BuildErrorPayloadOptions {
29
+ /** Absolute app dir, used to resolve `filename` to disk for the codeframe. */
30
+ appDir?: string;
31
+ /** Extra fields merged into the returned payload (last writer wins). */
32
+ extra?: Record<string, unknown>;
33
+ }
34
+ /**
35
+ * Build the canonical HMR error payload from an arbitrary thrown value. Handles
36
+ * Error instances, plain strings, and objects exposing `.loc`/`.position`
37
+ * (acorn / VeskError shapes). When a line is recoverable it best-effort reads
38
+ * the source file from disk to attach a ±context codeframe; otherwise line/
39
+ * column are null and no codeframe is included (the client handles that case).
40
+ */
41
+ export declare function buildErrorPayload(error: unknown, filename: string, opts?: BuildErrorPayloadOptions): HmrErrorPayload;
3
42
  export declare function createHmrServer(httpServer: Server, appDir: string, devDir: string, componentMap?: Map<string, string>): {
4
43
  broadcast: (type: string, data?: Record<string, unknown>) => void;
5
44
  handleFileChange: (filename: string | null, doFullBuild: () => Promise<void>, routeTree: RouteNode[]) => Promise<void>;
45
+ getHmrState: () => ReturnType<typeof getHmrState>;
6
46
  };
7
47
  //# sourceMappingURL=hmr.d.ts.map
package/dist/hmr.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../src/hmr.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAQxC,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,yBAAyB,CAAC;AA6RzE,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC;IAAE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAAC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAmI/L"}
1
+ {"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../src/hmr.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AASxC,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEvG,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,yBAAyB,CAAC;AAIzE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAaD,wBAAgB,WAAW,IAAI;IAC7B,MAAM,EAAE,IAAI,GAAG,QAAQ,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAQA;AAED,MAAM,WAAW,wBAAwB;IACvC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,wBAA6B,GAClC,eAAe,CA4DjB;AA2RD,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC;IACD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAClE,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvH,WAAW,EAAE,MAAM,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;CACnD,CAyJA"}
package/dist/hmr.js CHANGED
@@ -3,10 +3,106 @@ import { readFileSync, existsSync, writeFileSync } from 'node:fs';
3
3
  import { resolve, dirname } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { compileClient } from '@vesk/compiler/src/client-codegen';
6
+ import { buildHmrEvalSnippet } from './client-bundle';
6
7
  import { resolveComponentName, randomToken } from '@vesk/compiler/src/server-codegen';
7
8
  import { resolveErrorFile } from '@vesk/adapter/src/ssr-function';
8
9
  import { isAllowedWsUpgrade } from '@vesk/adapter/src/paths';
10
+ import { parseCompilerError, buildCodeframe } from '@vesk/adapter/src/error-codeframe';
11
+ import { suggestFor } from '@vesk/adapter/src/error-tips';
9
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ /**
14
+ * The last enriched error payload broadcast over HMR, plus the last successful
15
+ * compile duration (ms). Cleared invariants:
16
+ * - `lastError` set ONLY on `'error'` broadcasts; cleared on every
17
+ * `'update'`/`'reload'` broadcast and on HMR watch start.
18
+ * - `lastCompileMs` set from `'update'`/`'reload'` `time`; cleared on error.
19
+ */
20
+ let lastError = null;
21
+ let lastCompileMs = null;
22
+ let lastComponentCount = null;
23
+ export function getHmrState() {
24
+ return {
25
+ status: 'up',
26
+ lastCompileMs,
27
+ error: lastError,
28
+ hasError: lastError !== null,
29
+ ...(lastComponentCount !== null ? { componentCount: lastComponentCount } : {}),
30
+ };
31
+ }
32
+ /**
33
+ * Build the canonical HMR error payload from an arbitrary thrown value. Handles
34
+ * Error instances, plain strings, and objects exposing `.loc`/`.position`
35
+ * (acorn / VeskError shapes). When a line is recoverable it best-effort reads
36
+ * the source file from disk to attach a ±context codeframe; otherwise line/
37
+ * column are null and no codeframe is included (the client handles that case).
38
+ */
39
+ export function buildErrorPayload(error, filename, opts = {}) {
40
+ const file = typeof filename === 'string' && filename ? filename : 'unknown';
41
+ const parsed = parseCompilerError(error, file);
42
+ const fallbackMessage = error instanceof Error ? error.message
43
+ : typeof error === 'string' ? error
44
+ : (() => { try {
45
+ return String(error);
46
+ }
47
+ catch {
48
+ return 'Unknown error';
49
+ } })();
50
+ const message = parsed && parsed.message ? parsed.message : fallbackMessage;
51
+ const line = parsed ? parsed.line : null;
52
+ const column = parsed ? parsed.column : null;
53
+ const stack = parsed && typeof parsed.stack === 'string' && parsed.stack
54
+ ? parsed.stack
55
+ : error instanceof Error && error.stack
56
+ ? error.stack
57
+ : undefined;
58
+ let codeframe;
59
+ if (line !== null && line >= 1 && typeof opts.appDir === 'string') {
60
+ let src;
61
+ try {
62
+ const absPath = resolve(opts.appDir, file);
63
+ if (existsSync(absPath))
64
+ src = readFileSync(absPath, 'utf-8');
65
+ }
66
+ catch {
67
+ src = undefined;
68
+ }
69
+ if (typeof src === 'string' && src.length > 0) {
70
+ const cf = buildCodeframe(src, line, column ?? 1);
71
+ if (cf) {
72
+ cf.file = file;
73
+ codeframe = cf;
74
+ }
75
+ }
76
+ }
77
+ const tipsData = suggestFor(message);
78
+ const filePath = typeof opts.appDir === 'string'
79
+ ? resolve(opts.appDir, file)
80
+ : undefined;
81
+ const payload = {
82
+ file,
83
+ line,
84
+ column,
85
+ message,
86
+ };
87
+ if (filePath)
88
+ payload.filePath = filePath;
89
+ if (codeframe)
90
+ payload.codeframe = codeframe;
91
+ if (tipsData.tips && tipsData.tips.length)
92
+ payload.tips = tipsData.tips;
93
+ if (tipsData.suggestions && tipsData.suggestions.length)
94
+ payload.suggestions = tipsData.suggestions;
95
+ if (tipsData.nextSteps && tipsData.nextSteps.length)
96
+ payload.nextSteps = tipsData.nextSteps;
97
+ if (stack)
98
+ payload.stack = stack;
99
+ if (opts.extra) {
100
+ for (const [k, v] of Object.entries(opts.extra)) {
101
+ payload[k] = v;
102
+ }
103
+ }
104
+ return payload;
105
+ }
10
106
  function findRouteForSource(routeTree, sourceDir) {
11
107
  for (const node of routeTree) {
12
108
  if (node.sourceDir === sourceDir)
@@ -302,6 +398,11 @@ export function createHmrServer(httpServer, appDir, devDir, componentMap) {
302
398
  // appendHmrGlobals in client-bundle.ts). Broadcast with every update.
303
399
  const hmrNonce = randomToken(16);
304
400
  globalThis.__vesk_hmr_nonce = hmrNonce;
401
+ // HMR watch start — clear any stale error state so a fresh session starts
402
+ // clean (the client also pulls live state via /__vesk/hmr/state).
403
+ lastError = null;
404
+ lastCompileMs = null;
405
+ lastComponentCount = null;
305
406
  const wss = new WebSocketServer({ noServer: true });
306
407
  const clients = new Set();
307
408
  // Origin-checked upgrade: cross-site pages always attach an Origin header
@@ -321,6 +422,22 @@ export function createHmrServer(httpServer, appDir, devDir, componentMap) {
321
422
  ws.on('error', () => clients.delete(ws));
322
423
  });
323
424
  function broadcast(type, data) {
425
+ // Keep live state in lockstep with what clients receive. `lastError` is set
426
+ // ONLY on 'error' broadcasts and cleared on any successful update/reload;
427
+ // `lastCompileMs` tracks the most recent successful compile duration.
428
+ const d = (data ?? {});
429
+ if (type === 'error') {
430
+ lastError = (d && typeof d.message === 'string') ? d : null;
431
+ lastCompileMs = null;
432
+ }
433
+ else if (type === 'update' || type === 'reload') {
434
+ lastError = null;
435
+ if (typeof d.time === 'number')
436
+ lastCompileMs = d.time;
437
+ if (type === 'update' && d && typeof d.components === 'object' && d.components !== null) {
438
+ lastComponentCount = Object.keys(d.components).length;
439
+ }
440
+ }
324
441
  const msg = JSON.stringify({ type, nonce: hmrNonce, ...data });
325
442
  for (const ws of clients) {
326
443
  try {
@@ -347,16 +464,22 @@ export function createHmrServer(httpServer, appDir, devDir, componentMap) {
347
464
  const assignments = extractComponentAssignments(code);
348
465
  if (assignments.length > 0) {
349
466
  const components = {};
350
- const fnSources = {};
351
- for (const { name, raw } of assignments) {
467
+ for (const { name } of assignments) {
352
468
  components[name] = true;
353
- fnSources[name] = raw;
354
469
  }
355
- broadcast('update', {
356
- components,
357
- fnSources,
358
- time: Date.now() - start,
359
- });
470
+ // Send the whole file scope (not per-component slices): a sliced
471
+ // assignment closes over file top-level bindings (const navItems,
472
+ // helpers, …) that the eval context does not have, so the
473
+ // re-rendered component throws ReferenceError and the swap fails
474
+ // silently (no reload, stale DOM, zero page errors).
475
+ const snippet = buildHmrEvalSnippet(code);
476
+ if (snippet.trim()) {
477
+ broadcast('update', {
478
+ components,
479
+ fnSources: { _raw: snippet },
480
+ time: Date.now() - start,
481
+ });
482
+ }
360
483
  }
361
484
  if (sourceDir !== null) {
362
485
  const routeNode = findRouteForSource(routeTree, sourceDir);
@@ -368,9 +491,9 @@ export function createHmrServer(httpServer, appDir, devDir, componentMap) {
368
491
  console.error(`vesk hmr: ${assignments.map(a => a.name).join(', ')} (${Date.now() - start}ms)`);
369
492
  }
370
493
  catch (e) {
371
- const message = e instanceof Error ? e.message : String(e);
372
- broadcast('error', { message, file: filename });
373
- console.error(`vesk hmr: error — ${message}`);
494
+ const payload = buildErrorPayload(e, filename, { appDir });
495
+ broadcast('error', payload);
496
+ console.error(`vesk hmr: error — ${payload.message}`);
374
497
  }
375
498
  return;
376
499
  }
@@ -381,8 +504,7 @@ export function createHmrServer(httpServer, appDir, devDir, componentMap) {
381
504
  broadcast('reload', { reason: `API: ${filename}`, time: Date.now() - start });
382
505
  }
383
506
  catch (e) {
384
- const message = e instanceof Error ? e.message : String(e);
385
- broadcast('error', { message, file: filename });
507
+ broadcast('error', buildErrorPayload(e, filename, { appDir }));
386
508
  }
387
509
  return;
388
510
  }
@@ -394,8 +516,7 @@ export function createHmrServer(httpServer, appDir, devDir, componentMap) {
394
516
  console.error(`vesk hmr: middleware ${filename} rebuilt (${Date.now() - start}ms)`);
395
517
  }
396
518
  catch (e) {
397
- const message = e instanceof Error ? e.message : String(e);
398
- broadcast('error', { message, file: filename });
519
+ broadcast('error', buildErrorPayload(e, filename, { appDir }));
399
520
  }
400
521
  return;
401
522
  }
@@ -408,8 +529,7 @@ export function createHmrServer(httpServer, appDir, devDir, componentMap) {
408
529
  console.error(`vesk hmr: ${filename} rebuilt (${Date.now() - start}ms)`);
409
530
  }
410
531
  catch (e) {
411
- const message = e instanceof Error ? e.message : String(e);
412
- broadcast('error', { message, file: filename });
532
+ broadcast('error', buildErrorPayload(e, filename, { appDir }));
413
533
  }
414
534
  return;
415
535
  }
@@ -419,9 +539,8 @@ export function createHmrServer(httpServer, appDir, devDir, componentMap) {
419
539
  broadcast('reload', { reason: `${filename} changed`, time: Date.now() - start });
420
540
  }
421
541
  catch (e) {
422
- const message = e instanceof Error ? e.message : String(e);
423
- broadcast('error', { message, file: filename });
542
+ broadcast('error', buildErrorPayload(e, filename, { appDir }));
424
543
  }
425
544
  }
426
- return { broadcast, handleFileChange };
545
+ return { broadcast, handleFileChange, getHmrState };
427
546
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,40 @@
1
- import type { BuildOptions, BuildResult } from '@vesk/adapter/src/types';
1
+ import type { BuildOptions, BuildResult, VeskPlugin } from '@vesk/adapter/src/types';
2
+ /**
3
+ * Plugin activation record as consumed by the build gate. At runtime these come
4
+ * from the plugin-manager module (`@vesk/adapter/src/plugins`): either
5
+ * `getPluginRecords` (full `PluginRecord[]` — only `name` + `active` are used
6
+ * here) or `readPluginState` (`.vesk/plugins.json` entries). This local shape is
7
+ * the minimal dual-view of those two sources.
8
+ */
9
+ export interface PluginStateRecord {
10
+ name: string;
11
+ active: boolean;
12
+ }
13
+ export interface PluginStateFile {
14
+ version: number;
15
+ plugins: PluginStateRecord[];
16
+ }
17
+ /**
18
+ * Defensive local mirror of the plugin-manager's `filterActivePlugins`
19
+ * (`@vesk/adapter/src/plugins`): keep when there is no matching record, or the
20
+ * record's `active` is true; drop when a name-matched record is explicitly
21
+ * inactive. Names match CASE-INSENSITIVELY to stay aligned with the manager —
22
+ * it reads/writes state via `eqIgnoreCase`. Only used as the fallback when the
23
+ * plugin-manager module is unavailable during a build; the live build gate in
24
+ * `build()` calls the module's own filter.
25
+ */
26
+ export declare function filterActivePlugins(plugins: VeskPlugin[], records: PluginStateRecord[] | null | undefined): VeskPlugin[];
27
+ /**
28
+ * Build-time enforcement gate (fallback): an INACTIVE plugin must NEVER ship —
29
+ * it must not have any hook invoked and must not appear in CSS / transformed
30
+ * JS / platform output. Returns the actives-only list. A null/absent state
31
+ * degrades to "all config plugins stay active" (defensive). The live gate in
32
+ * `build()` prefers `@vesk/adapter/src/plugins#filterActivePlugins`; this
33
+ * helper exists for the no-module fallback and for direct unit testing.
34
+ */
35
+ export declare function filterPluginsForBuild(plugins: VeskPlugin[], state: PluginStateFile | null | undefined): VeskPlugin[];
2
36
  export declare function build(appDir: string, options?: BuildOptions): Promise<BuildResult | undefined>;
3
37
  export { startProdServer } from '@vesk/adapter/src/prod-server';
38
+ export { createDevApiRouter, DEFAULT_CAPABILITIES, DEFAULT_COMMAND_ALLOWLIST, CapabilityTable, } from '@vesk/adapter/src/dev-api';
39
+ export type { DevApiRouterOptions, DevApiRouter, DevApiCapabilities, CapabilityName, DiagnosticFinding, DevPanelResponse, RebuildResult, CommandResult, } from '@vesk/adapter/src/dev-api';
4
40
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACe,YAAY,EAAE,WAAW,EAEnD,MAAM,yBAAyB,CAAC;AAgBjC,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CA+SpG;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACe,YAAY,EAAE,WAAW,EAC7B,UAAU,EAChC,MAAM,yBAAyB,CAAC;AAiBjC;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CACjB;AACD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,UAAU,EAAE,EACrB,OAAO,EAAE,iBAAiB,EAAE,GAAG,IAAI,GAAG,SAAS,GAC9C,UAAU,EAAE,CAQd;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,UAAU,EAAE,EACrB,KAAK,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,GACxC,UAAU,EAAE,CAGd;AAWD,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAyVpG;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAIhE,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,yBAAyB,EACzB,eAAe,GAChB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,aAAa,GACd,MAAM,2BAA2B,CAAC"}