sygnal 6.3.0 → 6.4.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 +51 -0
- package/dist/astro/index.cjs.js +69 -3
- package/dist/astro/index.cjs.js.map +1 -1
- package/dist/astro/index.mjs +69 -3
- 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/diagnostics.cjs.js +47 -8
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +47 -8
- package/dist/diagnostics.esm.js.map +1 -1
- package/dist/guide/mcp-apps.md +59 -4
- package/dist/index.cjs.js +9 -3
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.esm.js +9 -3
- 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/vike/config/package.json +1 -1
- package/dist/vite/plugin.cjs.js +69 -3
- package/dist/vite/plugin.cjs.js.map +1 -1
- package/dist/vite/plugin.mjs +69 -3
- package/dist/vite/plugin.mjs.map +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/src/extra/diagnostics/checks/dom.ts +10 -2
- package/src/extra/diagnostics/checks/next.ts +17 -1
- package/src/extra/diagnostics/checks/persist.ts +4 -2
- package/src/extra/diagnostics/checks/wiring.ts +14 -6
- package/src/extra/diagnostics/codes.ts +5 -0
- package/src/extra/pwa.ts +3 -1
- package/src/extra/reducers.ts +10 -4
- package/src/index.d.ts +9 -2
- package/src/vite/plugin.ts +63 -2
package/llms.txt
CHANGED
|
@@ -79,7 +79,7 @@ SaveButton.intent = ({ DOM }) => ({ SAVE: DOM.click('.save') })
|
|
|
79
79
|
SaveButton.model = {
|
|
80
80
|
SAVE: {
|
|
81
81
|
STATE: (state) => ({ ...state, saving: true }),
|
|
82
|
-
EVENTS: event('DOC_SAVED', (state) => ({ id: state.id })), // or event('RESET'), event('MODE', 'dark')
|
|
82
|
+
EVENTS: event('DOC_SAVED', (state) => ({ id: state.id })), // or event('RESET'), event('MODE', 'dark'); return ABORT to send nothing
|
|
83
83
|
},
|
|
84
84
|
}
|
|
85
85
|
function Toast({ state }) { // anywhere in the tree: EVENTS is a global bus
|
package/package.json
CHANGED
|
@@ -25,8 +25,10 @@
|
|
|
25
25
|
* that matches in one Collection item is fine for all of them.
|
|
26
26
|
*
|
|
27
27
|
* SYG103 noise control (conditional rendering is normal): a selector whose
|
|
28
|
-
* class/id/tag tokens all appear in the component's view source
|
|
29
|
-
*
|
|
28
|
+
* class/id/tag tokens all appear in the component's view source (or in the
|
|
29
|
+
* words sygnal/vite registered for it: markup drawn by view helpers in its
|
|
30
|
+
* module and its relative imports) is assumed to be conditionally rendered
|
|
31
|
+
* and is not reported. A selector that does not
|
|
30
32
|
* appear in the view (typo, or an element rendered elsewhere) is reported as
|
|
31
33
|
* info on the first sweep, and escalated per the rule above.
|
|
32
34
|
*
|
|
@@ -71,12 +73,18 @@ onReset(() => {
|
|
|
71
73
|
|
|
72
74
|
const unref = (t: any) => { if (t && typeof t.unref === 'function') t.unref(); return t }
|
|
73
75
|
|
|
76
|
+
/**
|
|
77
|
+
* The view's source, plus the words sygnal/vite registered for the component (its module's and
|
|
78
|
+
* its relative imports' string literals: markup a view helper draws), when it did
|
|
79
|
+
*/
|
|
74
80
|
function viewSource(component: any): string {
|
|
75
81
|
const view = component && component.view
|
|
76
82
|
if (typeof view !== 'function') return ''
|
|
77
83
|
let src = viewTokens.get(view)
|
|
78
84
|
if (src === undefined) {
|
|
79
85
|
try { src = Function.prototype.toString.call(view) } catch (_) { src = '' }
|
|
86
|
+
const registered = (globalThis as any).__SYGNAL_VIEW_TOKENS__?.get?.(view)
|
|
87
|
+
if (typeof registered == 'string') src += '\n' + registered
|
|
80
88
|
viewTokens.set(view, src as string)
|
|
81
89
|
}
|
|
82
90
|
return src as string
|
|
@@ -66,6 +66,18 @@ function removed(component: any, name: string, anchor: string, detail?: string):
|
|
|
66
66
|
})
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/** SYG427: CHILD.select(x) with no component (an import of a name the module doesn't export) */
|
|
70
|
+
function notComponent(f: any, x: any): void {
|
|
71
|
+
if (!once(`SYG427:${f.name}`)) return
|
|
72
|
+
const got = x === null ? 'null' : typeof x == 'object' ? 'an object' : String(x)
|
|
73
|
+
devReport('SYG427', {
|
|
74
|
+
component: f.name,
|
|
75
|
+
message: `${f.name}'s intent calls CHILD.select(${got}): that is not a component, so it never emits`,
|
|
76
|
+
fix: "Pass the child component itself, and check its import: a name its module doesn't export is undefined (Vitest runs on; the browser fails to link the module). Export it: export function Child() { … }",
|
|
77
|
+
data: {got},
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
69
81
|
/** the removed forms a definition shows (statics, view arity, model keys), once per function */
|
|
70
82
|
let seenDefs = new WeakSet<object>()
|
|
71
83
|
// G-332: resetChecks() forgets them (as the once() dedupe)
|
|
@@ -381,7 +393,11 @@ export function nextHooks(_api: any): any {
|
|
|
381
393
|
get(t: any, k: any) {
|
|
382
394
|
const v = t[k]
|
|
383
395
|
if (k !== 'CHILD' || !v || typeof v.select != 'function') return v
|
|
384
|
-
return {...v, select: (x: any, ...r: any[]) => {
|
|
396
|
+
return {...v, select: (x: any, ...r: any[]) => {
|
|
397
|
+
if (typeof x == 'string') removed(f, f.name, 'child-select-name', `CHILD.select('${x}')`)
|
|
398
|
+
else if (typeof x != 'function') notComponent(f, x)
|
|
399
|
+
return v.select(x, ...r)
|
|
400
|
+
}}
|
|
385
401
|
},
|
|
386
402
|
})
|
|
387
403
|
},
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* or restored. Under Vike's Layout/Wrapper shell (the `__vike` source) the message says
|
|
7
7
|
* persist isn't supported on Vike pages, layouts or wrappers yet.
|
|
8
8
|
* SYG223 (warn) — a `pick` / `omit` key of persist() that isn't a key of the component's
|
|
9
|
-
* `initialState` static (a
|
|
9
|
+
* `initialState` static or of its `uses` (a behavior's slice, such as a chat's), so it is
|
|
10
|
+
* a typo: that key is never saved, or never left out.
|
|
10
11
|
* Once per component name. Mechanism: onModel reads the static `view.persist` (the persist()
|
|
11
12
|
* value carries its options) and `view.initialState`.
|
|
12
13
|
*/
|
|
@@ -38,7 +39,8 @@ export const persistCheck: DiagnosticCheck = {
|
|
|
38
39
|
}
|
|
39
40
|
const o = p.options, init = view.initialState
|
|
40
41
|
if (!o || !init || typeof init != 'object') return
|
|
41
|
-
|
|
42
|
+
// a behavior's slice (`uses = { chat: chat() }`) is a state key too
|
|
43
|
+
const keys = [...Object.keys(init), ...Object.keys(view.uses || {})]
|
|
42
44
|
for (const opt of ['pick', 'omit']) {
|
|
43
45
|
for (const k of Array.isArray(o[opt]) ? o[opt] : []) {
|
|
44
46
|
if (keys.includes(k) || !once(`SYG223:${name}:${opt}:${k}`)) continue
|
|
@@ -36,12 +36,20 @@ const underTest = (c: any) => {
|
|
|
36
36
|
const dom = c?.sources?.[c.DOMSourceName || 'DOM']
|
|
37
37
|
return !!dom && typeof dom == 'object' && '_hub' in dom
|
|
38
38
|
}
|
|
39
|
-
// G-620: an LLM / chat sink gets the chat driver's fix,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
// G-620: the usual driver for a known sink name (an LLM / chat sink gets the chat driver's fix,
|
|
40
|
+
// SW the service worker's), the generic text for any other name
|
|
41
|
+
const DRIVER_FOR: Array<[RegExp, (sink: string) => string]> = [
|
|
42
|
+
[/^(LLM|CHAT|AI|ASSISTANT)$/, (s) => `run(App, { ${s}: makeChatDriver({ transport: openResponses({ baseURL: 'http://localhost:11434/v1', model: 'llama3.2' }) }) }) (from 'sygnal/ai'; uiMessageStream('/api/chat') for an AI SDK server)`],
|
|
43
|
+
[/^(SW|SERVICE_WORKER)$/, (s) => `run(App, { ${s}: makeServiceWorkerDriver('/sw.js', { enabled: import.meta.env.PROD }) }) (enabled: false keeps the driver, with no service worker, on a dev server)`],
|
|
44
|
+
[/^(HTTP|API|FETCH)$/, (s) => `run(App, { ${s}: makeFetchDriver() })`],
|
|
45
|
+
[/^(WS|SOCKET)$/, (s) => `run(App, { ${s}: makeSocketDriver() })`],
|
|
46
|
+
]
|
|
47
|
+
const driverFix = (sink: string) => {
|
|
48
|
+
const known = DRIVER_FOR.find(([re]) => re.test(sink))
|
|
49
|
+
return `Pass a driver named ${sink} to run(): ` +
|
|
50
|
+
(known ? known[1](sink) : `run(App, { ${sink}: makeFetchDriver() }) for HTTP, or driverFromAsync(fn) / your own driver`) +
|
|
51
|
+
`. Check the spelling against the drivers you pass`
|
|
52
|
+
}
|
|
45
53
|
|
|
46
54
|
export const wiringCheck: DiagnosticCheck = {
|
|
47
55
|
id: 'wiring',
|
|
@@ -278,6 +278,9 @@ export const CODE_TITLES: Record<string, string> = {
|
|
|
278
278
|
SYG425: 'isolatedState slice lacks initialState keys',
|
|
279
279
|
// PLAN-9 (D471): checks/next.ts; dev entry and sygnal-check
|
|
280
280
|
SYG426: 'Deprecated lowercase marker tag',
|
|
281
|
+
// checks/next.ts and sygnal-check: a component reference that is not a function (an import of
|
|
282
|
+
// a name the module doesn't export is undefined under Vitest)
|
|
283
|
+
SYG427: 'CHILD.select() of something that is not a component',
|
|
281
284
|
// PLAN-9 (D470): checks/next.ts; dev entry
|
|
282
285
|
SYG453: 'Two children with the same identity share one instance',
|
|
283
286
|
SYG601: "Retired in 6.0: Missing or invalid sources",
|
|
@@ -486,6 +489,8 @@ export const DEV_CODE_SEVERITY: Record<string, DiagnosticSeverity> = {
|
|
|
486
489
|
SYG425: 'warn',
|
|
487
490
|
// PLAN-9 (checks/next.ts): D471 a lowercase <collection> / <switchable> tag (deprecated in 6.3.0)
|
|
488
491
|
SYG426: 'warn',
|
|
492
|
+
// checks/next.ts: CHILD.select(undefined), usually a missing export
|
|
493
|
+
SYG427: 'error',
|
|
489
494
|
// PLAN-9 (checks/next.ts): D470 key identity, two children with one identity
|
|
490
495
|
SYG453: 'warn',
|
|
491
496
|
SYG612: 'error',
|
package/src/extra/pwa.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface ServiceWorkerCommand {
|
|
|
18
18
|
|
|
19
19
|
export interface ServiceWorkerOptions {
|
|
20
20
|
scope?: string;
|
|
21
|
+
/** false: register nothing, ignore commands, emit nothing (a dev server: `enabled: import.meta.env.PROD`) */
|
|
22
|
+
enabled?: boolean;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export interface InstallPrompt {
|
|
@@ -47,7 +49,7 @@ export function makeServiceWorkerDriver(
|
|
|
47
49
|
return function serviceWorkerDriver(sink$: Stream<ServiceWorkerCommand>): ServiceWorkerSource {
|
|
48
50
|
const events = new EventTarget();
|
|
49
51
|
|
|
50
|
-
if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {
|
|
52
|
+
if (options.enabled !== false && typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {
|
|
51
53
|
navigator.serviceWorker
|
|
52
54
|
.register(scriptUrl, {scope: options.scope})
|
|
53
55
|
.then((reg) => {
|
package/src/extra/reducers.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* shorthand factories for the most frequent reducer shapes.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import {ABORT, isAbort} from '../shared'
|
|
9
|
+
|
|
8
10
|
// ── set() ──────────────────────────────────────────────────────────
|
|
9
11
|
/**
|
|
10
12
|
* Create a reducer that merges a partial update into state.
|
|
@@ -40,13 +42,17 @@ export function toggle<S = any>(field: keyof S & string): (state: S) => S {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
// event(type, payload?) → EVENTS sink fn for the object form:
|
|
43
|
-
// `ACTION: { EVENTS: event('TYPE', (state, data) => payload) }` (static payloads allowed)
|
|
45
|
+
// `ACTION: { EVENTS: event('TYPE', (state, data) => payload) }` (static payloads allowed).
|
|
46
|
+
// A payload function that returns ABORT sends nothing, as ABORT does from any other sink.
|
|
44
47
|
export function event(
|
|
45
48
|
type: string,
|
|
46
49
|
payload?: any | ((state: any, data: any, next: Function, props: any) => any)
|
|
47
|
-
): (state: any, data: any, next: Function, props: any) => { type: string; data: any } {
|
|
50
|
+
): (state: any, data: any, next: Function, props: any) => { type: string; data: any } | typeof ABORT {
|
|
48
51
|
return typeof payload === 'function'
|
|
49
|
-
? (state, data, next, props) =>
|
|
52
|
+
? (state, data, next, props) => {
|
|
53
|
+
const d = payload(state, data, next, props)
|
|
54
|
+
return isAbort(d) ? ABORT : { type, data: d }
|
|
55
|
+
}
|
|
50
56
|
: () => ({ type, data: payload })
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -66,6 +72,6 @@ export function event(
|
|
|
66
72
|
export function emit(
|
|
67
73
|
type: string,
|
|
68
74
|
data?: any | ((state: any, actionData: any, next: Function, props: any) => any)
|
|
69
|
-
): { EVENTS: (state: any, actionData: any, next: Function, props: any) => { type: string; data: any } } {
|
|
75
|
+
): { EVENTS: (state: any, actionData: any, next: Function, props: any) => { type: string; data: any } | typeof ABORT } {
|
|
70
76
|
return { EVENTS: event(type, data) }
|
|
71
77
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -156,7 +156,7 @@ type EventNextFunction = (action: string, data?: any, delay?: number) => void
|
|
|
156
156
|
|
|
157
157
|
/** Payload function for `event()` / `emit()`: receives the reducer arguments, returns the event data. */
|
|
158
158
|
export type EventPayloadFunction<TYPE extends string = string, STATE = any, DATA = any> =
|
|
159
|
-
(state: STATE, data: DATA, next: EventNextFunction, props: any) => EventPayload<TYPE>
|
|
159
|
+
(state: STATE, data: DATA, next: EventNextFunction, props: any) => EventPayload<TYPE> | ABORT
|
|
160
160
|
|
|
161
161
|
/**
|
|
162
162
|
* Remaining arguments of `event()` / `emit()` when the payload is a static value.
|
|
@@ -1977,7 +1977,8 @@ export function emit<TYPE extends EventName>(
|
|
|
1977
1977
|
* }
|
|
1978
1978
|
*
|
|
1979
1979
|
* The payload is either a function `(state, data, next, props) => payload` or a static value:
|
|
1980
|
-
* `event('RESET')`, `event('SET_MODE', 'dark')`.
|
|
1980
|
+
* `event('RESET')`, `event('SET_MODE', 'dark')`. A payload function that returns `ABORT` sends
|
|
1981
|
+
* nothing: `event('SAVED', (state) => state.dirty ? { id: state.id } : ABORT)`.
|
|
1981
1982
|
*
|
|
1982
1983
|
* With a `SygnalEvents` registry, `type` must be a registered name and the payload must match
|
|
1983
1984
|
* its type. When used inside a model, the payload function's `state` and `data` parameters are
|
|
@@ -3005,6 +3006,12 @@ export interface ServiceWorkerCommand {
|
|
|
3005
3006
|
|
|
3006
3007
|
export interface ServiceWorkerOptions {
|
|
3007
3008
|
scope?: string;
|
|
3009
|
+
/**
|
|
3010
|
+
* `false`: the driver registers no service worker, ignores commands and emits nothing, so the
|
|
3011
|
+
* app keeps its `SW` wiring where a worker must not run: `enabled: import.meta.env.PROD`.
|
|
3012
|
+
* Default `true`.
|
|
3013
|
+
*/
|
|
3014
|
+
enabled?: boolean;
|
|
3008
3015
|
}
|
|
3009
3016
|
|
|
3010
3017
|
export function makeServiceWorkerDriver(
|
package/src/vite/plugin.ts
CHANGED
|
@@ -117,6 +117,12 @@
|
|
|
117
117
|
* /__sygnal/mcp (./mcp.ts: streamable HTTP, loopback / local Host / local Origin only)
|
|
118
118
|
* and the dev entries also import 'virtual:sygnal/mcp', which connects the page over the
|
|
119
119
|
* HMR channel (installMcpBridge from 'sygnal/devtools'). Never in `vite build` or Vitest.
|
|
120
|
+
* 11. SYG103 view words (dev server with diagnostics on; never `vite build`, SSR or Vitest): a
|
|
121
|
+
* module that sets `X.intent` or `X.uses` gets a tail registering, for each such X, the
|
|
122
|
+
* words of its string literals and of the relative modules it imports, in
|
|
123
|
+
* `globalThis.__SYGNAL_VIEW_TOKENS__`. The runtime SYG103 check then takes a selector drawn
|
|
124
|
+
* by a view helper (`{state.open && panel()}`) as conditionally rendered, as it does for
|
|
125
|
+
* markup in the view function itself (see viewTokensTail).
|
|
120
126
|
*
|
|
121
127
|
* Why not Vite's `define`? Vite's dependency optimizer does not apply user
|
|
122
128
|
* `define` replacements to pre-bundled dependencies (only process.env.NODE_ENV),
|
|
@@ -483,8 +489,17 @@ export default function sygnal(options: SygnalPluginOptions = {}) {
|
|
|
483
489
|
transform(this: any, code: string, id: string, opts?: { ssr?: boolean }) {
|
|
484
490
|
// SYG666 (PLAN-5 W-2): an adapter entry imported without its optional peers; every mode
|
|
485
491
|
const adapters = adapterImports(this, code, id)
|
|
486
|
-
|
|
487
|
-
|
|
492
|
+
const result = adapters
|
|
493
|
+
? checkPeers(this, adapters, id).then(() => devTransform.call(this, code, id, opts))
|
|
494
|
+
: devTransform.call(this, code, id, opts)
|
|
495
|
+
// 11. the words SYG103 may take as conditionally rendered markup (dev server, diagnostics on)
|
|
496
|
+
const tokens = isServe && !isVitest && devOn && !opts?.ssr ? viewTokensTail(this, code, id) : null
|
|
497
|
+
if (!tokens) return result
|
|
498
|
+
return Promise.all([result, tokens]).then(([r, tail]: [any, string]) => {
|
|
499
|
+
if (!tail) return r
|
|
500
|
+
if (r == null) return withSourcemap(code, [], tail, id)
|
|
501
|
+
return typeof r == 'string' ? r + tail : { ...r, code: r.code + tail }
|
|
502
|
+
})
|
|
488
503
|
},
|
|
489
504
|
}
|
|
490
505
|
|
|
@@ -572,6 +587,52 @@ export default function sygnal(options: SygnalPluginOptions = {}) {
|
|
|
572
587
|
}
|
|
573
588
|
}
|
|
574
589
|
|
|
590
|
+
// 11. SYG103 (the dev runtime check of an intent selector that never matched) lets a selector
|
|
591
|
+
// through as conditionally rendered when its words appear in the component's view; the runtime
|
|
592
|
+
// can read only the view function's own source. A component module (one that sets `X.intent` or
|
|
593
|
+
// `X.uses`) gets, in dev, a tail registering for each such component the words of its string
|
|
594
|
+
// literals and those of the relative modules it imports, where view helpers live
|
|
595
|
+
// (globalThis.__SYGNAL_VIEW_TOKENS__, a WeakMap read by checks/dom.ts).
|
|
596
|
+
const COMPONENT_WITH_SELECTORS_RE = /(?:^|[^\w$.])([A-Z][\w$]*)\.(?:intent|uses)\s*=(?!=)/g
|
|
597
|
+
const STRING_RE = /(['"])((?:\\.|(?!\1)[^\\\n])*)\1|`((?:\\[\s\S]|[^\\`])*)`/g
|
|
598
|
+
const RELATIVE_IMPORT_RE = /(?:\bfrom|\bimport)\s*\(?\s*(['"])(\.{1,2}\/[^'"]+)\1/g
|
|
599
|
+
|
|
600
|
+
// A string that is itself a selector ('.save', '#id', 'form.ask', '[data-x]') or a path / URL: its
|
|
601
|
+
// words aren't markup (the intent's own DOM.click('.svae') would otherwise vouch for its typo)
|
|
602
|
+
const NOT_MARKUP_RE = /(?:^|[\s>+~,(])[\w-]*[.#[][\w-]|^\s*(?:\/|[\w+-]+:\/\/)/
|
|
603
|
+
|
|
604
|
+
/** the class / id / tag-like words in a module's string and template literals (comments skipped) */
|
|
605
|
+
function stringWords(code: string, into: Set<string>): void {
|
|
606
|
+
for (const m of blankOut(code, false).matchAll(STRING_RE)) {
|
|
607
|
+
const text = m[2] ?? m[3] ?? ''
|
|
608
|
+
if (NOT_MARKUP_RE.test(text)) continue
|
|
609
|
+
for (const w of text.match(/[A-Za-z_][\w-]*/g) || []) into.add(w)
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function viewTokensTail(ctx: any, code: string, id: string): Promise<string> | null {
|
|
614
|
+
const file = cleanId(id)
|
|
615
|
+
if (!/\.[jt]sx?$/.test(file) || id.includes('node_modules') || id.startsWith('\0')) return null
|
|
616
|
+
const names = new Set<string>()
|
|
617
|
+
for (const m of blankOut(code, true).matchAll(COMPONENT_WITH_SELECTORS_RE)) names.add(m[1])
|
|
618
|
+
if (!names.size) return null
|
|
619
|
+
const words = new Set<string>()
|
|
620
|
+
stringWords(code, words)
|
|
621
|
+
const specs = new Set(Array.from(blankOut(code, false).matchAll(RELATIVE_IMPORT_RE), m => m[2]))
|
|
622
|
+
return Promise.all([...specs].map(async (spec) => {
|
|
623
|
+
try {
|
|
624
|
+
const r = await ctx.resolve(spec, id)
|
|
625
|
+
const dep = r && !r.external && cleanId(r.id)
|
|
626
|
+
if (!dep || !/\.[cm]?[jt]sx?$/.test(dep) || dep.includes('node_modules')) return
|
|
627
|
+
ctx.addWatchFile?.(dep)
|
|
628
|
+
stringWords(await fs.promises.readFile(dep, 'utf8'), words)
|
|
629
|
+
} catch (_) { /* unreadable: its words are just not added */ }
|
|
630
|
+
})).then(() => {
|
|
631
|
+
const comps = [...names].map(n => `typeof ${n} == 'function' && ${n}`).join(', ')
|
|
632
|
+
return `\n;(() => { try { const m = globalThis.__SYGNAL_VIEW_TOKENS__ ||= new WeakMap(), t = ${JSON.stringify([...words].join(' '))}; for (const c of [${comps}]) if (c) m.set(c, t) } catch (_) {} })();\n`
|
|
633
|
+
})
|
|
634
|
+
}
|
|
635
|
+
|
|
575
636
|
// PLAN-5 W-2 (D209): the adapter entries and the optional peer dependencies each one imports
|
|
576
637
|
// (the Zag packages pinned together: they depend on each other's exact versions)
|
|
577
638
|
const ZAG = '~1.45.0'
|