@cloudwerk/vite-plugin 0.6.2 → 0.6.5
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 +36 -3
- package/dist/index.js +594 -23
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
|
-
import { CloudwerkConfig,
|
|
2
|
+
import { CloudwerkConfig, QueueManifest, ServiceManifest, AuthManifest, RouteManifest, ScanResult } from '@cloudwerk/core/build';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @cloudwerk/vite-plugin - Types
|
|
@@ -96,6 +96,17 @@ interface ClientComponentInfo {
|
|
|
96
96
|
/** Absolute file path */
|
|
97
97
|
absolutePath: string;
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Information about a CSS import detected in a layout or page.
|
|
101
|
+
*/
|
|
102
|
+
interface CssImportInfo {
|
|
103
|
+
/** Absolute path to the CSS file */
|
|
104
|
+
absolutePath: string;
|
|
105
|
+
/** File that imports the CSS */
|
|
106
|
+
importedBy: string;
|
|
107
|
+
/** Whether the importing file is a layout */
|
|
108
|
+
isLayout: boolean;
|
|
109
|
+
}
|
|
99
110
|
/**
|
|
100
111
|
* Virtual module IDs used by the plugin.
|
|
101
112
|
*/
|
|
@@ -150,6 +161,20 @@ declare function cloudwerkPlugin(options?: CloudwerkVitePluginOptions): Plugin;
|
|
|
150
161
|
* a Hono app with all routes registered from the file-based routing manifest.
|
|
151
162
|
*/
|
|
152
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Asset manifest entry from Vite build.
|
|
166
|
+
*/
|
|
167
|
+
interface AssetManifestEntry {
|
|
168
|
+
file: string;
|
|
169
|
+
css?: string[];
|
|
170
|
+
assets?: string[];
|
|
171
|
+
isEntry?: boolean;
|
|
172
|
+
isDynamicEntry?: boolean;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Asset manifest from Vite build (maps source to output files).
|
|
176
|
+
*/
|
|
177
|
+
type AssetManifest = Record<string, AssetManifestEntry>;
|
|
153
178
|
/**
|
|
154
179
|
* Options for generating server entry.
|
|
155
180
|
*/
|
|
@@ -158,6 +183,12 @@ interface GenerateServerEntryOptions {
|
|
|
158
183
|
queueManifest?: QueueManifest | null;
|
|
159
184
|
/** Service manifest if services are configured */
|
|
160
185
|
serviceManifest?: ServiceManifest | null;
|
|
186
|
+
/** Asset manifest from Vite build for CSS injection */
|
|
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[]>;
|
|
161
192
|
}
|
|
162
193
|
/**
|
|
163
194
|
* Generate the server entry module code.
|
|
@@ -187,14 +218,16 @@ declare function generateServerEntry(manifest: RouteManifest, scanResult: ScanRe
|
|
|
187
218
|
* Generate the client entry module code.
|
|
188
219
|
*
|
|
189
220
|
* This creates a hydration bootstrap that:
|
|
221
|
+
* - Imports CSS files from layouts and pages
|
|
190
222
|
* - Finds all elements with data-hydrate-id attributes
|
|
191
223
|
* - Dynamically imports the corresponding component bundles
|
|
192
224
|
* - Hydrates each component with its serialized props
|
|
193
225
|
*
|
|
194
226
|
* @param clientComponents - Map of detected client components
|
|
227
|
+
* @param cssImports - Map of CSS imports from layouts and pages
|
|
195
228
|
* @param options - Resolved plugin options
|
|
196
229
|
* @returns Generated JavaScript code
|
|
197
230
|
*/
|
|
198
|
-
declare function generateClientEntry(clientComponents: Map<string, ClientComponentInfo>, options: ResolvedCloudwerkOptions): string;
|
|
231
|
+
declare function generateClientEntry(clientComponents: Map<string, ClientComponentInfo>, cssImports: Map<string, CssImportInfo[]>, options: ResolvedCloudwerkOptions): string;
|
|
199
232
|
|
|
200
|
-
export { type ClientComponentInfo, type CloudwerkVitePluginOptions, RESOLVED_VIRTUAL_IDS, type ResolvedCloudwerkOptions, VIRTUAL_MODULE_IDS, cloudwerkPlugin, cloudwerkPlugin as default, generateClientEntry, generateServerEntry };
|
|
233
|
+
export { type AssetManifest, type AssetManifestEntry, type ClientComponentInfo, type CloudwerkVitePluginOptions, type CssImportInfo, type GenerateServerEntryOptions, RESOLVED_VIRTUAL_IDS, type ResolvedCloudwerkOptions, VIRTUAL_MODULE_IDS, cloudwerkPlugin, cloudwerkPlugin as default, generateClientEntry, generateServerEntry };
|
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
|
|
@@ -39,6 +45,8 @@ import * as path from "path";
|
|
|
39
45
|
function generateServerEntry(manifest, scanResult, options, entryOptions) {
|
|
40
46
|
const queueManifest = entryOptions?.queueManifest;
|
|
41
47
|
const serviceManifest = entryOptions?.serviceManifest;
|
|
48
|
+
const assetManifest = entryOptions?.assetManifest;
|
|
49
|
+
const authManifest = entryOptions?.authManifest;
|
|
42
50
|
const imports = [];
|
|
43
51
|
const pageRegistrations = [];
|
|
44
52
|
const routeRegistrations = [];
|
|
@@ -57,6 +65,10 @@ function generateServerEntry(manifest, scanResult, options, entryOptions) {
|
|
|
57
65
|
let middlewareIndex = 0;
|
|
58
66
|
let errorIndex = 0;
|
|
59
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;
|
|
60
72
|
const ssgPageInfo = [];
|
|
61
73
|
for (const err of scanResult.errors) {
|
|
62
74
|
if (!importedModules.has(err.absolutePath)) {
|
|
@@ -88,6 +100,12 @@ function generateServerEntry(manifest, scanResult, options, entryOptions) {
|
|
|
88
100
|
const varName = notFoundModules.get(nf.absolutePath);
|
|
89
101
|
notFoundBoundaryMapEntries.push(` ['${normalizedDir}', ${varName}]`);
|
|
90
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
|
+
}
|
|
91
109
|
for (const route of manifest.routes) {
|
|
92
110
|
for (const middlewarePath of route.middleware) {
|
|
93
111
|
if (!importedModules.has(middlewarePath)) {
|
|
@@ -138,7 +156,40 @@ function generateServerEntry(manifest, scanResult, options, entryOptions) {
|
|
|
138
156
|
}
|
|
139
157
|
}
|
|
140
158
|
const rendererName = options.renderer;
|
|
141
|
-
|
|
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
|
+
}
|
|
170
|
+
let cssLinksCode = "";
|
|
171
|
+
if (options.isProduction && assetManifest) {
|
|
172
|
+
const clientEntry = assetManifest["virtual:cloudwerk/client-entry"];
|
|
173
|
+
if (clientEntry?.css && clientEntry.css.length > 0) {
|
|
174
|
+
const cssLinks = clientEntry.css.map((css) => `<link rel="stylesheet" href="/${css}" />`).join("");
|
|
175
|
+
cssLinksCode = `const CSS_LINKS = '${cssLinks}'`;
|
|
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
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (!cssLinksCode) {
|
|
190
|
+
cssLinksCode = `const CSS_LINKS = ''`;
|
|
191
|
+
}
|
|
192
|
+
const viteClientScript = options.isProduction ? "" : '<script type="module" src="/@vite/client"></script>';
|
|
142
193
|
return `/**
|
|
143
194
|
* Generated Cloudwerk Server Entry
|
|
144
195
|
* This file is auto-generated by @cloudwerk/vite-plugin - do not edit
|
|
@@ -176,6 +227,16 @@ const notFoundBoundaryMap = new Map([
|
|
|
176
227
|
${notFoundBoundaryMapEntries.join(",\n")}
|
|
177
228
|
])
|
|
178
229
|
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// Asset Injection Configuration
|
|
232
|
+
// ============================================================================
|
|
233
|
+
|
|
234
|
+
// CSS links from asset manifest (production) or empty (dev - CSS served by Vite)
|
|
235
|
+
${cssLinksCode}
|
|
236
|
+
|
|
237
|
+
// Vite client script for HMR (dev only)
|
|
238
|
+
const VITE_CLIENT = '${viteClientScript}'
|
|
239
|
+
|
|
179
240
|
// ============================================================================
|
|
180
241
|
// Helper Functions
|
|
181
242
|
// ============================================================================
|
|
@@ -395,20 +456,34 @@ function registerPage(app, pattern, pageModule, layoutModules, middlewareModules
|
|
|
395
456
|
}
|
|
396
457
|
|
|
397
458
|
/**
|
|
398
|
-
* Render element to a Response, injecting
|
|
459
|
+
* Render element to a Response, injecting CSS and scripts.
|
|
460
|
+
* - CSS links are injected before </head>
|
|
461
|
+
* - Vite client (dev) and hydration script are injected before </body>
|
|
399
462
|
*/
|
|
400
463
|
function renderWithHydration(element, status = 200) {
|
|
401
464
|
// Hono JSX elements have toString() for synchronous rendering
|
|
402
|
-
|
|
465
|
+
let html = '<!DOCTYPE html>' + String(element)
|
|
466
|
+
|
|
467
|
+
// Inject CSS links before </head> if present
|
|
468
|
+
if (CSS_LINKS) {
|
|
469
|
+
const headCloseRegex = /<\\/head>/i
|
|
470
|
+
if (headCloseRegex.test(html)) {
|
|
471
|
+
html = html.replace(headCloseRegex, CSS_LINKS + '</head>')
|
|
472
|
+
}
|
|
473
|
+
}
|
|
403
474
|
|
|
404
|
-
// Inject
|
|
405
|
-
|
|
475
|
+
// Inject scripts before </body>
|
|
476
|
+
// - Vite client for HMR (dev only)
|
|
477
|
+
// - Hydration script for client components
|
|
478
|
+
const scripts = VITE_CLIENT + '<script type="module" src="${clientEntryPath}"></script>'
|
|
406
479
|
const bodyCloseRegex = /<\\/body>/i
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
480
|
+
if (bodyCloseRegex.test(html)) {
|
|
481
|
+
html = html.replace(bodyCloseRegex, scripts + '</body>')
|
|
482
|
+
} else {
|
|
483
|
+
html = html + scripts
|
|
484
|
+
}
|
|
410
485
|
|
|
411
|
-
return new Response(
|
|
486
|
+
return new Response(html, {
|
|
412
487
|
status,
|
|
413
488
|
headers: {
|
|
414
489
|
'Content-Type': 'text/html; charset=utf-8',
|
|
@@ -434,7 +509,7 @@ function registerRoute(app, pattern, routeModule, middlewareModules) {
|
|
|
434
509
|
for (const method of HTTP_METHODS) {
|
|
435
510
|
const handler = routeModule[method]
|
|
436
511
|
if (handler && typeof handler === 'function') {
|
|
437
|
-
const h =
|
|
512
|
+
const h = createHandlerAdapter(handler)
|
|
438
513
|
switch (method) {
|
|
439
514
|
case 'GET': app.get(pattern, h); break
|
|
440
515
|
case 'POST': app.post(pattern, h); break
|
|
@@ -460,7 +535,10 @@ const app = new Hono({ strict: false })
|
|
|
460
535
|
|
|
461
536
|
// Add context middleware
|
|
462
537
|
app.use('*', contextMiddleware())
|
|
463
|
-
${
|
|
538
|
+
${rootMiddlewareVarName ? `
|
|
539
|
+
// Apply root middleware globally (for all routes including auth)
|
|
540
|
+
app.use('*', createMiddlewareAdapter(${rootMiddlewareVarName}))
|
|
541
|
+
` : ""}${options.isProduction ? `
|
|
464
542
|
// Serve static assets using Workers Static Assets binding (production only)
|
|
465
543
|
app.use('*', async (c, next) => {
|
|
466
544
|
// Check if ASSETS binding is available
|
|
@@ -469,12 +547,39 @@ app.use('*', async (c, next) => {
|
|
|
469
547
|
return
|
|
470
548
|
}
|
|
471
549
|
|
|
550
|
+
// Only serve static assets for GET/HEAD requests
|
|
551
|
+
// Other methods (POST, PUT, etc.) should go directly to route handlers
|
|
552
|
+
// to avoid consuming the request body
|
|
553
|
+
const method = c.req.method
|
|
554
|
+
if (method !== 'GET' && method !== 'HEAD') {
|
|
555
|
+
await next()
|
|
556
|
+
return
|
|
557
|
+
}
|
|
558
|
+
|
|
472
559
|
// Try to serve the request as a static asset
|
|
473
560
|
const response = await c.env.ASSETS.fetch(c.req.raw)
|
|
474
561
|
|
|
475
|
-
// If asset found (not 404), return it
|
|
562
|
+
// If asset found (not 404), return it with cache headers
|
|
476
563
|
if (response.status !== 404) {
|
|
477
|
-
|
|
564
|
+
const path = new URL(c.req.url).pathname
|
|
565
|
+
|
|
566
|
+
// Check if this is a hashed asset (Vite adds content hash to filename)
|
|
567
|
+
// Hashed assets are immutable and can be cached forever
|
|
568
|
+
const isHashedAsset = path.startsWith('/__cloudwerk/') ||
|
|
569
|
+
/-[a-zA-Z0-9]{8,}\\.(js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|webp|avif|ico)$/.test(path)
|
|
570
|
+
|
|
571
|
+
const cacheControl = isHashedAsset
|
|
572
|
+
? 'public, max-age=31536000, immutable'
|
|
573
|
+
: 'public, max-age=3600'
|
|
574
|
+
|
|
575
|
+
return new Response(response.body, {
|
|
576
|
+
status: response.status,
|
|
577
|
+
statusText: response.statusText,
|
|
578
|
+
headers: {
|
|
579
|
+
...Object.fromEntries(response.headers.entries()),
|
|
580
|
+
'Cache-Control': cacheControl,
|
|
581
|
+
},
|
|
582
|
+
})
|
|
478
583
|
}
|
|
479
584
|
|
|
480
585
|
// Asset not found, continue to routes
|
|
@@ -485,6 +590,9 @@ app.use('*', async (c, next) => {
|
|
|
485
590
|
${pageRegistrations.join("\n")}
|
|
486
591
|
${routeRegistrations.join("\n")}
|
|
487
592
|
|
|
593
|
+
// Register auth routes
|
|
594
|
+
${generateAuthRouteRegistrations(authManifest)}
|
|
595
|
+
|
|
488
596
|
// SSG routes endpoint - returns all static routes for build-time generation
|
|
489
597
|
app.get('/__ssg/routes', async (c) => {
|
|
490
598
|
const routes = []
|
|
@@ -720,14 +828,266 @@ function generateServiceRegistration(serviceManifest) {
|
|
|
720
828
|
}
|
|
721
829
|
return lines.join("\n");
|
|
722
830
|
}
|
|
831
|
+
function generateAuthRouteRegistrations(authManifest) {
|
|
832
|
+
if (!authManifest) {
|
|
833
|
+
return "";
|
|
834
|
+
}
|
|
835
|
+
const lines = [];
|
|
836
|
+
const imports = [];
|
|
837
|
+
const basePath = authManifest.config?.basePath || "/auth";
|
|
838
|
+
lines.push("");
|
|
839
|
+
lines.push("// ============================================================================");
|
|
840
|
+
lines.push("// Auth Route Registration");
|
|
841
|
+
lines.push("// ============================================================================");
|
|
842
|
+
lines.push("");
|
|
843
|
+
imports.push(`import {
|
|
844
|
+
handleSession,
|
|
845
|
+
handleProviders,
|
|
846
|
+
handleSignIn,
|
|
847
|
+
handleSignInProvider,
|
|
848
|
+
handleSignOutGet,
|
|
849
|
+
handleSignOutPost,
|
|
850
|
+
} from '@cloudwerk/auth/routes'`);
|
|
851
|
+
imports.push(`import { createSessionManager, createKVSessionAdapter } from '@cloudwerk/auth/session'`);
|
|
852
|
+
const passkeyProviders = authManifest.providers.filter((p) => p.type === "passkey" && !p.disabled);
|
|
853
|
+
if (passkeyProviders.length > 0) {
|
|
854
|
+
imports.push(`import {
|
|
855
|
+
handlePasskeyRegisterOptions,
|
|
856
|
+
handlePasskeyRegisterVerify,
|
|
857
|
+
handlePasskeyAuthenticateOptions,
|
|
858
|
+
handlePasskeyAuthenticateVerify,
|
|
859
|
+
} from '@cloudwerk/auth/routes'`);
|
|
860
|
+
imports.push(`import {
|
|
861
|
+
createKVChallengeStorage,
|
|
862
|
+
createD1CredentialStorage,
|
|
863
|
+
} from '@cloudwerk/auth/providers'`);
|
|
864
|
+
for (let i = 0; i < passkeyProviders.length; i++) {
|
|
865
|
+
const provider = passkeyProviders[i];
|
|
866
|
+
imports.push(`import passkeyProviderDef_${i} from '${provider.filePath}'`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
lines.push(imports.join("\n"));
|
|
870
|
+
lines.push("");
|
|
871
|
+
lines.push(`
|
|
872
|
+
/**
|
|
873
|
+
* Build auth context for standard auth routes.
|
|
874
|
+
*/
|
|
875
|
+
function buildAuthContext(c) {
|
|
876
|
+
const env = c.env || {}
|
|
877
|
+
|
|
878
|
+
// Get KV binding - fall back to common binding names
|
|
879
|
+
let kvBinding = undefined
|
|
880
|
+
for (const name of ['FLAGSHIP_AUTH_SESSIONS', 'AUTH_KV', 'AUTH_SESSIONS', 'KV']) {
|
|
881
|
+
const binding = env[name]
|
|
882
|
+
if (binding && typeof binding.get === 'function') {
|
|
883
|
+
kvBinding = binding
|
|
884
|
+
break
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// Create session manager from KV
|
|
889
|
+
const sessionAdapter = kvBinding ? createKVSessionAdapter({ binding: kvBinding, enableUserIndex: true }) : undefined
|
|
890
|
+
const sessionManager = sessionAdapter ? createSessionManager({ adapter: sessionAdapter }) : undefined
|
|
891
|
+
|
|
892
|
+
// Build providers map
|
|
893
|
+
const providers = new Map()
|
|
894
|
+
|
|
895
|
+
return {
|
|
896
|
+
request: c.req.raw,
|
|
897
|
+
env,
|
|
898
|
+
config: { basePath: '${basePath}', session: { strategy: '${authManifest.config?.sessionStrategy || "database"}' } },
|
|
899
|
+
sessionManager,
|
|
900
|
+
providers,
|
|
901
|
+
user: c.get?.('user') ?? null,
|
|
902
|
+
session: c.get?.('session') ?? null,
|
|
903
|
+
url: new URL(c.req.url),
|
|
904
|
+
responseHeaders: new Headers(),
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
`);
|
|
908
|
+
lines.push(`
|
|
909
|
+
// Standard auth routes
|
|
910
|
+
app.get('${basePath}/session', async (c) => {
|
|
911
|
+
const ctx = buildAuthContext(c)
|
|
912
|
+
return handleSession(ctx)
|
|
913
|
+
})
|
|
914
|
+
|
|
915
|
+
app.get('${basePath}/providers', async (c) => {
|
|
916
|
+
const ctx = buildAuthContext(c)
|
|
917
|
+
return handleProviders(ctx)
|
|
918
|
+
})
|
|
919
|
+
|
|
920
|
+
app.get('${basePath}/signin', async (c) => {
|
|
921
|
+
const ctx = buildAuthContext(c)
|
|
922
|
+
return handleSignIn(ctx)
|
|
923
|
+
})
|
|
924
|
+
|
|
925
|
+
app.get('${basePath}/signin/:provider', async (c) => {
|
|
926
|
+
const ctx = buildAuthContext(c)
|
|
927
|
+
const providerId = c.req.param('provider')
|
|
928
|
+
return handleSignInProvider(ctx, providerId)
|
|
929
|
+
})
|
|
930
|
+
|
|
931
|
+
app.get('${basePath}/signout', async (c) => {
|
|
932
|
+
const ctx = buildAuthContext(c)
|
|
933
|
+
return handleSignOutGet(ctx)
|
|
934
|
+
})
|
|
935
|
+
|
|
936
|
+
app.post('${basePath}/signout', async (c) => {
|
|
937
|
+
const ctx = buildAuthContext(c)
|
|
938
|
+
return handleSignOutPost(ctx)
|
|
939
|
+
})
|
|
940
|
+
`);
|
|
941
|
+
if (passkeyProviders.length > 0) {
|
|
942
|
+
lines.push(`
|
|
943
|
+
/**
|
|
944
|
+
* Build auth context for passkey handlers.
|
|
945
|
+
*/
|
|
946
|
+
function buildPasskeyAuthContext(c, passkeyProvider) {
|
|
947
|
+
const env = c.env || {}
|
|
948
|
+
|
|
949
|
+
// Get KV binding for challenges - use provider config or fall back to common names
|
|
950
|
+
const kvBindingName = passkeyProvider.kvBinding
|
|
951
|
+
let kvBinding = kvBindingName ? env[kvBindingName] : undefined
|
|
952
|
+
if (!kvBinding) {
|
|
953
|
+
// Fall back to common binding names
|
|
954
|
+
for (const name of ['AUTH_KV', 'AUTH_SESSIONS', 'KV']) {
|
|
955
|
+
const binding = env[name]
|
|
956
|
+
if (binding && typeof binding.get === 'function') {
|
|
957
|
+
kvBinding = binding
|
|
958
|
+
break
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
const challengeStorage = kvBinding ? createKVChallengeStorage(kvBinding, 'auth:challenge:') : undefined
|
|
963
|
+
|
|
964
|
+
// Get D1 binding for credentials - use provider config or fall back to common names
|
|
965
|
+
const d1BindingName = passkeyProvider.d1Binding
|
|
966
|
+
const d1Binding = d1BindingName ? env[d1BindingName] : (env.DB || env.D1 || env.DATABASE)
|
|
967
|
+
const credentialStorage = d1Binding ? createD1CredentialStorage(d1Binding, 'webauthn_credentials') : undefined
|
|
968
|
+
|
|
969
|
+
// Create user adapter from D1
|
|
970
|
+
const userAdapter = d1Binding ? {
|
|
971
|
+
async getUserByEmail(email) {
|
|
972
|
+
const user = await d1Binding.prepare(
|
|
973
|
+
'SELECT id, email, email_verified, name, image, created_at, updated_at FROM users WHERE email = ?'
|
|
974
|
+
).bind(email).first()
|
|
975
|
+
if (!user) return null
|
|
976
|
+
return {
|
|
977
|
+
id: user.id,
|
|
978
|
+
email: user.email,
|
|
979
|
+
emailVerified: user.email_verified ? new Date(user.email_verified) : null,
|
|
980
|
+
name: user.name,
|
|
981
|
+
image: user.image,
|
|
982
|
+
createdAt: new Date(user.created_at),
|
|
983
|
+
updatedAt: new Date(user.updated_at),
|
|
984
|
+
}
|
|
985
|
+
},
|
|
986
|
+
async createUser(userData) {
|
|
987
|
+
const id = crypto.randomUUID()
|
|
988
|
+
const now = new Date().toISOString()
|
|
989
|
+
await d1Binding.prepare(
|
|
990
|
+
'INSERT INTO users (id, email, email_verified, name, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)'
|
|
991
|
+
).bind(id, userData.email, userData.emailVerified?.toISOString() ?? null, userData.name ?? null, now, now).run()
|
|
992
|
+
return { id, email: userData.email, emailVerified: userData.emailVerified, name: userData.name ?? null, image: null, createdAt: new Date(now), updatedAt: new Date(now) }
|
|
993
|
+
},
|
|
994
|
+
async getUser(id) {
|
|
995
|
+
const user = await d1Binding.prepare(
|
|
996
|
+
'SELECT id, email, email_verified, name, image, created_at, updated_at FROM users WHERE id = ?'
|
|
997
|
+
).bind(id).first()
|
|
998
|
+
if (!user) return null
|
|
999
|
+
return {
|
|
1000
|
+
id: user.id,
|
|
1001
|
+
email: user.email,
|
|
1002
|
+
emailVerified: user.email_verified ? new Date(user.email_verified) : null,
|
|
1003
|
+
name: user.name,
|
|
1004
|
+
image: user.image,
|
|
1005
|
+
createdAt: new Date(user.created_at),
|
|
1006
|
+
updatedAt: new Date(user.updated_at),
|
|
1007
|
+
}
|
|
1008
|
+
},
|
|
1009
|
+
} : undefined
|
|
1010
|
+
|
|
1011
|
+
// Create session manager from KV
|
|
1012
|
+
const sessionAdapter = kvBinding ? createKVSessionAdapter({ binding: kvBinding, enableUserIndex: true }) : undefined
|
|
1013
|
+
const sessionManager = sessionAdapter ? createSessionManager({ adapter: sessionAdapter }) : undefined
|
|
1014
|
+
|
|
1015
|
+
// Build providers map
|
|
1016
|
+
const providers = new Map()
|
|
1017
|
+
providers.set(passkeyProvider.id, passkeyProvider)
|
|
1018
|
+
|
|
1019
|
+
return {
|
|
1020
|
+
request: c.req.raw,
|
|
1021
|
+
env,
|
|
1022
|
+
config: { basePath: '${basePath}', session: { strategy: '${authManifest.config?.sessionStrategy || "database"}' } },
|
|
1023
|
+
sessionManager,
|
|
1024
|
+
providers,
|
|
1025
|
+
user: null,
|
|
1026
|
+
session: null,
|
|
1027
|
+
url: new URL(c.req.url),
|
|
1028
|
+
responseHeaders: new Headers(),
|
|
1029
|
+
challengeStorage,
|
|
1030
|
+
credentialStorage,
|
|
1031
|
+
userAdapter,
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
`);
|
|
1035
|
+
for (let i = 0; i < passkeyProviders.length; i++) {
|
|
1036
|
+
const provider = passkeyProviders[i];
|
|
1037
|
+
lines.push(`
|
|
1038
|
+
// Get provider from definition
|
|
1039
|
+
const passkeyProvider_${i} = passkeyProviderDef_${i}.provider || passkeyProviderDef_${i}
|
|
1040
|
+
`);
|
|
1041
|
+
lines.push(`
|
|
1042
|
+
// Passkey registration routes
|
|
1043
|
+
app.post('${basePath}/passkey/register/options', async (c) => {
|
|
1044
|
+
const ctx = buildPasskeyAuthContext(c, passkeyProvider_${i})
|
|
1045
|
+
return handlePasskeyRegisterOptions(ctx, '${provider.id}')
|
|
1046
|
+
})
|
|
1047
|
+
|
|
1048
|
+
app.post('${basePath}/passkey/register/verify', async (c) => {
|
|
1049
|
+
const ctx = buildPasskeyAuthContext(c, passkeyProvider_${i})
|
|
1050
|
+
return handlePasskeyRegisterVerify(ctx, '${provider.id}')
|
|
1051
|
+
})
|
|
1052
|
+
|
|
1053
|
+
app.post('${basePath}/passkey/authenticate/options', async (c) => {
|
|
1054
|
+
const ctx = buildPasskeyAuthContext(c, passkeyProvider_${i})
|
|
1055
|
+
return handlePasskeyAuthenticateOptions(ctx, '${provider.id}')
|
|
1056
|
+
})
|
|
1057
|
+
|
|
1058
|
+
app.post('${basePath}/passkey/authenticate/verify', async (c) => {
|
|
1059
|
+
const ctx = buildPasskeyAuthContext(c, passkeyProvider_${i})
|
|
1060
|
+
return handlePasskeyAuthenticateVerify(ctx, '${provider.id}')
|
|
1061
|
+
})
|
|
1062
|
+
`);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return lines.join("\n");
|
|
1066
|
+
}
|
|
723
1067
|
|
|
724
1068
|
// src/virtual-modules/client-entry.ts
|
|
725
|
-
function
|
|
1069
|
+
function collectCssImports(cssImports) {
|
|
1070
|
+
const allCss = /* @__PURE__ */ new Set();
|
|
1071
|
+
for (const imports of cssImports.values()) {
|
|
1072
|
+
for (const info of imports) {
|
|
1073
|
+
allCss.add(info.absolutePath);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return Array.from(allCss);
|
|
1077
|
+
}
|
|
1078
|
+
function generateCssImportStatements(cssPaths) {
|
|
1079
|
+
if (cssPaths.length === 0) {
|
|
1080
|
+
return "";
|
|
1081
|
+
}
|
|
1082
|
+
return cssPaths.map((cssPath) => `import '${cssPath}'`).join("\n") + "\n\n";
|
|
1083
|
+
}
|
|
1084
|
+
function generateClientEntry(clientComponents, cssImports, options) {
|
|
726
1085
|
const { renderer, hydrationEndpoint, isProduction } = options;
|
|
1086
|
+
const cssPaths = collectCssImports(cssImports);
|
|
727
1087
|
if (renderer === "react") {
|
|
728
|
-
return generateReactClientEntry(clientComponents, hydrationEndpoint, isProduction);
|
|
1088
|
+
return generateReactClientEntry(clientComponents, cssPaths, hydrationEndpoint, isProduction);
|
|
729
1089
|
}
|
|
730
|
-
return generateHonoClientEntry(clientComponents, hydrationEndpoint, isProduction);
|
|
1090
|
+
return generateHonoClientEntry(clientComponents, cssPaths, hydrationEndpoint, isProduction);
|
|
731
1091
|
}
|
|
732
1092
|
function generateStaticImportsAndMap(clientComponents) {
|
|
733
1093
|
const components = Array.from(clientComponents.values());
|
|
@@ -746,7 +1106,7 @@ function generateProductionClientEntry(clientComponents, config) {
|
|
|
746
1106
|
* This file is auto-generated by @cloudwerk/vite-plugin - do not edit
|
|
747
1107
|
*/
|
|
748
1108
|
|
|
749
|
-
${config.rendererImports}
|
|
1109
|
+
${config.cssImports}${config.rendererImports}
|
|
750
1110
|
|
|
751
1111
|
// Static component imports
|
|
752
1112
|
${imports}
|
|
@@ -810,10 +1170,12 @@ if (document.readyState === 'loading') {
|
|
|
810
1170
|
export { hydrate }
|
|
811
1171
|
`;
|
|
812
1172
|
}
|
|
813
|
-
function generateHonoClientEntry(clientComponents, _hydrationEndpoint, isProduction = false) {
|
|
1173
|
+
function generateHonoClientEntry(clientComponents, cssPaths, _hydrationEndpoint, isProduction = false) {
|
|
1174
|
+
const cssImportStatements = generateCssImportStatements(cssPaths);
|
|
814
1175
|
if (isProduction) {
|
|
815
1176
|
return generateProductionClientEntry(clientComponents, {
|
|
816
1177
|
header: "Generated Cloudwerk Client Entry (Hono JSX - Production)",
|
|
1178
|
+
cssImports: cssImportStatements,
|
|
817
1179
|
rendererImports: `import { render } from 'hono/jsx/dom'
|
|
818
1180
|
import { jsx } from 'hono/jsx/jsx-runtime'`,
|
|
819
1181
|
additionalDeclarations: "",
|
|
@@ -830,7 +1192,7 @@ import { jsx } from 'hono/jsx/jsx-runtime'`,
|
|
|
830
1192
|
* This file is auto-generated by @cloudwerk/vite-plugin - do not edit
|
|
831
1193
|
*/
|
|
832
1194
|
|
|
833
|
-
import { render } from 'hono/jsx/dom'
|
|
1195
|
+
${cssImportStatements}import { render } from 'hono/jsx/dom'
|
|
834
1196
|
import { jsx } from 'hono/jsx/jsx-runtime'
|
|
835
1197
|
|
|
836
1198
|
// Bundle map for component lookups
|
|
@@ -914,10 +1276,12 @@ if (document.readyState === 'loading') {
|
|
|
914
1276
|
export { hydrate }
|
|
915
1277
|
`;
|
|
916
1278
|
}
|
|
917
|
-
function generateReactClientEntry(clientComponents, _hydrationEndpoint, isProduction = false) {
|
|
1279
|
+
function generateReactClientEntry(clientComponents, cssPaths, _hydrationEndpoint, isProduction = false) {
|
|
1280
|
+
const cssImportStatements = generateCssImportStatements(cssPaths);
|
|
918
1281
|
if (isProduction) {
|
|
919
1282
|
return generateProductionClientEntry(clientComponents, {
|
|
920
1283
|
header: "Generated Cloudwerk Client Entry (React - Production)",
|
|
1284
|
+
cssImports: cssImportStatements,
|
|
921
1285
|
rendererImports: `import { hydrateRoot } from 'react-dom/client'
|
|
922
1286
|
import { createElement } from 'react'`,
|
|
923
1287
|
additionalDeclarations: `
|
|
@@ -937,7 +1301,7 @@ const rootCache = new Map()
|
|
|
937
1301
|
* This file is auto-generated by @cloudwerk/vite-plugin - do not edit
|
|
938
1302
|
*/
|
|
939
1303
|
|
|
940
|
-
import { hydrateRoot, createRoot } from 'react-dom/client'
|
|
1304
|
+
${cssImportStatements}import { hydrateRoot, createRoot } from 'react-dom/client'
|
|
941
1305
|
import { createElement } from 'react'
|
|
942
1306
|
|
|
943
1307
|
// Bundle map for component lookups
|
|
@@ -1594,6 +1958,66 @@ async function scanClientComponents(root, state) {
|
|
|
1594
1958
|
}
|
|
1595
1959
|
await scanDir(appDir);
|
|
1596
1960
|
}
|
|
1961
|
+
var CSS_IMPORT_REGEX = /import\s+(?:['"]([^'"]+\.css)['"]|(?:\w+\s+from\s+)?['"]([^'"]+\.css)['"])/g;
|
|
1962
|
+
function extractCssImports(code) {
|
|
1963
|
+
const imports = [];
|
|
1964
|
+
let match;
|
|
1965
|
+
CSS_IMPORT_REGEX.lastIndex = 0;
|
|
1966
|
+
while ((match = CSS_IMPORT_REGEX.exec(code)) !== null) {
|
|
1967
|
+
const cssPath = match[1] || match[2];
|
|
1968
|
+
if (cssPath) {
|
|
1969
|
+
imports.push(cssPath);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
return imports;
|
|
1973
|
+
}
|
|
1974
|
+
function isLayoutOrPage(filePath) {
|
|
1975
|
+
const basename2 = path3.basename(filePath);
|
|
1976
|
+
const nameWithoutExt = basename2.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
1977
|
+
return {
|
|
1978
|
+
isLayout: nameWithoutExt === "layout",
|
|
1979
|
+
isPage: nameWithoutExt === "page"
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
async function scanCssImports(root, state) {
|
|
1983
|
+
const appDir = path3.resolve(root, state.options.appDir);
|
|
1984
|
+
try {
|
|
1985
|
+
await fs2.promises.access(appDir);
|
|
1986
|
+
} catch {
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
async function scanDir(dir) {
|
|
1990
|
+
const entries = await fs2.promises.readdir(dir, { withFileTypes: true });
|
|
1991
|
+
await Promise.all(
|
|
1992
|
+
entries.map(async (entry) => {
|
|
1993
|
+
const fullPath = path3.join(dir, entry.name);
|
|
1994
|
+
if (entry.isDirectory()) {
|
|
1995
|
+
if (entry.name !== "node_modules" && !entry.name.startsWith(".")) {
|
|
1996
|
+
await scanDir(fullPath);
|
|
1997
|
+
}
|
|
1998
|
+
} else if (entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts"))) {
|
|
1999
|
+
const { isLayout, isPage } = isLayoutOrPage(fullPath);
|
|
2000
|
+
if (isLayout || isPage) {
|
|
2001
|
+
const content = await fs2.promises.readFile(fullPath, "utf-8");
|
|
2002
|
+
const cssImportPaths = extractCssImports(content);
|
|
2003
|
+
if (cssImportPaths.length > 0) {
|
|
2004
|
+
const cssInfos = cssImportPaths.map((cssPath) => ({
|
|
2005
|
+
absolutePath: path3.resolve(path3.dirname(fullPath), cssPath),
|
|
2006
|
+
importedBy: fullPath,
|
|
2007
|
+
isLayout
|
|
2008
|
+
}));
|
|
2009
|
+
state.cssImports.set(fullPath, cssInfos);
|
|
2010
|
+
if (state.options.verbose) {
|
|
2011
|
+
console.log(`[cloudwerk] Found ${cssInfos.length} CSS import(s) in ${path3.relative(root, fullPath)}`);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
})
|
|
2017
|
+
);
|
|
2018
|
+
}
|
|
2019
|
+
await scanDir(appDir);
|
|
2020
|
+
}
|
|
1597
2021
|
function cloudwerkPlugin(options = {}) {
|
|
1598
2022
|
let state = null;
|
|
1599
2023
|
let server = null;
|
|
@@ -1672,6 +2096,62 @@ function cloudwerkPlugin(options = {}) {
|
|
|
1672
2096
|
console.log(`[cloudwerk] Found ${state.serviceManifest.services.length} service(s)`);
|
|
1673
2097
|
}
|
|
1674
2098
|
}
|
|
2099
|
+
async function buildImageManifestIfExists(root) {
|
|
2100
|
+
if (!state) {
|
|
2101
|
+
throw new Error("Plugin state not initialized");
|
|
2102
|
+
}
|
|
2103
|
+
const imagesPath = path3.resolve(root, state.options.appDir, IMAGES_DIR);
|
|
2104
|
+
try {
|
|
2105
|
+
await fs2.promises.access(imagesPath);
|
|
2106
|
+
} catch {
|
|
2107
|
+
state.imageScanResult = null;
|
|
2108
|
+
state.imageManifest = null;
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
state.imageScanResult = await scanImages(
|
|
2112
|
+
path3.resolve(root, state.options.appDir),
|
|
2113
|
+
{ extensions: state.options.config.extensions }
|
|
2114
|
+
);
|
|
2115
|
+
state.imageManifest = buildImageManifest(
|
|
2116
|
+
state.imageScanResult,
|
|
2117
|
+
root
|
|
2118
|
+
);
|
|
2119
|
+
if (state.options.verbose && state.imageManifest.images.length > 0) {
|
|
2120
|
+
console.log(`[cloudwerk] Found ${state.imageManifest.images.length} image(s)`);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
async function buildAuthManifestIfExists(root) {
|
|
2124
|
+
if (!state) {
|
|
2125
|
+
throw new Error("Plugin state not initialized");
|
|
2126
|
+
}
|
|
2127
|
+
const authPath = path3.resolve(root, state.options.appDir, AUTH_DIR);
|
|
2128
|
+
try {
|
|
2129
|
+
await fs2.promises.access(authPath);
|
|
2130
|
+
} catch {
|
|
2131
|
+
state.authScanResult = null;
|
|
2132
|
+
state.authManifest = null;
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
state.authScanResult = await scanAuth(
|
|
2136
|
+
path3.resolve(root, state.options.appDir),
|
|
2137
|
+
{ extensions: state.options.config.extensions }
|
|
2138
|
+
);
|
|
2139
|
+
state.authManifest = await buildAuthManifestWithModules(state.authScanResult);
|
|
2140
|
+
state.serverEntryCache = null;
|
|
2141
|
+
if (state.options.verbose && state.authManifest.providers.length > 0) {
|
|
2142
|
+
console.log(`[cloudwerk] Found ${state.authManifest.providers.length} auth provider(s)`);
|
|
2143
|
+
for (const provider of state.authManifest.providers) {
|
|
2144
|
+
console.log(`[cloudwerk] - ${provider.id} (${provider.type})`);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
function isAuthFile(filePath) {
|
|
2149
|
+
if (!state) return false;
|
|
2150
|
+
const authDir = path3.resolve(state.options.root, state.options.appDir, AUTH_DIR);
|
|
2151
|
+
if (!filePath.startsWith(authDir)) return false;
|
|
2152
|
+
const ext = path3.extname(filePath);
|
|
2153
|
+
return state.options.config.extensions.includes(ext);
|
|
2154
|
+
}
|
|
1675
2155
|
function isRouteFile(filePath) {
|
|
1676
2156
|
if (!state) return false;
|
|
1677
2157
|
const appDir = path3.resolve(state.options.root, state.options.appDir);
|
|
@@ -1699,6 +2179,15 @@ function cloudwerkPlugin(options = {}) {
|
|
|
1699
2179
|
if (nameWithoutExt !== SERVICE_FILE_NAME) return false;
|
|
1700
2180
|
return state.options.config.extensions.includes(ext);
|
|
1701
2181
|
}
|
|
2182
|
+
function isImageFile(filePath) {
|
|
2183
|
+
if (!state) return false;
|
|
2184
|
+
const imagesDir = path3.resolve(state.options.root, state.options.appDir, IMAGES_DIR);
|
|
2185
|
+
if (!filePath.startsWith(imagesDir)) return false;
|
|
2186
|
+
const relativePath = path3.relative(imagesDir, filePath);
|
|
2187
|
+
if (relativePath.includes(path3.sep)) return false;
|
|
2188
|
+
const ext = path3.extname(filePath);
|
|
2189
|
+
return state.options.config.extensions.includes(ext);
|
|
2190
|
+
}
|
|
1702
2191
|
function invalidateVirtualModules() {
|
|
1703
2192
|
if (!server) return;
|
|
1704
2193
|
const idsToInvalidate = [
|
|
@@ -1793,14 +2282,22 @@ function cloudwerkPlugin(options = {}) {
|
|
|
1793
2282
|
queueScanResult: null,
|
|
1794
2283
|
serviceManifest: null,
|
|
1795
2284
|
serviceScanResult: null,
|
|
2285
|
+
imageManifest: null,
|
|
2286
|
+
imageScanResult: null,
|
|
2287
|
+
authManifest: null,
|
|
2288
|
+
authScanResult: null,
|
|
1796
2289
|
clientComponents: /* @__PURE__ */ new Map(),
|
|
2290
|
+
cssImports: /* @__PURE__ */ new Map(),
|
|
1797
2291
|
serverEntryCache: null,
|
|
1798
2292
|
clientEntryCache: null
|
|
1799
2293
|
};
|
|
1800
2294
|
await buildManifest(root);
|
|
1801
2295
|
await buildQueueManifestIfExists(root);
|
|
1802
2296
|
await buildServiceManifestIfExists(root);
|
|
2297
|
+
await buildImageManifestIfExists(root);
|
|
2298
|
+
await buildAuthManifestIfExists(root);
|
|
1803
2299
|
await scanClientComponents(root, state);
|
|
2300
|
+
await scanCssImports(root, state);
|
|
1804
2301
|
},
|
|
1805
2302
|
/**
|
|
1806
2303
|
* Configure the dev server with file watching.
|
|
@@ -1835,6 +2332,22 @@ function cloudwerkPlugin(options = {}) {
|
|
|
1835
2332
|
await buildServiceManifestIfExists(state.options.root);
|
|
1836
2333
|
invalidateVirtualModules();
|
|
1837
2334
|
}
|
|
2335
|
+
if (isImageFile(filePath)) {
|
|
2336
|
+
const imagesDir = path3.resolve(root, state.options.appDir, IMAGES_DIR);
|
|
2337
|
+
if (state?.options.verbose) {
|
|
2338
|
+
console.log(`[cloudwerk] Image added: ${path3.relative(imagesDir, filePath)}`);
|
|
2339
|
+
}
|
|
2340
|
+
await buildImageManifestIfExists(state.options.root);
|
|
2341
|
+
invalidateVirtualModules();
|
|
2342
|
+
}
|
|
2343
|
+
if (isAuthFile(filePath)) {
|
|
2344
|
+
const authDir = path3.resolve(root, state.options.appDir, AUTH_DIR);
|
|
2345
|
+
if (state?.options.verbose) {
|
|
2346
|
+
console.log(`[cloudwerk] Auth file added: ${path3.relative(authDir, filePath)}`);
|
|
2347
|
+
}
|
|
2348
|
+
await buildAuthManifestIfExists(state.options.root);
|
|
2349
|
+
invalidateVirtualModules();
|
|
2350
|
+
}
|
|
1838
2351
|
});
|
|
1839
2352
|
devServer.watcher.on("unlink", async (filePath) => {
|
|
1840
2353
|
if (isRouteFile(filePath)) {
|
|
@@ -1860,6 +2373,22 @@ function cloudwerkPlugin(options = {}) {
|
|
|
1860
2373
|
await buildServiceManifestIfExists(state.options.root);
|
|
1861
2374
|
invalidateVirtualModules();
|
|
1862
2375
|
}
|
|
2376
|
+
if (isImageFile(filePath)) {
|
|
2377
|
+
const imagesDir = path3.resolve(root, state.options.appDir, IMAGES_DIR);
|
|
2378
|
+
if (state?.options.verbose) {
|
|
2379
|
+
console.log(`[cloudwerk] Image removed: ${path3.relative(imagesDir, filePath)}`);
|
|
2380
|
+
}
|
|
2381
|
+
await buildImageManifestIfExists(state.options.root);
|
|
2382
|
+
invalidateVirtualModules();
|
|
2383
|
+
}
|
|
2384
|
+
if (isAuthFile(filePath)) {
|
|
2385
|
+
const authDir = path3.resolve(root, state.options.appDir, AUTH_DIR);
|
|
2386
|
+
if (state?.options.verbose) {
|
|
2387
|
+
console.log(`[cloudwerk] Auth file removed: ${path3.relative(authDir, filePath)}`);
|
|
2388
|
+
}
|
|
2389
|
+
await buildAuthManifestIfExists(state.options.root);
|
|
2390
|
+
invalidateVirtualModules();
|
|
2391
|
+
}
|
|
1863
2392
|
});
|
|
1864
2393
|
devServer.watcher.on("change", async (filePath) => {
|
|
1865
2394
|
if (isRouteFile(filePath)) {
|
|
@@ -1885,6 +2414,22 @@ function cloudwerkPlugin(options = {}) {
|
|
|
1885
2414
|
await buildServiceManifestIfExists(state.options.root);
|
|
1886
2415
|
invalidateVirtualModules();
|
|
1887
2416
|
}
|
|
2417
|
+
if (isImageFile(filePath)) {
|
|
2418
|
+
const imagesDir = path3.resolve(root, state.options.appDir, IMAGES_DIR);
|
|
2419
|
+
if (state?.options.verbose) {
|
|
2420
|
+
console.log(`[cloudwerk] Image changed: ${path3.relative(imagesDir, filePath)}`);
|
|
2421
|
+
}
|
|
2422
|
+
await buildImageManifestIfExists(state.options.root);
|
|
2423
|
+
invalidateVirtualModules();
|
|
2424
|
+
}
|
|
2425
|
+
if (isAuthFile(filePath)) {
|
|
2426
|
+
const authDir = path3.resolve(root, state.options.appDir, AUTH_DIR);
|
|
2427
|
+
if (state?.options.verbose) {
|
|
2428
|
+
console.log(`[cloudwerk] Auth file changed: ${path3.relative(authDir, filePath)}`);
|
|
2429
|
+
}
|
|
2430
|
+
await buildAuthManifestIfExists(state.options.root);
|
|
2431
|
+
invalidateVirtualModules();
|
|
2432
|
+
}
|
|
1888
2433
|
const wranglerPath2 = findWranglerTomlPath(root);
|
|
1889
2434
|
if (wranglerPath2 && filePath === wranglerPath2) {
|
|
1890
2435
|
if (verbose) {
|
|
@@ -1932,7 +2477,9 @@ function cloudwerkPlugin(options = {}) {
|
|
|
1932
2477
|
state.options,
|
|
1933
2478
|
{
|
|
1934
2479
|
queueManifest: state.queueManifest,
|
|
1935
|
-
serviceManifest: state.serviceManifest
|
|
2480
|
+
serviceManifest: state.serviceManifest,
|
|
2481
|
+
authManifest: state.authManifest,
|
|
2482
|
+
cssImports: state.cssImports
|
|
1936
2483
|
}
|
|
1937
2484
|
);
|
|
1938
2485
|
}
|
|
@@ -1945,6 +2492,7 @@ function cloudwerkPlugin(options = {}) {
|
|
|
1945
2492
|
if (!state.clientEntryCache) {
|
|
1946
2493
|
state.clientEntryCache = generateClientEntry(
|
|
1947
2494
|
state.clientComponents,
|
|
2495
|
+
state.cssImports,
|
|
1948
2496
|
state.options
|
|
1949
2497
|
);
|
|
1950
2498
|
}
|
|
@@ -2001,6 +2549,13 @@ function cloudwerkPlugin(options = {}) {
|
|
|
2001
2549
|
"getDurableObject",
|
|
2002
2550
|
"hasDurableObject",
|
|
2003
2551
|
"getDurableObjectNames",
|
|
2552
|
+
"images",
|
|
2553
|
+
"getImages",
|
|
2554
|
+
"hasImages",
|
|
2555
|
+
"getImagesNames",
|
|
2556
|
+
"registerLocalImages",
|
|
2557
|
+
"unregisterLocalImages",
|
|
2558
|
+
"clearLocalImages",
|
|
2004
2559
|
"createLazyBinding"
|
|
2005
2560
|
];
|
|
2006
2561
|
const runtimeImports = [];
|
|
@@ -2031,6 +2586,22 @@ function cloudwerkPlugin(options = {}) {
|
|
|
2031
2586
|
return parts.join("\n");
|
|
2032
2587
|
});
|
|
2033
2588
|
}
|
|
2589
|
+
const { isLayout, isPage } = isLayoutOrPage(id);
|
|
2590
|
+
if (isLayout || isPage) {
|
|
2591
|
+
const cssImportPaths = extractCssImports(transformedCode);
|
|
2592
|
+
if (cssImportPaths.length > 0) {
|
|
2593
|
+
const cssInfos = cssImportPaths.map((cssPath) => ({
|
|
2594
|
+
absolutePath: path3.resolve(path3.dirname(id), cssPath),
|
|
2595
|
+
importedBy: id,
|
|
2596
|
+
isLayout
|
|
2597
|
+
}));
|
|
2598
|
+
state.cssImports.set(id, cssInfos);
|
|
2599
|
+
state.clientEntryCache = null;
|
|
2600
|
+
if (state.options.verbose) {
|
|
2601
|
+
console.log(`[cloudwerk] Detected ${cssInfos.length} CSS import(s) in ${path3.relative(state.options.root, id)}`);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2034
2605
|
if (hasUseClientDirective(transformedCode)) {
|
|
2035
2606
|
const componentId = generateComponentId(id, state.options.root);
|
|
2036
2607
|
const bundlePath = `${state.options.hydrationEndpoint}/${componentId}.js`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudwerk/vite-plugin",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
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.
|
|
23
|
-
"@cloudwerk/ui": "^0.15.
|
|
22
|
+
"@cloudwerk/core": "^0.15.1",
|
|
23
|
+
"@cloudwerk/ui": "^0.15.1"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"vite": "^5.0.0 || ^6.0.0",
|