sygnal 6.5.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +156 -0
- package/README.md +2 -0
- package/dist/astro/index.cjs.js +423 -27
- package/dist/astro/index.cjs.js.map +1 -1
- package/dist/astro/index.mjs +423 -27
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/server.cjs.js.map +1 -1
- package/dist/astro/server.mjs.map +1 -1
- package/dist/devtools.cjs.js +141 -3
- package/dist/devtools.cjs.js.map +1 -1
- package/dist/devtools.esm.js +141 -4
- package/dist/devtools.esm.js.map +1 -1
- package/dist/diagnostics.cjs.js +169 -14
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +168 -15
- package/dist/diagnostics.esm.js.map +1 -1
- package/dist/guide/accessibility.md +2 -0
- package/dist/guide/ai-chat.md +6 -6
- package/dist/guide/behaviors.md +1 -1
- package/dist/guide/element-commands.md +4 -2
- package/dist/guide/forms-reference.md +89 -3
- package/dist/guide/forms.md +8 -8
- package/dist/guide/http.md +77 -7
- package/dist/guide/migrating-to-7.md +314 -0
- package/dist/guide/recipes/ai-summarize.md +2 -1
- package/dist/guide/recipes/i18n.md +1 -1
- package/dist/guide/ui/tabs.md +5 -4
- package/dist/guide/ui/tooltip.md +24 -0
- package/dist/index.cjs.js +781 -320
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +319 -43
- package/dist/index.esm.js +781 -320
- package/dist/index.esm.js.map +1 -1
- package/dist/sygnal.min.js +1 -1
- package/dist/sygnal.min.js.map +1 -1
- package/dist/ui.cjs.js +96 -11
- package/dist/ui.cjs.js.map +1 -1
- package/dist/ui.esm.js +96 -12
- package/dist/ui.esm.js.map +1 -1
- package/dist/vike/config/package.json +1 -1
- package/dist/vike/onRenderClient.cjs.js +63 -43
- package/dist/vike/onRenderClient.cjs.js.map +1 -1
- package/dist/vike/onRenderClient.mjs +63 -43
- package/dist/vike/onRenderClient.mjs.map +1 -1
- package/dist/vike/onRenderHtml.cjs.js +21 -25
- package/dist/vike/onRenderHtml.cjs.js.map +1 -1
- package/dist/vike/onRenderHtml.mjs +21 -25
- package/dist/vike/onRenderHtml.mjs.map +1 -1
- package/dist/vite/plugin.cjs.js +410 -27
- package/dist/vite/plugin.cjs.js.map +1 -1
- package/dist/vite/plugin.mjs +410 -27
- package/dist/vite/plugin.mjs.map +1 -1
- package/llms.txt +17 -16
- package/package.json +1 -1
- package/src/astro/index.d.ts +8 -0
- package/src/astro/index.ts +18 -0
- package/src/core/actions.ts +28 -21
- package/src/core/define.ts +2 -0
- package/src/core/hooks.ts +8 -2
- package/src/core/hosts/collection.ts +4 -3
- package/src/core/instance.ts +60 -16
- package/src/core/runtime.ts +5 -1
- package/src/core/view.ts +2 -0
- package/src/cycle/dom/utils.ts +4 -2
- package/src/devtools.d.ts +13 -0
- package/src/devtools.ts +3 -0
- package/src/extra/ai/chat/behavior.ts +3 -3
- package/src/extra/ai/commandBar.ts +3 -3
- package/src/extra/ai/compose/behavior.ts +3 -3
- package/src/extra/ai/transports/shared.ts +8 -2
- package/src/extra/behaviors.ts +21 -0
- package/src/extra/devGraph.ts +111 -0
- package/src/extra/devMcp.ts +1 -1
- package/src/extra/devtools.ts +18 -0
- package/src/extra/diagnostics/checks/fetch.ts +21 -1
- package/src/extra/diagnostics/checks/forms.ts +4 -3
- package/src/extra/diagnostics/checks/index.ts +2 -1
- package/src/extra/diagnostics/checks/inspect.ts +68 -6
- package/src/extra/diagnostics/checks/next.ts +3 -0
- package/src/extra/diagnostics/checks/public.d.ts +133 -12
- package/src/extra/diagnostics/checks/timingAudit.ts +47 -0
- package/src/extra/diagnostics/codes.ts +19 -0
- package/src/extra/fetchDriver.ts +112 -7
- package/src/extra/fetchEventSource.ts +113 -0
- package/src/extra/form.ts +68 -26
- package/src/extra/formHelpers.ts +3 -1
- package/src/extra/lens.ts +3 -2
- package/src/extra/queryCache.ts +4 -3
- package/src/extra/socketDriver.ts +10 -4
- package/src/extra/testing.ts +68 -11
- package/src/index.d.ts +199 -30
- package/src/index.ts +1 -0
- package/src/shared.ts +3 -0
- package/src/ui/dialog.ts +3 -2
- package/src/ui/tooltips.ts +92 -0
- package/src/ui.d.ts +35 -0
- package/src/ui.ts +1 -0
- package/src/vike/onRenderClient.ts +44 -46
- package/src/vike/onRenderHtml.ts +5 -26
- package/src/vike/shell.ts +33 -0
- package/src/vite/graph.ts +255 -0
- package/src/vite/mcp.ts +3 -3
- package/src/vite/plugin.d.ts +19 -0
- package/src/vite/plugin.ts +144 -25
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLAN-10 W-2 / P-2: the live app graph page of sygnal/vite's dev server (D543, D544).
|
|
3
|
+
*
|
|
4
|
+
* `/__sygnal/graph` serves sygnal-check's graph page (`graphHtml`, the same file `sygnal-check
|
|
5
|
+
* --graph=html --watch` serves) in live mode, and `/__sygnal/graph/events` its SSE stream: the
|
|
6
|
+
* whole graph (`event: graph`, `id: <version>`) on connect (unless `Last-Event-ID` already has it)
|
|
7
|
+
* and after each build whose graph changed, a `: ping` comment every 25 s. Dev server only.
|
|
8
|
+
*
|
|
9
|
+
* One live project (sygnal-check's createLiveProject, W-1) feeds the page, the check overlay and
|
|
10
|
+
* the console (the plugin's `check`): each build gives the graph and the diagnostics, so they agree.
|
|
11
|
+
* It builds on start and 100 ms after the last source or config file change Vite's watcher reports.
|
|
12
|
+
*
|
|
13
|
+
* P-2: each build also gives the top-level binding and static key (D521: 'file#name', '#2' for a
|
|
14
|
+
* second of the same name in a file) of every component in a scanned file (sygnal-check's
|
|
15
|
+
* componentStamps); the dev transform appends to those modules a tail that sets a non-enumerable
|
|
16
|
+
* `__sygnalKey` on each component function, and the runtime inspect() reports it as `key`. A
|
|
17
|
+
* module whose text changed since the last build is built again first, so a stamp is never stale.
|
|
18
|
+
*
|
|
19
|
+
* The page is served on the app's origin: its "Live data" (graphView/liveData.js) joins the open
|
|
20
|
+
* app tabs over a BroadcastChannel (src/extra/devGraph.ts), with no server in between.
|
|
21
|
+
*
|
|
22
|
+
* Security: the same guard as `/__sygnal/mcp` (./mcp.ts refusal(): a loopback peer, a local Host
|
|
23
|
+
* (or one in `server.allowedHosts`), a local Origin when there is one); GET and HEAD only; the page
|
|
24
|
+
* gets `Cache-Control: no-store`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`,
|
|
25
|
+
* `X-Frame-Options: DENY` and a CSP (inline style and script, `connect-src 'self'`, no framing).
|
|
26
|
+
* Read only: it serves the page and the stream, nothing else.
|
|
27
|
+
*/
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
import fs from 'node:fs'
|
|
30
|
+
// @ts-ignore
|
|
31
|
+
import path from 'node:path'
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
import { createRequire } from 'node:module'
|
|
34
|
+
import { refusal } from './mcp'
|
|
35
|
+
|
|
36
|
+
export const GRAPH_PATH = '/__sygnal/graph'
|
|
37
|
+
export const GRAPH_EVENTS = GRAPH_PATH + '/events'
|
|
38
|
+
export const GRAPH_CLIENT = 'virtual:sygnal/graph'
|
|
39
|
+
export const GRAPH_CHANNEL = 'sygnal:graph'
|
|
40
|
+
/** The property the dev transform sets on component functions (read by inspect()) */
|
|
41
|
+
export const STAMP_PROP = '__sygnalKey'
|
|
42
|
+
|
|
43
|
+
const SOURCE_RE = /\.[cm]?[jt]sx?$/
|
|
44
|
+
const CONFIG_FILES = ['sygnal-check.config.json', 'package.json']
|
|
45
|
+
const EMPTY = { version: 1, source: 'static', components: [], events: {}, diagnostics: [] }
|
|
46
|
+
const PAGE_CSP = "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; img-src data: blob:; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"
|
|
47
|
+
const SECURITY: Record<string, string> = { 'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff', 'Referrer-Policy': 'no-referrer', 'X-Frame-Options': 'DENY' }
|
|
48
|
+
|
|
49
|
+
const posix = (p: string) => p.split(path.sep).join('/')
|
|
50
|
+
|
|
51
|
+
/** Whether the project's sygnal-check has the live graph page with "Live data" (checked without loading it). */
|
|
52
|
+
export function graphAvailable(root: string): boolean {
|
|
53
|
+
try {
|
|
54
|
+
const main = createRequire(path.join(root, 'package.json')).resolve('sygnal-check')
|
|
55
|
+
return fs.existsSync(path.join(path.dirname(main), 'graphView', 'liveData.js'))
|
|
56
|
+
} catch (_) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface LiveBuild { graph: any; json: string; version: number; changed: boolean; diagnostics: any[]; stamps: Map<string, Array<{ binding: string; key: string }>> | null; files: string[] }
|
|
62
|
+
|
|
63
|
+
export interface LiveEngine {
|
|
64
|
+
/** build now; listeners hear about it */
|
|
65
|
+
build(): LiveBuild | null
|
|
66
|
+
readonly last: LiveBuild | null
|
|
67
|
+
onBuild(fn: (b: LiveBuild) => void): void
|
|
68
|
+
/** the stamps of a module (absolute path), after building again when its text changed */
|
|
69
|
+
stampsFor(file: string): Array<{ binding: string; key: string }> | null
|
|
70
|
+
/** follow Vite's watcher (100 ms debounce) */
|
|
71
|
+
watch(server: any): void
|
|
72
|
+
readonly mod: any
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The live project behind the page and the checker. `settings` are live.js's (paths = the check's
|
|
77
|
+
* include, strict / a11y / ignore / defaults as the checker resolves them).
|
|
78
|
+
*/
|
|
79
|
+
export function createEngine(mod: any, root: string, settings: any, logger: any, stamps: boolean): LiveEngine {
|
|
80
|
+
const live = mod.createLiveProject({ root })
|
|
81
|
+
const listeners: Array<(b: LiveBuild) => void> = []
|
|
82
|
+
let last: LiveBuild | null = null
|
|
83
|
+
// the stamps by the live project's path of each file; `known` maps a module path as Vite gives it
|
|
84
|
+
// (posix, possibly through a symlink) to that path
|
|
85
|
+
let stampsByFile = new Map<string, Array<{ binding: string; key: string }>>()
|
|
86
|
+
let known = new Map<string, string>()
|
|
87
|
+
let failed = ''
|
|
88
|
+
const real = (f: string) => { try { return posix(fs.realpathSync(f)) } catch (_) { return posix(f) } }
|
|
89
|
+
|
|
90
|
+
const build = (): LiveBuild | null => {
|
|
91
|
+
let b: any
|
|
92
|
+
try {
|
|
93
|
+
b = live.build({ settings, stamps })
|
|
94
|
+
} catch (err: any) {
|
|
95
|
+
const msg = String(err?.message || err)
|
|
96
|
+
if (msg !== failed) logger.warn(`[sygnal] sygnal-check failed: ${msg}`, { timestamp: true })
|
|
97
|
+
failed = msg
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
failed = ''
|
|
101
|
+
if (!b.graph) b = { ...b, json: JSON.stringify(EMPTY) }
|
|
102
|
+
last = b
|
|
103
|
+
stampsByFile = b.stamps || new Map()
|
|
104
|
+
known = new Map()
|
|
105
|
+
for (const f of b.files || []) { known.set(posix(f), f); known.set(real(f), f) }
|
|
106
|
+
for (const fn of listeners) { try { fn(b) } catch (_) { /* a listener never stops the others */ } }
|
|
107
|
+
return b
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const stampsFor = (file: string) => {
|
|
111
|
+
if (!last) return null
|
|
112
|
+
const own = known.get(posix(file)) ?? known.get(real(file))
|
|
113
|
+
if (!own) return null
|
|
114
|
+
let text: string | null = null
|
|
115
|
+
try { text = fs.readFileSync(own, 'utf8') } catch (_) {}
|
|
116
|
+
const entry = live.cache?.get?.(own)
|
|
117
|
+
// changed since the last build (Vite transforms it before the watcher's debounced build)
|
|
118
|
+
if (text !== null && (!entry || entry.text !== text)) build()
|
|
119
|
+
return stampsByFile.get(own) || null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let timer: any
|
|
123
|
+
const watch = (server: any) => {
|
|
124
|
+
const onChange = (file: string) => {
|
|
125
|
+
if (String(file).split(/[\\/]/).includes('node_modules')) return
|
|
126
|
+
if (!SOURCE_RE.test(file) && !CONFIG_FILES.includes(path.basename(file))) return
|
|
127
|
+
clearTimeout(timer)
|
|
128
|
+
timer = setTimeout(build, 100)
|
|
129
|
+
}
|
|
130
|
+
const watcher = server?.watcher
|
|
131
|
+
if (watcher && typeof watcher.on === 'function') {
|
|
132
|
+
watcher.on('change', onChange)
|
|
133
|
+
watcher.on('add', onChange)
|
|
134
|
+
watcher.on('unlink', onChange)
|
|
135
|
+
}
|
|
136
|
+
try { server.httpServer?.once?.('close', () => clearTimeout(timer)) } catch (_) {}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
build, stampsFor, watch, mod,
|
|
141
|
+
get last() { return last },
|
|
142
|
+
onBuild(fn) { listeners.push(fn) },
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The tail the dev transform appends to a module: a non-enumerable key on each component function. */
|
|
147
|
+
export function stampTail(list: Array<{ binding: string; key: string }>): string {
|
|
148
|
+
const ok = list.filter(s => /^[A-Za-z_$][\w$]*$/.test(s.binding))
|
|
149
|
+
if (!ok.length) return ''
|
|
150
|
+
const pairs = ok.map(s => `[typeof ${s.binding} == 'function' ? ${s.binding} : null, ${JSON.stringify(s.key)}]`).join(', ')
|
|
151
|
+
return `\n;(() => { try { for (const [f, k] of [${pairs}]) if (f) Object.defineProperty(f, '${STAMP_PROP}', { value: k, configurable: true }) } catch (_) {} })();\n`
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** 'virtual:sygnal/graph': the app side of the page's "Live data" (dev only) */
|
|
155
|
+
export function graphClientModule(): string {
|
|
156
|
+
return `import { installGraphBridge } from 'sygnal/devtools';\ninstallGraphBridge({ channel: ${JSON.stringify(GRAPH_CHANNEL)} });\n`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function deny(res: any, status: number, text: string, headers: Record<string, string> = {}) {
|
|
160
|
+
res.statusCode = status
|
|
161
|
+
for (const k in { ...SECURITY, ...headers }) res.setHeader(k, ({ ...SECURITY, ...headers } as any)[k])
|
|
162
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
|
|
163
|
+
res.end(text + '\n')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function projectTitle(root: string): string {
|
|
167
|
+
try {
|
|
168
|
+
const name = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).name
|
|
169
|
+
if (typeof name === 'string' && name) return name
|
|
170
|
+
} catch (_) {}
|
|
171
|
+
return path.basename(root)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The middleware for `/__sygnal/graph` and its stream. `engine` resolves to null when sygnal-check
|
|
176
|
+
* isn't installed (or too old): the route then answers 404 with the install hint.
|
|
177
|
+
*/
|
|
178
|
+
export function graphMiddleware(server: any, root: string, engine: Promise<LiveEngine | null>, ping = 25000) {
|
|
179
|
+
const streams = new Set<any>()
|
|
180
|
+
let subscribed = false
|
|
181
|
+
let pinger: any = null
|
|
182
|
+
const event = (b: LiveBuild) => `event: graph\nid: ${b.version}\ndata: ${b.json}\n\n`
|
|
183
|
+
const subscribe = (e: LiveEngine) => {
|
|
184
|
+
if (subscribed) return
|
|
185
|
+
subscribed = true
|
|
186
|
+
e.onBuild((b) => { if (b.changed) for (const res of streams) res.write(event(b)) })
|
|
187
|
+
pinger = setInterval(() => { for (const res of streams) res.write(': ping\n\n') }, ping)
|
|
188
|
+
pinger.unref?.()
|
|
189
|
+
try {
|
|
190
|
+
server.httpServer?.once?.('close', () => {
|
|
191
|
+
clearInterval(pinger)
|
|
192
|
+
for (const res of streams) res.end()
|
|
193
|
+
streams.clear()
|
|
194
|
+
})
|
|
195
|
+
} catch (_) {}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return async (req: any, res: any, next: any) => {
|
|
199
|
+
const url = String(req.url || '').replace(/[?#].*$/, '')
|
|
200
|
+
if (url !== GRAPH_PATH && url !== GRAPH_PATH + '/' && url !== GRAPH_EVENTS) return next()
|
|
201
|
+
const why = refusal(req, server?.config?.server?.allowedHosts, 'the Sygnal graph page')
|
|
202
|
+
if (why) return deny(res, 403, `Forbidden: ${why}`)
|
|
203
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') return deny(res, 405, 'Method not allowed', { Allow: 'GET, HEAD' })
|
|
204
|
+
const e = await engine
|
|
205
|
+
if (!e) return deny(res, 404, 'The Sygnal graph page needs a sygnal-check with the live graph page in the project: npm i -D sygnal-check@latest')
|
|
206
|
+
subscribe(e)
|
|
207
|
+
const b = e.last || e.build()
|
|
208
|
+
if (!b) return deny(res, 500, 'sygnal-check failed: see the dev server output')
|
|
209
|
+
if (url === GRAPH_EVENTS) {
|
|
210
|
+
res.writeHead(200, { ...SECURITY, 'Content-Type': 'text/event-stream; charset=utf-8', Connection: 'keep-alive', 'Content-Security-Policy': "default-src 'none'" })
|
|
211
|
+
if (req.method === 'HEAD') return res.end()
|
|
212
|
+
res.write('retry: 1000\n\n')
|
|
213
|
+
if (req.headers['last-event-id'] !== String(b.version)) res.write(event(b))
|
|
214
|
+
streams.add(res)
|
|
215
|
+
req.on('close', () => streams.delete(res))
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
let body: string
|
|
219
|
+
try {
|
|
220
|
+
body = e.mod.graphHtml(b.graph || EMPTY, {
|
|
221
|
+
title: projectTitle(root),
|
|
222
|
+
subtitle: 'dev server',
|
|
223
|
+
root,
|
|
224
|
+
live: { events: GRAPH_EVENTS, version: b.version },
|
|
225
|
+
appData: { channel: GRAPH_CHANNEL },
|
|
226
|
+
})
|
|
227
|
+
} catch (err: any) {
|
|
228
|
+
return deny(res, 500, `The graph page failed: ${err?.message || err}`)
|
|
229
|
+
}
|
|
230
|
+
res.writeHead(200, { ...SECURITY, 'Content-Type': 'text/html; charset=utf-8', 'Content-Security-Policy': PAGE_CSP, 'Content-Length': (globalThis as any).Buffer.byteLength(body) })
|
|
231
|
+
res.end(req.method === 'HEAD' ? undefined : body)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** The banner line when the project has no sygnal-check with the graph page (it used to print nothing). */
|
|
236
|
+
export const GRAPH_MISSING_LINE = ' ➜ Sygnal graph: needs sygnal-check 0.10.0 or later in this project (npm i -D sygnal-check)'
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Adds the page's URL under Vite's own when the dev server prints them, or, when the project's
|
|
240
|
+
* sygnal-check is missing or too old for the page (`available` false), a line saying what to install.
|
|
241
|
+
*/
|
|
242
|
+
export function printGraphUrl(server: any, available = true) {
|
|
243
|
+
const print = server?.printUrls
|
|
244
|
+
if (typeof print !== 'function') return
|
|
245
|
+
server.printUrls = function (this: any, ...args: any[]) {
|
|
246
|
+
const out = print.apply(this, args)
|
|
247
|
+
try {
|
|
248
|
+
const logger = server.config?.logger || console
|
|
249
|
+
const local = server.resolvedUrls?.local?.[0]
|
|
250
|
+
if (!available) logger.info(GRAPH_MISSING_LINE)
|
|
251
|
+
else if (local) logger.info(` ➜ Sygnal graph: ${new URL(local).origin}${GRAPH_PATH}`)
|
|
252
|
+
} catch (_) {}
|
|
253
|
+
return out
|
|
254
|
+
}
|
|
255
|
+
}
|
package/src/vite/mcp.ts
CHANGED
|
@@ -337,10 +337,10 @@ function inAllowed(h: string, allowed: any): boolean {
|
|
|
337
337
|
})
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
/** Why the request is refused (403), or null */
|
|
341
|
-
export function refusal(req: any, allowedHosts?: any): string | null {
|
|
340
|
+
/** Why the request is refused (403), or null. PLAN-10 W-2: the graph route (./graph.ts) uses the same guard */
|
|
341
|
+
export function refusal(req: any, allowedHosts?: any, what = 'the Sygnal MCP endpoint'): string | null {
|
|
342
342
|
const remote = String(req?.socket?.remoteAddress || '')
|
|
343
|
-
if (!LOOPBACK_IP.test(remote)) return
|
|
343
|
+
if (!LOOPBACK_IP.test(remote)) return `${what} only answers requests from this machine (not ${remote || 'an unknown address'})`
|
|
344
344
|
const host = req?.headers?.host
|
|
345
345
|
if (typeof host !== 'string' || !host) return 'missing Host header'
|
|
346
346
|
const h = hostname(host)
|
package/src/vite/plugin.d.ts
CHANGED
|
@@ -12,6 +12,13 @@ export interface DiagnosticsPluginOptions {
|
|
|
12
12
|
* @default false
|
|
13
13
|
*/
|
|
14
14
|
strict?: boolean
|
|
15
|
+
/**
|
|
16
|
+
* 7.0 timing audit (SYG257, info): the dev snippet sets `globalThis.__SYGNAL_TIMING_AUDIT__ =
|
|
17
|
+
* true`, and each output sink that reads a top-level state field its action's STATE changed is
|
|
18
|
+
* reported (since 7.0 it gets the new value; 6.x gave it the old one). For migrating 6.x code.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
timingAudit?: boolean
|
|
15
22
|
/** Diagnostic codes to drop, e.g. ['SYG105'] (runtime and static) */
|
|
16
23
|
ignore?: string[]
|
|
17
24
|
}
|
|
@@ -136,6 +143,18 @@ export interface SygnalPluginOptions {
|
|
|
136
143
|
* @default false
|
|
137
144
|
*/
|
|
138
145
|
mcp?: boolean | McpPluginOptions
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Serve the live app graph page at `/__sygnal/graph` on the dev server (requests from this
|
|
149
|
+
* machine with a local Host / Origin only, as `/__sygnal/mcp`), built by sygnal-check and updated
|
|
150
|
+
* on every save; its URL is printed under Vite's. Skipped silently when sygnal-check isn't
|
|
151
|
+
* installed. In dev the component functions also get their static key (non-enumerable, read by
|
|
152
|
+
* inspect() as `key`), and with DevTools on the open app tabs answer the page's "Live data"
|
|
153
|
+
* (mounted components, instance counts, action flashes, state). Dev server only: production
|
|
154
|
+
* builds contain none of it.
|
|
155
|
+
* @default true
|
|
156
|
+
*/
|
|
157
|
+
graph?: boolean
|
|
139
158
|
}
|
|
140
159
|
|
|
141
160
|
export interface McpPluginOptions {
|
package/src/vite/plugin.ts
CHANGED
|
@@ -123,6 +123,22 @@
|
|
|
123
123
|
* `globalThis.__SYGNAL_VIEW_TOKENS__`. The runtime SYG103 check then takes a selector drawn
|
|
124
124
|
* by a view helper (`{state.open && panel()}`) as conditionally rendered, as it does for
|
|
125
125
|
* markup in the view function itself (see viewTokensTail).
|
|
126
|
+
* 12. `graph` option (PLAN-10 W-2 / P-2, D543 / D544, default true; dev server only, never
|
|
127
|
+
* `vite build`, `vite preview` or Vitest): the live app graph page at /__sygnal/graph, with
|
|
128
|
+
* its SSE stream at /__sygnal/graph/events (./graph.ts; the /__sygnal/mcp guard: loopback
|
|
129
|
+
* peer, local Host or server.allowedHosts, local Origin). sygnal-check (from the project,
|
|
130
|
+
* skipped silently when missing; a sygnal-check without the page keeps the checker as in 5)
|
|
131
|
+
* builds it with createLiveProject on start and after each change: one build gives the page,
|
|
132
|
+
* the check overlay / console findings of 5, and the component stamps. Its URL is printed
|
|
133
|
+
* under Vite's ("➜ Sygnal graph: http://localhost:5173/__sygnal/graph"; without a sygnal-check
|
|
134
|
+
* 0.10.0+ in the project, a line saying so instead). The modules of the
|
|
135
|
+
* scanned files get a tail setting a non-enumerable `__sygnalKey` (the static 'file#name'
|
|
136
|
+
* key, D521) on each top-level component function, which the runtime inspect() reports as
|
|
137
|
+
* `key`; with DevTools on, the dev entries also import 'virtual:sygnal/graph'
|
|
138
|
+
* (installGraphBridge from 'sygnal/devtools'), which answers the page's "Live data" over a
|
|
139
|
+
* same-origin BroadcastChannel: the page fades components with no instance, shows instance
|
|
140
|
+
* counts, flashes a card when its action runs and shows an instance's state. `graph: false`:
|
|
141
|
+
* no route, stamps or bridge (the checker of 5 runs check() as before).
|
|
126
142
|
*
|
|
127
143
|
* Why not Vite's `define`? Vite's dependency optimizer does not apply user
|
|
128
144
|
* `define` replacements to pre-bundled dependencies (only process.env.NODE_ENV),
|
|
@@ -162,6 +178,8 @@ import { pathToFileURL } from 'node:url'
|
|
|
162
178
|
import { globalThisAlias } from './globalthis'
|
|
163
179
|
import { attachMcp, mcpClientModule } from './mcp'
|
|
164
180
|
import type { McpPluginOptions } from './mcp'
|
|
181
|
+
import { GRAPH_CLIENT, createEngine, graphAvailable, graphClientModule, graphMiddleware, printGraphUrl, stampTail } from './graph'
|
|
182
|
+
import type { LiveEngine } from './graph'
|
|
165
183
|
|
|
166
184
|
export type { McpPluginOptions } from './mcp'
|
|
167
185
|
|
|
@@ -174,6 +192,8 @@ export interface DiagnosticsPluginOptions {
|
|
|
174
192
|
mode?: DiagnosticsMode
|
|
175
193
|
/** Strict (canonical-form, SYG5xx) runtime checks: sets globalThis.__SYGNAL_STRICT__. @default false */
|
|
176
194
|
strict?: boolean
|
|
195
|
+
/** 7.0 timing audit (SYG257, info): sets globalThis.__SYGNAL_TIMING_AUDIT__. @default false */
|
|
196
|
+
timingAudit?: boolean
|
|
177
197
|
/** Diagnostic codes to drop, e.g. ['SYG105'] */
|
|
178
198
|
ignore?: string[]
|
|
179
199
|
}
|
|
@@ -282,6 +302,17 @@ export interface SygnalPluginOptions {
|
|
|
282
302
|
* @default false
|
|
283
303
|
*/
|
|
284
304
|
mcp?: boolean | McpPluginOptions
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* PLAN-10 W-2 (D543): serve the live app graph page at `/__sygnal/graph` on the dev server (local
|
|
308
|
+
* requests only, as `/__sygnal/mcp`), built by sygnal-check (skipped silently when it isn't
|
|
309
|
+
* installed) and updated on every save; its URL is printed under Vite's. P-2 (D544): in dev the
|
|
310
|
+
* component functions get their static key (a non-enumerable `__sygnalKey`), and the app tabs
|
|
311
|
+
* answer the page's "Live data" (mounted components, instance counts, action flashes, state).
|
|
312
|
+
* Dev server only: never `vite build`, `vite preview` or Vitest. false: no route, no stamps.
|
|
313
|
+
* @default true
|
|
314
|
+
*/
|
|
315
|
+
graph?: boolean
|
|
285
316
|
}
|
|
286
317
|
|
|
287
318
|
// Virtual modules (dev server only)
|
|
@@ -297,6 +328,8 @@ const OVERLAY_PLUGIN = 'sygnal-check'
|
|
|
297
328
|
// PLAN-6 E-1: the page side of the dev MCP endpoint
|
|
298
329
|
const MCP_CLIENT = 'virtual:sygnal/mcp'
|
|
299
330
|
const MCP_CLIENT_ID = '\0' + MCP_CLIENT
|
|
331
|
+
// PLAN-10 P-2: the page side of the graph page's "Live data"
|
|
332
|
+
const GRAPH_CLIENT_ID = '\0' + GRAPH_CLIENT
|
|
300
333
|
// Register the runtime checks with the core that loaded last (see the Vike wrapper)
|
|
301
334
|
const REINSTALL_IMPORT = `import { installChecks as __sygnalInstallChecks } from 'sygnal/diagnostics';`
|
|
302
335
|
const REINSTALL_CALL = 'try { __sygnalInstallChecks() } catch (e) { console.warn(e) }\n'
|
|
@@ -313,7 +346,7 @@ export default function sygnal(options: SygnalPluginOptions = {}) {
|
|
|
313
346
|
// Files whose `import ... from 'sygnal'` resolves to the run() wrapper
|
|
314
347
|
const runtimeImporters = new Set<string>()
|
|
315
348
|
// Statements run before the app starts: dev flag (+ strict flag)
|
|
316
|
-
const flags = DEV_FLAG + (diagnostics.strict ? STRICT_FLAG : '')
|
|
349
|
+
const flags = DEV_FLAG + (diagnostics.strict ? STRICT_FLAG : '') + (diagnostics.timingAudit ? AUDIT_FLAG : '')
|
|
317
350
|
const devImports = `import 'sygnal/diagnostics';import '${DEV_CLIENT}';`
|
|
318
351
|
// D77: the DevTools bridge, dev only. First, so the diagnostics entry finds it.
|
|
319
352
|
// E-1: the MCP endpoint needs the bridge, so `mcp` turns it on
|
|
@@ -321,11 +354,17 @@ export default function sygnal(options: SygnalPluginOptions = {}) {
|
|
|
321
354
|
const mcpOptions: McpPluginOptions = typeof options.mcp === 'object' && options.mcp ? options.mcp : {}
|
|
322
355
|
const devtoolsOn = options.devtools !== false || mcpOn
|
|
323
356
|
const redux = typeof options.devtools == 'object' && !!options.devtools?.redux
|
|
324
|
-
|
|
357
|
+
// PLAN-10 W-2 / P-2 (D543, D544): the graph page, the component stamps and (with DevTools and a
|
|
358
|
+
// sygnal-check that has the page, decided in config()) the app side of its "Live data"
|
|
359
|
+
const graphOn = options.graph !== false
|
|
360
|
+
let graphClient = false
|
|
361
|
+
let engine: Promise<LiveEngine | null> | null = null
|
|
362
|
+
const devtoolsBase = (!devtoolsOn ? ''
|
|
325
363
|
: redux ? `import { connectReduxDevtools as __sygnalReduxDevtools } from 'sygnal/devtools';__sygnalReduxDevtools();`
|
|
326
364
|
: `import 'sygnal/devtools';`) + (mcpOn ? `import '${MCP_CLIENT}';` : '')
|
|
365
|
+
let devtoolsImport = devtoolsBase
|
|
327
366
|
// What a dev entry gets: DevTools, then (unless diagnostics are 'off') flags + checks
|
|
328
|
-
|
|
367
|
+
let devSnippet = devtoolsImport + (devOn ? flags + devImports : '')
|
|
329
368
|
const devInject = devOn || devtoolsOn
|
|
330
369
|
let astroClient: Set<string> | undefined
|
|
331
370
|
|
|
@@ -336,6 +375,10 @@ export default function sygnal(options: SygnalPluginOptions = {}) {
|
|
|
336
375
|
isServe = env.command === 'serve'
|
|
337
376
|
isVitest = !!nodeProcess?.env?.VITEST
|
|
338
377
|
root = config?.root ? path.resolve(config.root) : nodeProcess.cwd()
|
|
378
|
+
// P-2: the graph page's app side, in the dev server only, when the page can exist
|
|
379
|
+
graphClient = isServe && !isVitest && graphOn && devtoolsOn && graphAvailable(root)
|
|
380
|
+
devtoolsImport = devtoolsBase + (graphClient ? `import '${GRAPH_CLIENT}';` : '')
|
|
381
|
+
devSnippet = devtoolsImport + (devOn ? flags + devImports : '')
|
|
339
382
|
|
|
340
383
|
const result: any = {
|
|
341
384
|
// Ensure sygnal is bundled for SSR rather than externalized.
|
|
@@ -440,6 +483,7 @@ export default function sygnal(options: SygnalPluginOptions = {}) {
|
|
|
440
483
|
if (!isServe || isVitest) return null
|
|
441
484
|
if (source === DEV_CLIENT) return DEV_CLIENT_ID
|
|
442
485
|
if (source === MCP_CLIENT && mcpOn) return MCP_CLIENT_ID
|
|
486
|
+
if (source === GRAPH_CLIENT && graphClient) return GRAPH_CLIENT_ID
|
|
443
487
|
if (source === 'sygnal' && importer && wrapRun && runtimeImporters.has(cleanId(importer))) {
|
|
444
488
|
return RUNTIME_ID
|
|
445
489
|
}
|
|
@@ -459,6 +503,7 @@ export default function sygnal(options: SygnalPluginOptions = {}) {
|
|
|
459
503
|
load(id: string) {
|
|
460
504
|
if (id === DEV_CLIENT_ID) return devClientModule()
|
|
461
505
|
if (id === MCP_CLIENT_ID && mcpOn) return mcpClientModule(mcpOptions)
|
|
506
|
+
if (id === GRAPH_CLIENT_ID && graphClient) return graphClientModule()
|
|
462
507
|
if (id === RUNTIME_ID) return runtimeModule(diagnostics)
|
|
463
508
|
// The checks register again once the real entry has loaded: a
|
|
464
509
|
// pre-bundled copy of it can carry its own copy of the Sygnal core
|
|
@@ -476,14 +521,25 @@ export default function sygnal(options: SygnalPluginOptions = {}) {
|
|
|
476
521
|
if (mcpOn && server?.middlewares) {
|
|
477
522
|
attachMcp(server, root, mcpOptions, sygnalVersion(root))
|
|
478
523
|
}
|
|
479
|
-
|
|
524
|
+
const checkOn = options.check !== false
|
|
525
|
+
if (!checkOn && !graphOn) return
|
|
480
526
|
const checkOptions = typeof options.check === 'object' && options.check ? options.check : {}
|
|
481
|
-
// Deferred: the dev server doesn't wait for the first check
|
|
482
527
|
const checkDefaults = { strict: diagnostics.strict, ignore: diagnostics.ignore }
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
528
|
+
// Deferred: the dev server doesn't wait for the first check. `engine`: the live graph
|
|
529
|
+
// project (W-2), null without a sygnal-check that has it
|
|
530
|
+
engine = new Promise<LiveEngine | null>((resolve) => {
|
|
531
|
+
setTimeout(() => {
|
|
532
|
+
startDev(server, root, checkOptions, checkDefaults, options.check !== undefined, checkOn, graphOn).then(resolve, (err) => {
|
|
533
|
+
server?.config?.logger?.warn?.(`[sygnal] sygnal-check could not start: ${err?.message || err}`)
|
|
534
|
+
resolve(null)
|
|
535
|
+
})
|
|
536
|
+
}, 0)
|
|
537
|
+
})
|
|
538
|
+
// W-2 (D543): the page and its stream, behind the MCP endpoint's guard; its URL in the banner
|
|
539
|
+
if (graphOn && server?.middlewares) {
|
|
540
|
+
server.middlewares.use(graphMiddleware(server, root, engine))
|
|
541
|
+
printGraphUrl(server, graphAvailable(root))
|
|
542
|
+
}
|
|
487
543
|
},
|
|
488
544
|
|
|
489
545
|
transform(this: any, code: string, id: string, opts?: { ssr?: boolean }) {
|
|
@@ -493,7 +549,10 @@ export default function sygnal(options: SygnalPluginOptions = {}) {
|
|
|
493
549
|
? checkPeers(this, adapters, id).then(() => devTransform.call(this, code, id, opts))
|
|
494
550
|
: devTransform.call(this, code, id, opts)
|
|
495
551
|
// 11. the words SYG103 may take as conditionally rendered markup (dev server, diagnostics on)
|
|
496
|
-
const
|
|
552
|
+
const words = isServe && !isVitest && devOn && !opts?.ssr ? viewTokensTail(this, code, id) : null
|
|
553
|
+
// 12. P-2 (D544): the static keys of the module's components (dev server, graph on)
|
|
554
|
+
const stamps = engine && isServe && !isVitest && !opts?.ssr ? componentStampTail(engine, id) : null
|
|
555
|
+
const tokens = words && stamps ? Promise.all([words, stamps]).then(([a, b]) => a + b) : words || stamps
|
|
497
556
|
if (!tokens) return result
|
|
498
557
|
return Promise.all([result, tokens]).then(([r, tail]: [any, string]) => {
|
|
499
558
|
if (!tail) return r
|
|
@@ -587,6 +646,19 @@ export default function sygnal(options: SygnalPluginOptions = {}) {
|
|
|
587
646
|
}
|
|
588
647
|
}
|
|
589
648
|
|
|
649
|
+
// 12. PLAN-10 P-2 (D544): a module of the scanned files gets, in dev, a tail that sets each of its
|
|
650
|
+
// components' static key (sygnal-check's D521 'file#name', from the live graph engine's last build,
|
|
651
|
+
// built again first when the module's text changed) as a non-enumerable `__sygnalKey` on the
|
|
652
|
+
// function; the runtime inspect() reports it as `key`, and the graph page joins instances to cards.
|
|
653
|
+
function componentStampTail(engine: Promise<LiveEngine | null>, id: string): Promise<string> | null {
|
|
654
|
+
const file = cleanId(id)
|
|
655
|
+
if (!/\.[cm]?[jt]sx?$/.test(file) || id.includes('node_modules') || id.startsWith('\0')) return null
|
|
656
|
+
return engine.then((e) => {
|
|
657
|
+
if (!e) return ''
|
|
658
|
+
try { return stampTail(e.stampsFor(file) || []) } catch (_) { return '' }
|
|
659
|
+
})
|
|
660
|
+
}
|
|
661
|
+
|
|
590
662
|
// 11. SYG103 (the dev runtime check of an intent selector that never matched) lets a selector
|
|
591
663
|
// through as conditionally rendered when its words appear in the component's view; the runtime
|
|
592
664
|
// can read only the view function's own source. A component module (one that sets `X.intent` or
|
|
@@ -721,7 +793,7 @@ function viteMajor(ctx: any, root: string): number {
|
|
|
721
793
|
return Infinity
|
|
722
794
|
}
|
|
723
795
|
|
|
724
|
-
interface NormalizedDiagnostics { mode: DiagnosticsMode, strict: boolean, ignore: string[] }
|
|
796
|
+
interface NormalizedDiagnostics { mode: DiagnosticsMode, strict: boolean, timingAudit: boolean, ignore: string[] }
|
|
725
797
|
|
|
726
798
|
const MODES: DiagnosticsMode[] = ['off', 'collect', 'warn', 'error']
|
|
727
799
|
|
|
@@ -731,7 +803,7 @@ function normalizeDiagnostics(value: SygnalPluginOptions['diagnostics']): Normal
|
|
|
731
803
|
if (!MODES.includes(mode)) {
|
|
732
804
|
throw new Error(`[vite-plugin-sygnal] diagnostics mode must be one of ${MODES.map(m => `'${m}'`).join(', ')}; got ${JSON.stringify(mode)}`)
|
|
733
805
|
}
|
|
734
|
-
return { mode, strict: !!o.strict, ignore: Array.isArray(o.ignore) ? o.ignore.map(String) : [] }
|
|
806
|
+
return { mode, strict: !!o.strict, timingAudit: !!o.timingAudit, ignore: Array.isArray(o.ignore) ? o.ignore.map(String) : [] }
|
|
735
807
|
}
|
|
736
808
|
|
|
737
809
|
/** Module id without a query or hash. */
|
|
@@ -899,16 +971,41 @@ function checkInclude(root: string, include: string[] | undefined, notice: (m: s
|
|
|
899
971
|
return ['.']
|
|
900
972
|
}
|
|
901
973
|
|
|
902
|
-
/**
|
|
903
|
-
|
|
904
|
-
|
|
974
|
+
/**
|
|
975
|
+
* Load sygnal-check and start what dev uses it for: the checker (`check`, reported to the terminal
|
|
976
|
+
* and the browser) and the live graph engine (`graph`, PLAN-10 W-2: the page, its stream and the
|
|
977
|
+
* component stamps). With an engine, both come from one build. Resolves to the engine, or null.
|
|
978
|
+
*/
|
|
979
|
+
async function startDev(server: any, root: string, opts: CheckPluginOptions,
|
|
980
|
+
defaults: { strict: boolean, ignore: string[] }, explicit: boolean, checkOn: boolean, graphOn: boolean): Promise<LiveEngine | null> {
|
|
905
981
|
const mod = await loadSygnalCheck(root)
|
|
906
982
|
const logger = server?.config?.logger || console
|
|
907
983
|
if (!mod || typeof mod.check !== 'function') {
|
|
908
|
-
if (explicit) logger.info('[sygnal] sygnal-check is not installed, so static checks are off (npm i -D sygnal-check)')
|
|
909
|
-
return
|
|
984
|
+
if (explicit && checkOn) logger.info('[sygnal] sygnal-check is not installed, so static checks are off (npm i -D sygnal-check)')
|
|
985
|
+
return null
|
|
910
986
|
}
|
|
911
|
-
const
|
|
987
|
+
const setup = checkSetup(mod, root, opts, defaults, (m: string) => logger.info(m))
|
|
988
|
+
// PLAN-10 W-2: a sygnal-check with the live project and the page (feature-detected, so an older
|
|
989
|
+
// one keeps the checker as it was)
|
|
990
|
+
const engine = graphOn && setup.configAware && typeof mod.createLiveProject === 'function' && typeof mod.graphHtml === 'function'
|
|
991
|
+
? createEngine(mod, root, {
|
|
992
|
+
paths: setup.include, strict: setup.checkOptions.strict, a11y: setup.checkOptions.a11y,
|
|
993
|
+
ignore: setup.checkOptions.ignore, defaults: setup.checkOptions.defaults,
|
|
994
|
+
}, logger, true)
|
|
995
|
+
: null
|
|
996
|
+
if (checkOn) startChecker(server, root, mod, setup, opts, engine)
|
|
997
|
+
if (engine) {
|
|
998
|
+
if (!engine.last) engine.build()
|
|
999
|
+
engine.watch(server)
|
|
1000
|
+
}
|
|
1001
|
+
return engine
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
interface CheckSetup { include: string[]; checkOptions: any; configAware: boolean }
|
|
1005
|
+
|
|
1006
|
+
/** What sygnal-check checks, with which options (see the `check` option). */
|
|
1007
|
+
function checkSetup(mod: any, root: string, opts: CheckPluginOptions,
|
|
1008
|
+
defaults: { strict: boolean, ignore: string[] }, notice: (m: string) => void): CheckSetup {
|
|
912
1009
|
// PLAN-7 C-5 (D299): a sygnal-check with config support resolves the settings itself:
|
|
913
1010
|
// explicit check.* options > its config file > the `diagnostics` defaults passed as `defaults`.
|
|
914
1011
|
// An older one gets exactly what it got before.
|
|
@@ -940,6 +1037,17 @@ async function startChecker(server: any, root: string, opts: CheckPluginOptions,
|
|
|
940
1037
|
a11y: opts.a11y === 'error' ? 'error' : 'warn',
|
|
941
1038
|
}
|
|
942
1039
|
}
|
|
1040
|
+
return { include, checkOptions, configAware }
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Run sygnal-check now and after source changes; report to the terminal and the browser. With the
|
|
1045
|
+
* live graph engine (W-2) the findings come from its builds (one build for the page and the
|
|
1046
|
+
* checker); else from check() on each change.
|
|
1047
|
+
*/
|
|
1048
|
+
function startChecker(server: any, root: string, mod: any, setup: CheckSetup, opts: CheckPluginOptions, engine: LiveEngine | null) {
|
|
1049
|
+
const logger = server?.config?.logger || console
|
|
1050
|
+
const { include, checkOptions, configAware } = setup
|
|
943
1051
|
const a11y = checkOptions.a11y
|
|
944
1052
|
// Only error-severity findings open Vite's overlay: while an overlay is open
|
|
945
1053
|
// Vite's client reloads the page on the first HMR update, so warnings stay
|
|
@@ -966,13 +1074,16 @@ async function startChecker(server: any, root: string, opts: CheckPluginOptions,
|
|
|
966
1074
|
} catch (_) {}
|
|
967
1075
|
}
|
|
968
1076
|
|
|
969
|
-
const run = (initial = false) => {
|
|
1077
|
+
const run = (initial = false, built?: any[]) => {
|
|
970
1078
|
let diags: any[]
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1079
|
+
if (built) diags = built
|
|
1080
|
+
else {
|
|
1081
|
+
try {
|
|
1082
|
+
diags = mod.check(include, checkOptions)
|
|
1083
|
+
} catch (err: any) {
|
|
1084
|
+
logger.warn(`[sygnal] sygnal-check failed: ${err?.message || err}`, { timestamp: true })
|
|
1085
|
+
return
|
|
1086
|
+
}
|
|
976
1087
|
}
|
|
977
1088
|
// A sygnal-check from before D144 reports SYG7xx as errors under strict: keep them warnings
|
|
978
1089
|
// (one with config support postdates D144 and resolves a11y itself)
|
|
@@ -1011,7 +1122,12 @@ async function startChecker(server: any, root: string, opts: CheckPluginOptions,
|
|
|
1011
1122
|
if (!initial) broadcast(changed)
|
|
1012
1123
|
}
|
|
1013
1124
|
|
|
1014
|
-
|
|
1125
|
+
if (engine) {
|
|
1126
|
+
// every build of the engine: the first on start, then after changes (its own watcher)
|
|
1127
|
+
let first = !engine.last
|
|
1128
|
+
engine.onBuild((b) => { const initial = first; first = false; run(initial, b.diagnostics || []) })
|
|
1129
|
+
if (engine.last) run(true, engine.last.diagnostics || [])
|
|
1130
|
+
} else run(true)
|
|
1015
1131
|
// A page that loads (or reloads) asks for the current findings: answer
|
|
1016
1132
|
// that client only, never every client.
|
|
1017
1133
|
try {
|
|
@@ -1023,6 +1139,7 @@ async function startChecker(server: any, root: string, opts: CheckPluginOptions,
|
|
|
1023
1139
|
})
|
|
1024
1140
|
} catch (_) {}
|
|
1025
1141
|
|
|
1142
|
+
if (engine) return
|
|
1026
1143
|
let timer: any
|
|
1027
1144
|
const onChange = (file: string) => {
|
|
1028
1145
|
if (file.split(/[\\/]/).includes('node_modules')) return
|
|
@@ -1041,6 +1158,8 @@ async function startChecker(server: any, root: string, opts: CheckPluginOptions,
|
|
|
1041
1158
|
|
|
1042
1159
|
const DEV_FLAG = 'if (globalThis.__SYGNAL_DEV__ === undefined) globalThis.__SYGNAL_DEV__ = true;'
|
|
1043
1160
|
const STRICT_FLAG = 'if (globalThis.__SYGNAL_STRICT__ === undefined) globalThis.__SYGNAL_STRICT__ = true;'
|
|
1161
|
+
// 7.0: the opt-in timing audit (SYG257, 'sygnal/diagnostics' checks/timingAudit.ts)
|
|
1162
|
+
const AUDIT_FLAG = 'if (globalThis.__SYGNAL_TIMING_AUDIT__ === undefined) globalThis.__SYGNAL_TIMING_AUDIT__ = true;'
|
|
1044
1163
|
|
|
1045
1164
|
// A top-level (column 0) run(Component, ...) statement: bare, or declaring a
|
|
1046
1165
|
// new binding. Assignments to existing variables, nested calls and indented
|