@useavalon/avalon 0.2.1-canary.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/mod.d.ts +2 -0
- package/dist/mod.js +1 -1
- package/dist/src/cron/define.d.ts +60 -0
- package/dist/src/cron/define.js +1 -0
- package/dist/src/cron/dev-scheduler.d.ts +20 -0
- package/dist/src/cron/dev-scheduler.js +1 -0
- package/dist/src/cron/matcher.d.ts +21 -0
- package/dist/src/cron/matcher.js +1 -0
- package/dist/src/nitro/config.d.ts +34 -0
- package/dist/src/nitro/config.js +1 -1
- package/dist/src/nitro/cron.d.ts +51 -0
- package/dist/src/nitro/cron.js +1 -0
- package/dist/src/schemas/cron.d.ts +63 -0
- package/dist/src/schemas/cron.js +1 -0
- package/dist/src/schemas/routing/index.d.ts +2 -2
- package/dist/src/schemas/routing.d.ts +4 -4
- package/dist/src/vite-plugin/nitro-integration.js +14 -14
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -44,6 +44,44 @@ export default function Home() {
|
|
|
44
44
|
}
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
## Scheduled jobs (cron)
|
|
48
|
+
|
|
49
|
+
Run serverless functions on a schedule. Drop a task file in `tasks/` and map a
|
|
50
|
+
schedule to it — Avalon wires up the right runner for your deployment preset
|
|
51
|
+
(Vercel Cron, Cloudflare Triggers, or the built-in scheduler for the node
|
|
52
|
+
server), built on Nitro's native task system.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// tasks/cleanup.ts
|
|
56
|
+
import { defineCronJob } from '@useavalon/avalon/cron';
|
|
57
|
+
|
|
58
|
+
export default defineCronJob({
|
|
59
|
+
meta: { description: 'Purge expired sessions' },
|
|
60
|
+
async run() {
|
|
61
|
+
await db.sessions.deleteExpired();
|
|
62
|
+
return { result: 'ok' };
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// vite.config.ts
|
|
69
|
+
avalon({
|
|
70
|
+
nitro: {
|
|
71
|
+
cron: [
|
|
72
|
+
// Point at a task file (name is derived from the path -> "cleanup")
|
|
73
|
+
{ schedule: '0 * * * *', handler: 'tasks/cleanup.ts' },
|
|
74
|
+
// Or schedule an auto-discovered task by name, with a named alias
|
|
75
|
+
{ schedule: '@daily', task: 'reports:digest' },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Schedules accept standard 5- or 6-field cron expressions or aliases like
|
|
82
|
+
`@hourly` and `@daily`. Trigger a job on demand with `runCronJob(name)` from
|
|
83
|
+
`@useavalon/avalon/cron`.
|
|
84
|
+
|
|
47
85
|
## Links
|
|
48
86
|
|
|
49
87
|
- [Documentation](https://useavalon.dev/docs/introduction)
|
package/dist/mod.d.ts
CHANGED
|
@@ -45,9 +45,11 @@ export type { LayoutCache, LayoutConfig, LayoutContext, LayoutData, LayoutDiscov
|
|
|
45
45
|
export * from "./src/layout-system.ts";
|
|
46
46
|
export { clearDiscoveryCache, clearMiddlewareCache, discoverScopedMiddleware, executeScopedMiddleware, getContextValue, getMatchingMiddleware, getMiddlewareCacheSize, hasContextValue, invalidateMiddleware, setContextValue, } from "./src/middleware/index.ts";
|
|
47
47
|
export type { MiddlewareDiscoveryOptions, MiddlewareExecutorOptions, MiddlewareFileExport, MiddlewareHandler, MiddlewareRoute, } from "./src/middleware/types.ts";
|
|
48
|
+
export type { ResolvedCronConfig } from "./src/nitro/cron.ts";
|
|
48
49
|
export type { MiddlewareContext } from "./src/nitro/middleware-adapter.ts";
|
|
49
50
|
export { usePersistentState } from "./src/persistence/use-persistent-state.ts";
|
|
50
51
|
export type { ApiMethod, ApiRoute } from "./src/schemas/api.ts";
|
|
51
52
|
export type { MetaTag, RenderOptions, ScriptConfig } from "./src/schemas/core.ts";
|
|
53
|
+
export { CRON_ALIASES, type CronConfig, CronConfigSchema, type CronJob, CronJobSchema, CronScheduleSchema, isValidCronExpression, } from "./src/schemas/cron.ts";
|
|
52
54
|
export type { EnhancedLayoutContext, ErrorRecoveryStrategy, IslandState, IslandStateClearer, IslandStateLoader, IslandStateSaver, LayoutErrorBoundaryProps, LayoutErrorHandler, LayoutFallbackRenderer, LayoutMatcherFunction, LayoutRetryFunction, PersistentIslandContext, PersistentIslandProps, StreamingComponent, StreamingLayoutProps, StreamingReadyCheck, } from "./src/schemas/layout.ts";
|
|
53
55
|
export type { IEnhancedLayoutResolver, IIslandPersistence, ILayoutComponent, ILayoutComposer, ILayoutDiscovery, ILayoutErrorBoundaryComponent, ILayoutErrorRecovery, ILayoutEventEmitter, ILayoutMatcher, ILayoutStreaming, IPersistentIslandComponent, IStreamingLayoutComponent, LayoutDebugInfo, LayoutEventData, LayoutEventHandler, LayoutEventType, LayoutModule, LayoutPerformanceMetrics, LayoutResolutionContext, PageModule, } from "./src/types/layout.ts";
|
package/dist/mod.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{ACTION_ERROR_STATUS,ActionError,createActionClient,defineAction,isAction,isActionError}from"./src/actions/define.js";export{findActionsEntryInfo,generateActionTypes,getActionsWatchDir}from"./src/build/actions-types-generator.js";export{generateIslandManifest,getIslandBundlePath,loadIslandManifest}from"./src/build/island-manifest.js";export{generateIslandTypes,watchAndGenerateTypes}from"./src/build/island-types-generator.js";export{mdxIslandTransform}from"./src/build/mdx-island-transform.js";export{pageIslandTransform}from"./src/build/page-island-transform.js";export{registry as integrationRegistry}from"./src/core/integrations/registry.js";export{registerBuiltinDirectives}from"./src/islands/builtin-directives.js";export{analyzeComponentFile,renderComponentSSROnly}from"./src/islands/component-analysis.js";export{addSvelteSSRCSS,clearSvelteComponentCSS,generateComponentScopeId,getSvelteComponentCSS,getSvelteSSRCSS,getSvelteSSRCSSForHead,getSvelteSSRCSSStats}from"./src/islands/css-utils.js";export{createIslandRegistry,createIslandResolver,createIslandValidator,createIslandWatcher,DEFAULT_DISCOVERY_CONFIG,discoverAllIslands,discoverIslandDirectories,discoverIslandsInDirectory,formatCircularDependency,formatValidationError,formatValidationResult,formatValidationWarning,getDefaultIslandsPath,getQualifiedIslandName,hasDefaultIslandsDirectory,ISLAND_FILE_EXTENSIONS,IslandRegistry,IslandResolver,IslandValidator,IslandWatcher,isIslandsDirectory,isSupportedIslandExtension,parseQualifiedIslandName,validateAllIslands}from"./src/islands/discovery/index.js";export{detectFramework,detectFrameworkFromSrc,resolveIslandPath}from"./src/islands/framework-detection.js";export{getDirective,getRegisteredDirectives,isCustomDirective,registerHydrationDirective,unregisterHydrationDirective}from"./src/islands/hydration-directives.js";export{DEFAULT_PRELOAD_FRAMEWORKS,detectAndLoadIntegration,detectFrameworksFromPageContent,loadIntegration,preloadIntegrations}from"./src/islands/integration-loader.js";export{default as Island,renderIsland}from"./src/islands/island.js";export{clearCache,clearIslandCache,configureCache,getCacheConfig,getCacheStats,invalidateCacheForFile,invalidateCacheForPath,logCacheStats}from"./src/islands/render-cache.js";export{renderToHtml}from"./src/render/ssr.js";export{createNitroCoordinationPlugin,createNitroIntegration,createVirtualModulesPlugin,getAvalonConfig,getViteDevServer,isDevelopmentMode,RESOLVED_VIRTUAL_IDS,VIRTUAL_MODULE_IDS}from"./src/vite-plugin/nitro-integration.js";export{avalon,getLayoutsDir,getNitroConfig,getPagesDir,getResolvedConfig,isNitroEnabled}from"./src/vite-plugin/plugin.js";export async function build(e){throw Error("avalon build() is not available in the published package. Use `vite build` or the Avalon CLI instead.")}export{IslandErrorBoundary,withIslandErrorBoundary}from"./src/components/IslandErrorBoundary.js";export{LayoutErrorBoundary}from"./src/components/LayoutErrorBoundary.js";export*from"./src/layout-system.js";export{clearDiscoveryCache,clearMiddlewareCache,discoverScopedMiddleware,executeScopedMiddleware,getContextValue,getMatchingMiddleware,getMiddlewareCacheSize,hasContextValue,invalidateMiddleware,setContextValue}from"./src/middleware/index.js";export{usePersistentState}from"./src/persistence/use-persistent-state.js";
|
|
1
|
+
export{ACTION_ERROR_STATUS,ActionError,createActionClient,defineAction,isAction,isActionError}from"./src/actions/define.js";export{findActionsEntryInfo,generateActionTypes,getActionsWatchDir}from"./src/build/actions-types-generator.js";export{generateIslandManifest,getIslandBundlePath,loadIslandManifest}from"./src/build/island-manifest.js";export{generateIslandTypes,watchAndGenerateTypes}from"./src/build/island-types-generator.js";export{mdxIslandTransform}from"./src/build/mdx-island-transform.js";export{pageIslandTransform}from"./src/build/page-island-transform.js";export{registry as integrationRegistry}from"./src/core/integrations/registry.js";export{registerBuiltinDirectives}from"./src/islands/builtin-directives.js";export{analyzeComponentFile,renderComponentSSROnly}from"./src/islands/component-analysis.js";export{addSvelteSSRCSS,clearSvelteComponentCSS,generateComponentScopeId,getSvelteComponentCSS,getSvelteSSRCSS,getSvelteSSRCSSForHead,getSvelteSSRCSSStats}from"./src/islands/css-utils.js";export{createIslandRegistry,createIslandResolver,createIslandValidator,createIslandWatcher,DEFAULT_DISCOVERY_CONFIG,discoverAllIslands,discoverIslandDirectories,discoverIslandsInDirectory,formatCircularDependency,formatValidationError,formatValidationResult,formatValidationWarning,getDefaultIslandsPath,getQualifiedIslandName,hasDefaultIslandsDirectory,ISLAND_FILE_EXTENSIONS,IslandRegistry,IslandResolver,IslandValidator,IslandWatcher,isIslandsDirectory,isSupportedIslandExtension,parseQualifiedIslandName,validateAllIslands}from"./src/islands/discovery/index.js";export{detectFramework,detectFrameworkFromSrc,resolveIslandPath}from"./src/islands/framework-detection.js";export{getDirective,getRegisteredDirectives,isCustomDirective,registerHydrationDirective,unregisterHydrationDirective}from"./src/islands/hydration-directives.js";export{DEFAULT_PRELOAD_FRAMEWORKS,detectAndLoadIntegration,detectFrameworksFromPageContent,loadIntegration,preloadIntegrations}from"./src/islands/integration-loader.js";export{default as Island,renderIsland}from"./src/islands/island.js";export{clearCache,clearIslandCache,configureCache,getCacheConfig,getCacheStats,invalidateCacheForFile,invalidateCacheForPath,logCacheStats}from"./src/islands/render-cache.js";export{renderToHtml}from"./src/render/ssr.js";export{createNitroCoordinationPlugin,createNitroIntegration,createVirtualModulesPlugin,getAvalonConfig,getViteDevServer,isDevelopmentMode,RESOLVED_VIRTUAL_IDS,VIRTUAL_MODULE_IDS}from"./src/vite-plugin/nitro-integration.js";export{avalon,getLayoutsDir,getNitroConfig,getPagesDir,getResolvedConfig,isNitroEnabled}from"./src/vite-plugin/plugin.js";export async function build(e){throw Error("avalon build() is not available in the published package. Use `vite build` or the Avalon CLI instead.")}export{IslandErrorBoundary,withIslandErrorBoundary}from"./src/components/IslandErrorBoundary.js";export{LayoutErrorBoundary}from"./src/components/LayoutErrorBoundary.js";export*from"./src/layout-system.js";export{clearDiscoveryCache,clearMiddlewareCache,discoverScopedMiddleware,executeScopedMiddleware,getContextValue,getMatchingMiddleware,getMiddlewareCacheSize,hasContextValue,invalidateMiddleware,setContextValue}from"./src/middleware/index.js";export{usePersistentState}from"./src/persistence/use-persistent-state.js";export{CRON_ALIASES,CronConfigSchema,CronJobSchema,CronScheduleSchema,isValidCronExpression}from"./src/schemas/cron.js";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime helpers for authoring Avalon cron jobs.
|
|
3
|
+
*
|
|
4
|
+
* A cron job is just a Nitro task with a run function. Drop a file in your
|
|
5
|
+
* project's `tasks/` directory (auto-discovered by Nitro) or point a `cron`
|
|
6
|
+
* config entry at it via its `handler` path, then default-export the result of
|
|
7
|
+
* `defineCronJob`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // tasks/cleanup.ts
|
|
12
|
+
* import { defineCronJob } from "@useavalon/avalon/cron";
|
|
13
|
+
*
|
|
14
|
+
* export default defineCronJob({
|
|
15
|
+
* meta: { description: "Purge expired sessions" },
|
|
16
|
+
* async run({ payload }) {
|
|
17
|
+
* await db.sessions.deleteExpired();
|
|
18
|
+
* return { result: "ok" };
|
|
19
|
+
* },
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Then schedule it in your Vite config:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* avalon({
|
|
27
|
+
* nitro: {
|
|
28
|
+
* cron: [{ schedule: "0 * * * *", handler: "tasks/cleanup.ts" }],
|
|
29
|
+
* },
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
import type { Task, TaskContext, TaskPayload, TaskResult } from "nitro/types";
|
|
34
|
+
/**
|
|
35
|
+
* Defines a cron job (a Nitro task) with a strongly-typed run function.
|
|
36
|
+
*
|
|
37
|
+
* This is a thin, semantically-named wrapper over Nitro's `defineTask` so cron
|
|
38
|
+
* handler files read clearly. The returned value must be the file's default
|
|
39
|
+
* export so Nitro can discover and run it.
|
|
40
|
+
*
|
|
41
|
+
* @typeParam RT - The type of the value returned by the task's `run` function.
|
|
42
|
+
* @param definition - The task definition (`meta` + `run`).
|
|
43
|
+
* @returns The Nitro task, ready to be default-exported.
|
|
44
|
+
*/
|
|
45
|
+
export declare function defineCronJob<RT = unknown>(definition: Task<RT>): Task<RT>;
|
|
46
|
+
/**
|
|
47
|
+
* Manually triggers a registered cron job / task by name.
|
|
48
|
+
*
|
|
49
|
+
* Useful for running a scheduled job on demand (e.g. from an API route or a
|
|
50
|
+
* test) without waiting for its schedule.
|
|
51
|
+
*
|
|
52
|
+
* @param name - The task name (as registered / derived from its handler path).
|
|
53
|
+
* @param options - Optional payload and context passed to the task's `run`.
|
|
54
|
+
* @returns The task result.
|
|
55
|
+
*/
|
|
56
|
+
export declare function runCronJob<RT = unknown>(name: string, options?: {
|
|
57
|
+
payload?: TaskPayload;
|
|
58
|
+
context?: TaskContext;
|
|
59
|
+
}): Promise<TaskResult<RT>>;
|
|
60
|
+
export type { Task, TaskContext, TaskPayload, TaskResult } from "nitro/types";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineTask as e,runTask as t}from"nitro/task";export function defineCronJob(t){return e(t)}export function runCronJob(e,n){return t(e,n)}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-mode cron scheduler.
|
|
3
|
+
*
|
|
4
|
+
* In production, cron jobs run through Nitro's native task scheduler (wired up
|
|
5
|
+
* per deployment preset). But enabling Nitro's task system during `vite dev`
|
|
6
|
+
* makes Nitro take over the SSR environment (swapping Avalon's runnable SSR for
|
|
7
|
+
* a fetchable dev-worker), which changes rendering behavior. To keep dev SSR
|
|
8
|
+
* owned by Avalon, we do NOT enable Nitro tasks in dev — instead this plugin
|
|
9
|
+
* runs the configured jobs directly in the Vite process.
|
|
10
|
+
*
|
|
11
|
+
* Each second it checks every job's schedule and, on a match, loads the task
|
|
12
|
+
* handler via Vite's SSR module loader and invokes its `run` function.
|
|
13
|
+
*/
|
|
14
|
+
import type { Plugin } from "vite";
|
|
15
|
+
import type { CronConfig } from "../schemas/cron.ts";
|
|
16
|
+
/**
|
|
17
|
+
* Creates the dev-only cron scheduler plugin. No-op when `cron` is empty or
|
|
18
|
+
* outside of `vite dev`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createCronDevSchedulerPlugin(cron: CronConfig | undefined, verbose?: boolean): Plugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isAbsolute as e,resolve as t}from"node:path";import{pathToFileURL as n}from"node:url";import{isRunnableDevEnvironment as r}from"vite";import{hasSecondsField as i,matchesCron as a}from"./matcher.js";function o(n,r){if(n.handler){let i=e(n.handler)?n.handler:t(r,n.handler);return{name:n.name??n.handler,handlerPath:i}}if(n.task){let e=n.task.replaceAll(`:`,`/`);return{name:n.task,handlerPath:t(r,`tasks`,`${e}.ts`)}}return null}export function createCronDevSchedulerPlugin(e,t=!1){return{name:`avalon:cron-dev-scheduler`,apply:`serve`,configureServer(s){if(!e||e.length===0)return;let c=s.config.root||process.cwd(),l=[];for(let t of e){let e=o(t,c);e&&l.push({schedule:t.schedule,...e,lastKey:-1})}if(l.length===0)return;let u=async e=>{let t=s.environments?.ssr;return t&&r(t)?await t.runner.import(e):await import(n(e).href)},d=async e=>{try{let n=(await u(e.handlerPath))?.default;if(!n||typeof n.run!=`function`){s.config.logger.warn(`[avalon:cron] Task "${e.name}" has no default export with a run() function.`);return}let r=await n.run({payload:{},context:{}});t&&s.config.logger.info(`[avalon:cron] Ran "${e.name}" (${e.schedule}).`),s.ws.send({type:`custom`,event:`avalon:cron:run`,data:{name:e.name,schedule:e.schedule,at:new Date().toISOString(),result:r?.result??null}})}catch(t){s.config.logger.error(`[avalon:cron] Job "${e.name}" (${e.schedule}) failed: ${t instanceof Error?t.stack||t.message:String(t)}`)}},f=setInterval(()=>{let e=new Date;for(let t of l){if(!a(t.schedule,e))continue;let n=i(t.schedule)?Math.floor(e.getTime()/1e3):Math.floor(e.getTime()/6e4);n!==t.lastKey&&(t.lastKey=n,d(t))}},1e3);typeof f.unref==`function`&&f.unref(),s.httpServer?.once(`close`,()=>clearInterval(f));let p=l.map(e=>`${e.name} @ ${e.schedule}`).join(`, `),m=l.length===1?``:`s`;s.config.logger.info(`\n ⏰ Avalon cron: scheduling ${l.length} dev job${m} (${p})`)}}}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal cron expression matcher used by the dev-mode scheduler.
|
|
3
|
+
*
|
|
4
|
+
* Production scheduling is handled by Nitro's native task runner (which uses
|
|
5
|
+
* `croner`). During `vite dev` we deliberately avoid enabling Nitro's task
|
|
6
|
+
* system (it would take over the SSR environment), so this lightweight matcher
|
|
7
|
+
* lets Avalon fire jobs itself while keeping SSR intact.
|
|
8
|
+
*
|
|
9
|
+
* Supports standard 5-field (min hour dom month dow) and 6-field
|
|
10
|
+
* (sec min hour dom month dow) expressions with `*`, `,`, `-`, and `/`, plus
|
|
11
|
+
* the common named aliases (`@daily`, etc.).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Returns true if the given date matches the cron expression.
|
|
15
|
+
*
|
|
16
|
+
* Day-of-month and day-of-week follow standard cron semantics: when both are
|
|
17
|
+
* restricted (neither is `*`), a match on *either* is sufficient.
|
|
18
|
+
*/
|
|
19
|
+
export declare function matchesCron(expression: string, date: Date): boolean;
|
|
20
|
+
/** True if the expression includes a seconds field (6-field form). */
|
|
21
|
+
export declare function hasSecondsField(expression: string): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={"@yearly":`0 0 1 1 *`,"@annually":`0 0 1 1 *`,"@monthly":`0 0 1 * *`,"@weekly":`0 0 * * 0`,"@daily":`0 0 * * *`,"@midnight":`0 0 * * *`,"@hourly":`0 * * * *`};function t(e,t,n,r){if(e===`*`||e===``)return{start:n,end:r};let[i,a]=e.split(`-`),o=Number.parseInt(i,10);return a===void 0?{start:o,end:t?r:o}:{start:o,end:Number.parseInt(a,10)}}function n(e,{min:n,max:r}){let i=new Set;for(let a of e.split(`,`)){let[e,o]=a.split(`/`),s=o?Number.parseInt(o,10):1;if(!Number.isFinite(s)||s<1)continue;let{start:c,end:l}=t(e,o!==void 0,n,r);if(!(!Number.isFinite(c)||!Number.isFinite(l)))for(let e=c;e<=l;e+=s)e>=n&&e<=r&&i.add(e)}return i}export function matchesCron(t,r){let i=(e[t.trim()]??t.trim()).split(/\s+/),a=`0`,o,s,c,l,u;if(i.length===6)[a,o,s,c,l,u]=i;else if(i.length===5)[o,s,c,l,u]=i;else return!1;let d=n(a,{min:0,max:59}).has(r.getSeconds()),f=n(o,{min:0,max:59}).has(r.getMinutes()),p=n(s,{min:0,max:23}).has(r.getHours()),m=n(l,{min:1,max:12}).has(r.getMonth()+1),h=c!==`*`&&c!==`?`,g=u!==`*`&&u!==`?`,_=n(c===`?`?`*`:c,{min:1,max:31}).has(r.getDate()),v=n(u===`?`?`*`:u,{min:0,max:7}),y=v.has(r.getDay())||r.getDay()===0&&v.has(7),b;return b=h&&g?_||y:_&&y,d&&f&&p&&m&&b}export function hasSecondsField(t){return(e[t.trim()]??t.trim()).split(/\s+/).length===6}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* - `/*.html`, `/*.css`: Short cache with revalidation
|
|
20
20
|
* - `/favicon.ico`: Medium cache (1 day)
|
|
21
21
|
*/
|
|
22
|
+
import type { CronConfig } from "../schemas/cron.ts";
|
|
22
23
|
import type { ResolvedAvalonConfig } from "../vite-plugin/types.ts";
|
|
23
24
|
/**
|
|
24
25
|
* Cache configuration options for route rules
|
|
@@ -190,6 +191,30 @@ export interface AvalonNitroConfig {
|
|
|
190
191
|
* @example ["app/shared/styles/main.css"]
|
|
191
192
|
*/
|
|
192
193
|
globalCSS?: string[];
|
|
194
|
+
/**
|
|
195
|
+
* Scheduled cron jobs.
|
|
196
|
+
*
|
|
197
|
+
* First-class cron support built on Nitro's native task scheduler. Each
|
|
198
|
+
* entry maps a cron schedule to a task that runs on that schedule. Nitro
|
|
199
|
+
* wires up the correct runner for the active deployment preset (Vercel cron,
|
|
200
|
+
* Cloudflare triggers, or the built-in scheduler for the node-server preset).
|
|
201
|
+
*
|
|
202
|
+
* Reference the code to run either with `handler` (a path to a task file
|
|
203
|
+
* relative to the project root) or `task` (the name of a task in the
|
|
204
|
+
* auto-scanned `tasks/` directory). Task files default-export a job created
|
|
205
|
+
* with `defineCronJob` (from `@useavalon/avalon/cron`).
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```ts
|
|
209
|
+
* cron: [
|
|
210
|
+
* // Run a task file every hour
|
|
211
|
+
* { schedule: "0 * * * *", handler: "tasks/cleanup.ts" },
|
|
212
|
+
* // Schedule an auto-discovered task by name
|
|
213
|
+
* { schedule: "@daily", task: "reports:digest" },
|
|
214
|
+
* ]
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
cron?: CronConfig;
|
|
193
218
|
}
|
|
194
219
|
/**
|
|
195
220
|
* Nitro configuration output structure
|
|
@@ -243,6 +268,15 @@ export interface NitroConfigOutput {
|
|
|
243
268
|
staticAssets?: StaticAssetsConfig;
|
|
244
269
|
/** Prerender configuration for SSG */
|
|
245
270
|
prerender?: AvalonNitroConfig["prerender"];
|
|
271
|
+
/** Whether Nitro's experimental task system must be enabled (set when cron jobs exist) */
|
|
272
|
+
experimentalTasks?: boolean;
|
|
273
|
+
/** Nitro `tasks` map: task name -> handler/description (from cron config) */
|
|
274
|
+
tasks?: Record<string, {
|
|
275
|
+
handler?: string;
|
|
276
|
+
description?: string;
|
|
277
|
+
}>;
|
|
278
|
+
/** Nitro `scheduledTasks` map: cron expression -> task name(s) */
|
|
279
|
+
scheduledTasks?: Record<string, string | string[]>;
|
|
246
280
|
}
|
|
247
281
|
/**
|
|
248
282
|
* Avalon-specific runtime configuration stored in Nitro's runtimeConfig
|
package/dist/src/nitro/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DEFAULT_STATIC_ASSETS_CONFIG={publicDir:`public`,buildDir:`dist`,compression:!0,cacheControl:`public, max-age=31536000, immutable`,mutableCacheControl:`public, max-age=0, must-revalidate`,headers:{}};export const VALID_V3_PRESETS=[`node_server`,`node_middleware`,`vercel`,`cloudflare_module`,`cloudflare_pages`,`deno_deploy`,`deno_server`,`netlify`,`netlify_functions`,`netlify_edge`,`aws_lambda`,`azure_swa`,`firebase_functions`,`render_com`,`static`,`browser`];export function resolvePresetName(e){if(VALID_V3_PRESETS.includes(e))return e;throw Error(`Unknown Nitro preset: "${e}". Valid presets: ${VALID_V3_PRESETS.join(`, `)}`)}export const DEFAULT_NITRO_CONFIG={preset:`node_server`,serverDir:`server`,streaming:!0,compressPublicAssets:!0};export function createNitroConfig(
|
|
1
|
+
import{resolveCronConfig as e}from"./cron.js";export const DEFAULT_STATIC_ASSETS_CONFIG={publicDir:`public`,buildDir:`dist`,compression:!0,cacheControl:`public, max-age=31536000, immutable`,mutableCacheControl:`public, max-age=0, must-revalidate`,headers:{}};export const VALID_V3_PRESETS=[`node_server`,`node_middleware`,`vercel`,`cloudflare_module`,`cloudflare_pages`,`deno_deploy`,`deno_server`,`netlify`,`netlify_functions`,`netlify_edge`,`aws_lambda`,`azure_swa`,`firebase_functions`,`render_com`,`static`,`browser`];export function resolvePresetName(e){if(VALID_V3_PRESETS.includes(e))return e;throw Error(`Unknown Nitro preset: "${e}". Valid presets: ${VALID_V3_PRESETS.join(`, `)}`)}export const DEFAULT_NITRO_CONFIG={preset:`node_server`,serverDir:`server`,streaming:!0,compressPublicAssets:!0};export function createNitroConfig(n,i){let a=n.pagesDir??i.pagesDir,o=i.layoutsDir,s={streaming:n.streaming??DEFAULT_NITRO_CONFIG.streaming,pagesDir:a,layoutsDir:o};if(n.runtimeConfig&&`nitro`in n.runtimeConfig)throw Error(`The "nitro" key in runtimeConfig is reserved by Nitro v3 and cannot be used.`);let c={avalon:s,...n.runtimeConfig},l={...DEFAULT_STATIC_ASSETS_CONFIG,...n.staticAssets},u=mergeRouteRules(createDefaultStaticAssetRouteRules(l),n.routeRules??{}),d=[{dir:l.publicDir??DEFAULT_STATIC_ASSETS_CONFIG.publicDir,baseURL:`/`,maxAge:0}],f=e(n.cron,process.cwd()),p=n.renderer===!1?!1:n.renderer?{...n.renderer}:void 0;return{preset:resolvePresetName(process.env.NITRO_PRESET??n.preset??DEFAULT_NITRO_CONFIG.preset),serverDir:n.serverDir??DEFAULT_NITRO_CONFIG.serverDir,routeRules:u,runtimeConfig:c,publicRuntimeConfig:n.publicRuntimeConfig,renderer:p,compatibilityDate:n.compatibilityDate,traceDeps:n.traceDeps,rolldownConfig:n.rolldownConfig,serverEntry:n.serverEntry,compressPublicAssets:n.compressPublicAssets??DEFAULT_NITRO_CONFIG.compressPublicAssets,publicAssets:d,staticAssets:l,prerender:n.prerender,experimentalTasks:f.experimentalTasks,tasks:f.tasks,scheduledTasks:f.scheduledTasks}}export function createDefaultStaticAssetRouteRules(e){let n=e.cacheControl??DEFAULT_STATIC_ASSETS_CONFIG.cacheControl,r=e.mutableCacheControl??DEFAULT_STATIC_ASSETS_CONFIG.mutableCacheControl;return{"/assets/**":{headers:{"Cache-Control":n,...e.headers}},"/islands/**":{headers:{"Cache-Control":n,...e.headers}},"/chunks/**":{headers:{"Cache-Control":n,...e.headers}},"/_nuxt/**":{headers:{"Cache-Control":n,...e.headers}},"/**/*.woff":{headers:{"Cache-Control":n,...e.headers}},"/**/*.woff2":{headers:{"Cache-Control":n,...e.headers}},"/**/*.html":{headers:{"Cache-Control":r,...e.headers}},"/favicon.ico":{headers:{"Cache-Control":`public, max-age=86400`,...e.headers}},"/**/*.css":{headers:{"Cache-Control":r,...e.headers}}}}export function isValidPreset(e){return VALID_V3_PRESETS.includes(e)}export function mergeRouteRules(e,t){let n={...e};for(let[e,r]of Object.entries(t))n[e]?n[e]={...n[e],...r,headers:{...n[e].headers,...r.headers}}:n[e]=r;return n}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron / scheduled task resolution for Avalon's Nitro integration.
|
|
3
|
+
*
|
|
4
|
+
* Translates Avalon's high-level `cron` config into the three pieces of native
|
|
5
|
+
* Nitro configuration required to run scheduled tasks:
|
|
6
|
+
*
|
|
7
|
+
* - `experimental.tasks` — must be enabled for Nitro's task system.
|
|
8
|
+
* - `tasks` — a map of task name -> `{ handler, description }`. Only jobs that
|
|
9
|
+
* provide an explicit `handler` file are registered here; jobs referencing an
|
|
10
|
+
* auto-scanned task by `name` rely on Nitro's `tasks/` directory scanning.
|
|
11
|
+
* - `scheduledTasks` — a map of cron expression -> task name(s).
|
|
12
|
+
*
|
|
13
|
+
* Nitro then registers the correct scheduled runner for the active deployment
|
|
14
|
+
* preset (e.g. the Vercel cron handler, Cloudflare triggers, or the in-process
|
|
15
|
+
* scheduler used by the node-server preset).
|
|
16
|
+
*/
|
|
17
|
+
import { type CronConfig } from "../schemas/cron.ts";
|
|
18
|
+
/**
|
|
19
|
+
* The Nitro-native task configuration produced from an Avalon cron config.
|
|
20
|
+
*/
|
|
21
|
+
export interface ResolvedCronConfig {
|
|
22
|
+
/** Whether Nitro's experimental task system must be enabled. */
|
|
23
|
+
experimentalTasks: boolean;
|
|
24
|
+
/** Nitro `tasks` map: task name -> handler/description. */
|
|
25
|
+
tasks: Record<string, {
|
|
26
|
+
handler?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
}>;
|
|
29
|
+
/** Nitro `scheduledTasks` map: cron expression -> task name(s). */
|
|
30
|
+
scheduledTasks: Record<string, string | string[]>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Derives a Nitro task name from a handler file path.
|
|
34
|
+
*
|
|
35
|
+
* When the handler lives inside the project's `tasks/` directory, the name
|
|
36
|
+
* mirrors Nitro's own scanning convention (path relative to `tasks/`, without
|
|
37
|
+
* extension, with `/` replaced by `:`) so the two never register duplicates.
|
|
38
|
+
* Otherwise the file's base name (without extension) is used.
|
|
39
|
+
*/
|
|
40
|
+
export declare function deriveTaskName(handlerPath: string, projectRoot: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Validates and resolves an Avalon cron config into native Nitro task config.
|
|
43
|
+
*
|
|
44
|
+
* @param cron - The user-provided cron configuration (validated with Zod).
|
|
45
|
+
* @param projectRoot - Absolute path to the project root, used to resolve
|
|
46
|
+
* handler file paths and derive task names.
|
|
47
|
+
* @returns The resolved Nitro task configuration.
|
|
48
|
+
* @throws {ValidationError} If the cron config fails schema validation.
|
|
49
|
+
* @throws {Error} If two jobs resolve to the same task name with different handlers.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveCronConfig(cron: CronConfig | undefined, projectRoot: string): ResolvedCronConfig;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{basename as e,isAbsolute as t,relative as n,resolve as r}from"node:path";import{CronConfigSchema as i}from"../schemas/cron.js";import{validate as a}from"../schemas/index.js";export function deriveTaskName(i,a){let o=t(i)?i:r(a,i),s=n(r(a,`tasks`),o);return s&&!s.startsWith(`..`)&&!t(s)?s.replace(/\.[A-Za-z]+$/,``).replace(/\/index$/,``).replace(/[/\\]/g,`:`):e(o).replace(/\.[A-Za-z]+$/,``)}function s(e,t,n){let r=e[t];if(r===void 0){e[t]=n;return}e[t]=Array.isArray(r)?[...r,n]:[r,n]}export function resolveCronConfig(e,n){let o={experimentalTasks:!1,tasks:{},scheduledTasks:{}};if(!e||e.length===0)return o;let c=a(i,e,`Invalid cron configuration`),u={},d={};for(let e of c){let i=l(e,n);if(e.handler){let a=t(e.handler)?e.handler:r(n,e.handler),o=u[i];if(o?.handler&&o.handler!==a)throw Error(`Cron task name "${i}" is used by two different handlers ("${o.handler}" and "${a}"). Give one of the jobs an explicit, unique \`name\`.`);u[i]={handler:a,description:e.description??o?.description??``}}s(d,e.schedule.trim(),i)}return{experimentalTasks:!0,tasks:u,scheduledTasks:d}}function l(e,t){if(e.task)return e.task;if(e.name)return e.name;if(e.handler)return deriveTaskName(e.handler,t);throw Error(`Cron job must specify either 'handler' or 'task'.`)}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Cron / scheduled task schemas for Avalon.
|
|
4
|
+
*
|
|
5
|
+
* Avalon's cron support is a thin, first-class layer over Nitro v3's native
|
|
6
|
+
* task scheduler (which uses `croner` under the hood). A cron config maps a
|
|
7
|
+
* schedule to a task, and Nitro registers the appropriate scheduled runner for
|
|
8
|
+
* the active deployment preset (Vercel cron, Cloudflare triggers, or the
|
|
9
|
+
* built-in in-process scheduler for the node-server preset).
|
|
10
|
+
*
|
|
11
|
+
* Two ways to reference the code that runs on a schedule:
|
|
12
|
+
*
|
|
13
|
+
* 1. `handler` — a path to a task file (relative to the project root). Avalon
|
|
14
|
+
* registers it as a Nitro task and schedules it. The task file default-exports
|
|
15
|
+
* a task created with `defineCronJob` (or Nitro's `defineTask`).
|
|
16
|
+
*
|
|
17
|
+
* 2. `task` — the name of a task that already lives in the auto-scanned
|
|
18
|
+
* `tasks/` directory. Avalon only wires up the schedule.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Named cron aliases supported by croner / Nitro, in addition to standard
|
|
22
|
+
* 5- or 6-field cron expressions.
|
|
23
|
+
*/
|
|
24
|
+
export declare const CRON_ALIASES: readonly ["@yearly", "@annually", "@monthly", "@weekly", "@daily", "@midnight", "@hourly"];
|
|
25
|
+
/**
|
|
26
|
+
* Validates a cron schedule string.
|
|
27
|
+
*
|
|
28
|
+
* Accepts either a named alias (e.g. `@daily`) or a standard cron expression
|
|
29
|
+
* with 5 fields (minute hour day-of-month month day-of-week) or 6 fields
|
|
30
|
+
* (with a leading seconds field). Each field may contain digits, `*`, `,`,
|
|
31
|
+
* `-`, `/`, `?`, `L`, `W`, and `#` — the character set croner understands.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isValidCronExpression(value: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Zod schema for a single cron schedule string.
|
|
36
|
+
*/
|
|
37
|
+
export declare const CronScheduleSchema: z.ZodString;
|
|
38
|
+
/**
|
|
39
|
+
* Zod schema for a single cron job definition.
|
|
40
|
+
*
|
|
41
|
+
* Exactly one of `handler` or `task` must be provided.
|
|
42
|
+
*/
|
|
43
|
+
export declare const CronJobSchema: z.ZodObject<{
|
|
44
|
+
schedule: z.ZodString;
|
|
45
|
+
handler: z.ZodOptional<z.ZodString>;
|
|
46
|
+
task: z.ZodOptional<z.ZodString>;
|
|
47
|
+
name: z.ZodOptional<z.ZodString>;
|
|
48
|
+
description: z.ZodOptional<z.ZodString>;
|
|
49
|
+
}, z.core.$strip>;
|
|
50
|
+
/**
|
|
51
|
+
* Zod schema for the full cron configuration: an array of cron jobs.
|
|
52
|
+
*/
|
|
53
|
+
export declare const CronConfigSchema: z.ZodArray<z.ZodObject<{
|
|
54
|
+
schedule: z.ZodString;
|
|
55
|
+
handler: z.ZodOptional<z.ZodString>;
|
|
56
|
+
task: z.ZodOptional<z.ZodString>;
|
|
57
|
+
name: z.ZodOptional<z.ZodString>;
|
|
58
|
+
description: z.ZodOptional<z.ZodString>;
|
|
59
|
+
}, z.core.$strip>>;
|
|
60
|
+
/** A single cron job definition. */
|
|
61
|
+
export type CronJob = z.infer<typeof CronJobSchema>;
|
|
62
|
+
/** The full cron configuration passed to `nitro.cron`. */
|
|
63
|
+
export type CronConfig = z.infer<typeof CronConfigSchema>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{z as e}from"zod";export const CRON_ALIASES=[`@yearly`,`@annually`,`@monthly`,`@weekly`,`@daily`,`@midnight`,`@hourly`];export function isValidCronExpression(e){let t=e.trim();if(t.length===0)return!1;if(CRON_ALIASES.includes(t))return!0;let n=t.split(/\s+/);if(n.length<5||n.length>6)return!1;let r=/^[0-9*,\-/?LW#]+$/i;return n.every(e=>r.test(e))}export const CronScheduleSchema=e.string().min(1,`Cron schedule cannot be empty`).refine(isValidCronExpression,{message:`Invalid cron expression. Use a 5- or 6-field cron string (e.g. '0 0 * * *') or a named alias (e.g. '@daily').`});export const CronJobSchema=e.object({schedule:CronScheduleSchema,handler:e.string().min(1).optional(),task:e.string().min(1).optional(),name:e.string().min(1).regex(/^[A-Za-z0-9_:.-]+$/,`Task name may only contain letters, numbers, and the characters _ : . -`).optional(),description:e.string().optional()}).refine(e=>!!e.handler!=!!e.task,{message:`Each cron job must specify exactly one of 'handler' or 'task'.`});export const CronConfigSchema=e.array(CronJobSchema);
|
|
@@ -3,7 +3,7 @@ export declare const routingValidators: {
|
|
|
3
3
|
readonly fileSystemRoute: (data: unknown) => {
|
|
4
4
|
pattern: any;
|
|
5
5
|
filePath: string;
|
|
6
|
-
routeType: "group" | "static" | "
|
|
6
|
+
routeType: "group" | "static" | "dynamic" | "catch-all" | "index";
|
|
7
7
|
dynamicSegments: string[];
|
|
8
8
|
priority: number;
|
|
9
9
|
isPrivate: boolean;
|
|
@@ -93,7 +93,7 @@ export declare const safeRoutingValidators: {
|
|
|
93
93
|
readonly fileSystemRoute: (data: unknown) => import("../index.ts").ValidationResult<{
|
|
94
94
|
pattern: any;
|
|
95
95
|
filePath: string;
|
|
96
|
-
routeType: "group" | "static" | "
|
|
96
|
+
routeType: "group" | "static" | "dynamic" | "catch-all" | "index";
|
|
97
97
|
dynamicSegments: string[];
|
|
98
98
|
priority: number;
|
|
99
99
|
isPrivate: boolean;
|
|
@@ -6,9 +6,9 @@ import { z } from "zod";
|
|
|
6
6
|
export declare const RouteTypeSchema: z.ZodEnum<{
|
|
7
7
|
group: "group";
|
|
8
8
|
static: "static";
|
|
9
|
-
index: "index";
|
|
10
9
|
dynamic: "dynamic";
|
|
11
10
|
"catch-all": "catch-all";
|
|
11
|
+
index: "index";
|
|
12
12
|
}>;
|
|
13
13
|
/**
|
|
14
14
|
* File System Route Schema - Represents a discovered route from the file system
|
|
@@ -19,9 +19,9 @@ export declare const FileSystemRouteSchema: z.ZodObject<{
|
|
|
19
19
|
routeType: z.ZodEnum<{
|
|
20
20
|
group: "group";
|
|
21
21
|
static: "static";
|
|
22
|
-
index: "index";
|
|
23
22
|
dynamic: "dynamic";
|
|
24
23
|
"catch-all": "catch-all";
|
|
24
|
+
index: "index";
|
|
25
25
|
}>;
|
|
26
26
|
dynamicSegments: z.ZodArray<z.ZodString>;
|
|
27
27
|
priority: z.ZodNumber;
|
|
@@ -268,9 +268,9 @@ export declare const RouteHandlerSchema: z.ZodObject<{
|
|
|
268
268
|
routeType: z.ZodEnum<{
|
|
269
269
|
group: "group";
|
|
270
270
|
static: "static";
|
|
271
|
-
index: "index";
|
|
272
271
|
dynamic: "dynamic";
|
|
273
272
|
"catch-all": "catch-all";
|
|
273
|
+
index: "index";
|
|
274
274
|
}>;
|
|
275
275
|
priority: z.ZodNumber;
|
|
276
276
|
dynamicSegments: z.ZodArray<z.ZodString>;
|
|
@@ -286,9 +286,9 @@ export declare const RouteCacheEntrySchema: z.ZodObject<{
|
|
|
286
286
|
routeType: z.ZodEnum<{
|
|
287
287
|
group: "group";
|
|
288
288
|
static: "static";
|
|
289
|
-
index: "index";
|
|
290
289
|
dynamic: "dynamic";
|
|
291
290
|
"catch-all": "catch-all";
|
|
291
|
+
index: "index";
|
|
292
292
|
}>;
|
|
293
293
|
dynamicSegments: z.ZodArray<z.ZodString>;
|
|
294
294
|
priority: z.ZodNumber;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import{existsSync as e,readdirSync as t,readFileSync as n,rmSync as r}from"node:fs";import{stat as i}from"node:fs/promises";import{createRequire as a}from"node:module";import{dirname as o,join as s,relative as c,resolve as l}from"node:path";import{nitro as u}from"nitro/vite";import{isRunnableDevEnvironment as d}from"vite";import{generateActionTypes as f}from"../build/actions-types-generator.js";import{
|
|
1
|
+
import{existsSync as e,readdirSync as t,readFileSync as n,rmSync as r}from"node:fs";import{stat as i}from"node:fs/promises";import{createRequire as a}from"node:module";import{dirname as o,join as s,relative as c,resolve as l}from"node:path";import{nitro as u}from"nitro/vite";import{isRunnableDevEnvironment as d}from"vite";import{generateActionTypes as f}from"../build/actions-types-generator.js";import{createCronDevSchedulerPlugin as p}from"../cron/dev-scheduler.js";import{getUniversalCSSForHead as m}from"../islands/universal-css-collector.js";import{getUniversalHeadForInjection as h,injectSolidHydrationScriptIfNeeded as g}from"../islands/universal-head-collector.js";import{clearMiddlewareCache as _,discoverScopedMiddleware as v,executeScopedMiddleware as y}from"../middleware/index.js";import{createNitroConfig as b}from"../nitro/config.js";import{createIslandManifestPlugin as x,createNitroBuildPlugin as S,createSourceMapConfig as C,createSourceMapPlugin as w}from"../nitro/index.js";import{collectCssFromModuleGraph as T,injectSsrCss as E}from"../render/collect-css.js";import{generateErrorPage as D,generateFallback404 as O}from"../render/error-pages.js";import{generateComponentId as k}from"../server-islands/manifest.js";import{resolveToRelativePath as ee}from"./server-islands-plugin.js";function te(e){let t=oe(e),r=Object.entries(t),i=`export const serverIslandManifest = ${JSON.stringify(t)};\n\n`;i+=`export const serverIslandLoaders = {
|
|
2
2
|
`;for(let[e,t]of r)i+=` ${JSON.stringify(e)}: () => import(${JSON.stringify(t)}),\n`;i+=`};
|
|
3
3
|
|
|
4
|
-
`;let a={};for(let[t,i]of r)if(i.endsWith(`.svelte`))try{let r=n(s(e,i.startsWith(`/`)?i.slice(1):i),`utf8`).match(/<style[^>]*>([\s\S]*?)<\/style>/);r&&(a[t]=r[1].trim())}catch{}return i+=`export const serverIslandCSS = ${JSON.stringify(a)};\n`,i}function
|
|
5
|
-
`}function
|
|
6
|
-
`)}function
|
|
4
|
+
`;let a={};for(let[t,i]of r)if(i.endsWith(`.svelte`))try{let r=n(s(e,i.startsWith(`/`)?i.slice(1):i),`utf8`).match(/<style[^>]*>([\s\S]*?)<\/style>/);r&&(a[t]=r[1].trim())}catch{}return i+=`export const serverIslandCSS = ${JSON.stringify(a)};\n`,i}function A(e,t){return new Promise((n,r)=>{let i=[],a=e=>i.push(e),o=()=>{l(),n(Buffer.concat(i).toString(`utf8`))},s=e=>{l(),r(e)},c=setTimeout(()=>{l(),r(Error(t))},15e3);function l(){clearTimeout(c),e.off(`data`,a),e.off(`end`,o),e.off(`error`,s)}e.on(`data`,a),e.on(`end`,o),e.on(`error`,s)})}const j=/^[ \t]*import\s+([A-Z]\w*)\s+from\s+(['"][^'"]+['"])/gm,ne=new Set([`node_modules`,`dist`,`.output`,`.netlify`,`.vercel`,`.cloudflare`,`.wrangler`,`.firebase`,`.amplify-hosting`,`.git`]);function re(e,t){return new RegExp(String.raw`<${t}\s[\s\S]*?\bserver\s*[={/>]`).test(e)}function ie(e,t){return!(!/\.(tsx|jsx)$/.test(e)||/\.(test|spec)\.[jt]sx$/.test(e)||t.includes(`__tests__`))}function M(e,n){let r;try{r=t(e,{withFileTypes:!0})}catch{return}for(let t of r){let r=s(e,t.name);t.isDirectory()?ne.has(t.name)||M(r,n):t.isFile()&&ie(t.name,r)&&n.push(r)}}function ae(e,t,n,r){j.lastIndex=0;let i=j.exec(t);for(;i!==null;i=j.exec(t)){let a=i[1],o=i[2].slice(1,-1);if(!re(t,a))continue;let s=ee(o,e,n),c=k(s);c in r||(r[c]=s)}}function oe(t){let r={},i=[];for(let n of[`app`,`src`]){let r=s(t,n);e(r)&&M(r,i)}for(let e of i){let i;try{i=n(e,`utf8`)}catch{continue}/\bserver\s*[={/>]/.test(i)&&ae(e,i,t,r)}return r}function N(t){let n=[`ts`,`tsx`,`js`,`jsx`,`mts`,`mjs`];for(let r of[`app`,`src`])for(let i of n){let n=`/${r}/actions/index.${i}`;if(e(s(t,n.slice(1))))return n}}function se(e){let t=N(e);return t?`export { server } from ${JSON.stringify(t)};\n`:`export const server = {};
|
|
5
|
+
`}function ce(){return[`import { createActionClient } from "@useavalon/avalon/actions";`,`export const actions = createActionClient();`,`export default actions;`,``].join(`
|
|
6
|
+
`)}function le(e,t){try{let n;if(e.startsWith(`dev.`)){let t=e.slice(4);n=Buffer.from(t,`base64url`).toString(`utf8`)}else n=t(e);let r=JSON.parse(n),i,a;return r.__island&&(i=r.__island,delete r.__island),r.__src&&(a=r.__src,delete r.__src),{ok:!0,props:r,srcPath:a,islandMeta:i}}catch{return{ok:!1}}}function ue(e,t,n,r){let i=e.split(`/`)[2]?.split(`?`)[0]||``,a=t.elementId??`si-${i}`,o=t.componentSrc??n,s=JSON.stringify(r),c=t.condition??`on:client`,l=t.framework??`preact`;return`<script type="module">
|
|
7
7
|
import{hydrateServerIsland}from"/@fs${P(`src/client/server-island-hydrate.ts`)}";
|
|
8
8
|
hydrateServerIsland(${JSON.stringify(a)},${JSON.stringify(o)},${s},${JSON.stringify(c)},${JSON.stringify(l)});
|
|
9
|
-
<\/script>`}function P(t){let n=s(o(a(import.meta.url).resolve(`@useavalon/avalon`)),t);if(t.endsWith(`.ts`)&&!e(n)){let t=n.replace(/\.ts$/,`.js`);if(e(t))return t}return n}function
|
|
10
|
-
`)}\n`}export function createNitroIntegration(e,t={}){let n=y(t,e),r=process.cwd(),i=P(`src/server-islands/route.ts`),a=P(`src/actions/route.ts`),o={preset:n.preset,serverDir:t.serverDir??n.serverDir??`./server`,routeRules:n.routeRules,runtimeConfig:n.runtimeConfig,compatibilityDate:n.compatibilityDate,scanDirs:[`.`],noExternals:[`estree-walker`,/^@useavalon\//,/^estree-util/,/^preact/,...de(e.core),/^vue/,/^@vue\//],handlers:[{route:`/_server-islands/**`,handler:i},{route:`/_actions/**`,handler:a}],virtual:{"virtual:server-island-manifest":()=>te(r),"virtual:avalon-actions-manifest":()=>oe(r),"virtual:server-island-key":()=>{let e=process.env.AVALON_KEY??``;return`export const serverIslandKey = ${JSON.stringify(e)};\n`},"virtual:server-island-integrations":()=>he(e.integrations)}};t.renderer===!1?o.renderer=!1:n.renderer&&(o.renderer=n.renderer),n.publicRuntimeConfig&&(o.publicRuntimeConfig=n.publicRuntimeConfig),n.publicAssets&&(o.publicAssets=n.publicAssets),n.compressPublicAssets&&(o.compressPublicAssets=n.compressPublicAssets),n.serverEntry&&(o.serverEntry=n.serverEntry);let s=n.traceDeps??[];o.traceDeps=[...new Set([`undici`,...s])],n.prerender&&(o.prerender={routes:[],crawlLinks:!1});let c=u(o),l=createNitroCoordinationPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),d=createVirtualModulesPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),f=x(e,t),p=b(e,{verbose:e.verbose,generatePreloadHints:!0}),m=C(S(t.preset??`node_server`,e.isDev));return{nitroOptions:n,plugins:[...Array.isArray(c)?c:[c],l,d,f,p,m]}}export function createNitroCoordinationPlugin(e){let{avalonConfig:t,verbose:n}=e;return{name:`avalon:nitro-coordination`,enforce:`pre`,config(e,{command:t}){if(t===`serve`)return{server:{watch:{ignored:[`**/.output/**`,`**/dist/**`,`**/.netlify/**`,`**/.vercel/**`,`**/.cloudflare/**`,`**/.wrangler/**`,`**/.firebase/**`,`**/.amplify-hosting/**`]},fs:{deny:[`.output`,`dist`,`.netlify`,`.vercel`,`.cloudflare`,`.wrangler`,`.firebase`,`.amplify-hosting`]}}}},configResolved(e){globalThis.__avalonHydrationMode=e.command===`serve`?`entry-client`:`per-island`;try{f(e.root||process.cwd())}catch{}},configureServer(e){globalThis.__viteDevServer=e;let i=e.config.root||process.cwd(),a=e=>{if(e.includes(`/actions/`))try{f(i)}catch{}};e.watcher.on(`add`,a),e.watcher.on(`unlink`,a);let o=e.config.root||process.cwd(),c=e.config.build?.outDir?s(o,e.config.build.outDir):s(o,`dist`);try{r(c,{recursive:!0,force:!0})}catch{}try{r(s(o,`.output`),{recursive:!0,force:!0})}catch{}let l=!1,u=new Promise(t=>{let n=()=>{(e.environments?.nitro)?.devServer?.entry||l?(l=!0,t()):setTimeout(n,50)};setTimeout(n,100),setTimeout(()=>{l=!0,t()},15e3)});e.middlewares.use(async(e,t,n)=>{if(l)return n();let r=e.url||`/`;if(r.startsWith(`/@`)||r.startsWith(`/__`)||/\/[^/]+\.[a-z0-9]+(\?|$)/i.test(r))return n();await u,n()});let p=null;async function m(){return p||=await _({baseDir:`${e.config.root||process.cwd()}/src`,devMode:!1}),p}function h(){p=null}ye(e,t,n,h),m().catch(e=>{console.warn(`[middleware] Failed to discover middleware:`,e)});let g=e.environments?.ssr,v=!!g&&d(g);v&&I(e,t.integrations,n).catch(e=>{console.error(`[prewarm] Core modules pre-warm failed:`,e)}),e.middlewares.use(async(n,r,i)=>{let a=n.url||`/`;if(!a.startsWith(`/_server-islands/`)||!v)return i();try{let{decrypt:i}=await e.ssrLoadModule(P(`src/server-islands/encryption.ts`)),{h:o,renderToString:s}=await V(e,t.core),c=new URL(a,`http://${n.headers.host||`localhost`}`).searchParams.get(`p`)||``;if(!c&&n.method===`POST`&&(c=await k(n,`Timed out reading server island request body`)),!c){r.statusCode=400,r.setHeader(`Content-Type`,`text/plain`),r.end(`Missing encrypted props`);return}let l=ce(c,i);if(!l.ok){r.statusCode=400,r.setHeader(`Content-Type`,`text/plain`),r.end(`Bad Request: decryption failed`);return}let{props:u,srcPath:d,islandMeta:f}=l;if(!d){r.statusCode=404,r.setHeader(`Content-Type`,`text/plain`),r.end(`Component not found (no __src in payload)`);return}let p=(await e.ssrLoadModule(d)).default;if(typeof p!=`function`){r.statusCode=500,r.setHeader(`Content-Type`,`text/plain`),r.end(`Module "${d}" does not export a default component function`);return}let m=s(o(p,u));f&&(m+=le(a,f,d,u)),r.statusCode=200,r.setHeader(`Content-Type`,`text/html`),r.setHeader(`Cache-Control`,`private, no-store`),r.end(m)}catch(e){console.error(`[server-islands] Dev handler error:`,e),r.statusCode=500,r.setHeader(`Content-Type`,`text/plain`),r.end(`Server island render failed: ${e instanceof Error?e.message:String(e)}`)}}),e.middlewares.use(async(t,n,r)=>{let i=t.url||`/`;if(!i.startsWith(`/_actions/`)||!v)return r();try{let r=N(e.config.root||process.cwd()),[a,o,s]=await Promise.all([e.ssrLoadModule(P(`src/actions/endpoint.ts`)),e.ssrLoadModule(P(`src/actions/registry.ts`)),r?e.ssrLoadModule(r):Promise.resolve({server:{}})]),c=o.flattenActions(s.server),l=a.defineActionHandler({registry:c,isDev:!0}),u=new URL(i,`http://${t.headers.host||`localhost`}`),d=(t.method||`GET`).toUpperCase(),f;d===`POST`&&(f=await k(t,`Timed out reading action request body`));let p=await l({url:u,web:{request:new Request(u,{method:d,headers:t.headers,body:f})},context:{}});n.statusCode=p.status,p.headers.forEach((e,t)=>{n.setHeader(t,e)}),n.end(await p.text())}catch(e){console.error(`[actions] Dev handler error:`,e),n.statusCode=500,n.setHeader(`Content-Type`,`application/json`),n.end(JSON.stringify({error:{code:`INTERNAL_SERVER_ERROR`,message:e instanceof Error?e.message:String(e)}}))}}),e.middlewares.use(async(r,i,a)=>{if(!v)return a();let o=r.url||`/`;if(o.endsWith(`.html`)&&(o=o.slice(0,-5)||`/`),o===`/index`&&(o=`/`),o.startsWith(`/@`)||o.startsWith(`/__`)||o.startsWith(`/node_modules/`)||o.startsWith(`/src/client/`)||o.startsWith(`/packages/`)||o.includes(`.`)&&!o.endsWith(`/`)||o.startsWith(`/api/`))return a();try{if(await ge(e,o,r,i,m,n)||await Le(e,o,t,i))return;let a=await Re(e,o,t);if(a){i.statusCode=200,i.setHeader(`Content-Type`,`text/html`),i.end(a);return}await F(e,o,i,t)}catch(e){console.error(`[SSR Error]`,e),i.statusCode=500,i.setHeader(`Content-Type`,`text/html`),i.end(E(e))}})},buildStart(){}}}async function ge(e,t,n,r,i,a){let o=performance.now(),s=await i();if(s.length===0)return!1;let c={};for(let[e,t]of Object.entries(n.headers))typeof t==`string`?c[e]=t:Array.isArray(t)&&(c[e]=t.join(`, `));let l=`http://${n.headers.host||`localhost`}${t}`,u=await v({url:l,method:n.method||`GET`,path:t,node:{req:n,res:r},req:new Request(l,{method:n.method||`GET`,headers:c}),context:{}},s,{devMode:!1}),d=performance.now()-o;return d>100&&console.warn(`⚠️ Slow middleware: ${d.toFixed(0)}ms for ${t}`),u?(r.statusCode=u.status,u.headers.forEach((e,t)=>{r.setHeader(t,e)}),r.end(await u.text()),!0):!1}async function F(e,t,n,r){try{let{discoverErrorPages:i,getErrorPageModule:a,generateDefaultErrorPage:o}=await import(`../nitro/error-handler.js`),s=a(404,await i({isDev:r.isDev,pagesDir:r.pagesDir,loadPageModule:async t=>await e.ssrLoadModule(t)}));if(s?.default&&typeof s.default==`function`){let{renderToHtml:e}=await import(`../render/ssr.js`),r=s.default,i=await e({component:()=>r({statusCode:404,message:`Page not found: ${t}`,url:t})},{});n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(i);return}let c=o(404,`Page not found: ${t}`,r.isDev);n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(c)}catch{n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(D(t))}}async function I(e,t,n){let r=performance.now(),i=[{path:P(`src/render/ssr.ts`),assignTo:`ssr`},{path:P(`src/core/layout/enhanced-layout-resolver.ts`),assignTo:`layout`},{path:P(`src/middleware/index.ts`),assignTo:null},...t.map(e=>({path:ue(e,`server/renderer.ts`),assignTo:null}))],a=(await Promise.allSettled(i.map(async({path:t,assignTo:n})=>{let r=await e.ssrLoadModule(t);n===`ssr`&&(z=r),n===`layout`&&(B=r)}))).filter(e=>e.status===`fulfilled`).length,o=performance.now()-r;n&&a>0&&console.log(`🔥 SSR ready in ${o.toFixed(0)}ms (${a}/${i.length} core modules)`)}export function createVirtualModulesPlugin(e){let{avalonConfig:n,nitroConfig:r,verbose:i}=e,a=null,o=s,u=c,d=l,f=process.cwd(),p=[];function m(e){try{let n=t(e,{withFileTypes:!0});for(let t of n){let n=o(e,t.name);if(t.isDirectory()&&t.name!==`node_modules`&&!t.name.startsWith(`.`))m(n);else if(t.isFile()&&t.name.endsWith(`.css`)){let e=u(f,n).replaceAll(`\\`,`/`);p.push(e.startsWith(`/`)?e:`/${e}`)}}}catch{}}function h(){p.length=0;for(let e of r.globalCSS??[])p.push(e.startsWith(`/`)?e:`/${e}`);n.modules&&m(d(f,n.modules.dir)),m(d(f,n.layoutsDir));let e=d(f,n.layoutsDir,`..`);e!==f&&e!==d(f,n.layoutsDir)&&e.startsWith(f)&&m(e)}return n.isDev&&h(),{name:`avalon:nitro-virtual-modules`,enforce:`pre`,resolveId(e){return e===VIRTUAL_MODULE_IDS.PAGE_ROUTES?RESOLVED_VIRTUAL_IDS.PAGE_ROUTES:e===VIRTUAL_MODULE_IDS.PAGE_LOADER?RESOLVED_VIRTUAL_IDS.PAGE_LOADER:e===VIRTUAL_MODULE_IDS.ISLAND_MANIFEST?RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST:e===VIRTUAL_MODULE_IDS.RUNTIME_CONFIG?RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG:e===VIRTUAL_MODULE_IDS.CONFIG?RESOLVED_VIRTUAL_IDS.CONFIG:e===VIRTUAL_MODULE_IDS.LAYOUTS?RESOLVED_VIRTUAL_IDS.LAYOUTS:e===VIRTUAL_MODULE_IDS.ASSETS?RESOLVED_VIRTUAL_IDS.ASSETS:e===VIRTUAL_MODULE_IDS.RENDERER?RESOLVED_VIRTUAL_IDS.RENDERER:e===VIRTUAL_MODULE_IDS.CLIENT_ENTRY?RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY:e===VIRTUAL_MODULE_IDS.INTEGRATION_LOADER?RESOLVED_VIRTUAL_IDS.INTEGRATION_LOADER:e===VIRTUAL_MODULE_IDS.ACTIONS?RESOLVED_VIRTUAL_IDS.ACTIONS:null},async load(e){return e===RESOLVED_VIRTUAL_IDS.PAGE_ROUTES?await be(n,i):e===RESOLVED_VIRTUAL_IDS.PAGE_LOADER?await xe(n,i):e===RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST?Se():e===RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG?Ce(n,r):e===RESOLVED_VIRTUAL_IDS.CONFIG?generateConfigModule(n,r):e===RESOLVED_VIRTUAL_IDS.LAYOUTS?(a||=await we(n,r,p),a):e===RESOLVED_VIRTUAL_IDS.ASSETS?Te(r):e===RESOLVED_VIRTUAL_IDS.RENDERER?Ee(n):e===RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY?await ke(n,r):e===RESOLVED_VIRTUAL_IDS.INTEGRATION_LOADER?generateIntegrationLoaderModule(n):e===RESOLVED_VIRTUAL_IDS.ACTIONS?se():null},handleHotUpdate({file:e,server:t}){let{isPage:n,isComponent:r,isLayout:i,isCss:o}=_e(e);(n||r||i||o)&&(n&&(L(t,RESOLVED_VIRTUAL_IDS.PAGE_ROUTES),L(t,RESOLVED_VIRTUAL_IDS.PAGE_LOADER)),i&&(a=null,h(),L(t,RESOLVED_VIRTUAL_IDS.LAYOUTS),L(t,RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY)),setTimeout(()=>{t.ws.send({type:`full-reload`,path:`*`})},500)),ve(e)&&L(t,RESOLVED_VIRTUAL_IDS.CONFIG)}}}function L(e,t){let n=e.moduleGraph.getModuleById(t);n&&e.moduleGraph.invalidateModule(n)}function _e(e){let t=e.endsWith(`.css`);return{isPage:e.includes(`/pages/`)&&!t,isComponent:e.includes(`/components/`)&&/\.[tj]sx?$/.test(e),isLayout:(e.includes(`/layouts/`)||e.includes(`_layout`))&&/\.[tj]sx?$/.test(e),isCss:t}}function ve(e){return e.includes(`vite.config`)||e.includes(`avalon.config`)||e.includes(`nitro.config`)}function ye(e,t,n,r){e.watcher.on(`change`,e=>{e.includes(`_middleware`)&&(g(),r?.()),(e.includes(`/render/`)||e.includes(`/layout/`)||e.includes(`/islands/`))&&(z=null,B=null),(e.includes(`/layouts/`)||e.includes(`_layout`))&&globalThis.__avalonLayoutResolver?.clearCache?.()}),e.watcher.on(`add`,e=>{e.includes(`_middleware`)&&(g(),r?.())}),e.watcher.on(`unlink`,e=>{e.includes(`_middleware`)&&(g(),r?.())})}async function be(e,t){try{let{getAllPageDirs:t}=await import(`./module-discovery.js`),{discoverPageRoutesFromMultipleDirs:n}=await import(`../nitro/route-discovery.js`),r=await n(await t(e.pagesDir,e.modules,process.cwd()),{developmentMode:e.isDev});return`export const pageRoutes = ${JSON.stringify(r,null,2)};\nexport default pageRoutes;\n`}catch{return`export const pageRoutes = [];
|
|
9
|
+
<\/script>`}function P(t){let n=s(o(a(import.meta.url).resolve(`@useavalon/avalon`)),t);if(t.endsWith(`.ts`)&&!e(n)){let t=n.replace(/\.ts$/,`.js`);if(e(t))return t}return n}function de(t,n){let r=s(o(a(s(process.cwd(),`package.json`)).resolve(`@useavalon/${t}`)),n);if(n.endsWith(`.ts`)&&!e(r)){let t=r.replace(/\.ts$/,`.js`);if(e(t))return t}return r}export const VIRTUAL_MODULE_IDS={PAGE_ROUTES:`virtual:avalon/page-routes`,PAGE_LOADER:`virtual:avalon/page-loader`,ISLAND_MANIFEST:`virtual:avalon/island-manifest`,RUNTIME_CONFIG:`virtual:avalon/runtime-config`,CONFIG:`virtual:avalon/config`,LAYOUTS:`virtual:avalon/layouts`,ASSETS:`virtual:avalon/assets`,RENDERER:`virtual:avalon/renderer`,CLIENT_ENTRY:`virtual:avalon/client-entry`,INTEGRATION_LOADER:`virtual:avalon/integration-loader`,ACTIONS:`virtual:avalon/actions`};export const RESOLVED_VIRTUAL_IDS={PAGE_ROUTES:`\0${VIRTUAL_MODULE_IDS.PAGE_ROUTES}`,PAGE_LOADER:`\0${VIRTUAL_MODULE_IDS.PAGE_LOADER}`,ISLAND_MANIFEST:`\0${VIRTUAL_MODULE_IDS.ISLAND_MANIFEST}`,RUNTIME_CONFIG:`\0${VIRTUAL_MODULE_IDS.RUNTIME_CONFIG}`,CONFIG:`\0${VIRTUAL_MODULE_IDS.CONFIG}`,LAYOUTS:`\0${VIRTUAL_MODULE_IDS.LAYOUTS}`,ASSETS:`\0${VIRTUAL_MODULE_IDS.ASSETS}`,RENDERER:`\0${VIRTUAL_MODULE_IDS.RENDERER}`,CLIENT_ENTRY:`\0${VIRTUAL_MODULE_IDS.CLIENT_ENTRY}`,INTEGRATION_LOADER:`\0${VIRTUAL_MODULE_IDS.INTEGRATION_LOADER}`,ACTIONS:`\0${VIRTUAL_MODULE_IDS.ACTIONS}`};function fe(e){return e===`react`?[]:[/^react/,/^react-dom/]}function pe(e){return e===`react`?[`import { createElement as h } from 'react';`,`import { renderToString as preactRenderToString } from 'react-dom/server';`]:[`import { h } from 'preact';`,`import preactRenderToString from 'preact-render-to-string';`]}function me(e){return e===`react`?[`import { setShellRenderToString, setShellElementFactory } from '@useavalon/avalon/render/shell-engine';`,`import { renderToString as __reactRenderToString } from 'react-dom/server';`,`import { createElement as __reactCreateElement, Fragment as __reactFragment } from 'react';`,`setShellRenderToString((vnode) => __reactRenderToString(vnode));`,`setShellElementFactory(__reactCreateElement, __reactFragment);`]:[]}function he(e){return e===`react`?`@useavalon/react/client`:`@useavalon/preact/client`}function ge(e){let t=P(`src/core/integrations/registry.ts`),n=[`solid`,`vue`,`svelte`,`lit`].filter(t=>e.includes(t));return`${[`import { registry } from "${t}";`,...n.map(e=>`import { ${e}Integration } from "@useavalon/${e}";`),...n.map(e=>`if (${e}Integration) registry.register(${e}Integration);`)].join(`
|
|
10
|
+
`)}\n`}export function createNitroIntegration(e,t={}){let n=b(t,e),r=process.cwd(),i=P(`src/server-islands/route.ts`),a=P(`src/actions/route.ts`),o={preset:n.preset,serverDir:t.serverDir??n.serverDir??`./server`,routeRules:n.routeRules,runtimeConfig:n.runtimeConfig,compatibilityDate:n.compatibilityDate,scanDirs:[`.`],noExternals:[`estree-walker`,/^@useavalon\//,/^estree-util/,/^preact/,...fe(e.core),/^vue/,/^@vue\//],handlers:[{route:`/_server-islands/**`,handler:i},{route:`/_actions/**`,handler:a}],virtual:{"virtual:server-island-manifest":()=>te(r),"virtual:avalon-actions-manifest":()=>se(r),"virtual:server-island-key":()=>{let e=process.env.AVALON_KEY??``;return`export const serverIslandKey = ${JSON.stringify(e)};\n`},"virtual:server-island-integrations":()=>ge(e.integrations)}};t.renderer===!1?o.renderer=!1:n.renderer&&(o.renderer=n.renderer),n.publicRuntimeConfig&&(o.publicRuntimeConfig=n.publicRuntimeConfig),n.publicAssets&&(o.publicAssets=n.publicAssets),n.compressPublicAssets&&(o.compressPublicAssets=n.compressPublicAssets),n.serverEntry&&(o.serverEntry=n.serverEntry);let s=process.argv.includes(`build`)||process.env.NODE_ENV===`production`;n.experimentalTasks&&s&&(o.experimental={...o.experimental,tasks:!0},n.tasks&&Object.keys(n.tasks).length>0&&(o.tasks=n.tasks),n.scheduledTasks&&Object.keys(n.scheduledTasks).length>0&&(o.scheduledTasks=n.scheduledTasks));let c=n.traceDeps??[];o.traceDeps=[...new Set([`undici`,...c])],n.prerender&&(o.prerender={routes:[],crawlLinks:!1});let l=u(o),d=createNitroCoordinationPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),f=createVirtualModulesPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),m=S(e,t),h=x(e,{verbose:e.verbose,generatePreloadHints:!0}),g=w(C(t.preset??`node_server`,e.isDev)),_=p(t.cron,e.verbose);return{nitroOptions:n,plugins:[...Array.isArray(l)?l:[l],d,f,m,h,g,_]}}export function createNitroCoordinationPlugin(e){let{avalonConfig:t,verbose:n}=e;return{name:`avalon:nitro-coordination`,enforce:`pre`,config(e,{command:t}){if(t===`serve`)return{server:{watch:{ignored:[`**/.output/**`,`**/dist/**`,`**/.netlify/**`,`**/.vercel/**`,`**/.cloudflare/**`,`**/.wrangler/**`,`**/.firebase/**`,`**/.amplify-hosting/**`]},fs:{deny:[`.output`,`dist`,`.netlify`,`.vercel`,`.cloudflare`,`.wrangler`,`.firebase`,`.amplify-hosting`]}}}},configResolved(e){globalThis.__avalonHydrationMode=e.command===`serve`?`entry-client`:`per-island`;try{f(e.root||process.cwd())}catch{}},configureServer(e){globalThis.__viteDevServer=e;let i=e.config.root||process.cwd(),a=e=>{if(e.includes(`/actions/`))try{f(i)}catch{}};e.watcher.on(`add`,a),e.watcher.on(`unlink`,a);let o=e.config.root||process.cwd(),c=e.config.build?.outDir?s(o,e.config.build.outDir):s(o,`dist`);try{r(c,{recursive:!0,force:!0})}catch{}try{r(s(o,`.output`),{recursive:!0,force:!0})}catch{}let l=!1,u=new Promise(t=>{let n=()=>{(e.environments?.nitro)?.devServer?.entry||l?(l=!0,t()):setTimeout(n,50)};setTimeout(n,100),setTimeout(()=>{l=!0,t()},15e3)});e.middlewares.use(async(e,t,n)=>{if(l)return n();let r=e.url||`/`;if(r.startsWith(`/@`)||r.startsWith(`/__`)||/\/[^/]+\.[a-z0-9]+(\?|$)/i.test(r))return n();await u,n()});let p=null;async function m(){return p||=await v({baseDir:`${e.config.root||process.cwd()}/src`,devMode:!1}),p}function h(){p=null}ye(e,t,n,h),m().catch(e=>{console.warn(`[middleware] Failed to discover middleware:`,e)});let g=e.environments?.ssr,_=!!g&&d(g);_&&L(e,t.integrations,n).catch(e=>{console.error(`[prewarm] Core modules pre-warm failed:`,e)}),e.middlewares.use(async(n,r,i)=>{let a=n.url||`/`;if(!a.startsWith(`/_server-islands/`)||!_)return i();try{let{decrypt:i}=await e.ssrLoadModule(P(`src/server-islands/encryption.ts`)),{h:o,renderToString:s}=await H(e,t.core),c=new URL(a,`http://${n.headers.host||`localhost`}`).searchParams.get(`p`)||``;if(!c&&n.method===`POST`&&(c=await A(n,`Timed out reading server island request body`)),!c){r.statusCode=400,r.setHeader(`Content-Type`,`text/plain`),r.end(`Missing encrypted props`);return}let l=le(c,i);if(!l.ok){r.statusCode=400,r.setHeader(`Content-Type`,`text/plain`),r.end(`Bad Request: decryption failed`);return}let{props:u,srcPath:d,islandMeta:f}=l;if(!d){r.statusCode=404,r.setHeader(`Content-Type`,`text/plain`),r.end(`Component not found (no __src in payload)`);return}let p=(await e.ssrLoadModule(d)).default;if(typeof p!=`function`){r.statusCode=500,r.setHeader(`Content-Type`,`text/plain`),r.end(`Module "${d}" does not export a default component function`);return}let m=s(o(p,u));f&&(m+=ue(a,f,d,u)),r.statusCode=200,r.setHeader(`Content-Type`,`text/html`),r.setHeader(`Cache-Control`,`private, no-store`),r.end(m)}catch(e){console.error(`[server-islands] Dev handler error:`,e),r.statusCode=500,r.setHeader(`Content-Type`,`text/plain`),r.end(`Server island render failed: ${e instanceof Error?e.message:String(e)}`)}}),e.middlewares.use(async(t,n,r)=>{let i=t.url||`/`;if(!i.startsWith(`/_actions/`)||!_)return r();try{let r=N(e.config.root||process.cwd()),[a,o,s]=await Promise.all([e.ssrLoadModule(P(`src/actions/endpoint.ts`)),e.ssrLoadModule(P(`src/actions/registry.ts`)),r?e.ssrLoadModule(r):Promise.resolve({server:{}})]),c=o.flattenActions(s.server),l=a.defineActionHandler({registry:c,isDev:!0}),u=new URL(i,`http://${t.headers.host||`localhost`}`),d=(t.method||`GET`).toUpperCase(),f;d===`POST`&&(f=await A(t,`Timed out reading action request body`));let p=await l({url:u,web:{request:new Request(u,{method:d,headers:t.headers,body:f})},context:{}});n.statusCode=p.status,p.headers.forEach((e,t)=>{n.setHeader(t,e)}),n.end(await p.text())}catch(e){console.error(`[actions] Dev handler error:`,e),n.statusCode=500,n.setHeader(`Content-Type`,`application/json`),n.end(JSON.stringify({error:{code:`INTERNAL_SERVER_ERROR`,message:e instanceof Error?e.message:String(e)}}))}}),e.middlewares.use(async(r,i,a)=>{if(!_)return a();let o=r.url||`/`;if(o.endsWith(`.html`)&&(o=o.slice(0,-5)||`/`),o===`/index`&&(o=`/`),o.startsWith(`/@`)||o.startsWith(`/__`)||o.startsWith(`/node_modules/`)||o.startsWith(`/src/client/`)||o.startsWith(`/packages/`)||o.includes(`.`)&&!o.endsWith(`/`)||o.startsWith(`/api/`))return a();try{if(await F(e,o,r,i,m,n)||await Le(e,o,t,i))return;let a=await Re(e,o,t);if(a){i.statusCode=200,i.setHeader(`Content-Type`,`text/html`),i.end(a);return}await I(e,o,i,t)}catch(e){console.error(`[SSR Error]`,e),i.statusCode=500,i.setHeader(`Content-Type`,`text/html`),i.end(D(e))}})},buildStart(){}}}async function F(e,t,n,r,i,a){let o=performance.now(),s=await i();if(s.length===0)return!1;let c={};for(let[e,t]of Object.entries(n.headers))typeof t==`string`?c[e]=t:Array.isArray(t)&&(c[e]=t.join(`, `));let l=`http://${n.headers.host||`localhost`}${t}`,u=await y({url:l,method:n.method||`GET`,path:t,node:{req:n,res:r},req:new Request(l,{method:n.method||`GET`,headers:c}),context:{}},s,{devMode:!1}),d=performance.now()-o;return d>100&&console.warn(`⚠️ Slow middleware: ${d.toFixed(0)}ms for ${t}`),u?(r.statusCode=u.status,u.headers.forEach((e,t)=>{r.setHeader(t,e)}),r.end(await u.text()),!0):!1}async function I(e,t,n,r){try{let{discoverErrorPages:i,getErrorPageModule:a,generateDefaultErrorPage:o}=await import(`../nitro/error-handler.js`),s=a(404,await i({isDev:r.isDev,pagesDir:r.pagesDir,loadPageModule:async t=>await e.ssrLoadModule(t)}));if(s?.default&&typeof s.default==`function`){let{renderToHtml:e}=await import(`../render/ssr.js`),r=s.default,i=await e({component:()=>r({statusCode:404,message:`Page not found: ${t}`,url:t})},{});n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(i);return}let c=o(404,`Page not found: ${t}`,r.isDev);n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(c)}catch{n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(O(t))}}async function L(e,t,n){let r=performance.now(),i=[{path:P(`src/render/ssr.ts`),assignTo:`ssr`},{path:P(`src/core/layout/enhanced-layout-resolver.ts`),assignTo:`layout`},{path:P(`src/middleware/index.ts`),assignTo:null},...t.map(e=>({path:de(e,`server/renderer.ts`),assignTo:null}))],a=(await Promise.allSettled(i.map(async({path:t,assignTo:n})=>{let r=await e.ssrLoadModule(t);n===`ssr`&&(B=r),n===`layout`&&(V=r)}))).filter(e=>e.status===`fulfilled`).length,o=performance.now()-r;n&&a>0&&console.log(`🔥 SSR ready in ${o.toFixed(0)}ms (${a}/${i.length} core modules)`)}export function createVirtualModulesPlugin(e){let{avalonConfig:n,nitroConfig:r,verbose:i}=e,a=null,o=s,u=c,d=l,f=process.cwd(),p=[];function m(e){try{let n=t(e,{withFileTypes:!0});for(let t of n){let n=o(e,t.name);if(t.isDirectory()&&t.name!==`node_modules`&&!t.name.startsWith(`.`))m(n);else if(t.isFile()&&t.name.endsWith(`.css`)){let e=u(f,n).replaceAll(`\\`,`/`);p.push(e.startsWith(`/`)?e:`/${e}`)}}}catch{}}function h(){p.length=0;for(let e of r.globalCSS??[])p.push(e.startsWith(`/`)?e:`/${e}`);n.modules&&m(d(f,n.modules.dir)),m(d(f,n.layoutsDir));let e=d(f,n.layoutsDir,`..`);e!==f&&e!==d(f,n.layoutsDir)&&e.startsWith(f)&&m(e)}return n.isDev&&h(),{name:`avalon:nitro-virtual-modules`,enforce:`pre`,resolveId(e){return e===VIRTUAL_MODULE_IDS.PAGE_ROUTES?RESOLVED_VIRTUAL_IDS.PAGE_ROUTES:e===VIRTUAL_MODULE_IDS.PAGE_LOADER?RESOLVED_VIRTUAL_IDS.PAGE_LOADER:e===VIRTUAL_MODULE_IDS.ISLAND_MANIFEST?RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST:e===VIRTUAL_MODULE_IDS.RUNTIME_CONFIG?RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG:e===VIRTUAL_MODULE_IDS.CONFIG?RESOLVED_VIRTUAL_IDS.CONFIG:e===VIRTUAL_MODULE_IDS.LAYOUTS?RESOLVED_VIRTUAL_IDS.LAYOUTS:e===VIRTUAL_MODULE_IDS.ASSETS?RESOLVED_VIRTUAL_IDS.ASSETS:e===VIRTUAL_MODULE_IDS.RENDERER?RESOLVED_VIRTUAL_IDS.RENDERER:e===VIRTUAL_MODULE_IDS.CLIENT_ENTRY?RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY:e===VIRTUAL_MODULE_IDS.INTEGRATION_LOADER?RESOLVED_VIRTUAL_IDS.INTEGRATION_LOADER:e===VIRTUAL_MODULE_IDS.ACTIONS?RESOLVED_VIRTUAL_IDS.ACTIONS:null},async load(e){return e===RESOLVED_VIRTUAL_IDS.PAGE_ROUTES?await be(n,i):e===RESOLVED_VIRTUAL_IDS.PAGE_LOADER?await xe(n,i):e===RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST?Se():e===RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG?Ce(n,r):e===RESOLVED_VIRTUAL_IDS.CONFIG?generateConfigModule(n,r):e===RESOLVED_VIRTUAL_IDS.LAYOUTS?(a||=await we(n,r,p),a):e===RESOLVED_VIRTUAL_IDS.ASSETS?Te(r):e===RESOLVED_VIRTUAL_IDS.RENDERER?Ee(n):e===RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY?await ke(n,r):e===RESOLVED_VIRTUAL_IDS.INTEGRATION_LOADER?generateIntegrationLoaderModule(n):e===RESOLVED_VIRTUAL_IDS.ACTIONS?ce():null},handleHotUpdate({file:e,server:t}){let{isPage:n,isComponent:r,isLayout:i,isCss:o}=_e(e);(n||r||i||o)&&(n&&(R(t,RESOLVED_VIRTUAL_IDS.PAGE_ROUTES),R(t,RESOLVED_VIRTUAL_IDS.PAGE_LOADER)),i&&(a=null,h(),R(t,RESOLVED_VIRTUAL_IDS.LAYOUTS),R(t,RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY)),setTimeout(()=>{t.ws.send({type:`full-reload`,path:`*`})},500)),ve(e)&&R(t,RESOLVED_VIRTUAL_IDS.CONFIG)}}}function R(e,t){let n=e.moduleGraph.getModuleById(t);n&&e.moduleGraph.invalidateModule(n)}function _e(e){let t=e.endsWith(`.css`);return{isPage:e.includes(`/pages/`)&&!t,isComponent:e.includes(`/components/`)&&/\.[tj]sx?$/.test(e),isLayout:(e.includes(`/layouts/`)||e.includes(`_layout`))&&/\.[tj]sx?$/.test(e),isCss:t}}function ve(e){return e.includes(`vite.config`)||e.includes(`avalon.config`)||e.includes(`nitro.config`)}function ye(e,t,n,r){e.watcher.on(`change`,e=>{e.includes(`_middleware`)&&(_(),r?.()),(e.includes(`/render/`)||e.includes(`/layout/`)||e.includes(`/islands/`))&&(B=null,V=null),(e.includes(`/layouts/`)||e.includes(`_layout`))&&globalThis.__avalonLayoutResolver?.clearCache?.()}),e.watcher.on(`add`,e=>{e.includes(`_middleware`)&&(_(),r?.())}),e.watcher.on(`unlink`,e=>{e.includes(`_middleware`)&&(_(),r?.())})}async function be(e,t){try{let{getAllPageDirs:t}=await import(`./module-discovery.js`),{discoverPageRoutesFromMultipleDirs:n}=await import(`../nitro/route-discovery.js`),r=await n(await t(e.pagesDir,e.modules,process.cwd()),{developmentMode:e.isDev});return`export const pageRoutes = ${JSON.stringify(r,null,2)};\nexport default pageRoutes;\n`}catch{return`export const pageRoutes = [];
|
|
11
11
|
export default pageRoutes;
|
|
12
12
|
`}}async function xe(e,t){try{let{getAllPageDirs:t}=await import(`./module-discovery.js`),{discoverPageRoutesFromMultipleDirs:n}=await import(`../nitro/route-discovery.js`),{relative:r}=await import(`node:path`),i=process.cwd(),a=await n(await t(e.pagesDir,e.modules,i),{developmentMode:e.isDev}),o=[],s=[];for(let e=0;e<a.length;e++){let t=a[e],n=`page_${e}`,c=r(i,t.filePath).replaceAll(`\\`,`/`),l=c.startsWith(`/`)?c:`/${c}`;o.push(`import * as ${n} from '${l}';`),s.push(` { pattern: ${JSON.stringify(t.pattern)}, params: ${JSON.stringify(t.params)}, module: ${n} }`)}return[...o,``,`const routes = [`,s.join(`,
|
|
13
13
|
`),`];`,``,`/**`,` * Match a pathname against discovered routes and return the page module.`,` * Uses the same pattern matching as Avalon's route discovery.`,` */`,`export function loadPage(pathname) {`,` const cleanPath = pathname.split('?')[0];`,` for (const route of routes) {`,` if (matchRoute(cleanPath, route.pattern, route.params)) {`,` return route.module;`,` }`,` }`,` return null;`,`}`,``,`function matchRoute(pathname, pattern, paramNames) {`,` // Exact match`,` if (pattern === pathname) return true;`,` // Normalize trailing slashes`,String.raw` const normPath = pathname === '/' ? '/' : pathname.replace(/\/$/, '');`,String.raw` const normPattern = pattern === '/' ? '/' : pattern.replace(/\/$/, '');`,` if (normPath === normPattern) return true;`,` // Dynamic segments: /users/:id matches /users/123`,` if (paramNames.length > 0) {`,` const patternParts = normPattern.split('/');`,` const pathParts = normPath.split('/');`,` if (patternParts.length !== pathParts.length) return false;`,` return patternParts.every((part, i) => part.startsWith(':') || part === pathParts[i]);`,` }`,` return false;`,`}`,``,`export default { loadPage, routes };`,``].join(`
|
|
@@ -15,17 +15,17 @@ export default pageRoutes;
|
|
|
15
15
|
export default { loadPage, routes: [] };
|
|
16
16
|
`}}function Se(){return`export const islandManifest = { islands: {}, clientEntry: "", css: [] };
|
|
17
17
|
export default islandManifest;
|
|
18
|
-
`}function Ce(e,t){let n={avalon:{streaming:t.streaming??!0,pagesDir:e.pagesDir,layoutsDir:e.layoutsDir,isDev:e.isDev},...t.runtimeConfig};return`export const runtimeConfig = ${JSON.stringify(n,null,2)};\nexport function useRuntimeConfig() { return runtimeConfig; }\nexport default runtimeConfig;\n`}export function generateConfigModule(e,t){let n=globalThis.__avalonConfig?.isDev??e.isDev,r={streaming:t.streaming??!0,pagesDir:e.pagesDir,layoutsDir:e.layoutsDir,isDev:n,...t.runtimeConfig};return`globalThis.__avalonHydrationMode = "${n?`entry-client`:`per-island`}";\nconst config = ${JSON.stringify(r,null,2)};\nexport function useAvalonConfig() { return config; }\nexport default config;\n`}async function we(e,t,n){let{getAllLayoutDirs:r}=await import(`./module-discovery.js`),{relative:i,resolve:a}=await import(`node:path`),{stat:o,readFile:c}=await import(`node:fs/promises`),l=process.cwd(),u=await r(e.layoutsDir,e.modules,l),d=[],f=0,p=a(l,e.layoutsDir);for(let{dir:t,prefix:n}of u){let r=s(t,`_layout.tsx`);try{if(!(await o(r)).isFile())continue}catch{continue}let a=i(l,r).replaceAll(`\\`,`/`),c=a.startsWith(`/`)?a:`/${a}`,u=t.startsWith(p),m=u&&!e.modules?`RootLayout`:`Layout_${f}`;d.push({prefix:n,importPath:c,varName:m,isShared:u,filePath:r}),f++}let m=d.filter(e=>e.isShared),h=d.filter(e=>!e.isShared),g=new Map;for(let e of h)try{let t=(await c(e.filePath,`utf8`)).match(/layoutConfig\s*=\s*{[\s\S]*?skipLayouts\s*:\s*\[([^\]]*)\]/)?.[1]??``,n=/['"`]_layout['"`]/.test(t);g.set(e.importPath,n)}catch{g.set(e.importPath,!1)}let _=d.map(e=>`import ${e.varName} from '${e.importPath}';`),v=h.toSorted((e,t)=>t.prefix.length-e.prefix.length).map(e=>{let t=e.prefix===`/`,n=g.get(e.importPath)??!1,r=t||n;return` { prefix: ${JSON.stringify(e.prefix)}, Layout: ${e.varName}, skipRoot: ${r} }`}),y=m.length>0?m[0].varName:`null`,b=JSON.stringify(n);return[`// Auto-generated by Avalon — do not edit`,...
|
|
18
|
+
`}function Ce(e,t){let n={avalon:{streaming:t.streaming??!0,pagesDir:e.pagesDir,layoutsDir:e.layoutsDir,isDev:e.isDev},...t.runtimeConfig};return`export const runtimeConfig = ${JSON.stringify(n,null,2)};\nexport function useRuntimeConfig() { return runtimeConfig; }\nexport default runtimeConfig;\n`}export function generateConfigModule(e,t){let n=globalThis.__avalonConfig?.isDev??e.isDev,r={streaming:t.streaming??!0,pagesDir:e.pagesDir,layoutsDir:e.layoutsDir,isDev:n,...t.runtimeConfig};return`globalThis.__avalonHydrationMode = "${n?`entry-client`:`per-island`}";\nconst config = ${JSON.stringify(r,null,2)};\nexport function useAvalonConfig() { return config; }\nexport default config;\n`}async function we(e,t,n){let{getAllLayoutDirs:r}=await import(`./module-discovery.js`),{relative:i,resolve:a}=await import(`node:path`),{stat:o,readFile:c}=await import(`node:fs/promises`),l=process.cwd(),u=await r(e.layoutsDir,e.modules,l),d=[],f=0,p=a(l,e.layoutsDir);for(let{dir:t,prefix:n}of u){let r=s(t,`_layout.tsx`);try{if(!(await o(r)).isFile())continue}catch{continue}let a=i(l,r).replaceAll(`\\`,`/`),c=a.startsWith(`/`)?a:`/${a}`,u=t.startsWith(p),m=u&&!e.modules?`RootLayout`:`Layout_${f}`;d.push({prefix:n,importPath:c,varName:m,isShared:u,filePath:r}),f++}let m=d.filter(e=>e.isShared),h=d.filter(e=>!e.isShared),g=new Map;for(let e of h)try{let t=(await c(e.filePath,`utf8`)).match(/layoutConfig\s*=\s*{[\s\S]*?skipLayouts\s*:\s*\[([^\]]*)\]/)?.[1]??``,n=/['"`]_layout['"`]/.test(t);g.set(e.importPath,n)}catch{g.set(e.importPath,!1)}let _=d.map(e=>`import ${e.varName} from '${e.importPath}';`),v=h.toSorted((e,t)=>t.prefix.length-e.prefix.length).map(e=>{let t=e.prefix===`/`,n=g.get(e.importPath)??!1,r=t||n;return` { prefix: ${JSON.stringify(e.prefix)}, Layout: ${e.varName}, skipRoot: ${r} }`}),y=m.length>0?m[0].varName:`null`,b=JSON.stringify(n);return[`// Auto-generated by Avalon — do not edit`,...pe(e.core),`import { getUniversalCSSForHead } from '@useavalon/avalon/islands/universal-css-collector';`,`import { getUniversalHeadForInjection, injectSolidHydrationScriptIfNeeded } from '@useavalon/avalon/islands/universal-head-collector';`,..._,``,`const RootLayoutComponent = ${y};`,`const _cssLinks = ${b};`,``,`const moduleLayouts = [`,v.join(`,
|
|
19
19
|
`),`];`,``,`function getLayoutsForPath(pathname) {`,` for (const entry of moduleLayouts) {`,` if (entry.prefix === '/' ? pathname === '/' : pathname.startsWith(entry.prefix)) {`,` return entry;`,` }`,` }`,` return null;`,`}`,``,`function injectUniversalAssets(html) {`,` // In dev, inject <link> tags for all discovered CSS files.`,` // The ?direct suffix makes Vite return raw CSS (text/css) instead`,` // of a JS module wrapper, so <link rel="stylesheet"> works.`,` if (process.env.NODE_ENV !== 'production' && _cssLinks.length > 0) {`,` var links = _cssLinks.map(function(href) {`,` return '<link rel="stylesheet" href="' + href + '?direct">';`,String.raw` }).join('\n');`,` if (html.includes('</head>')) {`,String.raw` html = html.replace('</head>', links + '\n</head>');`,` }`,` }`,` const universalCSS = getUniversalCSSForHead(true);`,` if (universalCSS && html.includes('</head>')) {`,String.raw` html = html.replace('</head>', universalCSS + '\n</head>');`,` }`,` const universalHead = getUniversalHeadForInjection(true);`,` if (universalHead && html.includes('</head>')) {`,String.raw` html = html.replace('</head>', universalHead + '\n</head>');`,` }`,` html = injectSolidHydrationScriptIfNeeded(html);`,` return html;`,`}`,``,`export async function wrapWithLayouts(pageHtml, pageModule, context, injectAssets) {`,` const pathname = context.url.pathname;`,` const frontmatter = {`,` ...(pageModule.frontmatter || {}),`,` ...(pageModule.metadata || {}),`,` currentPath: pathname,`,` };`,` const pageLayoutConfig = pageModule.layoutConfig;`,` const skipAll = pageLayoutConfig?.skipLayouts?.includes('_layout');`,``,` const layoutEntry = getLayoutsForPath(pathname);`,` const routeInfo = { path: pathname, params: context.params, query: context.url.searchParams };`,` let html;`,``,` if (!layoutEntry || skipAll) {`,` if (RootLayoutComponent && !skipAll) {`,` const rootProps = {`,` children: h('avalon-page', { id: 'app', dangerouslySetInnerHTML: { __html: pageHtml } }),`,` frontmatter,`,` data: {},`,` route: routeInfo,`,` };`,` const rootResult = RootLayoutComponent(rootProps);`,` const resolvedRoot = rootResult instanceof Promise ? await rootResult : rootResult;`,String.raw` html = '<!DOCTYPE html>\n' + preactRenderToString(resolvedRoot);`,` } else {`,` // skipAll is true — page provides its own HTML shell or needs a minimal one`,` if (pageHtml.trimStart().startsWith('<html')) {`,String.raw` html = '<!DOCTYPE html>\n' + pageHtml;`,` } else {`,` const title = String(frontmatter.title || 'Avalon');`,` html = [`,` '<!DOCTYPE html>',`,` '<html lang="en">',`,` '<head>',`,` '<meta charset="utf-8">',`,` '<meta name="viewport" content="width=device-width, initial-scale=1">',`,` '<title>' + title + '</title>',`,` '</head>',`,` '<body>',`,` '<div id="app">' + pageHtml + '</div>',`,` '</body>',`,` '</html>',`,String.raw` ].join('\n');`,` }`,` }`,` } else {`,` const layoutProps = {`,` children: h('avalon-page', { dangerouslySetInnerHTML: { __html: pageHtml } }),`,` frontmatter,`,` data: {},`,` route: routeInfo,`,` };`,` const layoutResult = layoutEntry.Layout(layoutProps);`,` const resolvedLayout = layoutResult instanceof Promise ? await layoutResult : layoutResult;`,` let wrappedHtml = preactRenderToString(resolvedLayout);`,``,` // If the layout provides its own HTML shell (starts with <html),`,` // skip root wrapping regardless of skipRoot flag — the layout IS the document.`,` const layoutProvidesShell = wrappedHtml.trimStart().startsWith('<html');`,``,` if (!layoutProvidesShell && !layoutEntry.skipRoot && RootLayoutComponent) {`,` const rootProps = {`,` children: h('avalon-page', { dangerouslySetInnerHTML: { __html: wrappedHtml } }),`,` frontmatter,`,` data: {},`,` route: routeInfo,`,` };`,` const rootResult = RootLayoutComponent(rootProps);`,` const resolvedRoot = rootResult instanceof Promise ? await rootResult : rootResult;`,` wrappedHtml = preactRenderToString(resolvedRoot);`,` }`,``,String.raw` html = '<!DOCTYPE html>\n' + wrappedHtml;`,` }`,``,` if (injectAssets) {`,` html = injectAssets(html);`,` }`,` return injectUniversalAssets(html);`,`}`,``,`export default { wrapWithLayouts };`,``].join(`
|
|
20
20
|
`)}function Te(e){let t=e.clientEntry??`app/entry-client`;return[`// Auto-generated by Avalon — do not edit`,`// @ts-ignore — virtual import resolved by Nitro's Vite assets plugin at build time`,`import clientAssets from '${t.startsWith(`/`)?t:`/${t}`}?assets=client';`,``,`function buildAssetTags() {`,` const cssLinks = (clientAssets?.css ?? [])`,` .filter(attr => {`,` const href = attr.href || '';`,` // The SSR build produces ssr-index-*.css containing all global, reset,`,` // token, and layout CSS module styles. The client build may also emit`,` // entry-client-*.css (a subset) and index-*.css. To avoid duplicates,`,` // prefer ssr-index when it exists; otherwise fall back to the others.`,` var hasSsrIndex = (clientAssets?.css ?? []).some(function(a) { return (a.href || '').includes('ssr-index'); });`,` if (hasSsrIndex) return href.includes('ssr-index');`,` if (href.includes('entry-client') && href.endsWith('.css')) return true;`,String.raw` if (/\/index-[^/]+\.css$/.test(href)) return true;`,` return false;`,` })`,` .map(attr => '<link rel="stylesheet" href="' + attr.href + '">')`,String.raw` .join('\n');`,` const jsPreloads = (clientAssets?.js ?? [])`,` .map(attr => '<link rel="modulepreload" href="' + attr.href + '">')`,String.raw` .join('\n');`,` const entryScript = clientAssets?.entry`,` ? '<script type="module" src="' + clientAssets.entry + '"><\/script>'`,` : '';`,` return { cssLinks, jsPreloads, entryScript };`,`}`,``,`export function injectAssets(html) {`,` const { cssLinks, jsPreloads, entryScript } = buildAssetTags();`,` if (html.includes('</head>')) {`,String.raw` html = html.replace('</head>', cssLinks + '\n' + jsPreloads + '\n</head>');`,` }`,` if (html.includes('</body>')) {`,String.raw` html = html.replace('</body>', entryScript + '\n</body>');`,` }`,` return html;`,`}`,``,`export { clientAssets };`,`export default { injectAssets, clientAssets };`,``].join(`
|
|
21
|
-
`)}function Ee(e){let t=Array.isArray(e.integrations)?e.integrations:[],n=[],r=[];for(let e of t){let t=`${e}Integration`;n.push(`import { ${t} } from '@useavalon/${e}';`),r.push(`registry.register(${t});`)}let i=
|
|
21
|
+
`)}function Ee(e){let t=Array.isArray(e.integrations)?e.integrations:[],n=[],r=[];for(let e of t){let t=`${e}Integration`;n.push(`import { ${t} } from '@useavalon/${e}';`),r.push(`registry.register(${t});`)}let i=me(e.core);return[`// Auto-generated by Avalon — do not edit`,`import { createNitroRenderer } from '@useavalon/avalon/nitro/renderer';`,`import { registerBuiltinDirectives } from '@useavalon/avalon';`,`import { registry } from '@useavalon/avalon/islands/integration-registry';`,`import avalonConfig from 'virtual:avalon/config';`,`import { loadPage } from 'virtual:avalon/page-loader';`,`import { wrapWithLayouts } from 'virtual:avalon/layouts';`,`import { injectAssets } from 'virtual:avalon/assets';`,...n,``,...i,``,`// Pre-register framework integrations for SSR`,...r,``,`// Register built-in custom hydration directives (on:delay, on:scroll, etc.)`,`registerBuiltinDirectives();`,``,`// Set hydration mode flag — intentionally duplicated from virtual:avalon/config`,`// for module-load-order resilience (config import may be tree-shaken or deferred).`,`globalThis.__avalonHydrationMode = avalonConfig.isDev ? "entry-client" : "per-island";`,``,`export default createNitroRenderer({`,` avalonConfig,`,` isDev: avalonConfig.isDev,`,` resolvePageRoute: async (pathname) => {`,` const mod = loadPage(pathname);`,` if (!mod || !('default' in mod)) return null;`,` return { filePath: '[virtual:' + pathname + ']', pattern: pathname, params: {} };`,` },`,` loadPageModule: async (filePath) => {`,String.raw` const match = filePath.match(/^\[virtual:(.+)\]$/);`,` const pathname = match ? match[1] : filePath;`,` const mod = loadPage(pathname);`,` if (mod) return mod;`,` return { default: () => null, metadata: { title: 'Avalon' } };`,` },`,` wrapWithLayouts: (pageHtml, pageModule, context) =>`,` wrapWithLayouts(pageHtml, pageModule, context, injectAssets),`,`});`,``].join(`
|
|
22
22
|
`)}async function De(e,t){let{readdir:n}=await import(`node:fs/promises`),{relative:r,join:i}=await import(`node:path`),a=[];for(let{dir:o}of e){let e;try{e=await n(o,{withFileTypes:!0})}catch{continue}for(let n of e){if(!n.isFile()||!n.name.endsWith(`.css`))continue;let e=r(t,i(o,n.name)).replaceAll(`\\`,`/`);a.push(e.startsWith(`/`)?e:`/${e}`)}}return a}function Oe(e){if(e.length===0)return[];let t=[`// Layout CSS (auto-discovered)`],n=0;for(let r of e)r.includes(`.module.`)?(t.push(`import _lcss${n} from '${r}';`),n++):t.push(`import '${r}';`);return t}async function ke(e,t){let{getAllLayoutDirs:n}=await import(`./module-discovery.js`),r=process.cwd(),i=await De(await n(e.layoutsDir,e.modules,r),r),a=globalThis.__avalonConfig?.isDev??e.isDev?`@useavalon/avalon/client/main`:`@useavalon/avalon/client/main-slim`,o=[`// Auto-generated by Avalon — do not edit`,`// Island hydration runtime`];(globalThis.__avalonHydrationMode??`entry-client`)===`entry-client`?o.push(`import '${a}';`):o.push(`// Per-island hydration mode — no shared runtime needed`),o.push(``);let s=t.globalCSS??[];for(let e of s){let t=e.startsWith(`/`)?e:`/${e}`;o.push(`// Global CSS`,`import '${t}';`)}return s.length>0&&o.push(``),o.push(...Oe(i),``),o.join(`
|
|
23
|
-
`)}function Ae(){return[`// --- Inlined Solid adapter (production) ---`,`// Eliminates a separate chunk + network request for the Solid client adapter.`,`// Only imports hydrate/createComponent — no render() fallback (saves ~1-2 KiB).`,`function _ensureHydrationContext() {`,` if (!globalThis._$HY) {`,` globalThis._$HY = { events: [], completed: new WeakSet(), r: {}, fe() {} };`,` }`,`}`,``,`async function _solidHydrate(container, Component, props) {`,` if (!container) throw new Error("Container element is required for hydration");`,` if (!Component || typeof Component !== "function") {`,` throw new Error("Invalid Solid component: expected function, got " + typeof Component);`,` }`,` var el = container;`,` var renderId = el.dataset.solidRenderId || el.dataset.renderId;`,` var { hydrate: solidHydrate, createComponent } = await import("solid-js/web");`,` _ensureHydrationContext();`,` solidHydrate(function() { return createComponent(Component, props || {}); }, el, { renderId: renderId || "" });`,`}`,``,`var _solidModule = { hydrate: _solidHydrate };`,``]}function je(e,t,n){let r=[];for(let i of e){let e=t[i];e&&(i===`solid`&&!n?r.push(` case "solid":`,` return _solidModule;`):i===`react`?r.push(` case "react":`):r.push(` case "${i}":`,` return import("${e}");`))}return e.includes(`react`)&&!e.includes(`preact`)&&r.push(` return import("${t.react}");`),r}function Me(e,t){let n=[];for(let r of e){let e=t[r];e&&n.push(` case "${r}":`,` return import("${e}").then(m => m.${r}Adapter);`)}return n}export function generateIntegrationLoaderModule(e){let t=(e.integrations??[]).map(e=>typeof e==`string`?e:e.name),n={preact:`@useavalon/preact/client`,react:
|
|
24
|
-
`)}export function getViteDevServer(){return globalThis.__viteDevServer}export function getAvalonConfig(){return globalThis.__avalonConfig}export function isDevelopmentMode(){return globalThis.__avalonConfig?.isDev??!0}const
|
|
25
|
-
`)}</style>`;n.includes(`</head>`)?n=n.replace(`</head>`,`${e}\n</head>`):n+=e}n.trim().toLowerCase().startsWith(`<!doctype`)||(n=`<!DOCTYPE html>\n${n}`);let r=
|
|
23
|
+
`)}function Ae(){return[`// --- Inlined Solid adapter (production) ---`,`// Eliminates a separate chunk + network request for the Solid client adapter.`,`// Only imports hydrate/createComponent — no render() fallback (saves ~1-2 KiB).`,`function _ensureHydrationContext() {`,` if (!globalThis._$HY) {`,` globalThis._$HY = { events: [], completed: new WeakSet(), r: {}, fe() {} };`,` }`,`}`,``,`async function _solidHydrate(container, Component, props) {`,` if (!container) throw new Error("Container element is required for hydration");`,` if (!Component || typeof Component !== "function") {`,` throw new Error("Invalid Solid component: expected function, got " + typeof Component);`,` }`,` var el = container;`,` var renderId = el.dataset.solidRenderId || el.dataset.renderId;`,` var { hydrate: solidHydrate, createComponent } = await import("solid-js/web");`,` _ensureHydrationContext();`,` solidHydrate(function() { return createComponent(Component, props || {}); }, el, { renderId: renderId || "" });`,`}`,``,`var _solidModule = { hydrate: _solidHydrate };`,``]}function je(e,t,n){let r=[];for(let i of e){let e=t[i];e&&(i===`solid`&&!n?r.push(` case "solid":`,` return _solidModule;`):i===`react`?r.push(` case "react":`):r.push(` case "${i}":`,` return import("${e}");`))}return e.includes(`react`)&&!e.includes(`preact`)&&r.push(` return import("${t.react}");`),r}function Me(e,t){let n=[];for(let r of e){let e=t[r];e&&n.push(` case "${r}":`,` return import("${e}").then(m => m.${r}Adapter);`)}return n}export function generateIntegrationLoaderModule(e){let t=(e.integrations??[]).map(e=>typeof e==`string`?e:e.name),n={preact:`@useavalon/preact/client`,react:he(e.core),vue:`@useavalon/vue/client`,svelte:`@useavalon/svelte/client`,solid:`@useavalon/solid/client`,lit:`@useavalon/lit/client`,qwik:`@useavalon/qwik/client`},r={preact:`@useavalon/preact/client/hmr`,react:`@useavalon/react/client/hmr`,vue:`@useavalon/vue/client/hmr`,svelte:`@useavalon/svelte/client/hmr`,solid:`@useavalon/solid/client/hmr`,lit:`@useavalon/lit/client/hmr`,qwik:`@useavalon/qwik/client/hmr`},i=t.includes(`lit`),a=t.includes(`solid`),o=(globalThis.__avalonConfig?.isDev??e.isDev)===!0,s=[`// Auto-generated by Avalon — only includes configured integrations`,``];return a&&!o&&s.push(...Ae()),s.push(`// --- loadIntegrationModule ---`,`export async function loadIntegrationModule(framework) {`,` switch (framework) {`,...je(t,n,o),` default:`," throw new Error(`Unknown or unconfigured framework: ${framework}`);",` }`,`}`,``,`// --- Lit hydration pre-load (only if Lit is configured) ---`),i?s.push(`export async function preLitHydration() {`,` await import("@useavalon/lit/client");`,`}`):s.push(`export async function preLitHydration() {}`),s.push(``,`// --- HMR adapter loader ---`,`export async function loadHMRAdapter(framework) {`,` switch (framework) {`,...Me(t,r),` default: return null;`,` }`,`}`),s.join(`
|
|
24
|
+
`)}export function getViteDevServer(){return globalThis.__viteDevServer}export function getAvalonConfig(){return globalThis.__avalonConfig}export function isDevelopmentMode(){return globalThis.__avalonConfig?.isDev??!0}const z=`<!--AVALON_STREAM_BOUNDARY-->`;let B=null,V=null;async function H(e,t){if(t===`react`){let e=await import(`react`),t=await import(`react-dom/server`),n=e.default??e,r=t.default??t;return{h:e.createElement??n.createElement,renderToString:t.renderToString??r.renderToString}}let n=await e.ssrLoadModule(`preact`),r=await e.ssrLoadModule(`preact-render-to-string`);return{h:n.h,renderToString:r.render??r.default}}function U(e){return e.split(`/`).pop()?.replace(/\.[^.]+$/,``)||``}async function W(e,t,n){let r=[];for(let n of t){let t=await e.ssrLoadModule(n);r.push({file:n,module:t})}for(let r of t){let t=await T(e,r);n.push(...t)}return r}async function G(e,t,n,r){let i=[],a=[];for(let o of e){let e=o.module.default;if(!(!e||typeof e!=`function`))try{let s=e({...t,children:n(`div`,null,`test`)}),c=r(s instanceof Promise?await s:s);c.trim().startsWith(`<html`)||c.includes(`<!DOCTYPE`)?i.push(o):a.push(o)}catch{a.push(o)}}return{shellLayouts:i,wrapperLayouts:a}}async function Ne(e,t,n,r){try{let i=e({...t,children:n(`div`,{dangerouslySetInnerHTML:{__html:z}})});return r(i instanceof Promise?await i:i)}catch{return null}}function Pe(e,t){let n=e;if(t.length>0){let e=`<style data-avalon-ssr-css>${t.join(`
|
|
25
|
+
`)}</style>`;n.includes(`</head>`)?n=n.replace(`</head>`,`${e}\n</head>`):n+=e}n.trim().toLowerCase().startsWith(`<!doctype`)||(n=`<!DOCTYPE html>\n${n}`);let r=m(!0);r&&n.includes(`</head>`)&&(n=n.replace(`</head>`,`${r}\n</head>`));let i=h(!0);return i&&n.includes(`</head>`)&&(n=n.replace(`</head>`,`${i}\n</head>`)),g(n)}async function K(e,t,n){try{let n=typeof e==`function`?e():e;return t(n instanceof Promise?await n:n)}catch(e){return console.error(`${n} Error rendering page component:`,e),`<div>Error rendering page</div>`}}async function q(e,t,n,r,i,a){let o=e;for(let{module:e}of t){let t=e.default;if(!(!t||typeof t!=`function`))try{let e=t({...n,children:r(`div`,{dangerouslySetInnerHTML:{__html:o}})});o=i(e instanceof Promise?await e:e)}catch(e){console.error(`${a} Error rendering wrapper layout:`,e)}}return o}async function Fe(e,t,n,r,i){if(t.length===0)return e;let{module:a}=t.at(-1),o=a.default;if(!o||typeof o!=`function`)return e;try{let t=o({...n,children:r(`div`,{dangerouslySetInnerHTML:{__html:e}})});return i(t instanceof Promise?await t:t)}catch(t){return console.error(`[SSR] Error rendering shell layout:`,t),e}}function Ie(e){if(e.includes(`/src/client/main.js`)||e.includes(`/@vite/client`))return e;let t=e.lastIndexOf(`</body>`);return t===-1?e:e.slice(0,t)+`
|
|
26
26
|
<script type="module" src="/@vite/client"><\/script>
|
|
27
27
|
<script type="module" src="/src/client/main.js"><\/script>
|
|
28
|
-
`+e.slice(t)}async function Le(e,t,n,r){if(!n.modules)return!1;let i=t.split(`?`)[0],a=await Q(i,n,e);if(!a)return!1;try{let t=await e.ssrLoadModule(a),o=t.default;if(!o)return!1;let s=t.layoutConfig,c=await
|
|
28
|
+
`+e.slice(t)}async function Le(e,t,n,r){if(!n.modules)return!1;let i=t.split(`?`)[0],a=await Q(i,n,e);if(!a)return!1;try{let t=await e.ssrLoadModule(a),o=t.default;if(!o)return!1;let s=t.layoutConfig,c=await T(e,a),l=await W(e,await Y(i,e),c);if(l.length===0)return!1;let{h:u,renderToString:d}=await H(e,n.core),f=s?.skipLayouts||[],p=l.filter(({file:e})=>!f.includes(U(e))),m=t.frontmatter,h=t.metadata,g={children:null,frontmatter:{...m,...h,currentPath:i},params:{},url:i},{shellLayouts:_,wrapperLayouts:v}=await G(p,g,u,d);if(_.length===0)return!1;let{module:y}=_.at(-1),b=y.default;if(!b||typeof b!=`function`)return!1;let x=await Ne(b,g,u,d);if(x===null)return!1;let S=x.indexOf(z);if(S===-1)return!1;let C=x.slice(0,S),w=x.slice(S+29),E=Pe(C,c);r.statusCode=200,r.setHeader(`Content-Type`,`text/html; charset=utf-8`),r.setHeader(`Transfer-Encoding`,`chunked`),r.setHeader(`X-Avalon-Streaming`,`1`),r.flushHeaders(),r.write(E);let D=await K(o,d,`[SSR Streaming]`);if(D.trim().startsWith(`<!DOCTYPE html>`)||D.trim().startsWith(`<html`))return r.end(D),!0;let O=Ie(await q(D,v,g,u,d,`[SSR Streaming]`)+w);return r.end(O),!0}catch(e){return r.headersSent?(r.end(`<div>Streaming SSR error: ${e.message}</div></body></html>`),!0):!1}}async function Re(e,t,n){let r=t.split(`?`)[0],i=await Q(r,n,e);if(!i)return null;try{let t=await e.ssrLoadModule(i),a=t.default;if(!a)return console.warn(`[SSR] Page ${i} has no default export`),null;let o=await T(e,i),s=await W(e,await Y(r,e),o),c;return c=n.modules&&s.length>0?await ze(a,t,s,r,n,e):await Ge(a,t,r,n,e),o.length>0&&(c=E(c,o)),c}catch(e){throw console.error(`[SSR] Error rendering ${i}:`,e),e}}async function ze(e,t,n,r,i,a){let{h:o,renderToString:s}=await H(a,i.core),c=t.layoutConfig?.skipLayouts||[],l=n.filter(({file:e})=>!c.includes(U(e))),u=await K(e,s,`[SSR]`);if(u.trim().startsWith(`<!DOCTYPE html>`)||u.trim().startsWith(`<html`))return J(u);let d=t.frontmatter,f=t.metadata,p={children:null,frontmatter:{...d,...f,currentPath:r},params:{},url:r},{shellLayouts:m,wrapperLayouts:h}=await G(l,p,o,s),g=await Fe(await q(u,h,p,o,s,`[SSR]`),m,p,o,s);if(g.trim().startsWith(`<!DOCTYPE html>`)||g.trim().startsWith(`<html`))return J(g);let _=t.metadata||{},v=_.title||`Avalon App`,y=_.description||``;return`<!DOCTYPE html>
|
|
29
29
|
<html lang="en">
|
|
30
30
|
<head>
|
|
31
31
|
<meta charset="utf-8">
|
|
@@ -38,12 +38,12 @@ export default islandManifest;
|
|
|
38
38
|
${g}
|
|
39
39
|
<script type="module" src="/src/client/main.js"><\/script>
|
|
40
40
|
</body>
|
|
41
|
-
</html>`}function
|
|
41
|
+
</html>`}function J(e){let t=e;if(t.trim().toLowerCase().startsWith(`<!doctype`)||(t=`<!DOCTYPE html>\n${t}`),!t.includes(`data-universal-ssr="true"`)){let e=m(!0);e&&t.includes(`</head>`)&&(t=t.replace(`</head>`,`${e}\n</head>`))}let n=h(!0);if(n&&t.includes(`</head>`)&&(t=t.replace(`</head>`,`${n}\n</head>`)),t=g(t),t.includes(`/src/client/main.js`)||t.includes(`/@vite/client`))return t;let r=t.lastIndexOf(`</body>`);return r===-1?t+`
|
|
42
42
|
<script type="module" src="/@vite/client"><\/script>
|
|
43
43
|
<script type="module" src="/src/client/main.js"><\/script>`:t.slice(0,r)+`
|
|
44
44
|
<script type="module" src="/@vite/client"><\/script>
|
|
45
45
|
<script type="module" src="/src/client/main.js"><\/script>
|
|
46
|
-
`+t.slice(r)}async function
|
|
46
|
+
`+t.slice(r)}async function Y(e,t){let n=t.config.root||process.cwd(),r=globalThis.__avalonConfig,a=`_layout.tsx`,o=[],s=e.split(`/`).filter(Boolean),c=[``];for(let e=0;e<s.length;e++)c.push(`/${s.slice(0,e+1).join(`/`)}`);async function l(e){try{if((await i(e)).isFile()){let t=e.slice(n.length);o.includes(t)||o.push(t)}}catch{}}if(r?.layoutsDir&&await l(`${`${n}/${r.layoutsDir}`}/${a}`),r?.modules){let e=`${n}/${r.modules.dir}`,t=r.modules.layoutsDirName,i=s[0]||``,o=[`home`,`root`,`main`,`index`];if(!i||o.includes(i.toLowerCase()))for(let n of o)await l(`${e}/${n}/${t}/${a}`);else await l(`${e}/${i}/${t}/${a}`)}let u=`${n}/src/layouts`;for(let e of c)await l(e===``?`${u}/${a}`:`${u}${e}/${a}`);return o}async function X(e,t){try{if((await i(`${e}/${t}`)).isFile())return`/${t}`}catch{}return null}async function Z(e,t,n){for(let r of n){let n=await X(e,`${t}${r}`);if(n)return n}if(!t.endsWith(`/index`))for(let r of n){let n=await X(e,`${t}/index${r}`);if(n)return n}return null}function Be(e,t,n){let r=t.split(`/`).filter(Boolean),i=r[0]||``,a=[`home`,`root`,`main`,`index`],o,s;if(!i||a.includes(i.toLowerCase()))o=`home`,s=n;else{o=i;let e=r.slice(1);s=e.length>0?`/${e.join(`/`)}`:`/index`}return`${e.dir}/${o}/${e.pagesDirName}${s}`}async function Q(e,t,n){let r=e;r.endsWith(`/`)&&r!==`/`&&(r=r.slice(0,-1)),r===`/`&&(r=`/index`);let i=[`.tsx`,`.ts`,`.jsx`,`.js`,`.mdx`,`.md`],a=n.config.root||process.cwd();if(t.modules){let n=await Z(a,Be(t.modules,e,r),i);if(n)return n}return Z(a,`${t.pagesDir}${r}`,i)}function Ve(e,t,n){if(globalThis.__avalonLayoutResolver)return;let r=e.EnhancedLayoutResolver,i=t.layoutsDir||`src/layouts`;globalThis.__avalonLayoutResolver=new r({baseDirectory:`${n}/${i}`,filePattern:`_layout.tsx`,excludeDirectories:[`node_modules`,`.git`,`dist`,`build`],enableWatching:!0,developmentMode:!1,enableCaching:!0,cacheTTL:60*1e3,maxCacheSize:100,enableStreaming:!0,enableErrorBoundaries:!0,enableMetrics:!1,enableDebugInfo:!1,modulesDir:t.modules?`${n}/${t.modules.dir}`:void 0,modulesLayoutsDirName:t.modules?.layoutsDirName})}async function He(e,t,n,r){try{let{renderToString:r}=await H(t,n.core);return typeof e==`function`?r(e()):``}catch{return`<p>Loading page: ${$(r)}</p>`}}async function Ue(e,t,n,r,i,a,o){if(!e.renderToHtmlWithLayouts||!t.EnhancedLayoutResolver||!t.EnhancedLayoutResolverUtils)return null;try{Ve(t,r,i.config.root||process.cwd());let s=`http://localhost${a}`,c={params:{},query:{},url:s,request:{method:`GET`,url:s,headers:new Headers}};return await e.renderToHtmlWithLayouts(n,globalThis.__avalonLayoutResolver,c,a,{title:o.title||`Avalon App`},void 0,{suppressWarnings:!0})}catch{return null}}async function We(e,t,n,r,i,a){try{B||=await i.ssrLoadModule(P(`src/render/ssr.ts`));let o=B;V||=await i.ssrLoadModule(P(`src/core/layout/enhanced-layout-resolver.ts`));let s=V,c={component:()=>typeof e==`function`?e():e,options:{title:a.title||`Avalon App`},frontmatter:t.frontmatter},l=await Ue(o,s,c,r,i,n,a);if(l!==null)return l;if(o.renderToHtml)return await o.renderToHtml(c,{title:a.title||`Avalon App`},void 0,{suppressWarnings:!0})}catch{}return null}async function Ge(e,t,n,r,i){let a=t.metadata||{},o=await We(e,t,n,r,i,a);if(o!==null)return o;let s=a.title||`Avalon App`,c=a.description||``,l=await He(e,i,r,n);return`<!DOCTYPE html>
|
|
47
47
|
<html lang="en">
|
|
48
48
|
<head>
|
|
49
49
|
<meta charset="utf-8">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@useavalon/avalon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Multi-framework islands architecture for the modern web",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
"types": "./dist/src/actions/define.d.ts",
|
|
48
48
|
"default": "./dist/src/actions/define.js"
|
|
49
49
|
},
|
|
50
|
+
"./cron": {
|
|
51
|
+
"types": "./dist/src/cron/define.d.ts",
|
|
52
|
+
"default": "./dist/src/cron/define.js"
|
|
53
|
+
},
|
|
50
54
|
"./client": {
|
|
51
55
|
"types": "./dist/src/client/components.d.ts",
|
|
52
56
|
"default": "./dist/src/client/components.js"
|
|
@@ -155,7 +159,7 @@
|
|
|
155
159
|
}
|
|
156
160
|
},
|
|
157
161
|
"dependencies": {
|
|
158
|
-
"@useavalon/core": "^0.
|
|
162
|
+
"@useavalon/core": "^0.3.0",
|
|
159
163
|
"@mdx-js/rollup": "^3.0.0",
|
|
160
164
|
"h3": "^2.0.1-rc.16",
|
|
161
165
|
"marked": "^17.0.4",
|