@pyreon/zero 0.21.0 → 0.23.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 +211 -57
- package/lib/_chunks/app-BbPT0Y5M.js +36 -0
- package/lib/{fs-router-Bacdhsq-.js → _chunks/fs-router-DvBlRzmP.js} +21 -5
- package/lib/_chunks/use-intersection-observer-C6opeplh.js +29 -0
- package/lib/actions.js +24 -3
- package/lib/ai.js +1 -102
- package/lib/client.js +3 -33
- package/lib/csp.js +12 -9
- package/lib/favicon.js +1 -1
- package/lib/font.js +1 -1
- package/lib/image-plugin.js +1 -1
- package/lib/image.js +3 -27
- package/lib/index.js +8 -1085
- package/lib/link.js +3 -27
- package/lib/meta.js +1 -25
- package/lib/script.js +2 -26
- package/lib/seo.js +4 -4
- package/lib/server.js +275 -2129
- package/lib/testing.js +1 -69
- package/lib/theme.js +52 -22
- package/lib/types/config.d.ts +115 -0
- package/lib/types/csp.d.ts +9 -1
- package/lib/types/index.d.ts +120 -1
- package/lib/types/server.d.ts +192 -17
- package/lib/types/theme.d.ts +11 -2
- package/package.json +10 -10
- package/src/actions.ts +43 -5
- package/src/adapters/bun.ts +35 -7
- package/src/adapters/cloudflare.ts +17 -12
- package/src/adapters/netlify.ts +7 -1
- package/src/adapters/node.ts +33 -6
- package/src/adapters/vercel.ts +25 -4
- package/src/csp.ts +10 -7
- package/src/fs-router.ts +2 -1
- package/src/isr.ts +256 -51
- package/src/manifest.ts +23 -10
- package/src/server.ts +2 -1
- package/src/ssg-plugin.ts +27 -7
- package/src/theme.tsx +94 -38
- package/src/types.ts +76 -0
- package/lib/api-routes-CMsLztoj.js +0 -148
- package/lib/fs-router-3xzp-4Wj.js +0 -32
- package/lib/rolldown-runtime-CjeV3_4I.js +0 -18
package/src/theme.tsx
CHANGED
|
@@ -2,6 +2,10 @@ import type { VNodeChild } from '@pyreon/core'
|
|
|
2
2
|
import { onMount } from '@pyreon/core'
|
|
3
3
|
import { effect, signal } from '@pyreon/reactivity'
|
|
4
4
|
|
|
5
|
+
// Dev-mode counter sink — see packages/internals/perf-harness for contract.
|
|
6
|
+
const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'
|
|
7
|
+
const _countSink = globalThis as { __pyreon_count__?: (name: string, n?: number) => void }
|
|
8
|
+
|
|
5
9
|
// ─── Theme system ───────────────────────────────────────────────────────────
|
|
6
10
|
//
|
|
7
11
|
// Provides dark/light/system theme support with:
|
|
@@ -75,52 +79,104 @@ export function setTheme(t: Theme) {
|
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
81
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
// Refcount + shared-teardown for `initTheme()`. The first mount runs the
|
|
83
|
+
// real setup (localStorage read + matchMedia listener + effect); subsequent
|
|
84
|
+
// mounts (other ThemeToggles, or an explicit `initTheme()` call in a
|
|
85
|
+
// layout alongside `<ThemeToggle>`) only bump the refcount. Each unmount
|
|
86
|
+
// decrements; when the count returns to 0 the shared teardown runs.
|
|
87
|
+
//
|
|
88
|
+
// Pre-fix every `initTheme()` call ran its own `onMount(setup)` — N
|
|
89
|
+
// mounted ThemeToggles → N matchMedia listeners + N effects, all writing
|
|
90
|
+
// the SAME values to the SAME `document.documentElement.dataset.theme`.
|
|
91
|
+
// Class D event-listener pile-up, real production case for any app with
|
|
92
|
+
// header + footer ThemeToggle widgets.
|
|
93
|
+
let _initRefCount = 0
|
|
94
|
+
let _disposeShared: (() => void) | null = null
|
|
95
|
+
|
|
96
|
+
function _setupShared(): () => void {
|
|
97
|
+
// Read persisted preference
|
|
98
|
+
try {
|
|
99
|
+
const stored = localStorage.getItem(STORAGE_KEY) as Theme | null
|
|
100
|
+
if (stored === 'light' || stored === 'dark' || stored === 'system') {
|
|
101
|
+
theme.set(stored)
|
|
92
102
|
}
|
|
103
|
+
} catch {
|
|
104
|
+
// localStorage may not be available
|
|
105
|
+
}
|
|
93
106
|
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
// Apply to document
|
|
108
|
+
document.documentElement.dataset.theme = resolvedTheme()
|
|
109
|
+
|
|
110
|
+
// Watch for system preference changes. Seed the signal from the
|
|
111
|
+
// current media-query state, then update reactively on each OS
|
|
112
|
+
// preference flip. Components reading `resolvedTheme()` pick up the
|
|
113
|
+
// change automatically (they subscribe to `_osPrefersDark` when
|
|
114
|
+
// `theme === 'system'`).
|
|
115
|
+
const mq = window.matchMedia('(prefers-color-scheme: dark)')
|
|
116
|
+
_osPrefersDark.set(mq.matches)
|
|
117
|
+
function onChange(e: MediaQueryListEvent) {
|
|
118
|
+
_osPrefersDark.set(e.matches)
|
|
119
|
+
}
|
|
120
|
+
mq.addEventListener('change', onChange)
|
|
121
|
+
|
|
122
|
+
// Re-apply when theme signal changes — updates data-theme + favicons
|
|
123
|
+
const dispose = effect(() => {
|
|
124
|
+
const mode = resolvedTheme()
|
|
125
|
+
document.documentElement.dataset.theme = mode
|
|
96
126
|
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// `theme === 'system'`).
|
|
102
|
-
const mq = window.matchMedia('(prefers-color-scheme: dark)')
|
|
103
|
-
_osPrefersDark.set(mq.matches)
|
|
104
|
-
function onChange(e: MediaQueryListEvent) {
|
|
105
|
-
_osPrefersDark.set(e.matches)
|
|
127
|
+
// Swap favicon variants (if dual-variant favicons are present)
|
|
128
|
+
const faviconLinks = document.querySelectorAll<HTMLLinkElement>('[data-favicon-theme]')
|
|
129
|
+
for (const link of faviconLinks) {
|
|
130
|
+
link.media = link.dataset.faviconTheme === mode ? '' : 'not all'
|
|
106
131
|
}
|
|
107
|
-
|
|
132
|
+
})
|
|
108
133
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
134
|
+
return () => {
|
|
135
|
+
mq.removeEventListener('change', onChange)
|
|
136
|
+
dispose?.dispose()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
113
139
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Reset the refcount + shared teardown. Useful for tests.
|
|
142
|
+
* @internal
|
|
143
|
+
*/
|
|
144
|
+
export function _resetInitThemeForTests(): void {
|
|
145
|
+
if (_disposeShared) _disposeShared()
|
|
146
|
+
_initRefCount = 0
|
|
147
|
+
_disposeShared = null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Initialize the theme system. Safe to call multiple times — uses a
|
|
152
|
+
* mount-based refcount so multiple `<ThemeToggle>` instances (or
|
|
153
|
+
* `<ThemeToggle>` plus an explicit `initTheme()` in your layout) share
|
|
154
|
+
* a SINGLE matchMedia listener + effect.
|
|
155
|
+
*
|
|
156
|
+
* Reads from localStorage, listens for system preference changes.
|
|
157
|
+
*/
|
|
158
|
+
export function initTheme() {
|
|
159
|
+
onMount(() => {
|
|
160
|
+
if (_initRefCount === 0) {
|
|
161
|
+
_disposeShared = _setupShared()
|
|
162
|
+
}
|
|
163
|
+
_initRefCount++
|
|
164
|
+
// Leak-class D diagnostic — emit per refcount++. Net (acquire -
|
|
165
|
+
// release) = currently-mounted ThemeToggle count; should be
|
|
166
|
+
// bounded by the user's UI shape. Steady-state monotonic growth
|
|
167
|
+
// signals the refcount guard regressed (every mount registers a
|
|
168
|
+
// fresh listener again).
|
|
169
|
+
if (__DEV__) _countSink.__pyreon_count__?.('theme.initRefAcquire')
|
|
120
170
|
|
|
121
171
|
return () => {
|
|
122
|
-
|
|
123
|
-
|
|
172
|
+
_initRefCount--
|
|
173
|
+
// Pair with `theme.initRefAcquire`. Equal counts at process exit
|
|
174
|
+
// = healthy lifecycle. Diff = active subscribers / orphan inits.
|
|
175
|
+
if (__DEV__) _countSink.__pyreon_count__?.('theme.initRefRelease')
|
|
176
|
+
if (_initRefCount === 0 && _disposeShared) {
|
|
177
|
+
_disposeShared()
|
|
178
|
+
_disposeShared = null
|
|
179
|
+
}
|
|
124
180
|
}
|
|
125
181
|
})
|
|
126
182
|
}
|
package/src/types.ts
CHANGED
|
@@ -103,6 +103,82 @@ export interface ISRConfig {
|
|
|
103
103
|
* }
|
|
104
104
|
*/
|
|
105
105
|
cacheKey?: (req: Request) => string
|
|
106
|
+
/**
|
|
107
|
+
* Pluggable cache backing for multi-instance / horizontally-scaled
|
|
108
|
+
* production. Default: in-memory `Map` per-process (capped by
|
|
109
|
+
* `maxEntries`). Pass a Redis / Vercel KV / Cloudflare KV / Upstash
|
|
110
|
+
* adapter (anything matching the `ISRStore` interface from
|
|
111
|
+
* `@pyreon/zero/server`) for state shared across instances — a
|
|
112
|
+
* revalidation in one pod is visible to all pods.
|
|
113
|
+
*
|
|
114
|
+
* The store interface accepts BOTH sync and async returns; the
|
|
115
|
+
* handler `await`s the result either way, so an in-memory store
|
|
116
|
+
* stays cheap (no Promise allocation per request) while a Redis
|
|
117
|
+
* store can return its native promises directly.
|
|
118
|
+
*
|
|
119
|
+
* When set, `maxEntries` is ignored — the custom store owns its own
|
|
120
|
+
* eviction / TTL policy.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* // Redis adapter (uses `ioredis` or `@upstash/redis`):
|
|
124
|
+
* const redis = new Redis(...)
|
|
125
|
+
* const store: ISRStore = {
|
|
126
|
+
* async get(key) {
|
|
127
|
+
* const v = await redis.get(`isr:${key}`)
|
|
128
|
+
* return v ? JSON.parse(v) : undefined
|
|
129
|
+
* },
|
|
130
|
+
* async set(key, entry) {
|
|
131
|
+
* await redis.set(`isr:${key}`, JSON.stringify(entry), 'EX', 86400)
|
|
132
|
+
* },
|
|
133
|
+
* async delete(key) {
|
|
134
|
+
* await redis.del(`isr:${key}`)
|
|
135
|
+
* },
|
|
136
|
+
* }
|
|
137
|
+
*
|
|
138
|
+
* isr: { revalidate: 60, store }
|
|
139
|
+
*/
|
|
140
|
+
// The actual type lives in `./isr` to avoid `types.ts` pulling the
|
|
141
|
+
// implementation file; we type it as `unknown` here and let consumers
|
|
142
|
+
// pass an `ISRStore` directly — `createISRHandler`'s signature checks
|
|
143
|
+
// the shape statically.
|
|
144
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
145
|
+
store?: import('./isr').ISRStore<any>
|
|
146
|
+
/**
|
|
147
|
+
* Construct the `Request` used for background revalidation. Default:
|
|
148
|
+
* the ORIGINAL user's request (headers, method, URL) — which means a
|
|
149
|
+
* `cacheKey`-bearing entry triggered by user A is revalidated against
|
|
150
|
+
* A's cookies / auth headers. For auth-gated `cacheKey` setups this
|
|
151
|
+
* is risky: if A's session expires before the revalidation runs, the
|
|
152
|
+
* new render may misbehave (auth-gate hits redirect path, or worse,
|
|
153
|
+
* still emits A's personalized HTML because the server hasn't yet
|
|
154
|
+
* invalidated the session token).
|
|
155
|
+
*
|
|
156
|
+
* Supply `revalidateRequest` to construct a request scoped to the
|
|
157
|
+
* cache key — e.g. anonymous for anonymous entries, service-account
|
|
158
|
+
* for shared entries. Returning `null` SKIPS revalidation entirely
|
|
159
|
+
* for this entry (stale stays stale until the next live request).
|
|
160
|
+
*
|
|
161
|
+
* Compatible with `store`: the revalidate path still reads/writes
|
|
162
|
+
* the configured store; this hook only controls what request the
|
|
163
|
+
* re-render runs against.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* isr: {
|
|
167
|
+
* revalidate: 60,
|
|
168
|
+
* cacheKey: (req) => {
|
|
169
|
+
* const session = req.headers.get('cookie')?.match(/session=([^;]+)/)?.[1] ?? 'anon'
|
|
170
|
+
* return `${new URL(req.url).pathname}::${session}`
|
|
171
|
+
* },
|
|
172
|
+
* revalidateRequest: (req) => {
|
|
173
|
+
* // Anonymous entries re-revalidate as anonymous (safe default).
|
|
174
|
+
* // Authenticated entries skip revalidation — the user's next
|
|
175
|
+
* // hit will re-render with their current cookies on cache miss.
|
|
176
|
+
* const hasAuth = /session=(?!anon)/.test(req.headers.get('cookie') ?? '')
|
|
177
|
+
* return hasAuth ? null : new Request(req.url, { method: 'GET' })
|
|
178
|
+
* },
|
|
179
|
+
* }
|
|
180
|
+
*/
|
|
181
|
+
revalidateRequest?: (req: Request) => Request | null
|
|
106
182
|
}
|
|
107
183
|
|
|
108
184
|
// ─── Zero config ─────────────────────────────────────────────────────────────
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-CjeV3_4I.js";
|
|
2
|
-
|
|
3
|
-
//#region src/api-routes.ts
|
|
4
|
-
var api_routes_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
-
apiFilePathToPattern: () => apiFilePathToPattern,
|
|
6
|
-
createApiMiddleware: () => createApiMiddleware,
|
|
7
|
-
generateApiRouteModule: () => generateApiRouteModule,
|
|
8
|
-
isApiRoute: () => isApiRoute,
|
|
9
|
-
matchApiRoute: () => matchApiRoute
|
|
10
|
-
});
|
|
11
|
-
/**
|
|
12
|
-
* Match a URL path against an API route pattern.
|
|
13
|
-
* Returns extracted params or null if no match.
|
|
14
|
-
*/
|
|
15
|
-
function matchApiRoute(pattern, path) {
|
|
16
|
-
const patternParts = pattern.split("/").filter(Boolean);
|
|
17
|
-
const pathParts = path.split("/").filter(Boolean);
|
|
18
|
-
const params = {};
|
|
19
|
-
const isUnsafeParam = (name) => name === "__proto__" || name === "constructor" || name === "prototype";
|
|
20
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
21
|
-
const pp = patternParts[i];
|
|
22
|
-
if (!pp) continue;
|
|
23
|
-
if (pp.endsWith("*")) {
|
|
24
|
-
const paramName = pp.slice(1, -1);
|
|
25
|
-
if (!isUnsafeParam(paramName)) params[paramName] = pathParts.slice(i).join("/");
|
|
26
|
-
return params;
|
|
27
|
-
}
|
|
28
|
-
if (i >= pathParts.length) return null;
|
|
29
|
-
if (pp.startsWith(":")) {
|
|
30
|
-
const paramName = pp.slice(1);
|
|
31
|
-
if (!isUnsafeParam(paramName)) params[paramName] = pathParts[i];
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
if (pp !== pathParts[i]) return null;
|
|
35
|
-
}
|
|
36
|
-
return patternParts.length === pathParts.length ? params : null;
|
|
37
|
-
}
|
|
38
|
-
const HTTP_METHODS = [
|
|
39
|
-
"GET",
|
|
40
|
-
"POST",
|
|
41
|
-
"PUT",
|
|
42
|
-
"PATCH",
|
|
43
|
-
"DELETE",
|
|
44
|
-
"HEAD",
|
|
45
|
-
"OPTIONS"
|
|
46
|
-
];
|
|
47
|
-
/**
|
|
48
|
-
* Create a middleware that dispatches API route requests.
|
|
49
|
-
* API routes are matched by URL pattern and HTTP method.
|
|
50
|
-
*/
|
|
51
|
-
function createApiMiddleware(routes) {
|
|
52
|
-
return async (ctx) => {
|
|
53
|
-
for (const route of routes) {
|
|
54
|
-
const params = matchApiRoute(route.pattern, ctx.path);
|
|
55
|
-
if (!params) continue;
|
|
56
|
-
const method = ctx.req.method.toUpperCase();
|
|
57
|
-
const handler = route.module[method];
|
|
58
|
-
if (!handler) {
|
|
59
|
-
const allowed = HTTP_METHODS.filter((m) => route.module[m]).join(", ");
|
|
60
|
-
return new Response(null, {
|
|
61
|
-
status: 405,
|
|
62
|
-
headers: {
|
|
63
|
-
Allow: allowed,
|
|
64
|
-
"Content-Type": "application/json"
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
return handler({
|
|
69
|
-
request: ctx.req,
|
|
70
|
-
url: ctx.url,
|
|
71
|
-
path: ctx.path,
|
|
72
|
-
params,
|
|
73
|
-
headers: ctx.req.headers
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Detect whether a route file is an API route.
|
|
80
|
-
* API routes are `.ts` or `.js` files inside an `api/` directory.
|
|
81
|
-
*/
|
|
82
|
-
function isApiRoute(filePath) {
|
|
83
|
-
const normalized = filePath.replace(/\\/g, "/");
|
|
84
|
-
return normalized.startsWith("api/") && (normalized.endsWith(".ts") || normalized.endsWith(".js")) && !normalized.endsWith(".tsx") && !normalized.endsWith(".jsx");
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Convert an API route file path to a URL pattern.
|
|
88
|
-
*
|
|
89
|
-
* Examples:
|
|
90
|
-
* "api/posts.ts" → "/api/posts"
|
|
91
|
-
* "api/posts/index.ts" → "/api/posts"
|
|
92
|
-
* "api/posts/[id].ts" → "/api/posts/:id"
|
|
93
|
-
* "api/[...path].ts" → "/api/:path*"
|
|
94
|
-
*/
|
|
95
|
-
function apiFilePathToPattern(filePath) {
|
|
96
|
-
let route = filePath;
|
|
97
|
-
for (const ext of [".ts", ".js"]) if (route.endsWith(ext)) {
|
|
98
|
-
route = route.slice(0, -ext.length);
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
const segments = route.split("/");
|
|
102
|
-
const urlSegments = [];
|
|
103
|
-
for (const seg of segments) {
|
|
104
|
-
if (seg === "index") continue;
|
|
105
|
-
const catchAll = seg.match(/^\[\.\.\.(\w+)\]$/);
|
|
106
|
-
if (catchAll) {
|
|
107
|
-
urlSegments.push(`:${catchAll[1]}*`);
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
const dynamic = seg.match(/^\[(\w+)\]$/);
|
|
111
|
-
if (dynamic) {
|
|
112
|
-
urlSegments.push(`:${dynamic[1]}`);
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
urlSegments.push(seg);
|
|
116
|
-
}
|
|
117
|
-
return `/${urlSegments.join("/")}`;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Generate a virtual module that exports API route entries.
|
|
121
|
-
* Each entry maps a URL pattern to a module with HTTP method handlers.
|
|
122
|
-
*/
|
|
123
|
-
function generateApiRouteModule(files, routesDir) {
|
|
124
|
-
const apiFiles = files.filter(isApiRoute);
|
|
125
|
-
if (apiFiles.length === 0) return "export const apiRoutes = []\n";
|
|
126
|
-
const imports = [];
|
|
127
|
-
const entries = [];
|
|
128
|
-
for (let i = 0; i < apiFiles.length; i++) {
|
|
129
|
-
const name = `_api${i}`;
|
|
130
|
-
const file = apiFiles[i];
|
|
131
|
-
if (!file) continue;
|
|
132
|
-
const fullPath = `${routesDir}/${file}`;
|
|
133
|
-
const pattern = apiFilePathToPattern(file);
|
|
134
|
-
imports.push(`import * as ${name} from "${fullPath}"`);
|
|
135
|
-
entries.push(` { pattern: ${JSON.stringify(pattern)}, module: ${name} }`);
|
|
136
|
-
}
|
|
137
|
-
return [
|
|
138
|
-
...imports,
|
|
139
|
-
"",
|
|
140
|
-
"export const apiRoutes = [",
|
|
141
|
-
entries.join(",\n"),
|
|
142
|
-
"]"
|
|
143
|
-
].join("\n");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
//#endregion
|
|
147
|
-
export { matchApiRoute as i, createApiMiddleware as n, generateApiRouteModule as r, api_routes_exports as t };
|
|
148
|
-
//# sourceMappingURL=api-routes-CMsLztoj.js.map
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
|
|
3
|
-
//#region src/fs-router.ts
|
|
4
|
-
const ROUTE_EXTENSIONS = [
|
|
5
|
-
".tsx",
|
|
6
|
-
".jsx",
|
|
7
|
-
".ts",
|
|
8
|
-
".js"
|
|
9
|
-
];
|
|
10
|
-
/**
|
|
11
|
-
* Scan a directory for route files.
|
|
12
|
-
* Returns paths relative to the routes directory.
|
|
13
|
-
*/
|
|
14
|
-
async function scanRouteFiles(routesDir) {
|
|
15
|
-
const { readdir } = await import("node:fs/promises");
|
|
16
|
-
const { relative } = await import("node:path");
|
|
17
|
-
const files = [];
|
|
18
|
-
async function walk(dir) {
|
|
19
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
20
|
-
for (const entry of entries) {
|
|
21
|
-
const fullPath = join(dir, entry.name);
|
|
22
|
-
if (entry.isDirectory()) await walk(fullPath);
|
|
23
|
-
else if (ROUTE_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) files.push(relative(routesDir, fullPath));
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
await walk(routesDir);
|
|
27
|
-
return files;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
//#endregion
|
|
31
|
-
export { scanRouteFiles };
|
|
32
|
-
//# sourceMappingURL=fs-router-3xzp-4Wj.js.map
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
//#region \0rolldown/runtime.js
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __exportAll = (all, no_symbols) => {
|
|
4
|
-
let target = {};
|
|
5
|
-
for (var name in all) {
|
|
6
|
-
__defProp(target, name, {
|
|
7
|
-
get: all[name],
|
|
8
|
-
enumerable: true
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
if (!no_symbols) {
|
|
12
|
-
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
13
|
-
}
|
|
14
|
-
return target;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
//#endregion
|
|
18
|
-
export { __exportAll as t };
|