@ubean/build 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions-plugin.d.ts +62 -0
- package/dist/actions-plugin.js +385 -0
- package/dist/codegen/index.d.ts +175 -0
- package/dist/codegen/index.js +2 -0
- package/dist/codegen-CYk6X-Hn.js +691 -0
- package/dist/index.d.ts +95 -7
- package/dist/index.js +4 -3
- package/dist/prerender.d.ts +129 -0
- package/dist/prerender.js +308 -0
- package/dist/production.d.ts +2 -0
- package/dist/production.js +51 -21
- package/dist/ssr-singleton-CEQi_H6-.js +113 -0
- package/dist/{virtual-modules-BDgTqx3t.js → virtual-modules-D5WBA3u7.js} +175 -68
- package/dist/virtual-registry-BF0jYPle.d.ts +30 -0
- package/dist/virtual-registry-BWkaQHXN.js +69 -0
- package/dist/vite-B3y8Qoan.js +226 -0
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +1 -210
- package/dist/vue-CeSUQZ1J.js +694 -0
- package/dist/vue.d.ts +29 -0
- package/dist/vue.js +2 -0
- package/package.json +43 -20
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import { isAbsolute, join, normalize, relative } from "pathe";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { glob } from "tinyglobby";
|
|
4
|
+
import { createUnimport, toTypeDeclarationFile } from "unimport";
|
|
5
|
+
import openapiTS, { astToString } from "openapi-typescript";
|
|
6
|
+
//#region src/codegen/auto-imports.ts
|
|
7
|
+
const VUE_PRESET = {
|
|
8
|
+
from: "vue",
|
|
9
|
+
imports: [
|
|
10
|
+
"ref",
|
|
11
|
+
"computed",
|
|
12
|
+
"reactive",
|
|
13
|
+
"readonly",
|
|
14
|
+
"watch",
|
|
15
|
+
"watchEffect",
|
|
16
|
+
"watchPostEffect",
|
|
17
|
+
"watchSyncEffect",
|
|
18
|
+
"onMounted",
|
|
19
|
+
"onUnmounted",
|
|
20
|
+
"onBeforeMount",
|
|
21
|
+
"onBeforeUnmount",
|
|
22
|
+
"onUpdated",
|
|
23
|
+
"onBeforeUpdate",
|
|
24
|
+
"onActivated",
|
|
25
|
+
"onDeactivated",
|
|
26
|
+
"onErrorCaptured",
|
|
27
|
+
"onServerPrefetch",
|
|
28
|
+
"onRenderTracked",
|
|
29
|
+
"onRenderTriggered",
|
|
30
|
+
"provide",
|
|
31
|
+
"inject",
|
|
32
|
+
"shallowRef",
|
|
33
|
+
"shallowReactive",
|
|
34
|
+
"shallowReadonly",
|
|
35
|
+
"isRef",
|
|
36
|
+
"isReactive",
|
|
37
|
+
"isReadonly",
|
|
38
|
+
"isProxy",
|
|
39
|
+
"unref",
|
|
40
|
+
"toRef",
|
|
41
|
+
"toRefs",
|
|
42
|
+
"toRaw",
|
|
43
|
+
"markRaw",
|
|
44
|
+
"triggerRef",
|
|
45
|
+
"customRef",
|
|
46
|
+
"effectScope",
|
|
47
|
+
"getCurrentScope",
|
|
48
|
+
"onScopeDispose",
|
|
49
|
+
"defineComponent",
|
|
50
|
+
"defineAsyncComponent",
|
|
51
|
+
"defineProps",
|
|
52
|
+
"defineEmits",
|
|
53
|
+
"defineExpose",
|
|
54
|
+
"defineOptions",
|
|
55
|
+
"defineSlots",
|
|
56
|
+
"defineModel",
|
|
57
|
+
"useSlots",
|
|
58
|
+
"useAttrs",
|
|
59
|
+
"useTemplateRef",
|
|
60
|
+
"nextTick",
|
|
61
|
+
"toValue",
|
|
62
|
+
"useId",
|
|
63
|
+
"useCssModule",
|
|
64
|
+
"useCssVars",
|
|
65
|
+
"useTransitionState"
|
|
66
|
+
]
|
|
67
|
+
};
|
|
68
|
+
const VUE_MACROS_PRESET = {
|
|
69
|
+
from: "vue/macros",
|
|
70
|
+
imports: [
|
|
71
|
+
"$",
|
|
72
|
+
"$$",
|
|
73
|
+
"$ref",
|
|
74
|
+
"$computed",
|
|
75
|
+
"$shallowRef",
|
|
76
|
+
"$customRef",
|
|
77
|
+
"$toRef"
|
|
78
|
+
]
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Client-safe symbols sourced from the first-class `ubean/client` entry.
|
|
82
|
+
* These are safe to auto-import in Vue components (browser-side) because
|
|
83
|
+
* `@ubean/client` has zero build-time dependencies and no `node:*` imports.
|
|
84
|
+
* (`ubean/runtime/vue` still works — it re-exports the same kernel — but
|
|
85
|
+
* new code should prefer `ubean/client`.)
|
|
86
|
+
*/
|
|
87
|
+
const UBEAN_CLIENT_PRESET = {
|
|
88
|
+
from: "ubean/client",
|
|
89
|
+
imports: [
|
|
90
|
+
"definePage",
|
|
91
|
+
"defineMiddleware",
|
|
92
|
+
"defineApp",
|
|
93
|
+
"applyAppConfig",
|
|
94
|
+
"createDefaultAppConfig",
|
|
95
|
+
"t",
|
|
96
|
+
"useI18n",
|
|
97
|
+
"setLocale",
|
|
98
|
+
"useLocalePath",
|
|
99
|
+
"useSwitchLocalePath",
|
|
100
|
+
"useLocaleRoute",
|
|
101
|
+
"useLocaleHead",
|
|
102
|
+
"useColorMode",
|
|
103
|
+
"useScript",
|
|
104
|
+
"useSearch",
|
|
105
|
+
"useSeoMeta",
|
|
106
|
+
"usePage",
|
|
107
|
+
"useRouter",
|
|
108
|
+
"useHead",
|
|
109
|
+
"useViewTransition",
|
|
110
|
+
"useAsyncData",
|
|
111
|
+
"useFetch",
|
|
112
|
+
"useCacheViews",
|
|
113
|
+
"enablePageCache",
|
|
114
|
+
"disablePageCache",
|
|
115
|
+
"excludePageCache",
|
|
116
|
+
"includePageCache",
|
|
117
|
+
"isPageExcluded",
|
|
118
|
+
"resetRouteCache",
|
|
119
|
+
"invalidatePageCache",
|
|
120
|
+
"isPageCached",
|
|
121
|
+
"usePageTransition",
|
|
122
|
+
"setPageTransition",
|
|
123
|
+
"clearPageTransition",
|
|
124
|
+
"useReloadSignal",
|
|
125
|
+
"reloadPage",
|
|
126
|
+
"isReloading"
|
|
127
|
+
]
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Server-only symbols that come from the main `ubean` package.
|
|
131
|
+
* These import the full ubean entry (which includes build tools like `vite`),
|
|
132
|
+
* so they must only be used in server-side files (API routes, middleware, etc.).
|
|
133
|
+
*/
|
|
134
|
+
const UBEAN_SERVER_PRESET = {
|
|
135
|
+
from: "ubean",
|
|
136
|
+
imports: [
|
|
137
|
+
"defineHandlerMeta",
|
|
138
|
+
"useData",
|
|
139
|
+
"useAsyncData",
|
|
140
|
+
"useFetch",
|
|
141
|
+
"defineAction",
|
|
142
|
+
"defineServerFn",
|
|
143
|
+
"invokeServerFn",
|
|
144
|
+
"createInternalAdapter",
|
|
145
|
+
"defineScheduled",
|
|
146
|
+
"defineQueue",
|
|
147
|
+
"sendMessage",
|
|
148
|
+
"sendMessages",
|
|
149
|
+
"getQueueStats",
|
|
150
|
+
"useDatabase",
|
|
151
|
+
"defineDatabase",
|
|
152
|
+
"useKV",
|
|
153
|
+
"createKV",
|
|
154
|
+
"useStorage"
|
|
155
|
+
]
|
|
156
|
+
};
|
|
157
|
+
const HONO_OPENAPI_PRESET = {
|
|
158
|
+
from: "hono-openapi",
|
|
159
|
+
imports: ["validator", "describeRoute"]
|
|
160
|
+
};
|
|
161
|
+
const BUILTIN_PRESETS = [
|
|
162
|
+
UBEAN_CLIENT_PRESET,
|
|
163
|
+
UBEAN_SERVER_PRESET,
|
|
164
|
+
HONO_OPENAPI_PRESET
|
|
165
|
+
];
|
|
166
|
+
function toPosixPath(p) {
|
|
167
|
+
return p.replace(/\\/g, "/");
|
|
168
|
+
}
|
|
169
|
+
function toCamelCase(str) {
|
|
170
|
+
return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase());
|
|
171
|
+
}
|
|
172
|
+
function toPascalCase(str) {
|
|
173
|
+
const camel = toCamelCase(str);
|
|
174
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
175
|
+
}
|
|
176
|
+
function fileBasename(p, ext) {
|
|
177
|
+
const parts = toPosixPath(p).split("/");
|
|
178
|
+
let base = parts[parts.length - 1] || "";
|
|
179
|
+
if (ext && base.endsWith(ext)) base = base.slice(0, -ext.length);
|
|
180
|
+
else if (!ext) {
|
|
181
|
+
const dotIdx = base.lastIndexOf(".");
|
|
182
|
+
if (dotIdx > 0) base = base.slice(0, dotIdx);
|
|
183
|
+
}
|
|
184
|
+
return base;
|
|
185
|
+
}
|
|
186
|
+
function fileDirname(p) {
|
|
187
|
+
const parts = toPosixPath(p).split("/");
|
|
188
|
+
parts.pop();
|
|
189
|
+
return parts.join("/") || ".";
|
|
190
|
+
}
|
|
191
|
+
function transformImportPath(filePath, srcDir) {
|
|
192
|
+
const posixPath = toPosixPath(normalize(filePath));
|
|
193
|
+
const posixSrcDir = toPosixPath(normalize(srcDir));
|
|
194
|
+
return `~/${toPosixPath(relative(posixSrcDir, posixPath)).replace(/\.(ts|js|mts|mjs|cts|cjs|tsx|jsx)$/, "")}`;
|
|
195
|
+
}
|
|
196
|
+
async function scanComponentsDir(dir, srcDir, directoryAsNamespace, ignore = [
|
|
197
|
+
"**/*.test.*",
|
|
198
|
+
"**/*.spec.*",
|
|
199
|
+
"**/_*"
|
|
200
|
+
]) {
|
|
201
|
+
const components = [];
|
|
202
|
+
const files = await glob("**/*.vue", {
|
|
203
|
+
cwd: dir,
|
|
204
|
+
dot: true,
|
|
205
|
+
ignore,
|
|
206
|
+
absolute: true
|
|
207
|
+
}).catch(() => []);
|
|
208
|
+
for (const fullPath of files.sort()) {
|
|
209
|
+
const relativeToSrc = toPosixPath(relative(srcDir, fullPath));
|
|
210
|
+
const relativeToDir = toPosixPath(relative(dir, fullPath));
|
|
211
|
+
const base = fileBasename(fullPath);
|
|
212
|
+
if (base.startsWith("_")) continue;
|
|
213
|
+
let name;
|
|
214
|
+
if (directoryAsNamespace) {
|
|
215
|
+
const dirPart = fileDirname(relativeToDir) === "." ? "" : fileDirname(relativeToDir);
|
|
216
|
+
const parts = dirPart ? dirPart.split("/").filter(Boolean) : [];
|
|
217
|
+
parts.push(base);
|
|
218
|
+
name = parts.map(toPascalCase).join("");
|
|
219
|
+
} else name = toPascalCase(base);
|
|
220
|
+
components.push({
|
|
221
|
+
name,
|
|
222
|
+
filePath: fullPath,
|
|
223
|
+
importPath: `~/${relativeToSrc}`,
|
|
224
|
+
pascalName: name
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return components;
|
|
228
|
+
}
|
|
229
|
+
async function generateAutoImports(_scanResult, options) {
|
|
230
|
+
const { cwd, srcDir, buildDir, composablesDirs = [], componentsDirs = [], dirs = {}, imports: importsConfig, components: componentsConfig } = options;
|
|
231
|
+
const outDir = join(cwd, buildDir);
|
|
232
|
+
await mkdir(outDir, { recursive: true });
|
|
233
|
+
const autoImportEnabled = importsConfig?.autoImport !== false;
|
|
234
|
+
const componentAutoImportEnabled = componentsConfig?.autoImport !== false;
|
|
235
|
+
const directoryAsNamespace = componentsConfig?.directoryAsNamespace ?? false;
|
|
236
|
+
const composablesDir = dirs.composables || "composables";
|
|
237
|
+
const componentsDir = dirs.components || "components";
|
|
238
|
+
let composablesImports = [];
|
|
239
|
+
let components = [];
|
|
240
|
+
const autoImportsDtsPath = join(outDir, "auto-imports.d.ts");
|
|
241
|
+
const componentsDtsPath = join(outDir, "components.d.ts");
|
|
242
|
+
if (autoImportEnabled) {
|
|
243
|
+
const allComposablesDirs = [join(srcDir, composablesDir), ...composablesDirs];
|
|
244
|
+
const existingDirs = [];
|
|
245
|
+
for (const dir of allComposablesDirs) try {
|
|
246
|
+
const { statSync } = await import("node:fs");
|
|
247
|
+
if (statSync(dir).isDirectory()) existingDirs.push(dir);
|
|
248
|
+
} catch {}
|
|
249
|
+
const unimport = createUnimport({
|
|
250
|
+
presets: BUILTIN_PRESETS,
|
|
251
|
+
dirs: existingDirs,
|
|
252
|
+
dirsScanOptions: {
|
|
253
|
+
cwd: srcDir,
|
|
254
|
+
filePatterns: ["*.{ts,js,mts,mjs,cts,cjs}"],
|
|
255
|
+
types: false
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
await unimport.init();
|
|
259
|
+
composablesImports = (await unimport.getImports()).map((imp) => {
|
|
260
|
+
if (imp.from === "vue" || imp.from === "vue/macros" || imp.from === "ubean" || imp.from.startsWith("ubean/")) return imp;
|
|
261
|
+
return {
|
|
262
|
+
...imp,
|
|
263
|
+
from: transformImportPath(imp.from, srcDir)
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
const dtsContent = toTypeDeclarationFile(composablesImports, { resolvePath: (imp) => {
|
|
267
|
+
if (imp.from === "vue" || imp.from === "vue/macros" || imp.from === "ubean" || imp.from.startsWith("ubean/")) return imp.from;
|
|
268
|
+
return transformImportPath(imp.from, srcDir);
|
|
269
|
+
} });
|
|
270
|
+
await writeFile(autoImportsDtsPath, dtsContent, "utf-8");
|
|
271
|
+
} else await writeFile(autoImportsDtsPath, "// Auto-generated by ubean - auto-imports disabled\n/* eslint-disable */\n// @ts-nocheck\nexport {}\n", "utf-8");
|
|
272
|
+
if (componentAutoImportEnabled) {
|
|
273
|
+
const allComponentsDirs = [join(srcDir, componentsDir), ...componentsDirs];
|
|
274
|
+
for (const dir of allComponentsDirs) {
|
|
275
|
+
const scanned = await scanComponentsDir(dir, srcDir, directoryAsNamespace);
|
|
276
|
+
components.push(...scanned);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const componentsDts = generateComponentsDts(components, componentsDtsPath);
|
|
280
|
+
await writeFile(componentsDtsPath, componentsDts, "utf-8");
|
|
281
|
+
return {
|
|
282
|
+
composablesImports,
|
|
283
|
+
components,
|
|
284
|
+
autoImportsDtsPath,
|
|
285
|
+
componentsDtsPath
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Generate the `.ubean/components.d.ts` in a format compatible with
|
|
290
|
+
* unplugin-vue-components (both writers own the same file).
|
|
291
|
+
*
|
|
292
|
+
* `ubean dev` runs codegen first, then unplugin-vue-components rewrites the
|
|
293
|
+
* file in dev mode using its append merge: it keeps entries scraped from the
|
|
294
|
+
* existing `GlobalComponents` interface but drops any surrounding
|
|
295
|
+
* `import`/`const` declaration lines. Entries must therefore be
|
|
296
|
+
* self-contained inline `typeof import('...')` declarations so they stay
|
|
297
|
+
* valid after unplugin merges them.
|
|
298
|
+
*/
|
|
299
|
+
function generateComponentsDts(components, dtsPath) {
|
|
300
|
+
const dtsDir = fileDirname(toPosixPath(normalize(dtsPath)));
|
|
301
|
+
const entries = /* @__PURE__ */ new Map();
|
|
302
|
+
for (const name of [
|
|
303
|
+
"Link",
|
|
304
|
+
"Head",
|
|
305
|
+
"PageView"
|
|
306
|
+
]) entries.set(name, `typeof import('ubean/client')['${name}']`);
|
|
307
|
+
for (const comp of components) {
|
|
308
|
+
const importPath = `./${toPosixPath(relative(dtsDir, toPosixPath(normalize(comp.filePath))))}`;
|
|
309
|
+
entries.set(comp.pascalName, `typeof import('${importPath}')['default']`);
|
|
310
|
+
}
|
|
311
|
+
const lines = [
|
|
312
|
+
"// Auto-generated by ubean - do not edit manually",
|
|
313
|
+
"/* eslint-disable */",
|
|
314
|
+
"// @ts-nocheck",
|
|
315
|
+
"// biome-ignore lint: disable",
|
|
316
|
+
"// oxlint-disable",
|
|
317
|
+
"",
|
|
318
|
+
"export {}",
|
|
319
|
+
"",
|
|
320
|
+
"/* prettier-ignore */",
|
|
321
|
+
"declare module 'vue' {",
|
|
322
|
+
" export interface GlobalComponents {"
|
|
323
|
+
];
|
|
324
|
+
const sorted = [...entries.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
325
|
+
lines.push(...sorted.map(([name, declaration]) => ` ${name}: ${declaration}`));
|
|
326
|
+
lines.push(" }");
|
|
327
|
+
lines.push("}");
|
|
328
|
+
lines.push("");
|
|
329
|
+
return `${lines.join("\n")}\n`;
|
|
330
|
+
}
|
|
331
|
+
function getBuiltinComposables() {
|
|
332
|
+
const imports = [];
|
|
333
|
+
for (const preset of BUILTIN_PRESETS) for (const name of preset.imports) if (typeof name === "string") imports.push({
|
|
334
|
+
name,
|
|
335
|
+
from: preset.from
|
|
336
|
+
});
|
|
337
|
+
else if (Array.isArray(name)) imports.push({
|
|
338
|
+
name: name[0],
|
|
339
|
+
as: name[1],
|
|
340
|
+
from: preset.from
|
|
341
|
+
});
|
|
342
|
+
return imports;
|
|
343
|
+
}
|
|
344
|
+
function getUbeanAutoImportConfig(options = {}) {
|
|
345
|
+
const cwd = options.cwd || process.cwd();
|
|
346
|
+
const srcDir = options.srcDir || join(cwd, "src");
|
|
347
|
+
const buildDir = options.buildDir || ".ubean";
|
|
348
|
+
const composablesDirs = [join(srcDir, "composables"), ...options.composablesDirs || []];
|
|
349
|
+
return {
|
|
350
|
+
imports: [
|
|
351
|
+
UBEAN_CLIENT_PRESET,
|
|
352
|
+
UBEAN_SERVER_PRESET,
|
|
353
|
+
HONO_OPENAPI_PRESET
|
|
354
|
+
],
|
|
355
|
+
dirs: composablesDirs,
|
|
356
|
+
dts: join(cwd, buildDir, "auto-imports.d.ts"),
|
|
357
|
+
vueTemplate: true,
|
|
358
|
+
eslintrc: { enabled: false }
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function getUbeanComponentsConfig(options = {}) {
|
|
362
|
+
const cwd = options.cwd || process.cwd();
|
|
363
|
+
const srcDir = options.srcDir || join(cwd, "src");
|
|
364
|
+
const buildDir = options.buildDir || ".ubean";
|
|
365
|
+
return {
|
|
366
|
+
dirs: [join(srcDir, "components"), ...options.componentsDirs || []],
|
|
367
|
+
extensions: ["vue"],
|
|
368
|
+
directoryAsNamespace: options.directoryAsNamespace ?? false,
|
|
369
|
+
dts: join(cwd, buildDir, "components.d.ts"),
|
|
370
|
+
deep: true
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function generateImportsTransform(imports) {
|
|
374
|
+
const importGroups = /* @__PURE__ */ new Map();
|
|
375
|
+
for (const imp of imports) {
|
|
376
|
+
if (!importGroups.has(imp.from)) importGroups.set(imp.from, /* @__PURE__ */ new Set());
|
|
377
|
+
const namePart = imp.as ? `${imp.name} as ${imp.as}` : imp.name;
|
|
378
|
+
importGroups.get(imp.from).add(namePart);
|
|
379
|
+
}
|
|
380
|
+
const importStatements = [];
|
|
381
|
+
for (const [from, names] of importGroups) importStatements.push(`import { ${Array.from(names).join(", ")} } from '${from}';`);
|
|
382
|
+
return { code: importStatements.join("\n") };
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region src/codegen/i18n-types.ts
|
|
386
|
+
function isIdent(key) {
|
|
387
|
+
return /^[A-Za-z_$][\w$]*$/.test(key);
|
|
388
|
+
}
|
|
389
|
+
/** Convert a JSON message tree into a TypeScript object type body. */
|
|
390
|
+
function messagesToTsType(value, indent = 1) {
|
|
391
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return "string";
|
|
392
|
+
const obj = value;
|
|
393
|
+
const keys = Object.keys(obj);
|
|
394
|
+
if (keys.length === 0) return "Record<string, never>";
|
|
395
|
+
const pad = " ".repeat(indent);
|
|
396
|
+
return `{\n${keys.map((k) => {
|
|
397
|
+
const key = isIdent(k) ? k : JSON.stringify(k);
|
|
398
|
+
return `${pad}${key}: ${messagesToTsType(obj[k], indent + 1)};`;
|
|
399
|
+
}).join("\n")}\n${" ".repeat(indent - 1)}}`;
|
|
400
|
+
}
|
|
401
|
+
function unwrapMessages(raw) {
|
|
402
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
403
|
+
const obj = raw;
|
|
404
|
+
if ((typeof obj.name === "string" || obj.dir === "ltr" || obj.dir === "rtl" || typeof obj.isDefault === "boolean") && obj.messages && typeof obj.messages === "object" && obj.messages !== null) return obj.messages;
|
|
405
|
+
return obj;
|
|
406
|
+
}
|
|
407
|
+
function pickDefaultLocaleFile(locales, defaultLocale) {
|
|
408
|
+
const jsonFiles = locales.filter((l) => {
|
|
409
|
+
const lower = l.fullPath.toLowerCase();
|
|
410
|
+
return (lower.endsWith(".json") || lower.endsWith(".json5")) && !l.namespace;
|
|
411
|
+
});
|
|
412
|
+
if (jsonFiles.length === 0) return void 0;
|
|
413
|
+
if (defaultLocale) {
|
|
414
|
+
const match = jsonFiles.find((l) => l.code === defaultLocale);
|
|
415
|
+
if (match) return match;
|
|
416
|
+
}
|
|
417
|
+
return jsonFiles.find((l) => l.isDefault) || jsonFiles[0];
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Generate `.ubean/i18n.d.ts` augmenting vue-i18n `DefineLocaleMessage`
|
|
421
|
+
* from the default locale JSON. Users can also hand-write the same
|
|
422
|
+
* module augmentation if they prefer.
|
|
423
|
+
*/
|
|
424
|
+
async function generateI18nTypes(result, options) {
|
|
425
|
+
const { outDir, fileName = "i18n.d.ts", defaultLocale } = options;
|
|
426
|
+
const source = pickDefaultLocaleFile(result.locales || [], defaultLocale);
|
|
427
|
+
if (!source) return null;
|
|
428
|
+
let raw;
|
|
429
|
+
try {
|
|
430
|
+
const text = await readFile(source.fullPath, "utf8");
|
|
431
|
+
raw = JSON.parse(text);
|
|
432
|
+
} catch {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
const messages = unwrapMessages(raw);
|
|
436
|
+
if (!messages) return null;
|
|
437
|
+
const typeBody = messagesToTsType(messages, 2);
|
|
438
|
+
const contents = `// Auto-generated by ubean from ${source.relativePath} — do not edit manually
|
|
439
|
+
// Typed \`t('key')\` via vue-i18n \`DefineLocaleMessage\`.
|
|
440
|
+
// Override by adding your own \`declare module 'vue-i18n'\` in src/.
|
|
441
|
+
/* eslint-disable */
|
|
442
|
+
export {};
|
|
443
|
+
|
|
444
|
+
declare module 'vue-i18n' {
|
|
445
|
+
export interface DefineLocaleMessage ${typeBody}
|
|
446
|
+
}
|
|
447
|
+
`;
|
|
448
|
+
await mkdir(outDir, { recursive: true });
|
|
449
|
+
const outPath = join(outDir, fileName);
|
|
450
|
+
await writeFile(outPath, contents, "utf8");
|
|
451
|
+
return outPath;
|
|
452
|
+
}
|
|
453
|
+
//#endregion
|
|
454
|
+
//#region src/codegen/route-types.ts
|
|
455
|
+
/**
|
|
456
|
+
* Compute a portable file path for a scanned route.
|
|
457
|
+
*
|
|
458
|
+
* - If `fullPath` is under `cwd`, returns a POSIX-style project-relative path
|
|
459
|
+
* (e.g. `src/routes/api/users/[id].ts`). This keeps generated `.d.ts` files
|
|
460
|
+
* portable across machines and free of leaked absolute paths.
|
|
461
|
+
* - Otherwise, falls back to the raw `fullPath` (already non-sensitive for
|
|
462
|
+
* non-local sources).
|
|
463
|
+
*/
|
|
464
|
+
function toPortableFilePath(route, cwd) {
|
|
465
|
+
return (isAbsolute(route.fullPath) ? relative(cwd, route.fullPath) : route.fullPath).replace(/\\/g, "/");
|
|
466
|
+
}
|
|
467
|
+
async function generateRouteTypes(result, options) {
|
|
468
|
+
const { cwd, outDir, fileName = "routes.d.ts" } = options;
|
|
469
|
+
await mkdir(outDir, { recursive: true });
|
|
470
|
+
const apiRoutes = result.apiRoutes;
|
|
471
|
+
const lines = [
|
|
472
|
+
"// Auto-generated by ubean - do not edit manually",
|
|
473
|
+
"/* eslint-disable */",
|
|
474
|
+
"// @ts-nocheck",
|
|
475
|
+
"",
|
|
476
|
+
"declare module \"ubean:routes\" {",
|
|
477
|
+
" export interface ApiRoute {",
|
|
478
|
+
" method: string;",
|
|
479
|
+
" path: string;",
|
|
480
|
+
" filePath: string;",
|
|
481
|
+
" meta?: Record<string, unknown>;",
|
|
482
|
+
" }",
|
|
483
|
+
""
|
|
484
|
+
];
|
|
485
|
+
if (apiRoutes.length > 0) {
|
|
486
|
+
const groups = /* @__PURE__ */ new Map();
|
|
487
|
+
for (const r of apiRoutes) {
|
|
488
|
+
const list = groups.get(r.route);
|
|
489
|
+
if (list) list.push(r);
|
|
490
|
+
else groups.set(r.route, [r]);
|
|
491
|
+
}
|
|
492
|
+
const entries = [];
|
|
493
|
+
for (const [path, routes] of groups) {
|
|
494
|
+
const methodEntries = routes.map((r) => {
|
|
495
|
+
const method = (r.method || "ALL").toUpperCase();
|
|
496
|
+
const filePath = toPortableFilePath(r, cwd);
|
|
497
|
+
return ` ${method}: { filePath: ${JSON.stringify(filePath)} }`;
|
|
498
|
+
}).join(";\n");
|
|
499
|
+
entries.push(` ${JSON.stringify(path)}: {\n${methodEntries}\n }`);
|
|
500
|
+
}
|
|
501
|
+
lines.push(" export type ApiRouteMap = {");
|
|
502
|
+
lines.push(`${entries.join(";\n")};`);
|
|
503
|
+
lines.push(" };");
|
|
504
|
+
} else lines.push(" export type ApiRouteMap = {};");
|
|
505
|
+
lines.push("");
|
|
506
|
+
if (apiRoutes.length > 0) lines.push(" export type ApiRoutePath = keyof ApiRouteMap;");
|
|
507
|
+
else lines.push(" export type ApiRoutePath = string;");
|
|
508
|
+
lines.push("");
|
|
509
|
+
const routeMethods = [...new Set(apiRoutes.map((r) => r.method?.toUpperCase()).filter(Boolean))].sort();
|
|
510
|
+
if (routeMethods.length > 0) {
|
|
511
|
+
lines.push(" export type ApiMethod =");
|
|
512
|
+
lines.push(routeMethods.map((m) => ` | ${JSON.stringify(m)}`).join("\n"));
|
|
513
|
+
lines.push(" ;");
|
|
514
|
+
} else lines.push(" export type ApiMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\" | \"OPTIONS\" | \"HEAD\";");
|
|
515
|
+
lines.push("");
|
|
516
|
+
lines.push(" export const routes: ApiRoute[];");
|
|
517
|
+
lines.push(" export const middlewares: { filePath: string; order: number; global: boolean }[];");
|
|
518
|
+
lines.push("}");
|
|
519
|
+
const content = `${lines.join("\n")}\n`;
|
|
520
|
+
const filePath = join(outDir, fileName);
|
|
521
|
+
await writeFile(filePath, content, "utf-8");
|
|
522
|
+
return filePath;
|
|
523
|
+
}
|
|
524
|
+
async function generatePageTypes(result, options) {
|
|
525
|
+
const { outDir, fileName = "pages.d.ts" } = options;
|
|
526
|
+
await mkdir(outDir, { recursive: true });
|
|
527
|
+
const pages = result.pages;
|
|
528
|
+
const layouts = result.layouts;
|
|
529
|
+
const lines = [
|
|
530
|
+
"// Auto-generated by ubean - do not edit manually",
|
|
531
|
+
"/* eslint-disable */",
|
|
532
|
+
"// @ts-nocheck",
|
|
533
|
+
"",
|
|
534
|
+
"declare module \"ubean:pages\" {",
|
|
535
|
+
" export interface PageInfo {",
|
|
536
|
+
" name: string;",
|
|
537
|
+
" path: string;",
|
|
538
|
+
" filePath: string;",
|
|
539
|
+
" layout?: string;",
|
|
540
|
+
" reuseTarget?: string;",
|
|
541
|
+
" }",
|
|
542
|
+
"",
|
|
543
|
+
" export interface LayoutInfo {",
|
|
544
|
+
" name: string;",
|
|
545
|
+
" filePath: string;",
|
|
546
|
+
" isDefault: boolean;",
|
|
547
|
+
" }",
|
|
548
|
+
""
|
|
549
|
+
];
|
|
550
|
+
const pageNames = pages.map((p) => JSON.stringify(p.name));
|
|
551
|
+
if (pageNames.length > 0) {
|
|
552
|
+
lines.push(" export type RouteName =");
|
|
553
|
+
lines.push(pageNames.map((n) => ` | ${n}`).join("\n"));
|
|
554
|
+
lines.push(" ;");
|
|
555
|
+
} else lines.push(" export type RouteName = string;");
|
|
556
|
+
lines.push("");
|
|
557
|
+
const layoutNames = layouts.map((l) => JSON.stringify(l.name));
|
|
558
|
+
if (layoutNames.length > 0) {
|
|
559
|
+
lines.push(" export type LayoutName =");
|
|
560
|
+
lines.push(layoutNames.map((n) => ` | ${n}`).join("\n"));
|
|
561
|
+
lines.push(" ;");
|
|
562
|
+
} else lines.push(" export type LayoutName = string;");
|
|
563
|
+
lines.push("");
|
|
564
|
+
lines.push(" export const pages: Record<RouteName, PageInfo>;");
|
|
565
|
+
lines.push(" export const layouts: Record<LayoutName, LayoutInfo>;");
|
|
566
|
+
lines.push("}");
|
|
567
|
+
const content = `${lines.join("\n")}\n`;
|
|
568
|
+
const filePath = join(outDir, fileName);
|
|
569
|
+
await writeFile(filePath, content, "utf-8");
|
|
570
|
+
return filePath;
|
|
571
|
+
}
|
|
572
|
+
//#endregion
|
|
573
|
+
//#region src/codegen/openapi-types.ts
|
|
574
|
+
/**
|
|
575
|
+
* OpenAPI 类型生成
|
|
576
|
+
*
|
|
577
|
+
* 从 OpenAPI schema 生成 `paths` 类型声明文件(.ubean/openapi.d.ts),
|
|
578
|
+
* 供 `createTypedClient<paths>` / `createTypedFlatClient<paths>` / `callTypedInternal<paths>` 消费。
|
|
579
|
+
*
|
|
580
|
+
* 底层使用 `openapi-typescript` 编译 schema 为 TypeScript 类型声明。
|
|
581
|
+
*/
|
|
582
|
+
/**
|
|
583
|
+
* 从 OpenAPI schema 对象生成 paths 类型声明文件。
|
|
584
|
+
*/
|
|
585
|
+
async function generateOpenApiTypes(schema, options) {
|
|
586
|
+
const { outDir, fileName = "openapi.d.ts" } = options;
|
|
587
|
+
await mkdir(outDir, { recursive: true });
|
|
588
|
+
const ast = await openapiTS(schema);
|
|
589
|
+
const typesContent = astToString(ast);
|
|
590
|
+
const content = `${[
|
|
591
|
+
"// Auto-generated by ubean - do not edit manually",
|
|
592
|
+
"/* eslint-disable */",
|
|
593
|
+
"/* @ts-nocheck */",
|
|
594
|
+
"",
|
|
595
|
+
""
|
|
596
|
+
].join("\n") + typesContent}\n`;
|
|
597
|
+
const filePath = join(outDir, fileName);
|
|
598
|
+
await writeFile(filePath, content, "utf-8");
|
|
599
|
+
return filePath;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* 从 dev server 的 `/_openapi.json` 路由获取 schema 并生成类型声明文件。
|
|
603
|
+
*/
|
|
604
|
+
async function generateOpenApiTypesFromServer(baseUrl, options) {
|
|
605
|
+
const url = `${baseUrl.replace(/\/$/, "")}/_openapi.json`;
|
|
606
|
+
const response = await fetch(url);
|
|
607
|
+
if (!response.ok) throw new Error(`[openapi-types] Failed to fetch OpenAPI schema from ${url}: ${response.status} ${response.statusText}`);
|
|
608
|
+
return generateOpenApiTypes(await response.json(), options);
|
|
609
|
+
}
|
|
610
|
+
//#endregion
|
|
611
|
+
//#region src/codegen/index.ts
|
|
612
|
+
const CODEGEN_CONTRACT_VERSION = 1;
|
|
613
|
+
const CODEGEN_FILES = [
|
|
614
|
+
{
|
|
615
|
+
name: "routes.d.ts",
|
|
616
|
+
module: "ubean:routes",
|
|
617
|
+
required: true,
|
|
618
|
+
types: [
|
|
619
|
+
"ApiRouteMap",
|
|
620
|
+
"ApiRoutePath",
|
|
621
|
+
"ApiMethod"
|
|
622
|
+
]
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
name: "pages.d.ts",
|
|
626
|
+
module: "ubean:pages",
|
|
627
|
+
required: true,
|
|
628
|
+
types: ["RouteName", "LayoutName"]
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
name: "i18n.d.ts",
|
|
632
|
+
module: "vue-i18n",
|
|
633
|
+
required: false,
|
|
634
|
+
types: ["DefineLocaleMessage"]
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
name: "auto-imports.d.ts",
|
|
638
|
+
module: null,
|
|
639
|
+
required: true,
|
|
640
|
+
types: []
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
name: "components.d.ts",
|
|
644
|
+
module: null,
|
|
645
|
+
required: true,
|
|
646
|
+
types: []
|
|
647
|
+
}
|
|
648
|
+
];
|
|
649
|
+
async function generateTypes(result, options) {
|
|
650
|
+
const { cwd, srcDir, buildDir, ...autoImportOptions } = options;
|
|
651
|
+
const outDir = join(cwd, buildDir);
|
|
652
|
+
await mkdir(outDir, { recursive: true });
|
|
653
|
+
const generated = [];
|
|
654
|
+
const routeTypesPath = await generateRouteTypes(result, {
|
|
655
|
+
cwd,
|
|
656
|
+
outDir
|
|
657
|
+
});
|
|
658
|
+
generated.push(routeTypesPath);
|
|
659
|
+
const pageTypesPath = await generatePageTypes(result, { outDir });
|
|
660
|
+
generated.push(pageTypesPath);
|
|
661
|
+
const i18nTypesPath = await generateI18nTypes(result, { outDir });
|
|
662
|
+
if (i18nTypesPath) generated.push(i18nTypesPath);
|
|
663
|
+
const { autoImportsDtsPath, componentsDtsPath } = await generateAutoImports(result, {
|
|
664
|
+
cwd,
|
|
665
|
+
srcDir,
|
|
666
|
+
buildDir,
|
|
667
|
+
...autoImportOptions
|
|
668
|
+
});
|
|
669
|
+
generated.push(autoImportsDtsPath, componentsDtsPath);
|
|
670
|
+
const manifest = {
|
|
671
|
+
contractVersion: 1,
|
|
672
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
673
|
+
files: CODEGEN_FILES.map((file) => ({
|
|
674
|
+
...file,
|
|
675
|
+
generated: generated.some((path) => path.replace(/\\/g, "/").endsWith(`/${file.name}`) || path.endsWith(file.name))
|
|
676
|
+
}))
|
|
677
|
+
};
|
|
678
|
+
const manifestPath = join(outDir, "codegen.manifest.json");
|
|
679
|
+
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
680
|
+
generated.push(manifestPath);
|
|
681
|
+
return {
|
|
682
|
+
routeTypesPath,
|
|
683
|
+
pageTypesPath,
|
|
684
|
+
i18nTypesPath,
|
|
685
|
+
autoImportsDtsPath,
|
|
686
|
+
componentsDtsPath,
|
|
687
|
+
generated
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
//#endregion
|
|
691
|
+
export { generateImportsTransform as _, generateOpenApiTypesFromServer as a, getUbeanComponentsConfig as b, generateI18nTypes as c, HONO_OPENAPI_PRESET as d, UBEAN_CLIENT_PRESET as f, generateAutoImports as g, VUE_PRESET as h, generateOpenApiTypes as i, messagesToTsType as l, VUE_MACROS_PRESET as m, CODEGEN_FILES as n, generatePageTypes as o, UBEAN_SERVER_PRESET as p, generateTypes as r, generateRouteTypes as s, CODEGEN_CONTRACT_VERSION as t, BUILTIN_PRESETS as u, getBuiltinComposables as v, getUbeanAutoImportConfig as y };
|