@cloudwerk/vite-plugin 0.6.3 → 0.6.6

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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Plugin } from 'vite';
2
- import { CloudwerkConfig, QueueManifest, ServiceManifest, RouteManifest, ScanResult } from '@cloudwerk/core/build';
2
+ import { CloudwerkConfig, QueueManifest, ServiceManifest, AuthManifest, RouteManifest, ScanResult } from '@cloudwerk/core/build';
3
3
 
4
4
  /**
5
5
  * @cloudwerk/vite-plugin - Types
@@ -185,6 +185,10 @@ interface GenerateServerEntryOptions {
185
185
  serviceManifest?: ServiceManifest | null;
186
186
  /** Asset manifest from Vite build for CSS injection */
187
187
  assetManifest?: AssetManifest | null;
188
+ /** Auth manifest if auth providers are configured */
189
+ authManifest?: AuthManifest | null;
190
+ /** CSS imports from layouts/pages (for dev mode injection) */
191
+ cssImports?: Map<string, CssImportInfo[]>;
188
192
  }
189
193
  /**
190
194
  * Generate the server entry module code.
package/dist/index.js CHANGED
@@ -19,7 +19,13 @@ import {
19
19
  scanServices,
20
20
  buildServiceManifest,
21
21
  SERVICES_DIR,
22
- SERVICE_FILE_NAME
22
+ SERVICE_FILE_NAME,
23
+ scanImages,
24
+ buildImageManifest,
25
+ IMAGES_DIR,
26
+ scanAuth,
27
+ buildAuthManifestWithModules,
28
+ AUTH_DIR
23
29
  } from "@cloudwerk/core/build";
24
30
 
25
31
  // src/types.ts
@@ -40,6 +46,7 @@ function generateServerEntry(manifest, scanResult, options, entryOptions) {
40
46
  const queueManifest = entryOptions?.queueManifest;
41
47
  const serviceManifest = entryOptions?.serviceManifest;
42
48
  const assetManifest = entryOptions?.assetManifest;
49
+ const authManifest = entryOptions?.authManifest;
43
50
  const imports = [];
44
51
  const pageRegistrations = [];
45
52
  const routeRegistrations = [];
@@ -58,6 +65,10 @@ function generateServerEntry(manifest, scanResult, options, entryOptions) {
58
65
  let middlewareIndex = 0;
59
66
  let errorIndex = 0;
60
67
  let notFoundIndex = 0;
68
+ const rootMiddleware = scanResult.middleware.find(
69
+ (m) => m.relativePath === "middleware.ts" || m.relativePath === "middleware.tsx"
70
+ );
71
+ let rootMiddlewareVarName = null;
61
72
  const ssgPageInfo = [];
62
73
  for (const err of scanResult.errors) {
63
74
  if (!importedModules.has(err.absolutePath)) {
@@ -89,6 +100,12 @@ function generateServerEntry(manifest, scanResult, options, entryOptions) {
89
100
  const varName = notFoundModules.get(nf.absolutePath);
90
101
  notFoundBoundaryMapEntries.push(` ['${normalizedDir}', ${varName}]`);
91
102
  }
103
+ if (rootMiddleware) {
104
+ rootMiddlewareVarName = `middleware_${middlewareIndex++}`;
105
+ middlewareImports.push(`import { middleware as ${rootMiddlewareVarName} } from '${rootMiddleware.absolutePath}'`);
106
+ middlewareModules.set(rootMiddleware.absolutePath, rootMiddlewareVarName);
107
+ importedModules.add(rootMiddleware.absolutePath);
108
+ }
92
109
  for (const route of manifest.routes) {
93
110
  for (const middlewarePath of route.middleware) {
94
111
  if (!importedModules.has(middlewarePath)) {
@@ -139,7 +156,17 @@ function generateServerEntry(manifest, scanResult, options, entryOptions) {
139
156
  }
140
157
  }
141
158
  const rendererName = options.renderer;
142
- const clientEntryPath = options.isProduction ? `${options.hydrationEndpoint}/client.js` : "/@id/__x00__virtual:cloudwerk/client-entry";
159
+ let clientEntryPath = "/@id/__x00__virtual:cloudwerk/client-entry";
160
+ if (options.isProduction && assetManifest) {
161
+ const clientEntry = assetManifest["virtual:cloudwerk/client-entry"];
162
+ if (clientEntry?.file) {
163
+ clientEntryPath = `/${clientEntry.file}`;
164
+ } else {
165
+ clientEntryPath = `${options.hydrationEndpoint}/client.js`;
166
+ }
167
+ } else if (options.isProduction) {
168
+ clientEntryPath = `${options.hydrationEndpoint}/client.js`;
169
+ }
143
170
  let cssLinksCode = "";
144
171
  if (options.isProduction && assetManifest) {
145
172
  const clientEntry = assetManifest["virtual:cloudwerk/client-entry"];
@@ -147,6 +174,17 @@ function generateServerEntry(manifest, scanResult, options, entryOptions) {
147
174
  const cssLinks = clientEntry.css.map((css) => `<link rel="stylesheet" href="/${css}" />`).join("");
148
175
  cssLinksCode = `const CSS_LINKS = '${cssLinks}'`;
149
176
  }
177
+ } else if (!options.isProduction && entryOptions?.cssImports) {
178
+ const allCss = /* @__PURE__ */ new Set();
179
+ for (const imports2 of entryOptions.cssImports.values()) {
180
+ for (const info of imports2) {
181
+ allCss.add(info.absolutePath);
182
+ }
183
+ }
184
+ if (allCss.size > 0) {
185
+ const cssLinks = Array.from(allCss).map((css) => `<link rel="stylesheet" href="/@fs${css}" />`).join("");
186
+ cssLinksCode = `const CSS_LINKS = '${cssLinks}'`;
187
+ }
150
188
  }
