@@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url"
55
66import { gridlandWebPlugin } from "@gridland/web/vite-plugin"
77import react from "@vitejs/plugin-react"
8- import { defineConfig , loadEnv , type Plugin , type PluginOption , type UserConfig } from "vite"
8+ import { defineConfig , loadEnv , type HookHandler , type Plugin , type PluginOption , type UserConfig } from "vite"
99import { type RawData , WebSocket , WebSocketServer } from "ws"
1010
1111const __filename = fileURLToPath ( import . meta. url )
@@ -166,7 +166,32 @@ const terminalWebSocketProxyPlugin = (apiTarget: string): PluginOption => ({
166166} )
167167
168168type VitePluginConfig = Omit < UserConfig , "plugins" >
169+ type ViteConfigHook = HookHandler < NonNullable < Plugin [ "config" ] > >
170+ type ViteConfigObjectHook = Exclude < NonNullable < Plugin [ "config" ] > , ViteConfigHook >
171+ type ViteConfigHookResult = ReturnType < ViteConfigHook >
169172
173+ /**
174+ * Removes the deprecated dependency optimizer esbuild bridge from optional Vite optimizeDeps config.
175+ *
176+ * @param optimizeDeps - Optional Vite dependency optimizer config emitted by a plugin.
177+ * @returns `undefined` when no config exists; otherwise a shallow copy without `esbuildOptions`.
178+ * @pure true
179+ * @precondition `optimizeDeps` is either undefined or a Vite optimizeDeps object.
180+ * @postcondition The result is undefined iff the input is undefined; otherwise `esbuildOptions` is absent.
181+ * @invariant Every non-`esbuildOptions` own field is preserved by key and value.
182+ * @complexity O(k) time / O(k) space, where k is the number of own optimizeDeps fields.
183+ * @throws Never.
184+ */
185+ // CHANGE: Strip only the deprecated optimizeDeps.esbuildOptions field.
186+ // WHY: Vite 8 warns on that bridge while all other optimizer settings remain valid input.
187+ // QUOTE(ТЗ): "Что бы оно не писалось"
188+ // REF: PR #356 review 4388758572
189+ // SOURCE: https://github.com/ProverCoderAI/docker-git/pull/356#pullrequestreview-4388758572
190+ // FORMAT THEOREM: ∀o ∈ OptimizeDeps: strip(o) = o \ {esbuildOptions}
191+ // PURITY: CORE
192+ // EFFECT: none
193+ // INVARIANT: ∀key ≠ esbuildOptions: result[key] = optimizeDeps[key]
194+ // COMPLEXITY: O(k) time / O(k) space
170195const removeDeprecatedOptimizeDepsOptions = (
171196 optimizeDeps : UserConfig [ "optimizeDeps" ]
172197) : UserConfig [ "optimizeDeps" ] => {
@@ -178,6 +203,28 @@ const removeDeprecatedOptimizeDepsOptions = (
178203 return remainingOptions
179204}
180205
206+ /**
207+ * Removes deprecated top-level and nested Gridland Vite config options from an optional plugin config.
208+ *
209+ * @param config - Optional Vite config fragment returned by the Gridland aliases plugin.
210+ * @returns The original nullish value, or a shallow config copy without deprecated esbuild fields.
211+ * @pure true
212+ * @precondition `config` is nullish or a Vite plugin config fragment without a `plugins` field.
213+ * @postcondition Returned object has no top-level `esbuild`; nested `optimizeDeps.esbuildOptions` is absent.
214+ * @invariant All non-deprecated config fields are preserved by key and value.
215+ * @complexity O(k + n) time / O(k + n) space, where k is config fields and n is optimizeDeps fields.
216+ * @throws Never.
217+ */
218+ // CHANGE: Normalize Gridland config fragments before Vite consumes them.
219+ // WHY: The deprecated fields are warning-only compatibility options, not required for web build semantics.
220+ // QUOTE(ТЗ): "Что бы оно не писалось"
221+ // REF: PR #356 review 4388758572
222+ // SOURCE: https://github.com/ProverCoderAI/docker-git/pull/356#pullrequestreview-4388758572
223+ // FORMAT THEOREM: ∀c ∈ Config: normalize(c).esbuild = undefined ∧ normalize(c).optimizeDeps.esbuildOptions = undefined
224+ // PURITY: CORE
225+ // EFFECT: none
226+ // INVARIANT: ∀key ∉ {esbuild,optimizeDeps.esbuildOptions}: normalize(c)[key] = c[key]
227+ // COMPLEXITY: O(k + n) time / O(k + n) space
181228const removeDeprecatedGridlandOptions = (
182229 config : VitePluginConfig | null | void
183230) : VitePluginConfig | null | void => {
@@ -195,23 +242,134 @@ const removeDeprecatedGridlandOptions = (
195242 }
196243}
197244
245+ /**
246+ * Tests whether a Vite plugin option is a concrete plugin object with a name.
247+ *
248+ * @param plugin - Vite plugin option produced by a plugin factory.
249+ * @returns True when the option is an object plugin; false for arrays, null, booleans, and functions.
250+ * @pure true
251+ * @precondition `plugin` is any value accepted by Vite as PluginOption.
252+ * @postcondition A true result narrows `plugin` to `Plugin` for property-safe access.
253+ * @invariant The predicate has no side effects and does not mutate the inspected value.
254+ * @complexity O(1) time / O(1) space.
255+ * @throws Never.
256+ */
257+ // CHANGE: Provide a pure predicate for concrete Vite plugin objects.
258+ // WHY: The wrapper must only inspect named object plugins and preserve every other plugin option shape.
259+ // QUOTE(ТЗ): "Что бы оно не писалось"
260+ // REF: PR #356 review 4388758572
261+ // SOURCE: https://github.com/ProverCoderAI/docker-git/pull/356#pullrequestreview-4388758572
262+ // FORMAT THEOREM: ∀p ∈ PluginOption: isVitePlugin(p) → "name" ∈ keys(p)
263+ // PURITY: CORE
264+ // EFFECT: none
265+ // INVARIANT: isVitePlugin(p) is a deterministic boolean predicate over p's runtime shape.
266+ // COMPLEXITY: O(1) time / O(1) space
198267const isVitePlugin = ( plugin : PluginOption ) : plugin is Plugin =>
199268 typeof plugin === "object" && plugin !== null && ! Array . isArray ( plugin ) && "name" in plugin
200269
270+ /**
271+ * Tests whether a Vite config hook is declared in object-hook form.
272+ *
273+ * @param hook - Concrete Vite config hook from a plugin.
274+ * @returns True when the hook has a callable `handler` property.
275+ * @pure true
276+ * @precondition `hook` is a non-null Vite config hook.
277+ * @postcondition A true result narrows `hook` to object-hook form.
278+ * @invariant The predicate does not call or mutate the hook.
279+ * @complexity O(1) time / O(1) space.
280+ * @throws Never.
281+ */
282+ // CHANGE: Recognize Vite object-hook config declarations.
283+ // WHY: Sanitization should be stable if Gridland changes from function hook to object hook.
284+ // QUOTE(ТЗ): "Что бы оно не писалось"
285+ // REF: PR #356 review 4388758572
286+ // SOURCE: https://github.com/ProverCoderAI/docker-git/pull/356#pullrequestreview-4388758572
287+ // FORMAT THEOREM: ∀h ∈ ConfigHook: isObjectHook(h) → callable(h.handler)
288+ // PURITY: CORE
289+ // EFFECT: none
290+ // INVARIANT: isViteConfigObjectHook(h) is deterministic over h's runtime shape.
291+ // COMPLEXITY: O(1) time / O(1) space
292+ const isViteConfigObjectHook = (
293+ hook : NonNullable < Plugin [ "config" ] >
294+ ) : hook is ViteConfigObjectHook =>
295+ typeof hook === "object" && hook !== null && "handler" in hook && typeof hook . handler === "function"
296+
297+ /**
298+ * Sanitizes either synchronous or asynchronous Gridland config hook output.
299+ *
300+ * @param result - Result returned by the original Gridland aliases config hook.
301+ * @returns The same sync/async shape with deprecated options removed from the resolved config.
302+ * @pure true
303+ * @precondition `result` is a valid Vite config hook result.
304+ * @postcondition Nullish results remain nullish; config objects are normalized after resolution.
305+ * @invariant Promise shape is preserved: Promise input yields Promise output; sync input yields sync output.
306+ * @complexity O(k + n) time / O(k + n) space after the hook result resolves.
307+ * @throws Never.
308+ */
309+ // CHANGE: Centralize sync and async Gridland config result normalization.
310+ // WHY: Function and object Vite hooks must share the same warning-suppression invariant.
311+ // QUOTE(ТЗ): "Что бы оно не писалось"
312+ // REF: PR #356 review 4388758572
313+ // SOURCE: https://github.com/ProverCoderAI/docker-git/pull/356#pullrequestreview-4388758572
314+ // FORMAT THEOREM: ∀r ∈ HookResult: sanitize(r) resolves to normalize(r)
315+ // PURITY: CORE
316+ // EFFECT: none
317+ // INVARIANT: Sync/async result shape is preserved while resolved config is normalized.
318+ // COMPLEXITY: O(k + n) time / O(k + n) space
319+ const sanitizeGridlandConfigResult = ( result : ViteConfigHookResult ) : ViteConfigHookResult =>
320+ result instanceof Promise
321+ ? result . then ( removeDeprecatedGridlandOptions )
322+ : removeDeprecatedGridlandOptions ( result )
323+
324+ /**
325+ * Produces Gridland web plugins with the aliases config hook wrapped to suppress deprecated Vite output.
326+ *
327+ * @returns Plugin options from `gridlandWebPlugin` with only `gridland-web-aliases` config sanitized.
328+ * @pure true
329+ * @precondition `gridlandWebPlugin` returns Vite plugin options.
330+ * @postcondition Non-object plugins and non-target plugins are preserved; target config output is normalized.
331+ * @invariant Plugin order and non-target plugin identity are preserved.
332+ * @complexity O(p) time / O(p) space, where p is the number of Gridland plugin options.
333+ * @throws Never.
334+ */
335+ // CHANGE: Wrap only the Gridland aliases plugin config hook.
336+ // WHY: The build warning source is localized to that plugin; unrelated plugins must retain their behavior.
337+ // QUOTE(ТЗ): "Что бы оно не писалось"
338+ // REF: PR #356 review 4388758572
339+ // SOURCE: https://github.com/ProverCoderAI/docker-git/pull/356#pullrequestreview-4388758572
340+ // FORMAT THEOREM: ∀p ≠ aliases: wrap(p) = p; aliases config output is normalized.
341+ // PURITY: CORE
342+ // EFFECT: none
343+ // INVARIANT: Plugin array length and order are unchanged.
344+ // COMPLEXITY: O(p) time / O(p) space
201345const gridlandWebPluginWithoutDeprecatedOptions = ( ) : ReadonlyArray < PluginOption > =>
202346 gridlandWebPlugin ( ) . map ( ( plugin ) => {
203- if ( ! isVitePlugin ( plugin ) || plugin . name !== "gridland-web-aliases" || typeof plugin . config !== "function" ) {
347+ if ( ! isVitePlugin ( plugin ) || plugin . name !== "gridland-web-aliases" || plugin . config === undefined ) {
348+ return plugin
349+ }
350+
351+ const gridlandConfig = plugin . config
352+ if ( typeof gridlandConfig === "function" ) {
353+ return {
354+ ...plugin ,
355+ config ( config , env ) {
356+ return sanitizeGridlandConfigResult ( gridlandConfig . call ( this , config , env ) )
357+ }
358+ }
359+ }
360+
361+ if ( ! isViteConfigObjectHook ( gridlandConfig ) ) {
204362 return plugin
205363 }
206364
207- const resolveGridlandConfig = plugin . config
365+ const resolveGridlandConfig = gridlandConfig . handler
208366 return {
209367 ...plugin ,
210- config ( config , env ) {
211- const result = resolveGridlandConfig . call ( this , config , env )
212- return result instanceof Promise
213- ? result . then ( removeDeprecatedGridlandOptions )
214- : removeDeprecatedGridlandOptions ( result )
368+ config : {
369+ ... gridlandConfig ,
370+ handler ( config , env ) {
371+ return sanitizeGridlandConfigResult ( resolveGridlandConfig . call ( this , config , env ) )
372+ }
215373 }
216374 }
217375 } )
0 commit comments