@@ -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 PluginOption } 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 )
@@ -165,14 +165,223 @@ const terminalWebSocketProxyPlugin = (apiTarget: string): PluginOption => ({
165165 }
166166} )
167167
168+ type VitePluginConfig = Omit < UserConfig , "plugins" >
169+ type ViteConfigHook = HookHandler < NonNullable < Plugin [ "config" ] > >
170+ type ViteConfigObjectHook = Exclude < NonNullable < Plugin [ "config" ] > , ViteConfigHook >
171+ type ViteConfigHookResult = ReturnType < ViteConfigHook >
172+
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
195+ const removeDeprecatedOptimizeDepsOptions = (
196+ optimizeDeps : UserConfig [ "optimizeDeps" ]
197+ ) : UserConfig [ "optimizeDeps" ] => {
198+ if ( optimizeDeps === undefined ) {
199+ return undefined
200+ }
201+
202+ const { esbuildOptions : _esbuildOptions , ...remainingOptions } = optimizeDeps
203+ return remainingOptions
204+ }
205+
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
228+ const removeDeprecatedGridlandOptions = (
229+ config : VitePluginConfig | null | void
230+ ) : VitePluginConfig | null | void => {
231+ if ( config === undefined || config === null ) {
232+ return config
233+ }
234+
235+ const { esbuild : _esbuild , optimizeDeps, ...remainingConfig } = config
236+ const sanitizedOptimizeDeps = removeDeprecatedOptimizeDepsOptions ( optimizeDeps )
237+ return sanitizedOptimizeDeps === undefined
238+ ? remainingConfig
239+ : {
240+ ...remainingConfig ,
241+ optimizeDeps : sanitizedOptimizeDeps
242+ }
243+ }
244+
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
267+ const isVitePlugin = ( plugin : PluginOption ) : plugin is Plugin =>
268+ typeof plugin === "object" && plugin !== null && ! Array . isArray ( plugin ) && "name" in plugin
269+
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
345+ const gridlandWebPluginWithoutDeprecatedOptions = ( ) : ReadonlyArray < PluginOption > =>
346+ gridlandWebPlugin ( ) . map ( ( plugin ) => {
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 ) ) {
362+ return plugin
363+ }
364+
365+ const resolveGridlandConfig = gridlandConfig . handler
366+ return {
367+ ...plugin ,
368+ config : {
369+ ...gridlandConfig ,
370+ handler ( config , env ) {
371+ return sanitizeGridlandConfigResult ( resolveGridlandConfig . call ( this , config , env ) )
372+ }
373+ }
374+ }
375+ } )
376+
168377export default defineConfig ( ( { mode } ) => {
169378 const env = loadEnv ( mode , __dirname , "" )
170379 const apiTarget = env [ "DOCKER_GIT_API_URL" ] ?. trim ( ) || defaultApiTarget
171380
172381 return {
173382 plugins : [
174383 terminalWebSocketProxyPlugin ( apiTarget ) ,
175- ...gridlandWebPlugin ( ) ,
384+ ...gridlandWebPluginWithoutDeprecatedOptions ( ) ,
176385 react ( )
177386 ] ,
178387 publicDir : false ,
@@ -211,14 +420,12 @@ export default defineConfig(({ mode }) => {
211420 build : {
212421 target : "esnext" ,
213422 outDir : "dist-web" ,
214- sourcemap : true
215- } ,
216- esbuild : {
217- target : "esnext"
218- } ,
219- optimizeDeps : {
220- esbuildOptions : {
221- target : "esnext"
423+ sourcemap : true ,
424+ chunkSizeWarningLimit : 1200 ,
425+ rolldownOptions : {
426+ checks : {
427+ invalidAnnotation : false
428+ }
222429 }
223430 }
224431 }
0 commit comments