151
189
  if (!cssLinksCode) {
152
190
  cssLinksCode = `const CSS_LINKS = ''`;
@@ -160,7 +198,7 @@ function generateServerEntry(manifest, scanResult, options, entryOptions) {
160
198
  import { Hono } from 'hono'
161
199
  import { ssgParams } from 'hono/ssg'
162
200
  import { contextMiddleware, createHandlerAdapter, createMiddlewareAdapter, setRouteConfig, NotFoundError, RedirectError } from '@cloudwerk/core/runtime'
163
- import { setActiveRenderer } from '@cloudwerk/ui'
201
+ import { setActiveRenderer${rendererName === "react" ? ", initReactRenderer" : ""} } from '@cloudwerk/ui'
164
202
 
165
203
  // Page and Route Imports
166
204
  ${imports.join("\n")}
@@ -289,7 +327,7 @@ async function renderErrorPage(error, errorModule, layoutModules, layoutLoaderDa
289
327
  element = await Promise.resolve(Layout(layoutProps))
290
328
  }
291
329
 
292
- return renderWithHydration(element, 500)
330
+ return await renderWithHydration(element, 500)
293
331
  }
294
332
 
295
333
  /**
@@ -316,7 +354,7 @@ async function renderNotFoundPage(notFoundModule, layoutModules, layoutLoaderDat
316
354
  element = await Promise.resolve(Layout(layoutProps))
317
355
  }
318
356
 
319
- return renderWithHydration(element, 404)
357
+ return await renderWithHydration(element, 404)
320
358
  }
321
359
 
322
360
  // ============================================================================
@@ -390,7 +428,7 @@ function registerPage(app, pattern, pageModule, layoutModules, middlewareModules
390
428
  }
391
429
 
392
430
  // Render the page with hydration script injection
393
- return renderWithHydration(element)
431
+ return await renderWithHydration(element)
394
432
  } catch (error) {
395
433
  // Handle NotFoundError (check both instanceof and name for module duplication)
396
434
  if (error instanceof NotFoundError || error?.name === 'NotFoundError') {
@@ -422,9 +460,12 @@ function registerPage(app, pattern, pageModule, layoutModules, middlewareModules
422
460
  * - CSS links are injected before </head>
423
461
  * - Vite client (dev) and hydration script are injected before </body>
424
462
  */
425
- function renderWithHydration(element, status = 200) {
426
- // Hono JSX elements have toString() for synchronous rendering
427
- let html = '<!DOCTYPE html>' + String(element)
463
+ async function renderWithHydration(element, status = 200) {
464
+ // Render element to HTML string using the active renderer
465
+ ${rendererName === "react" ? `// React: use renderToString from react-dom/server
466
+ const { renderToString } = await import('react-dom/server')
467
+ let html = '<!DOCTYPE html>' + renderToString(element)` : `// Hono JSX elements have toString() for synchronous rendering
468
+ let html = '<!DOCTYPE html>' + String(element)`}
428
469
 
429
470
  // Inject CSS links before </head> if present
430
471
  if (CSS_LINKS) {
@@ -471,7 +512,7 @@ function registerRoute(app, pattern, routeModule, middlewareModules) {
471
512
  for (const method of HTTP_METHODS) {
472
513
  const handler = routeModule[method]
473
514
  if (handler && typeof handler === 'function') {
474
- const h = handler.length === 2 ? createHandlerAdapter(handler) : handler
515
+ const h = createHandlerAdapter(handler)
475
516
  switch (method) {
476
517
  case 'GET': app.get(pattern, h); break
477
518
  case 'POST': app.post(pattern, h); break
@@ -490,14 +531,18 @@ function registerRoute(app, pattern, routeModule, middlewareModules) {
490
531
  // ============================================================================
491
532
 
492
533
  // Initialize renderer
493
- setActiveRenderer('${rendererName}')
534
+ ${rendererName === "react" ? `await initReactRenderer()
535
+ ` : ""}setActiveRenderer('${rendererName}')
494
536
 
495
537
  // Create Hono app
496
538
  const app = new Hono({ strict: false })
497
539
 
498
540
  // Add context middleware
499
541
  app.use('*', contextMiddleware())
500
- ${options.isProduction ? `
542
+ ${rootMiddlewareVarName ? `
543
+ // Apply root middleware globally (for all routes including auth)
544
+ app.use('*', createMiddlewareAdapter(${rootMiddlewareVarName}))
545
+ ` : ""}${options.isProduction ? `
501
546
  // Serve static assets using Workers Static Assets binding (production only)
502
547
  app.use('*', async (c, next) => {
503
548
  // Check if ASSETS binding is available
@@ -518,9 +563,27 @@ app.use('*', async (c, next) => {
518
563
  // Try to serve the request as a static asset
519
564
  const response = await c.env.ASSETS.fetch(c.req.raw)
520
565
 
521
- // If asset found (not 404), return it
566
+ // If asset found (not 404), return it with cache headers
522
567
  if (response.status !== 404) {
523
- return response
568
+ const path = new URL(c.req.url).pathname
569
+
570
+ // Check if this is a hashed asset (Vite adds content hash to filename)
571
+ // Hashed assets are immutable and can be cached forever
572
+ const isHashedAsset = path.startsWith('/__cloudwerk/') ||
573
+ /-[a-zA-Z0-9]{8,}\\.(js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|webp|avif|ico)$/.test(path)
574
+
575
+ const cacheControl = isHashedAsset
576
+ ? 'public, max-age=31536000, immutable'
577
+ : 'public, max-age=3600'
578
+
579
+ return new Response(response.body, {
580
+ status: response.status,
581
+ statusText: response.statusText,
582
+ headers: {
583
+ ...Object.fromEntries(response.headers.entries()),
584
+ 'Cache-Control': cacheControl,
585
+ },
586
+ })
524
587
  }
525
588
 
526
589
  // Asset not found, continue to routes
@@ -531,6 +594,9 @@ app.use('*', async (c, next) => {
531
594
  ${pageRegistrations.join("\n")}
532
595
  ${routeRegistrations.join("\n")}
533
596
 
597
+ // Register auth routes
598
+ ${generateAuthRouteRegistrations(authManifest)}
599
+
534
600
  // SSG routes endpoint - returns all static routes for build-time generation
535
601
  app.get('/__ssg/routes', async (c) => {
536
602
  const routes = []
@@ -766,6 +832,242 @@ function generateServiceRegistration(serviceManifest) {
766
832
  }
767
833
  return lines.join("\n");
768
834
  }
835
+ function generateAuthRouteRegistrations(authManifest) {
836
+ if (!authManifest) {
837
+ return "";
838
+ }
839
+ const lines = [];
840
+ const imports = [];
841
+ const basePath = authManifest.config?.basePath || "/auth";
842
+ lines.push("");
843
+ lines.push("// ============================================================================");
844
+ lines.push("// Auth Route Registration");
845
+ lines.push("// ============================================================================");
846
+ lines.push("");
847
+ imports.push(`import {
848
+ handleSession,
849
+ handleProviders,
850
+ handleSignIn,
851
+ handleSignInProvider,
852
+ handleSignOutGet,
853
+ handleSignOutPost,
854
+ } from '@cloudwerk/auth/routes'`);
855
+ imports.push(`import { createSessionManager, createKVSessionAdapter } from '@cloudwerk/auth/session'`);
856
+ const passkeyProviders = authManifest.providers.filter((p) => p.type === "passkey" && !p.disabled);
857
+ if (passkeyProviders.length > 0) {
858
+ imports.push(`import {
859
+ handlePasskeyRegisterOptions,
860
+ handlePasskeyRegisterVerify,
861
+ handlePasskeyAuthenticateOptions,
862
+ handlePasskeyAuthenticateVerify,
863
+ } from '@cloudwerk/auth/routes'`);
864
+ imports.push(`import {
865
+ createKVChallengeStorage,
866
+ createD1CredentialStorage,
867
+ } from '@cloudwerk/auth/providers'`);
868
+ for (let i = 0; i < passkeyProviders.length; i++) {
869
+ const provider = passkeyProviders[i];
870
+ imports.push(`import passkeyProviderDef_${i} from '${provider.filePath}'`);
871
+ }
872
+ }
873
+ lines.push(imports.join("\n"));
874
+ lines.push("");
875
+ lines.push(`
876
+ /**
877
+ * Build auth context for standard auth routes.
878
+ */
879
+ function buildAuthContext(c) {
880
+ const env = c.env || {}
881
+
882
+ // Get KV binding - fall back to common binding names
883
+ let kvBinding = undefined
884
+ for (const name of ['FLAGSHIP_AUTH_SESSIONS', 'AUTH_KV', 'AUTH_SESSIONS', 'KV']) {
885
+ const binding = env[name]
886
+ if (binding && typeof binding.get === 'function') {
887
+ kvBinding = binding
888
+ break
889
+ }
890
+ }
891
+
892
+ // Create session manager from KV
893
+ const sessionAdapter = kvBinding ? createKVSessionAdapter({ binding: kvBinding, enableUserIndex: true }) : undefined
894
+ const sessionManager = sessionAdapter ? createSessionManager({ adapter: sessionAdapter }) : undefined
895
+
896
+ // Build providers map
897
+ const providers = new Map()
898
+
899
+ return {
900
+ request: c.req.raw,
901
+ env,
902
+ config: { basePath: '${basePath}', session: { strategy: '${authManifest.config?.sessionStrategy || "database"}' } },
903
+ sessionManager,
904
+ providers,
905
+ user: c.get?.('user') ?? null,
906
+ session: c.get?.('session') ?? null,
907
+ url: new URL(c.req.url),
908
+ responseHeaders: new Headers(),
909
+ }
910
+ }
911
+ `);
912
+ lines.push(`
913
+ // Standard auth routes
914
+ app.get('${basePath}/session', async (c) => {
915
+ const ctx = buildAuthContext(c)
916
+ return handleSession(ctx)
917
+ })
918
+
919
+ app.get('${basePath}/providers', async (c) => {
920
+ const ctx = buildAuthContext(c)
921
+ return handleProviders(ctx)
922
+ })
923
+
924
+ app.get('${basePath}/signin', async (c) => {
925
+ const ctx = buildAuthContext(c)
926
+ return handleSignIn(ctx)
927
+ })
928
+
929
+ app.get('${basePath}/signin/:provider', async (c) => {
930
+ const ctx = buildAuthContext(c)
931
+ const providerId = c.req.param('provider')
932
+ return handleSignInProvider(ctx, providerId)
933
+ })
934
+
935
+ app.get('${basePath}/signout', async (c) => {
936
+ const ctx = buildAuthContext(c)
937
+ return handleSignOutGet(ctx)
938
+ })
939
+
940
+ app.post('${basePath}/signout', async (c) => {
941
+ const ctx = buildAuthContext(c)
942
+ return handleSignOutPost(ctx)
943
+ })
944
+ `);
945
+ if (passkeyProviders.length > 0) {
946
+ lines.push(`
947
+ /**
948
+ * Build auth context for passkey handlers.
949
+ */
950
+ function buildPasskeyAuthContext(c, passkeyProvider) {
951
+ const env = c.env || {}
952
+
953
+ // Get KV binding for challenges - use provider config or fall back to common names
954
+ const kvBindingName = passkeyProvider.kvBinding
955
+ let kvBinding = kvBindingName ? env[kvBindingName] : undefined
956
+ if (!kvBinding) {
957
+ // Fall back to common binding names
958
+ for (const name of ['AUTH_KV', 'AUTH_SESSIONS', 'KV']) {
959
+ const binding = env[name]
960
+ if (binding && typeof binding.get === 'function') {
961
+ kvBinding = binding
962
+ break
963
+ }
964
+ }
965
+ }
966
+ const challengeStorage = kvBinding ? createKVChallengeStorage(kvBinding, 'auth:challenge:') : undefined
967
+
968
+ // Get D1 binding for credentials - use provider config or fall back to common names
969
+ const d1BindingName = passkeyProvider.d1Binding
970
+ const d1Binding = d1BindingName ? env[d1BindingName] : (env.DB || env.D1 || env.DATABASE)
971
+ const credentialStorage = d1Binding ? createD1CredentialStorage(d1Binding, 'webauthn_credentials') : undefined
972
+
973
+ // Create user adapter from D1
974
+ const userAdapter = d1Binding ? {
975
+ async getUserByEmail(email) {
976
+ const user = await d1Binding.prepare(
977
+ 'SELECT id, email, email_verified, name, image, created_at, updated_at FROM users WHERE email = ?'
978
+ ).bind(email).first()
979
+ if (!user) return null
980
+ return {
981
+ id: user.id,
982
+ email: user.email,
983
+ emailVerified: user.email_verified ? new Date(user.email_verified) : null,
984
+ name: user.name,
985
+ image: user.image,
986
+ createdAt: new Date(user.created_at),
987
+ updatedAt: new Date(user.updated_at),
988
+ }
989
+ },
990
+ async createUser(userData) {
991
+ const id = crypto.randomUUID()
992
+ const now = new Date().toISOString()
993
+ await d1Binding.prepare(
994
+ 'INSERT INTO users (id, email, email_verified, name, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)'
995
+ ).bind(id, userData.email, userData.emailVerified?.toISOString() ?? null, userData.name ?? null, now, now).run()
996
+ return { id, email: userData.email, emailVerified: userData.emailVerified, name: userData.name ?? null, image: null, createdAt: new Date(now), updatedAt: new Date(now) }
997
+ },
998
+ async getUser(id) {
999
+ const user = await d1Binding.prepare(
1000
+ 'SELECT id, email, email_verified, name, image, created_at, updated_at FROM users WHERE id = ?'
1001
+ ).bind(id).first()
1002
+ if (!user) return null
1003
+ return {
1004
+ id: user.id,
1005
+ email: user.email,
1006
+ emailVerified: user.email_verified ? new Date(user.email_verified) : null,
1007
+ name: user.name,
1008
+ image: user.image,
1009
+ createdAt: new Date(user.created_at),
1010
+ updatedAt: new Date(user.updated_at),
1011
+ }
1012
+ },
1013
+ } : undefined
1014
+
1015
+ // Create session manager from KV
1016
+ const sessionAdapter = kvBinding ? createKVSessionAdapter({ binding: kvBinding, enableUserIndex: true }) : undefined
1017
+ const sessionManager = sessionAdapter ? createSessionManager({ adapter: sessionAdapter }) : undefined
1018
+
1019
+ // Build providers map
1020
+ const providers = new Map()
1021
+ providers.set(passkeyProvider.id, passkeyProvider)
1022
+
1023
+ return {
1024
+ request: c.req.raw,
1025
+ env,
1026
+ config: { basePath: '${basePath}', session: { strategy: '${authManifest.config?.sessionStrategy || "database"}' } },
1027
+ sessionManager,
1028
+ providers,
1029
+ user: null,
1030
+ session: null,
1031
+ url: new URL(c.req.url),
1032
+ responseHeaders: new Headers(),
1033
+ challengeStorage,
1034
+ credentialStorage,
1035
+ userAdapter,
1036
+ }
1037
+ }
1038
+ `);
1039
+ for (let i = 0; i < passkeyProviders.length; i++) {
1040
+ const provider = passkeyProviders[i];
1041
+ lines.push(`
1042
+ // Get provider from definition
1043
+ const passkeyProvider_${i} = passkeyProviderDef_${i}.provider || passkeyProviderDef_${i}
1044
+ `);
1045
+ lines.push(`
1046
+ // Passkey registration routes
1047
+ app.post('${basePath}/passkey/register/options', async (c) => {
1048
+ const ctx = buildPasskeyAuthContext(c, passkeyProvider_${i})
1049
+ return handlePasskeyRegisterOptions(ctx, '${provider.id}')
1050
+ })
1051
+
1052
+ app.post('${basePath}/passkey/register/verify', async (c) => {
1053
+ const ctx = buildPasskeyAuthContext(c, passkeyProvider_${i})
1054
+ return handlePasskeyRegisterVerify(ctx, '${provider.id}')
1055
+ })
1056
+
1057
+ app.post('${basePath}/passkey/authenticate/options', async (c) => {
1058
+ const ctx = buildPasskeyAuthContext(c, passkeyProvider_${i})
1059
+ return handlePasskeyAuthenticateOptions(ctx, '${provider.id}')
1060
+ })
1061
+
1062
+ app.post('${basePath}/passkey/authenticate/verify', async (c) => {
1063
+ const ctx = buildPasskeyAuthContext(c, passkeyProvider_${i})
1064
+ return handlePasskeyAuthenticateVerify(ctx, '${provider.id}')
1065
+ })
1066
+ `);
1067
+ }
1068
+ }
1069
+ return lines.join("\n");
1070
+ }
769
1071
 
770
1072
  // src/virtual-modules/client-entry.ts
771
1073
  function collectCssImports(cssImports) {
@@ -1798,6 +2100,62 @@ function cloudwerkPlugin(options = {}) {
1798
2100
  console.log(`[cloudwerk] Found ${state.serviceManifest.services.length} service(s)`);
1799
2101
  }
1800
2102
  }
2103
+ async function buildImageManifestIfExists(root) {
2104
+ if (!state) {
2105
+ throw new Error("Plugin state not initialized");
2106
+ }
2107
+ const imagesPath = path3.resolve(root, state.options.appDir, IMAGES_DIR);
2108
+ try {
2109
+ await fs2.promises.access(imagesPath);
2110
+ } catch {
2111
+ state.imageScanResult = null;
2112
+ state.imageManifest = null;
2113
+ return;
2114
+ }
2115
+ state.imageScanResult = await scanImages(
2116
+ path3.resolve(root, state.options.appDir),
2117
+ { extensions: state.options.config.extensions }
2118
+ );
2119
+ state.imageManifest = buildImageManifest(
2120
+ state.imageScanResult,
2121
+ root
2122
+ );
2123
+ if (state.options.verbose && state.imageManifest.images.length > 0) {
2124
+ console.log(`[cloudwerk] Found ${state.imageManifest.images.length} image(s)`);
2125
+ }
2126
+ }
2127
+ async function buildAuthManifestIfExists(root) {
2128
+ if (!state) {
2129
+ throw new Error("Plugin state not initialized");
2130
+ }
2131
+ const authPath = path3.resolve(root, state.options.appDir, AUTH_DIR);
2132
+ try {
2133
+ await fs2.promises.access(authPath);
2134
+ } catch {
2135
+ state.authScanResult = null;
2136
+ state.authManifest = null;
2137
+ return;
2138
+ }
2139
+ state.authScanResult = await scanAuth(
2140
+ path3.resolve(root, state.options.appDir),
2141
+ { extensions: state.options.config.extensions }
2142
+ );
2143
+ state.authManifest = await buildAuthManifestWithModules(state.authScanResult);
2144
+ state.serverEntryCache = null;
2145
+ if (state.options.verbose && state.authManifest.providers.length > 0) {
2146
+ console.log(`[cloudwerk] Found ${state.authManifest.providers.length} auth provider(s)`);
2147
+ for (const provider of state.authManifest.providers) {
2148
+ console.log(`[cloudwerk] - ${provider.id} (${provider.type})`);
2149
+ }
2150
+ }
2151
+ }
2152
+ function isAuthFile(filePath) {
2153
+ if (!state) return false;
2154
+ const authDir = path3.resolve(state.options.root, state.options.appDir, AUTH_DIR);
2155
+ if (!filePath.startsWith(authDir)) return false;
2156
+ const ext = path3.extname(filePath);
2157
+ return state.options.config.extensions.includes(ext);
2158
+ }
1801
2159
  function isRouteFile(filePath) {
1802
2160
  if (!state) return false;
1803
2161
  const appDir = path3.resolve(state.options.root, state.options.appDir);
@@ -1825,6 +2183,15 @@ function cloudwerkPlugin(options = {}) {
1825
2183
  if (nameWithoutExt !== SERVICE_FILE_NAME) return false;
1826
2184
  return state.options.config.extensions.includes(ext);
1827
2185
  }
2186
+ function isImageFile(filePath) {
2187
+ if (!state) return false;
2188
+ const imagesDir = path3.resolve(state.options.root, state.options.appDir, IMAGES_DIR);
2189
+ if (!filePath.startsWith(imagesDir)) return false;
2190
+ const relativePath = path3.relative(imagesDir, filePath);
2191
+ if (relativePath.includes(path3.sep)) return false;
2192
+ const ext = path3.extname(filePath);
2193
+ return state.options.config.extensions.includes(ext);
2194
+ }
1828
2195
  function invalidateVirtualModules() {
1829
2196
  if (!server) return;
1830
2197
  const idsToInvalidate = [
@@ -1919,6 +2286,10 @@ function cloudwerkPlugin(options = {}) {
1919
2286
  queueScanResult: null,
1920
2287
  serviceManifest: null,
1921
2288
  serviceScanResult: null,
2289
+ imageManifest: null,
2290
+ imageScanResult: null,
2291
+ authManifest: null,
2292
+ authScanResult: null,
1922
2293
  clientComponents: /* @__PURE__ */ new Map(),
1923
2294
  cssImports: /* @__PURE__ */ new Map(),
1924
2295
  serverEntryCache: null,
@@ -1927,6 +2298,8 @@ function cloudwerkPlugin(options = {}) {
1927
2298
  await buildManifest(root);
1928
2299
  await buildQueueManifestIfExists(root);
1929
2300
  await buildServiceManifestIfExists(root);
2301
+ await buildImageManifestIfExists(root);
2302
+ await buildAuthManifestIfExists(root);
1930
2303
  await scanClientComponents(root, state);
1931
2304
  await scanCssImports(root, state);
1932
2305
  },
@@ -1963,6 +2336,22 @@ function cloudwerkPlugin(options = {}) {
1963
2336
  await buildServiceManifestIfExists(state.options.root);
1964
2337
  invalidateVirtualModules();
1965
2338
  }
2339
+ if (isImageFile(filePath)) {
2340
+ const imagesDir = path3.resolve(root, state.options.appDir, IMAGES_DIR);
2341
+ if (state?.options.verbose) {
2342
+ console.log(`[cloudwerk] Image added: ${path3.relative(imagesDir, filePath)}`);
2343
+ }
2344
+ await buildImageManifestIfExists(state.options.root);
2345
+ invalidateVirtualModules();
2346
+ }
2347
+ if (isAuthFile(filePath)) {
2348
+ const authDir = path3.resolve(root, state.options.appDir, AUTH_DIR);
2349
+ if (state?.options.verbose) {
2350
+ console.log(`[cloudwerk] Auth file added: ${path3.relative(authDir, filePath)}`);
2351
+ }
2352
+ await buildAuthManifestIfExists(state.options.root);
2353
+ invalidateVirtualModules();
2354
+ }
1966
2355
  });
1967
2356
  devServer.watcher.on("unlink", async (filePath) => {
1968
2357
  if (isRouteFile(filePath)) {
@@ -1988,6 +2377,22 @@ function cloudwerkPlugin(options = {}) {
1988
2377
  await buildServiceManifestIfExists(state.options.root);
1989
2378
  invalidateVirtualModules();
1990
2379
  }
2380
+ if (isImageFile(filePath)) {
2381
+ const imagesDir = path3.resolve(root, state.options.appDir, IMAGES_DIR);
2382
+ if (state?.options.verbose) {
2383
+ console.log(`[cloudwerk] Image removed: ${path3.relative(imagesDir, filePath)}`);
2384
+ }
2385
+ await buildImageManifestIfExists(state.options.root);
2386
+ invalidateVirtualModules();
2387
+ }
2388
+ if (isAuthFile(filePath)) {
2389
+ const authDir = path3.resolve(root, state.options.appDir, AUTH_DIR);
2390
+ if (state?.options.verbose) {
2391
+ console.log(`[cloudwerk] Auth file removed: ${path3.relative(authDir, filePath)}`);
2392
+ }
2393
+ await buildAuthManifestIfExists(state.options.root);
2394
+ invalidateVirtualModules();
2395
+ }
1991
2396
  });
1992
2397
  devServer.watcher.on("change", async (filePath) => {
1993
2398
  if (isRouteFile(filePath)) {
@@ -2013,6 +2418,22 @@ function cloudwerkPlugin(options = {}) {
2013
2418
  await buildServiceManifestIfExists(state.options.root);
2014
2419
  invalidateVirtualModules();
2015
2420
  }
2421
+ if (isImageFile(filePath)) {
2422
+ const imagesDir = path3.resolve(root, state.options.appDir, IMAGES_DIR);
2423
+ if (state?.options.verbose) {
2424
+ console.log(`[cloudwerk] Image changed: ${path3.relative(imagesDir, filePath)}`);
2425
+ }
2426
+ await buildImageManifestIfExists(state.options.root);
2427
+ invalidateVirtualModules();
2428
+ }
2429
+ if (isAuthFile(filePath)) {
2430
+ const authDir = path3.resolve(root, state.options.appDir, AUTH_DIR);
2431
+ if (state?.options.verbose) {
2432
+ console.log(`[cloudwerk] Auth file changed: ${path3.relative(authDir, filePath)}`);
2433
+ }
2434
+ await buildAuthManifestIfExists(state.options.root);
2435
+ invalidateVirtualModules();
2436
+ }
2016
2437
  const wranglerPath2 = findWranglerTomlPath(root);
2017
2438
  if (wranglerPath2 && filePath === wranglerPath2) {
2018
2439
  if (verbose) {
@@ -2060,7 +2481,9 @@ function cloudwerkPlugin(options = {}) {
2060
2481
  state.options,
2061
2482
  {
2062
2483
  queueManifest: state.queueManifest,
2063
- serviceManifest: state.serviceManifest
2484
+ serviceManifest: state.serviceManifest,
2485
+ authManifest: state.authManifest,
2486
+ cssImports: state.cssImports
2064
2487
  }
2065
2488
  );
2066
2489
  }
@@ -2130,6 +2553,13 @@ function cloudwerkPlugin(options = {}) {
2130
2553
  "getDurableObject",
2131
2554
  "hasDurableObject",
2132
2555
  "getDurableObjectNames",
2556
+ "images",
2557
+ "getImages",
2558
+ "hasImages",
2559
+ "getImagesNames",
2560
+ "registerLocalImages",
2561
+ "unregisterLocalImages",
2562
+ "clearLocalImages",
2133
2563
  "createLazyBinding"
2134
2564
  ];
2135
2565
  const runtimeImports = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudwerk/vite-plugin",
3
- "version": "0.6.3",
3
+ "version": "0.6.6",
4
4
  "description": "Vite plugin for Cloudwerk file-based routing with virtual entry generation",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,8 +19,8 @@
19
19
  ],
20
20
  "dependencies": {
21
21
  "@swc/core": "^1.3.100",
22
- "@cloudwerk/core": "^0.15.1",
23
- "@cloudwerk/ui": "^0.15.1"
22
+ "@cloudwerk/core": "^0.15.3",
23
+ "@cloudwerk/ui": "^0.15.3"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "vite": "^5.0.0 || ^6.0.0",