@ubean/scan 0.2.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/LICENSE +21 -0
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +2 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +558 -0
- package/dist/types.d.ts +136 -0
- package/dist/types.js +19 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Soybean
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { DEFAULT_HEADER_COMMENT, GeneratorOptions, GeneratorResult, GeneratorScanInput, RouteFileGenerator, generateRouteFiles } from "@ubean/vue/generator";
|
|
2
|
+
export { DEFAULT_HEADER_COMMENT, type GeneratorOptions, type GeneratorResult, type GeneratorScanInput, RouteFileGenerator, generateRouteFiles };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AppEntry, DefineMetaResult, HttpMethod, PageHead, PageMeta, RouteMeta, ScanOptions, ScanPagesOptions, ScanPagesResult, ScanResult, ScannedApiRoute, ScannedAppEntry, ScannedCronTask, ScannedFile, ScannedLayout, ScannedLocale, ScannedMiddleware, ScannedPage, ScannedPageRoute, ScannedPlugin, ScannedQueue, ScannedServerEntry } from "./types.js";
|
|
2
|
+
import { extractDefinePage, extractDefinePageFromCode, extractSlotAndIntercept, filePathToRoute, generateLayoutName, generateRouteName, parseMatchers, stripRouteGroups } from "@ubean/vue/vite";
|
|
3
|
+
import { MatcherFunction, MatcherGuardOptions, clearMatchers, createMatcherGuard, defineMatcher, getMatcher, hasMatcher, listMatcherNames, validateParams } from "@ubean/vue";
|
|
4
|
+
//#region src/scan.d.ts
|
|
5
|
+
declare function scanProject(options: ScanOptions): Promise<ScanResult>;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/detect-exports.d.ts
|
|
8
|
+
interface DetectExportsResult {
|
|
9
|
+
exports: string[];
|
|
10
|
+
httpMethods: Lowercase<HttpMethod>[];
|
|
11
|
+
hasMeta: boolean;
|
|
12
|
+
fileMeta?: RouteMeta;
|
|
13
|
+
}
|
|
14
|
+
declare function detectHttpExports(filePath: string): Promise<DetectExportsResult>;
|
|
15
|
+
declare function detectHttpExportsFromCode(code: string): DetectExportsResult;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/api-route-id.d.ts
|
|
18
|
+
/**
|
|
19
|
+
* 服务端专用:生成 API 路由的唯一 ID(`${method}${RouteName}`)。
|
|
20
|
+
* (页面路由名生成器 `generateRouteName` 所有权在 `@ubean/vue`。)
|
|
21
|
+
*/
|
|
22
|
+
declare function generateApiRouteId(method: string, routePath: string): string;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/define-page.d.ts
|
|
25
|
+
declare function extractDefineMetaFromCode(code: string): DefineMetaResult | null;
|
|
26
|
+
declare function extractDefineMeta(filePath: string): Promise<DefineMetaResult | null>;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { type AppEntry, type DefineMetaResult, type HttpMethod, type MatcherFunction, type MatcherGuardOptions, type PageHead, type PageMeta, type ScanOptions, type ScanPagesOptions, type ScanPagesResult, type ScanResult, type ScannedApiRoute, type ScannedAppEntry, type ScannedCronTask, type ScannedFile, type ScannedLayout, type ScannedLocale, type ScannedMiddleware, type ScannedPage, type ScannedPageRoute, type ScannedPlugin, type ScannedQueue, type ScannedServerEntry, clearMatchers, createMatcherGuard, defineMatcher, detectHttpExports, detectHttpExportsFromCode, extractDefineMeta, extractDefineMetaFromCode, extractDefinePage, extractDefinePageFromCode, extractSlotAndIntercept, filePathToRoute, generateApiRouteId, generateLayoutName, generateRouteName, getMatcher, hasMatcher, listMatcherNames, parseMatchers, scanProject, stripRouteGroups, validateParams };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
import { GLOB_SCAN_PATTERN, HTTP_METHODS } from "./types.js";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { getLogger } from "@ubean/logger";
|
|
4
|
+
import { extractCallObject, extractDefinePage, extractDefinePageFromCode, extractSlotAndIntercept, filePathToRoute, filePathToRoute as filePathToRoute$1, generateLayoutName, generateRouteName, generateRouteName as generateRouteName$1, parseMatchers, scanPages, stripRouteGroups } from "@ubean/vue/vite";
|
|
5
|
+
import { basename, dirname, extname, isAbsolute, join, relative } from "pathe";
|
|
6
|
+
import { glob } from "tinyglobby";
|
|
7
|
+
import { clearMatchers, createMatcherGuard, defineMatcher, getMatcher, hasMatcher, listMatcherNames, validateParams } from "@ubean/vue";
|
|
8
|
+
//#region src/detect-exports.ts
|
|
9
|
+
const EXPORT_NAMED_REGEX = /export\s+(?:async\s+)?(?:function\s+|const\s+|let\s+|var\s+)?(\w+)/g;
|
|
10
|
+
const EXPORT_LIST_REGEX = /export\s*\{([^}]+)\}/g;
|
|
11
|
+
const EXPORT_DEFINE_META_REGEX = /defineHandlerMeta\s*\(/;
|
|
12
|
+
const EXPORT_CONST_META_REGEX = /export\s+const\s+meta\s*=\s*(\{[\s\S]*?\})(?:\s*;|\s*$)/m;
|
|
13
|
+
const META_REQUIRES_AUTH_REGEX = /requiresAuth\s*:\s*(true|false)/;
|
|
14
|
+
async function detectHttpExports(filePath) {
|
|
15
|
+
return detectHttpExportsFromCode(await readFile(filePath, "utf-8"));
|
|
16
|
+
}
|
|
17
|
+
function detectHttpExportsFromCode(code) {
|
|
18
|
+
const exports = /* @__PURE__ */ new Set();
|
|
19
|
+
const httpMethods = [];
|
|
20
|
+
let match;
|
|
21
|
+
EXPORT_NAMED_REGEX.lastIndex = 0;
|
|
22
|
+
while ((match = EXPORT_NAMED_REGEX.exec(code)) !== null) {
|
|
23
|
+
const name = match[1];
|
|
24
|
+
exports.add(name);
|
|
25
|
+
const lower = name.toLowerCase();
|
|
26
|
+
if (HTTP_METHODS.includes(name) && !httpMethods.includes(lower)) httpMethods.push(lower);
|
|
27
|
+
}
|
|
28
|
+
EXPORT_LIST_REGEX.lastIndex = 0;
|
|
29
|
+
while ((match = EXPORT_LIST_REGEX.exec(code)) !== null) {
|
|
30
|
+
const list = match[1];
|
|
31
|
+
for (const item of list.split(",")) {
|
|
32
|
+
const name = item.trim().split(/\s+as\s+/).pop()?.trim();
|
|
33
|
+
if (name) {
|
|
34
|
+
exports.add(name);
|
|
35
|
+
const upper = name.toUpperCase();
|
|
36
|
+
const lower = name.toLowerCase();
|
|
37
|
+
if (HTTP_METHODS.includes(upper) && !httpMethods.includes(lower)) httpMethods.push(lower);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const hasDefineMeta = EXPORT_DEFINE_META_REGEX.test(code);
|
|
42
|
+
const fileMeta = extractFileMeta(code);
|
|
43
|
+
const hasMeta = hasDefineMeta || !!fileMeta;
|
|
44
|
+
return {
|
|
45
|
+
exports: [...exports],
|
|
46
|
+
httpMethods,
|
|
47
|
+
hasMeta,
|
|
48
|
+
fileMeta
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function extractFileMeta(code) {
|
|
52
|
+
const match = code.match(EXPORT_CONST_META_REGEX);
|
|
53
|
+
if (!match) return void 0;
|
|
54
|
+
const metaBlock = match[1];
|
|
55
|
+
const meta = {};
|
|
56
|
+
const requiresAuthMatch = metaBlock.match(META_REQUIRES_AUTH_REGEX);
|
|
57
|
+
if (requiresAuthMatch) meta.requiresAuth = requiresAuthMatch[1] === "true";
|
|
58
|
+
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/scan.ts
|
|
62
|
+
/**
|
|
63
|
+
* 聚合层项目扫描:`@ubean/scan` = 服务端目录扫描(API 路由/中间件/
|
|
64
|
+
* 插件/crons/queues/locales/entries)+ 页面路由(委托 `@ubean/vue` 的
|
|
65
|
+
* 页面扫描器 —— 页面路由唯一所有者)。
|
|
66
|
+
*
|
|
67
|
+
* 页面/布局扫描、reuse cache 继承、markdown frontmatter、页面级 head、
|
|
68
|
+
* 特殊页(404/loading/error)、并行/拦截路由、`[param=matcher]` 语法均
|
|
69
|
+
* 在 `@ubean/vue/vite` 的 `scanPages` 中实现;本文件只做聚合与默认值
|
|
70
|
+
* (框架模式:markdown 开启、head 开启,保证 SSR `pageObj.head` 链路可用)。
|
|
71
|
+
*/
|
|
72
|
+
const logger = getLogger("routing");
|
|
73
|
+
const DEFAULT_DIRS = {
|
|
74
|
+
routes: "routes",
|
|
75
|
+
middleware: "middleware",
|
|
76
|
+
pages: "pages",
|
|
77
|
+
layouts: "layouts",
|
|
78
|
+
plugins: "plugins",
|
|
79
|
+
crons: "crons",
|
|
80
|
+
queues: "queues",
|
|
81
|
+
locales: "locales"
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Normalize a `string | string[]` dir entry into `string[]`. Falsy values
|
|
85
|
+
* fall back to the provided default so callers always receive a non-empty
|
|
86
|
+
* list. Empty arrays are also replaced with the default to keep downstream
|
|
87
|
+
* globbing simple.
|
|
88
|
+
*/
|
|
89
|
+
function normalizeDirs(value, fallback) {
|
|
90
|
+
if (value === void 0 || value === null || value === "") return [fallback];
|
|
91
|
+
if (Array.isArray(value)) {
|
|
92
|
+
const filtered = value.filter((d) => typeof d === "string" && d.length > 0);
|
|
93
|
+
return filtered.length > 0 ? filtered : [fallback];
|
|
94
|
+
}
|
|
95
|
+
return [value];
|
|
96
|
+
}
|
|
97
|
+
function splitOrderPrefix(name) {
|
|
98
|
+
const match = name.match(/^(\d+)\.(.+)$/);
|
|
99
|
+
if (match) return {
|
|
100
|
+
order: Number(match[1]),
|
|
101
|
+
cleanName: match[2]
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
order: 0,
|
|
105
|
+
cleanName: name
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function toPosixPath(p) {
|
|
109
|
+
return p.replace(/\\/g, "/");
|
|
110
|
+
}
|
|
111
|
+
async function scanProject(options) {
|
|
112
|
+
const dirs = {
|
|
113
|
+
...DEFAULT_DIRS,
|
|
114
|
+
...options.dirs
|
|
115
|
+
};
|
|
116
|
+
const ignore = options.ignore || [
|
|
117
|
+
"**/*.test.*",
|
|
118
|
+
"**/*.spec.*",
|
|
119
|
+
"**/_*",
|
|
120
|
+
"**/*.d.ts"
|
|
121
|
+
];
|
|
122
|
+
const srcDir = isAbsolute(options.srcDir) ? options.srcDir : join(options.cwd, options.srcDir);
|
|
123
|
+
const markdown = options.markdown === void 0 ? true : options.markdown;
|
|
124
|
+
const head = options.head === void 0 ? true : options.head;
|
|
125
|
+
const [apiRoutes, middlewares, pagesResult, plugins, crons, queues, locales, appEntry, serverEntry] = await Promise.all([
|
|
126
|
+
scanApiRoutes(srcDir, dirs.routes, ignore),
|
|
127
|
+
scanMiddlewares(srcDir, dirs.middleware, ignore),
|
|
128
|
+
scanPages({
|
|
129
|
+
cwd: options.cwd,
|
|
130
|
+
srcDir,
|
|
131
|
+
pagesDir: dirs.pages,
|
|
132
|
+
layoutsDir: dirs.layouts,
|
|
133
|
+
ignore: options.ignore,
|
|
134
|
+
markdown,
|
|
135
|
+
head
|
|
136
|
+
}),
|
|
137
|
+
scanPlugins(srcDir, dirs.plugins, ignore),
|
|
138
|
+
scanCrons(srcDir, dirs.crons, ignore),
|
|
139
|
+
scanQueues(srcDir, dirs.queues, ignore),
|
|
140
|
+
scanLocales(srcDir, dirs.locales, ignore),
|
|
141
|
+
scanAppEntry(srcDir),
|
|
142
|
+
scanServerEntry(srcDir)
|
|
143
|
+
]);
|
|
144
|
+
const { pages, layouts, notFoundPage, loadingPage, errorPage } = pagesResult;
|
|
145
|
+
return {
|
|
146
|
+
apiRoutes,
|
|
147
|
+
middlewares,
|
|
148
|
+
pages,
|
|
149
|
+
layouts,
|
|
150
|
+
plugins,
|
|
151
|
+
crons,
|
|
152
|
+
queues,
|
|
153
|
+
locales,
|
|
154
|
+
defaultLocale: locales.find((l) => l.isDefault)?.code || locales[0]?.code,
|
|
155
|
+
appEntry,
|
|
156
|
+
serverEntry,
|
|
157
|
+
notFoundPage,
|
|
158
|
+
loadingPage,
|
|
159
|
+
errorPage
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async function scanApiRoutes(srcDir, dirName, ignore) {
|
|
163
|
+
const dirNames = normalizeDirs(dirName, "routes");
|
|
164
|
+
const routes = [];
|
|
165
|
+
for (const single of dirNames) {
|
|
166
|
+
const dir = join(srcDir, single);
|
|
167
|
+
const files = await glob(GLOB_SCAN_PATTERN, {
|
|
168
|
+
cwd: dir,
|
|
169
|
+
dot: true,
|
|
170
|
+
ignore: [
|
|
171
|
+
...ignore,
|
|
172
|
+
"**/*.vue",
|
|
173
|
+
"**/_*"
|
|
174
|
+
],
|
|
175
|
+
absolute: true
|
|
176
|
+
}).catch(() => []);
|
|
177
|
+
for (const fullPath of files.sort()) {
|
|
178
|
+
const relativePath = toPosixPath(relative(dir, fullPath));
|
|
179
|
+
const base = basename(relativePath, extname(relativePath));
|
|
180
|
+
const dirPart = dirname(relativePath) === "." ? "" : dirname(relativePath);
|
|
181
|
+
const fileBase = dirPart ? `${dirPart}/${base}` : base;
|
|
182
|
+
const parsed = filePathToRoute$1(fileBase);
|
|
183
|
+
const detected = await detectHttpExports(fullPath).catch(() => ({
|
|
184
|
+
exports: [],
|
|
185
|
+
httpMethods: [],
|
|
186
|
+
hasMeta: false,
|
|
187
|
+
fileMeta: void 0
|
|
188
|
+
}));
|
|
189
|
+
const methods = detected.httpMethods.length > 0 ? detected.httpMethods : parsed.method ? [parsed.method.toLowerCase()] : [];
|
|
190
|
+
for (const method of methods.length > 0 ? methods : HTTP_METHODS.map((m) => m.toLowerCase())) routes.push({
|
|
191
|
+
fullPath,
|
|
192
|
+
relativePath,
|
|
193
|
+
dirname: dirname(relativePath),
|
|
194
|
+
basename: basename(relativePath),
|
|
195
|
+
route: parsed.route,
|
|
196
|
+
method,
|
|
197
|
+
env: parsed.env,
|
|
198
|
+
exports: detected.exports,
|
|
199
|
+
hasMeta: detected.hasMeta,
|
|
200
|
+
fileMeta: detected.fileMeta,
|
|
201
|
+
matchers: parsed.matchers
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return routes;
|
|
206
|
+
}
|
|
207
|
+
async function scanMiddlewares(srcDir, dirName, ignore) {
|
|
208
|
+
const dirNames = normalizeDirs(dirName, "middleware");
|
|
209
|
+
const results = [];
|
|
210
|
+
for (const single of dirNames) {
|
|
211
|
+
const dir = join(srcDir, single);
|
|
212
|
+
const files = await glob(GLOB_SCAN_PATTERN, {
|
|
213
|
+
cwd: dir,
|
|
214
|
+
dot: true,
|
|
215
|
+
ignore: [
|
|
216
|
+
...ignore,
|
|
217
|
+
"**/*.vue",
|
|
218
|
+
"**/index.*",
|
|
219
|
+
"**/_*"
|
|
220
|
+
],
|
|
221
|
+
absolute: true
|
|
222
|
+
}).catch(() => []);
|
|
223
|
+
for (const fullPath of files.sort()) {
|
|
224
|
+
const relativePath = toPosixPath(relative(dir, fullPath));
|
|
225
|
+
const { order, cleanName } = splitOrderPrefix(basename(relativePath, extname(relativePath)));
|
|
226
|
+
results.push({
|
|
227
|
+
fullPath,
|
|
228
|
+
relativePath,
|
|
229
|
+
dirname: dirname(relativePath),
|
|
230
|
+
basename: basename(relativePath),
|
|
231
|
+
order,
|
|
232
|
+
global: cleanName === "global" || cleanName.startsWith("global.")
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return results;
|
|
237
|
+
}
|
|
238
|
+
async function scanPlugins(srcDir, dirName, ignore) {
|
|
239
|
+
const dirNames = normalizeDirs(dirName, "plugins");
|
|
240
|
+
const results = [];
|
|
241
|
+
for (const single of dirNames) {
|
|
242
|
+
const dir = join(srcDir, single);
|
|
243
|
+
const files = await glob(GLOB_SCAN_PATTERN, {
|
|
244
|
+
cwd: dir,
|
|
245
|
+
dot: true,
|
|
246
|
+
ignore: [
|
|
247
|
+
...ignore,
|
|
248
|
+
"**/*.vue",
|
|
249
|
+
"**/_*"
|
|
250
|
+
],
|
|
251
|
+
absolute: true
|
|
252
|
+
}).catch(() => []);
|
|
253
|
+
for (const fullPath of files.sort()) {
|
|
254
|
+
const relativePath = toPosixPath(relative(dir, fullPath));
|
|
255
|
+
const { order } = splitOrderPrefix(basename(relativePath, extname(relativePath)));
|
|
256
|
+
results.push({
|
|
257
|
+
fullPath,
|
|
258
|
+
relativePath,
|
|
259
|
+
dirname: dirname(relativePath),
|
|
260
|
+
basename: basename(relativePath),
|
|
261
|
+
order
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return results;
|
|
266
|
+
}
|
|
267
|
+
const APP_EXTENSIONS = [
|
|
268
|
+
"ts",
|
|
269
|
+
"js",
|
|
270
|
+
"mjs",
|
|
271
|
+
"mts"
|
|
272
|
+
];
|
|
273
|
+
async function scanCrons(srcDir, dirName, ignore) {
|
|
274
|
+
const dirNames = normalizeDirs(dirName, "crons");
|
|
275
|
+
const results = [];
|
|
276
|
+
for (const single of dirNames) {
|
|
277
|
+
const dir = join(srcDir, single);
|
|
278
|
+
const files = await glob(GLOB_SCAN_PATTERN, {
|
|
279
|
+
cwd: dir,
|
|
280
|
+
dot: true,
|
|
281
|
+
ignore: [
|
|
282
|
+
...ignore,
|
|
283
|
+
"**/*.vue",
|
|
284
|
+
"**/_*"
|
|
285
|
+
],
|
|
286
|
+
absolute: true
|
|
287
|
+
}).catch(() => []);
|
|
288
|
+
for (const fullPath of files.sort()) {
|
|
289
|
+
const relativePath = toPosixPath(relative(dir, fullPath));
|
|
290
|
+
const { cleanName } = splitOrderPrefix(basename(relativePath, extname(relativePath)));
|
|
291
|
+
results.push({
|
|
292
|
+
fullPath,
|
|
293
|
+
relativePath,
|
|
294
|
+
dirname: dirname(relativePath),
|
|
295
|
+
basename: basename(relativePath),
|
|
296
|
+
name: cleanName
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return results;
|
|
301
|
+
}
|
|
302
|
+
async function scanQueues(srcDir, dirName, ignore) {
|
|
303
|
+
const dirNames = normalizeDirs(dirName, "queues");
|
|
304
|
+
const results = [];
|
|
305
|
+
for (const single of dirNames) {
|
|
306
|
+
const dir = join(srcDir, single);
|
|
307
|
+
const files = await glob(GLOB_SCAN_PATTERN, {
|
|
308
|
+
cwd: dir,
|
|
309
|
+
dot: true,
|
|
310
|
+
ignore: [
|
|
311
|
+
...ignore,
|
|
312
|
+
"**/*.vue",
|
|
313
|
+
"**/_*"
|
|
314
|
+
],
|
|
315
|
+
absolute: true
|
|
316
|
+
}).catch(() => []);
|
|
317
|
+
for (const fullPath of files.sort()) {
|
|
318
|
+
const relativePath = toPosixPath(relative(dir, fullPath));
|
|
319
|
+
const { cleanName } = splitOrderPrefix(basename(relativePath, extname(relativePath)));
|
|
320
|
+
results.push({
|
|
321
|
+
fullPath,
|
|
322
|
+
relativePath,
|
|
323
|
+
dirname: dirname(relativePath),
|
|
324
|
+
basename: basename(relativePath),
|
|
325
|
+
name: cleanName
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return results;
|
|
330
|
+
}
|
|
331
|
+
const LOCALE_GLOB_PATTERN = "**/*.{json,json5,yaml,yml,js,mjs,cjs,ts,mts,cts}";
|
|
332
|
+
function parseSimpleYaml(content) {
|
|
333
|
+
const result = {};
|
|
334
|
+
const lines = content.split("\n");
|
|
335
|
+
let currentObj = result;
|
|
336
|
+
const stack = [];
|
|
337
|
+
for (const line of lines) {
|
|
338
|
+
const trimmed = line.replace(/#.*$/, "").trimEnd();
|
|
339
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
340
|
+
const indent = line.length - line.trimStart().length;
|
|
341
|
+
const match = trimmed.match(/^([\w.-]+)\s*:\s*(.*)$/);
|
|
342
|
+
if (!match) continue;
|
|
343
|
+
const [, key, value] = match;
|
|
344
|
+
while (stack.length > 0 && stack[stack.length - 1].indent >= indent) stack.pop();
|
|
345
|
+
currentObj = stack.length > 0 ? stack[stack.length - 1].obj : result;
|
|
346
|
+
if (value === "" || value === void 0) {
|
|
347
|
+
const newObj = {};
|
|
348
|
+
currentObj[key] = newObj;
|
|
349
|
+
stack.push({
|
|
350
|
+
obj: newObj,
|
|
351
|
+
indent
|
|
352
|
+
});
|
|
353
|
+
currentObj = newObj;
|
|
354
|
+
} else {
|
|
355
|
+
let parsed = value;
|
|
356
|
+
if (value === "true") parsed = true;
|
|
357
|
+
else if (value === "false") parsed = false;
|
|
358
|
+
else if (value === "null") parsed = null;
|
|
359
|
+
else if (/^-?\d+$/.test(value)) parsed = Number.parseInt(value, 10);
|
|
360
|
+
else if (/^-?\d+\.\d+$/.test(value)) parsed = Number.parseFloat(value);
|
|
361
|
+
else if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) parsed = value.slice(1, -1);
|
|
362
|
+
currentObj[key] = parsed;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
async function loadLocaleFile(fullPath, ext) {
|
|
368
|
+
const extract = (data) => {
|
|
369
|
+
const isWrapper = (typeof data.name === "string" || data.dir === "ltr" || data.dir === "rtl" || typeof data.isDefault === "boolean") && typeof data.messages === "object" && data.messages !== null;
|
|
370
|
+
return {
|
|
371
|
+
messages: isWrapper ? data.messages : data,
|
|
372
|
+
meta: isWrapper ? {
|
|
373
|
+
name: data.name,
|
|
374
|
+
dir: data.dir,
|
|
375
|
+
isDefault: data.isDefault
|
|
376
|
+
} : void 0
|
|
377
|
+
};
|
|
378
|
+
};
|
|
379
|
+
if (ext === ".json" || ext === ".json5") {
|
|
380
|
+
const content = await readFile(fullPath, "utf-8");
|
|
381
|
+
return extract(JSON.parse(content));
|
|
382
|
+
}
|
|
383
|
+
if (ext === ".yaml" || ext === ".yml") return extract(parseSimpleYaml(await readFile(fullPath, "utf-8")));
|
|
384
|
+
if (ext === ".js" || ext === ".mjs" || ext === ".cjs" || ext === ".ts" || ext === ".mts" || ext === ".cts") {
|
|
385
|
+
const mod = await import(
|
|
386
|
+
/* @vite-ignore */
|
|
387
|
+
fullPath
|
|
388
|
+
).catch(() => null);
|
|
389
|
+
if (mod) return extract(mod.default || mod);
|
|
390
|
+
}
|
|
391
|
+
return { messages: {} };
|
|
392
|
+
}
|
|
393
|
+
async function scanLocales(srcDir, dirName, ignore) {
|
|
394
|
+
const dirNames = normalizeDirs(dirName, "locales");
|
|
395
|
+
const locales = [];
|
|
396
|
+
const seenFullPaths = /* @__PURE__ */ new Set();
|
|
397
|
+
for (const single of dirNames) {
|
|
398
|
+
const dir = join(srcDir, single);
|
|
399
|
+
const files = await glob(LOCALE_GLOB_PATTERN, {
|
|
400
|
+
cwd: dir,
|
|
401
|
+
dot: true,
|
|
402
|
+
ignore: [
|
|
403
|
+
...ignore,
|
|
404
|
+
"**/*.vue",
|
|
405
|
+
"**/_*"
|
|
406
|
+
],
|
|
407
|
+
absolute: true
|
|
408
|
+
}).catch(() => []);
|
|
409
|
+
for (const fullPath of files.sort()) {
|
|
410
|
+
if (seenFullPaths.has(fullPath)) continue;
|
|
411
|
+
seenFullPaths.add(fullPath);
|
|
412
|
+
const relativePath = toPosixPath(relative(dir, fullPath));
|
|
413
|
+
const ext = extname(relativePath);
|
|
414
|
+
const base = basename(relativePath, ext);
|
|
415
|
+
const { order: _order, cleanName: code } = splitOrderPrefix(base);
|
|
416
|
+
const dirPart = dirname(relativePath) === "." ? "" : dirname(relativePath);
|
|
417
|
+
let namespace;
|
|
418
|
+
let finalCode = code;
|
|
419
|
+
let isDefault = code === "default" || base.startsWith("default.");
|
|
420
|
+
if (dirPart) {
|
|
421
|
+
const dirParts = dirPart.split("/");
|
|
422
|
+
const isIndexFile = code === "index";
|
|
423
|
+
finalCode = dirParts[0];
|
|
424
|
+
const nsParts = [];
|
|
425
|
+
nsParts.push(...dirParts.slice(1));
|
|
426
|
+
if (!isIndexFile) nsParts.push(code);
|
|
427
|
+
if (nsParts.length > 0) namespace = nsParts.join(".");
|
|
428
|
+
}
|
|
429
|
+
let name;
|
|
430
|
+
let dirVal;
|
|
431
|
+
try {
|
|
432
|
+
const { meta } = await loadLocaleFile(fullPath, ext);
|
|
433
|
+
if (meta?.name) name = meta.name;
|
|
434
|
+
if (meta?.dir) dirVal = meta.dir;
|
|
435
|
+
if (meta?.isDefault) isDefault = true;
|
|
436
|
+
} catch {
|
|
437
|
+
logger.warn(`Failed to parse locale file: ${relativePath}`);
|
|
438
|
+
}
|
|
439
|
+
if (isDefault && finalCode === "default") finalCode = "en";
|
|
440
|
+
locales.push({
|
|
441
|
+
fullPath,
|
|
442
|
+
relativePath,
|
|
443
|
+
dirname: dirname(relativePath),
|
|
444
|
+
basename: basename(relativePath),
|
|
445
|
+
code: finalCode,
|
|
446
|
+
namespace,
|
|
447
|
+
isDefault,
|
|
448
|
+
name,
|
|
449
|
+
dir: dirVal
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return locales;
|
|
454
|
+
}
|
|
455
|
+
async function scanAppEntry(srcDir) {
|
|
456
|
+
const result = {
|
|
457
|
+
shared: { exists: false },
|
|
458
|
+
server: { exists: false },
|
|
459
|
+
client: { exists: false }
|
|
460
|
+
};
|
|
461
|
+
const findEntry = async (baseName) => {
|
|
462
|
+
for (const ext of APP_EXTENSIONS) {
|
|
463
|
+
const fullPath = join(srcDir, `${baseName}.${ext}`);
|
|
464
|
+
if (await fileExists(fullPath)) return {
|
|
465
|
+
exists: true,
|
|
466
|
+
fullPath,
|
|
467
|
+
relativePath: `${baseName}.${ext}`
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
return { exists: false };
|
|
471
|
+
};
|
|
472
|
+
const [shared, server, client] = await Promise.all([
|
|
473
|
+
findEntry("app"),
|
|
474
|
+
findEntry("app.server"),
|
|
475
|
+
findEntry("app.client")
|
|
476
|
+
]);
|
|
477
|
+
result.shared = shared;
|
|
478
|
+
result.server = server;
|
|
479
|
+
result.client = client;
|
|
480
|
+
return result;
|
|
481
|
+
}
|
|
482
|
+
async function scanServerEntry(srcDir) {
|
|
483
|
+
const result = {
|
|
484
|
+
shared: { exists: false },
|
|
485
|
+
dev: { exists: false },
|
|
486
|
+
prod: { exists: false }
|
|
487
|
+
};
|
|
488
|
+
const findEntry = async (baseName) => {
|
|
489
|
+
for (const ext of APP_EXTENSIONS) {
|
|
490
|
+
const fullPath = join(srcDir, `${baseName}.${ext}`);
|
|
491
|
+
if (await fileExists(fullPath)) return {
|
|
492
|
+
exists: true,
|
|
493
|
+
fullPath,
|
|
494
|
+
relativePath: `${baseName}.${ext}`
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
return { exists: false };
|
|
498
|
+
};
|
|
499
|
+
const [shared, dev, prod] = await Promise.all([
|
|
500
|
+
findEntry("server"),
|
|
501
|
+
findEntry("server.dev"),
|
|
502
|
+
findEntry("server.prod")
|
|
503
|
+
]);
|
|
504
|
+
result.shared = shared;
|
|
505
|
+
result.dev = dev;
|
|
506
|
+
result.prod = prod;
|
|
507
|
+
return result;
|
|
508
|
+
}
|
|
509
|
+
async function fileExists(path) {
|
|
510
|
+
const { existsSync } = await import("node:fs");
|
|
511
|
+
return existsSync(path);
|
|
512
|
+
}
|
|
513
|
+
//#endregion
|
|
514
|
+
//#region src/api-route-id.ts
|
|
515
|
+
/**
|
|
516
|
+
* 服务端专用:生成 API 路由的唯一 ID(`${method}${RouteName}`)。
|
|
517
|
+
* (页面路由名生成器 `generateRouteName` 所有权在 `@ubean/vue`。)
|
|
518
|
+
*/
|
|
519
|
+
function generateApiRouteId(method, routePath) {
|
|
520
|
+
const routeName = generateRouteName$1(routePath);
|
|
521
|
+
return `${method.toLowerCase()}${routeName}`;
|
|
522
|
+
}
|
|
523
|
+
//#endregion
|
|
524
|
+
//#region src/define-page.ts
|
|
525
|
+
function extractScriptContent(code) {
|
|
526
|
+
if (!code.includes("<script")) return code;
|
|
527
|
+
let scriptContent = "";
|
|
528
|
+
const scriptRegex = /<script([^>]*)>([\s\S]*?)<\/script>/g;
|
|
529
|
+
let match;
|
|
530
|
+
while ((match = scriptRegex.exec(code)) !== null) {
|
|
531
|
+
const attrs = match[1] || "";
|
|
532
|
+
const content = match[2] || "";
|
|
533
|
+
if (attrs.includes("setup")) return content;
|
|
534
|
+
scriptContent += `${content}\n`;
|
|
535
|
+
}
|
|
536
|
+
return scriptContent || null;
|
|
537
|
+
}
|
|
538
|
+
function extractDefineMetaFromCode(code) {
|
|
539
|
+
const scriptContent = extractScriptContent(code);
|
|
540
|
+
if (!scriptContent) return null;
|
|
541
|
+
const parsed = extractCallObject(scriptContent, "defineHandlerMeta");
|
|
542
|
+
if (!parsed) return null;
|
|
543
|
+
const result = {};
|
|
544
|
+
if (typeof parsed.requiresAuth === "boolean") result.requiresAuth = parsed.requiresAuth;
|
|
545
|
+
if (parsed.meta && typeof parsed.meta === "object") result.meta = parsed.meta;
|
|
546
|
+
else {
|
|
547
|
+
const knownKeys = /* @__PURE__ */ new Set(["requiresAuth", "meta"]);
|
|
548
|
+
const extra = {};
|
|
549
|
+
for (const [key, val] of Object.entries(parsed)) if (!knownKeys.has(key)) extra[key] = val;
|
|
550
|
+
if (Object.keys(extra).length > 0) result.meta = extra;
|
|
551
|
+
}
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
554
|
+
async function extractDefineMeta(filePath) {
|
|
555
|
+
return extractDefineMetaFromCode(await readFile(filePath, "utf-8"));
|
|
556
|
+
}
|
|
557
|
+
//#endregion
|
|
558
|
+
export { clearMatchers, createMatcherGuard, defineMatcher, detectHttpExports, detectHttpExportsFromCode, extractDefineMeta, extractDefineMetaFromCode, extractDefinePage, extractDefinePageFromCode, extractSlotAndIntercept, filePathToRoute, generateApiRouteId, generateLayoutName, generateRouteName, getMatcher, hasMatcher, listMatcherNames, parseMatchers, scanProject, stripRouteGroups, validateParams };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { PageHead, PageMeta, ScanPagesOptions, ScanPagesResult, ScannedLayout, ScannedPage, ScannedPage as ScannedPageRoute } from "@ubean/vue";
|
|
2
|
+
import { RouteMeta, RouteMeta as RouteMeta$1 } from "@ubean/shared";
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* HTTP methods supported by ubean API routes.
|
|
6
|
+
* Aligned with Hono's built-in methods (excludes TRACE).
|
|
7
|
+
*/
|
|
8
|
+
declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
9
|
+
type HttpMethod = (typeof HTTP_METHODS)[number];
|
|
10
|
+
interface ScannedFile {
|
|
11
|
+
fullPath: string;
|
|
12
|
+
relativePath: string;
|
|
13
|
+
dirname: string;
|
|
14
|
+
basename: string;
|
|
15
|
+
}
|
|
16
|
+
interface ScannedApiRoute extends ScannedFile {
|
|
17
|
+
route: string;
|
|
18
|
+
method?: HttpMethod | Lowercase<HttpMethod>;
|
|
19
|
+
env?: 'dev' | 'prod' | 'prerender';
|
|
20
|
+
exports: string[];
|
|
21
|
+
hasMeta: boolean;
|
|
22
|
+
fileMeta?: RouteMeta$1;
|
|
23
|
+
/**
|
|
24
|
+
* 路由参数 → matcher 名称映射。
|
|
25
|
+
*
|
|
26
|
+
* 由文件路由 `[param=matcher]` 语法解析得到,例如 `routes/api/users/[id=numeric].ts`
|
|
27
|
+
* → `{ id: 'numeric' }`。运行时 router 在匹配到该路由后,调用对应名称的 matcher
|
|
28
|
+
* 函数校验参数值,失败则视为不匹配(返回 404,让 Hono 继续匹配下一候选路由)。
|
|
29
|
+
*
|
|
30
|
+
* 与 `ScannedPage.matchers` 同义,但作用于 API 路由。
|
|
31
|
+
*/
|
|
32
|
+
matchers?: Record<string, string>;
|
|
33
|
+
}
|
|
34
|
+
interface ScannedMiddleware extends ScannedFile {
|
|
35
|
+
order: number;
|
|
36
|
+
global: boolean;
|
|
37
|
+
}
|
|
38
|
+
interface ScannedPlugin extends ScannedFile {
|
|
39
|
+
order: number;
|
|
40
|
+
}
|
|
41
|
+
interface ScannedCronTask extends ScannedFile {
|
|
42
|
+
name: string;
|
|
43
|
+
}
|
|
44
|
+
interface ScannedQueue extends ScannedFile {
|
|
45
|
+
name: string;
|
|
46
|
+
}
|
|
47
|
+
interface ScannedLocale extends ScannedFile {
|
|
48
|
+
code: string;
|
|
49
|
+
namespace?: string;
|
|
50
|
+
isDefault?: boolean;
|
|
51
|
+
name?: string;
|
|
52
|
+
dir?: 'ltr' | 'rtl';
|
|
53
|
+
}
|
|
54
|
+
interface AppEntry {
|
|
55
|
+
exists: boolean;
|
|
56
|
+
fullPath?: string;
|
|
57
|
+
relativePath?: string;
|
|
58
|
+
}
|
|
59
|
+
interface ScannedAppEntry {
|
|
60
|
+
shared: AppEntry;
|
|
61
|
+
server: AppEntry;
|
|
62
|
+
client: AppEntry;
|
|
63
|
+
}
|
|
64
|
+
interface ScannedServerEntry {
|
|
65
|
+
shared: AppEntry;
|
|
66
|
+
dev: AppEntry;
|
|
67
|
+
prod: AppEntry;
|
|
68
|
+
}
|
|
69
|
+
interface ScanOptions {
|
|
70
|
+
cwd: string;
|
|
71
|
+
srcDir: string;
|
|
72
|
+
dirs?: {
|
|
73
|
+
routes?: string | string[];
|
|
74
|
+
middleware?: string | string[];
|
|
75
|
+
pages?: string | string[];
|
|
76
|
+
layouts?: string | string[];
|
|
77
|
+
plugins?: string | string[];
|
|
78
|
+
crons?: string | string[];
|
|
79
|
+
queues?: string | string[];
|
|
80
|
+
locales?: string | string[];
|
|
81
|
+
};
|
|
82
|
+
ignore?: string[];
|
|
83
|
+
/**
|
|
84
|
+
* 页面 markdown 支持(委托给 `@ubean/vue` 扫描器)。
|
|
85
|
+
* 框架模式默认开启(`true` = `.md` + `.mdx`);传 `'md'` / `'mdx'` 精细控制。
|
|
86
|
+
*/
|
|
87
|
+
markdown?: boolean | 'mdx' | 'md';
|
|
88
|
+
/**
|
|
89
|
+
* 页面级 head 支持(委托给 `@ubean/vue` 扫描器)。
|
|
90
|
+
* 框架模式(SSR)默认开启(`true`),保证 `pageObj.head` 链路可用。
|
|
91
|
+
*/
|
|
92
|
+
head?: boolean;
|
|
93
|
+
}
|
|
94
|
+
interface ScanResult {
|
|
95
|
+
apiRoutes: ScannedApiRoute[];
|
|
96
|
+
middlewares: ScannedMiddleware[];
|
|
97
|
+
pages: import('@ubean/vue').ScannedPage[];
|
|
98
|
+
layouts: import('@ubean/vue').ScannedLayout[];
|
|
99
|
+
plugins: ScannedPlugin[];
|
|
100
|
+
crons: ScannedCronTask[];
|
|
101
|
+
queues: ScannedQueue[];
|
|
102
|
+
locales: ScannedLocale[];
|
|
103
|
+
defaultLocale?: string;
|
|
104
|
+
appEntry: ScannedAppEntry;
|
|
105
|
+
serverEntry: ScannedServerEntry;
|
|
106
|
+
/**
|
|
107
|
+
* `pages/404.vue`(或 `.tsx`/`.md`)自动检测的 404 页面。
|
|
108
|
+
* 不作为常规路由注册,而是作为 Vue Router 的 catch-all 路由
|
|
109
|
+
* `/:pathMatch(.*)*` 和 Hono 的兜底页面处理器。
|
|
110
|
+
* `undefined` 表示未找到 404 页面文件。
|
|
111
|
+
*/
|
|
112
|
+
notFoundPage?: import('@ubean/vue').ScannedPage;
|
|
113
|
+
/**
|
|
114
|
+
* `pages/loading.vue`(或 `.ts`/`.md`)自动检测的加载组件。
|
|
115
|
+
* 不作为路由注册,而是作为 `<Suspense>` 的 fallback 组件,
|
|
116
|
+
* 在 SPA 导航懒加载页面组件时显示。
|
|
117
|
+
* `undefined` 表示未找到 loading 页面文件。
|
|
118
|
+
*/
|
|
119
|
+
loadingPage?: import('@ubean/vue').ScannedPage;
|
|
120
|
+
/**
|
|
121
|
+
* `pages/error.vue`(或 `.ts`/`.md`)自动检测的错误兜底组件。
|
|
122
|
+
* 不作为路由注册,而是作为渲染错误边界(ErrorBoundary)组件,
|
|
123
|
+
* 当页面组件渲染、异步解析或 setup 抛出错误时显示。
|
|
124
|
+
* `undefined` 表示未找到 error 页面文件。
|
|
125
|
+
*/
|
|
126
|
+
errorPage?: import('@ubean/vue').ScannedPage;
|
|
127
|
+
}
|
|
128
|
+
declare const GLOB_SCAN_PATTERN = "**/*.{js,mjs,cjs,ts,mts,cts,tsx,jsx}";
|
|
129
|
+
declare const GLOB_VUE_PATTERN = "**/*.{vue,ts,md,mdx}";
|
|
130
|
+
declare const GLOB_LAYOUT_PATTERN = "**/*.{vue,ts}";
|
|
131
|
+
interface DefineMetaResult {
|
|
132
|
+
meta?: Record<string, unknown>;
|
|
133
|
+
requiresAuth?: boolean;
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
136
|
+
export { AppEntry, DefineMetaResult, GLOB_LAYOUT_PATTERN, GLOB_SCAN_PATTERN, GLOB_VUE_PATTERN, HTTP_METHODS, HttpMethod, type PageHead, type PageMeta, type RouteMeta, ScanOptions, type ScanPagesOptions, type ScanPagesResult, ScanResult, ScannedApiRoute, ScannedAppEntry, ScannedCronTask, ScannedFile, type ScannedLayout, ScannedLocale, ScannedMiddleware, type ScannedPage, type ScannedPageRoute, ScannedPlugin, ScannedQueue, ScannedServerEntry };
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/types.ts
|
|
2
|
+
/**
|
|
3
|
+
* HTTP methods supported by ubean API routes.
|
|
4
|
+
* Aligned with Hono's built-in methods (excludes TRACE).
|
|
5
|
+
*/
|
|
6
|
+
const HTTP_METHODS = [
|
|
7
|
+
"GET",
|
|
8
|
+
"POST",
|
|
9
|
+
"PUT",
|
|
10
|
+
"PATCH",
|
|
11
|
+
"DELETE",
|
|
12
|
+
"HEAD",
|
|
13
|
+
"OPTIONS"
|
|
14
|
+
];
|
|
15
|
+
const GLOB_SCAN_PATTERN = "**/*.{js,mjs,cjs,ts,mts,cts,tsx,jsx}";
|
|
16
|
+
const GLOB_VUE_PATTERN = "**/*.{vue,ts,md,mdx}";
|
|
17
|
+
const GLOB_LAYOUT_PATTERN = "**/*.{vue,ts}";
|
|
18
|
+
//#endregion
|
|
19
|
+
export { GLOB_LAYOUT_PATTERN, GLOB_SCAN_PATTERN, GLOB_VUE_PATTERN, HTTP_METHODS };
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ubean/scan",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Project scanner & route metadata for ubean — server-side directory scanning (API routes, middleware, plugins, crons, queues, locales, entries) plus page scanning delegated to @ubean/vue",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"module": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./generator": {
|
|
18
|
+
"types": "./dist/generator/index.d.ts",
|
|
19
|
+
"import": "./dist/generator/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./types": {
|
|
22
|
+
"types": "./dist/types.d.ts",
|
|
23
|
+
"import": "./dist/types.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"pathe": "^2.0.3",
|
|
28
|
+
"tinyglobby": "^0.2.17",
|
|
29
|
+
"@ubean/logger": "0.2.0",
|
|
30
|
+
"@ubean/shared": "0.2.0",
|
|
31
|
+
"@ubean/vue": "0.2.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^26.2.0",
|
|
35
|
+
"typescript": "7.0.2",
|
|
36
|
+
"vite-plus": "0.2.9",
|
|
37
|
+
"vue": "^3.5.41",
|
|
38
|
+
"vue-router": "^5.2.0",
|
|
39
|
+
"@ubean/markdown": "0.2.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"vue-router": "^4.0.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"vue-router": {
|
|
46
|
+
"optional": true
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "vp pack",
|
|
51
|
+
"dev": "vp pack --watch",
|
|
52
|
+
"test": "vp test",
|
|
53
|
+
"typecheck": "tsc --noEmit --skipLibCheck"
|
|
54
|
+
}
|
|
55
|
+
}
|