@valaxyjs/devtools 0.28.0-beta.7 → 0.28.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.
Files changed (50) hide show
  1. package/dist/client/assets/VDButton-BSVRcA6y.js +1 -0
  2. package/dist/client/assets/VDCheckbox-DIkwHeip.js +1 -0
  3. package/dist/client/assets/VDInput-6FQF1GB1.js +1 -0
  4. package/dist/client/assets/VDInput-D0VAYtEY.css +1 -0
  5. package/dist/client/assets/VDNumberField-DE-iSRFf.js +1 -0
  6. package/dist/client/assets/VDPostListItem-2eqHNS9y.js +1 -0
  7. package/dist/client/assets/VDPostPanel--4pxdw_3.js +1 -0
  8. package/dist/client/assets/VDPostPanel-BbehhYa0.css +1 -0
  9. package/dist/client/assets/VDSelect-UHvFCpyn.js +1 -0
  10. package/dist/client/assets/VDToggleGroup-BcQyHwGa.js +1 -0
  11. package/dist/client/assets/_plugin-vue_export-helper-PWPZAZD9.js +3 -0
  12. package/dist/client/assets/api-cJTeRRR-.js +1 -0
  13. package/dist/client/assets/app-Ciu-q_PC.js +1 -0
  14. package/dist/client/assets/archives-DcQNu1SH.js +1 -0
  15. package/dist/client/assets/batch-edit-CYvJIq6n.css +1 -0
  16. package/dist/client/assets/batch-edit-DrsKqmmP.js +1 -0
  17. package/dist/client/assets/categories-C55FLSil.js +1 -0
  18. package/dist/client/assets/collections-DXhCCFt9.js +1 -0
  19. package/dist/client/assets/config-Cj52oTt1.js +1 -0
  20. package/dist/client/assets/en-MQdkqnly.js +1 -0
  21. package/dist/client/assets/i18n-BF5OnflT.js +1 -0
  22. package/dist/client/assets/index-D1EvX8GP.css +1 -0
  23. package/dist/client/assets/index-fSQqY8Wi.js +4 -0
  24. package/dist/client/assets/pages-m9ooNUkH.js +1 -0
  25. package/dist/client/assets/posts-BXMZDxTh.js +1 -0
  26. package/dist/client/assets/posts-C0Pk3Kwo.css +1 -0
  27. package/dist/client/assets/rpc-1TCVqa5N.js +1 -0
  28. package/dist/client/assets/settings-DTDQHEsR.js +1 -0
  29. package/dist/client/assets/splitpanes.es-DbWSeMt3.js +1 -0
  30. package/dist/client/assets/tags-CFAHn3Df.js +1 -0
  31. package/dist/client/assets/useKbd-CutV7ZdL.js +1 -0
  32. package/dist/client/assets/vue.runtime.esm-bundler-DwHYEKV3.js +2 -0
  33. package/dist/client/assets/zh-CN-z7O7976b.js +1 -0
  34. package/dist/client/favicon.svg +33 -0
  35. package/dist/client/index.html +8 -4
  36. package/dist/index.mjs +404 -52
  37. package/package.json +7 -7
  38. package/rpc.d.ts +75 -0
  39. package/dist/client/assets/about-DgEDNyMK.js +0 -1
  40. package/dist/client/assets/app-D2GYSWUd.js +0 -1
  41. package/dist/client/assets/categories-DKPo6rvY.js +0 -1
  42. package/dist/client/assets/collections-rMAMCBCe.js +0 -1
  43. package/dist/client/assets/en-DHpmO8Nw.js +0 -1
  44. package/dist/client/assets/index-B12droL0.js +0 -148
  45. package/dist/client/assets/index-D8mzkHVf.css +0 -1
  46. package/dist/client/assets/migration-DsdxoodP.js +0 -788
  47. package/dist/client/assets/pages-DryDFQhn.js +0 -1
  48. package/dist/client/assets/tags-68AKj-NG.js +0 -1
  49. package/dist/client/assets/vue.runtime.esm-bundler-D01s-oMJ.js +0 -2
  50. package/dist/client/assets/zh-CN-vDynnsP5.js +0 -1
package/dist/index.mjs CHANGED
@@ -1,16 +1,16 @@
1
1
  import { colors } from 'consola/utils';
2
2
  import sirv from 'sirv';
3
- import { createRPCServer } from 'vite-dev-rpc';
4
3
  import { dirname, resolve } from 'node:path';
5
4
  import { fileURLToPath } from 'node:url';
6
5
  import bodyParser from 'body-parser';
7
- import fs from 'fs-extra';
8
- import matter from 'gray-matter';
9
- import { JSON_SCHEMA } from 'js-yaml';
10
6
  import process from 'node:process';
11
7
  import dayjs from 'dayjs';
12
8
  import fg from 'fast-glob';
9
+ import fs from 'fs-extra';
10
+ import matter from 'gray-matter';
13
11
  import pathe from 'pathe';
12
+ import { generateCode, parseModule } from 'magicast';
13
+ import { JSON_SCHEMA } from 'js-yaml';
14
14
 
15
15
  const NAMESPACE = "valaxy:devtools";
16
16
 
@@ -18,6 +18,166 @@ const DIR_DIST = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLT
18
18
  const DEVTOOLS_CLIENT_FOLDER = resolve(DIR_DIST, "../dist/client");
19
19
  const DIR_CLIENT = DEVTOOLS_CLIENT_FOLDER;
20
20
 
21
+ const DANGEROUS_FIELD_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
22
+ function validateFieldPath(fieldPath) {
23
+ const parts = fieldPath.split(".");
24
+ return parts.every((part) => part.length > 0 && !DANGEROUS_FIELD_KEYS.has(part));
25
+ }
26
+ function assertInsideRoot(root, targetFile) {
27
+ const relative = pathe.relative(root, targetFile);
28
+ if (relative.startsWith("..") || pathe.isAbsolute(relative))
29
+ throw new Error(`Config path is outside user root: ${targetFile}`);
30
+ }
31
+ function setNestedValue(obj, path, value) {
32
+ const parts = path.split(".");
33
+ let current = obj;
34
+ for (let i = 0; i < parts.length - 1; i++) {
35
+ const part = parts[i];
36
+ if (current[part] == null || typeof current[part] !== "object") {
37
+ current[part] = {};
38
+ }
39
+ current = current[part];
40
+ }
41
+ current[parts.at(-1)] = value;
42
+ }
43
+ async function parseConfigFile(filePath) {
44
+ const content = await fs.readFile(filePath, "utf-8");
45
+ const mod = parseModule(content);
46
+ return mod;
47
+ }
48
+ function serializeProxy(value) {
49
+ try {
50
+ if (value == null)
51
+ return value;
52
+ if (typeof value !== "object")
53
+ return value;
54
+ if (value.$type === "function-call") {
55
+ const callee = value.$callee ?? "";
56
+ const args = value.$args ?? [];
57
+ const serializedArgs = args.map(
58
+ (a) => typeof a === "string" ? `'${a}'` : JSON.stringify(a)
59
+ ).join(", ");
60
+ return `${callee}(${serializedArgs})`;
61
+ }
62
+ if (value.$type === "identifier") {
63
+ return String(value.$id ?? value);
64
+ }
65
+ if (value.$type && value.$type !== "object") {
66
+ return `<${value.$type}>`;
67
+ }
68
+ if (Array.isArray(value)) {
69
+ return value.map((item) => {
70
+ try {
71
+ return serializeProxy(item);
72
+ } catch {
73
+ return "<unsupported>";
74
+ }
75
+ });
76
+ }
77
+ const result = {};
78
+ for (const key of Object.keys(value)) {
79
+ if (key.startsWith("$"))
80
+ continue;
81
+ try {
82
+ result[key] = serializeProxy(value[key]);
83
+ } catch {
84
+ result[key] = "<unsupported expression>";
85
+ }
86
+ }
87
+ return result;
88
+ } catch {
89
+ return "<unsupported expression>";
90
+ }
91
+ }
92
+ async function readConfigs(userRoot) {
93
+ const siteConfigPath = pathe.resolve(userRoot, "site.config.ts");
94
+ const valaxyConfigPath = pathe.resolve(userRoot, "valaxy.config.ts");
95
+ let siteConfig = {};
96
+ let valaxyConfig = {};
97
+ let themeConfig = {};
98
+ const siteConfigExists = await fs.pathExists(siteConfigPath);
99
+ const valaxyConfigExists = await fs.pathExists(valaxyConfigPath);
100
+ if (siteConfigExists) {
101
+ try {
102
+ const mod = await parseConfigFile(siteConfigPath);
103
+ const defaultExport = mod.exports.default;
104
+ if (defaultExport && typeof defaultExport === "object") {
105
+ siteConfig = serializeProxy(defaultExport.$args?.[0] ?? defaultExport);
106
+ }
107
+ } catch (e) {
108
+ console.error("[devtools] Failed to parse site.config.ts:", e);
109
+ }
110
+ }
111
+ if (valaxyConfigExists) {
112
+ try {
113
+ const mod = await parseConfigFile(valaxyConfigPath);
114
+ const defaultExport = mod.exports.default;
115
+ if (defaultExport && typeof defaultExport === "object") {
116
+ const configObj = defaultExport.$args?.[0] ?? defaultExport;
117
+ const raw = serializeProxy(configObj);
118
+ if (raw.themeConfig) {
119
+ themeConfig = raw.themeConfig;
120
+ delete raw.themeConfig;
121
+ }
122
+ if (raw.siteConfig) {
123
+ delete raw.siteConfig;
124
+ }
125
+ valaxyConfig = raw;
126
+ }
127
+ } catch (e) {
128
+ console.error("[devtools] Failed to parse valaxy.config.ts:", e);
129
+ }
130
+ }
131
+ return {
132
+ siteConfig,
133
+ valaxyConfig,
134
+ themeConfig,
135
+ siteConfigExists,
136
+ valaxyConfigExists,
137
+ siteConfigPath,
138
+ valaxyConfigPath
139
+ };
140
+ }
141
+ const SITE_CONFIG_TEMPLATE = `import { defineSiteConfig } from 'valaxy'
142
+
143
+ export default defineSiteConfig({
144
+ })
145
+ `;
146
+ const VALAXY_CONFIG_TEMPLATE = `import { defineValaxyConfig } from 'valaxy'
147
+
148
+ export default defineValaxyConfig({
149
+ })
150
+ `;
151
+ async function writeConfigField(userRoot, configType, fieldPath, value) {
152
+ if (!validateFieldPath(fieldPath)) {
153
+ throw new Error(`Invalid field path: ${fieldPath}`);
154
+ }
155
+ const targetFile = configType === "site" ? pathe.resolve(userRoot, "site.config.ts") : pathe.resolve(userRoot, "valaxy.config.ts");
156
+ const template = configType === "site" ? SITE_CONFIG_TEMPLATE : VALAXY_CONFIG_TEMPLATE;
157
+ assertInsideRoot(userRoot, targetFile);
158
+ if (!await fs.pathExists(targetFile)) {
159
+ await fs.writeFile(targetFile, template, "utf-8");
160
+ }
161
+ const mod = await parseConfigFile(targetFile);
162
+ const defaultExport = mod.exports.default;
163
+ if (!defaultExport) {
164
+ throw new Error(`No default export found in ${targetFile}`);
165
+ }
166
+ const configObj = defaultExport.$args?.[0] ?? defaultExport;
167
+ if (configType === "theme") {
168
+ if (!configObj.themeConfig || typeof configObj.themeConfig !== "object") {
169
+ configObj.themeConfig = {};
170
+ }
171
+ setNestedValue(configObj.themeConfig, fieldPath, value);
172
+ } else if (configType === "valaxy") {
173
+ setNestedValue(configObj, fieldPath, value);
174
+ } else {
175
+ setNestedValue(configObj, fieldPath, value);
176
+ }
177
+ const { code } = generateCode(mod);
178
+ await fs.writeFile(targetFile, code, "utf-8");
179
+ }
180
+
21
181
  async function migration(path, frontmatter) {
22
182
  if (fs.existsSync(path)) {
23
183
  const rawMd = await fs.readFile(path, "utf-8");
@@ -37,51 +197,6 @@ async function migration(path, frontmatter) {
37
197
  }
38
198
  }
39
199
 
40
- const prefix = "/valaxy-devtools-api";
41
- const apis = [
42
- {
43
- route: "/frontmatter",
44
- fn: async (req, res) => {
45
- if (req.method === "POST") {
46
- const { pageData, frontmatter: newFm } = await req.body;
47
- const path = pageData.path;
48
- if (fs.existsSync(path)) {
49
- const rawMd = await fs.readFile(path, "utf-8");
50
- const matterFile = matter(rawMd);
51
- matterFile.data = newFm;
52
- const newMd = matter.stringify(matterFile.content, matterFile.data);
53
- await fs.writeFile(path, newMd);
54
- }
55
- }
56
- }
57
- },
58
- {
59
- route: "/migration",
60
- fn: async (req, res) => {
61
- if (req.method === "POST") {
62
- const { pageData, frontmatter } = await req.body;
63
- const worker = [];
64
- for (const item of pageData) {
65
- const path = item;
66
- worker.push(migration(path, frontmatter));
67
- }
68
- Promise.all(worker).then(() => {
69
- res.end("ok");
70
- }).catch((_) => {
71
- res.end("migration error");
72
- });
73
- }
74
- }
75
- }
76
- ];
77
- function registerApi(server, _viteConfig) {
78
- const app = server.middlewares;
79
- app.use(bodyParser.json());
80
- apis.forEach(({ route, fn }) => {
81
- app.use(prefix + route, fn);
82
- });
83
- }
84
-
85
200
  function ensurePrefix(prefix, str) {
86
201
  if (!str.startsWith(prefix))
87
202
  return prefix + str;
@@ -164,6 +279,22 @@ function getFunctions(server, devtoolsOptions) {
164
279
  frontmatter: data
165
280
  };
166
281
  },
282
+ async updateFrontmatter(req) {
283
+ const { filePath, frontmatter: newFm } = req;
284
+ const pagesDir = pathe.resolve(userRoot, "pages");
285
+ const resolved = pathe.resolve(filePath);
286
+ const rel = pathe.relative(pagesDir, resolved);
287
+ if (rel.startsWith("..") || pathe.isAbsolute(rel) || !resolved.endsWith(".md"))
288
+ throw new Error("Invalid file path: must be within pages directory and end with .md");
289
+ if (!fs.existsSync(resolved))
290
+ throw new Error(`File not found: ${resolved}`);
291
+ const rawMd = await fs.readFile(resolved, "utf-8");
292
+ const matterFile = matter(rawMd);
293
+ matterFile.data = newFm;
294
+ const newMd = matter.stringify(matterFile.content, matterFile.data);
295
+ await fs.writeFile(resolved, newMd);
296
+ return { success: true };
297
+ },
167
298
  async getCollectionList() {
168
299
  const collectionsRoot = pathe.resolve(userRoot, "pages", "collections");
169
300
  if (!await fs.pathExists(collectionsRoot))
@@ -245,9 +376,232 @@ function getFunctions(server, devtoolsOptions) {
245
376
  });
246
377
  }
247
378
  return result;
379
+ },
380
+ async batchUpdateFrontmatter(filePaths, operations) {
381
+ const pagesDir = pathe.resolve(userRoot, "pages");
382
+ const result = {
383
+ total: filePaths.length,
384
+ updated: 0,
385
+ errors: []
386
+ };
387
+ for (const filePath of filePaths) {
388
+ try {
389
+ const resolved = pathe.resolve(filePath);
390
+ const rel = pathe.relative(pagesDir, resolved);
391
+ if (rel.startsWith("..") || pathe.isAbsolute(rel) || !resolved.endsWith(".md")) {
392
+ result.errors.push({ filePath, error: "Invalid file path: must be within pages directory and end with .md" });
393
+ continue;
394
+ }
395
+ if (!await fs.pathExists(resolved)) {
396
+ result.errors.push({ filePath, error: "File not found" });
397
+ continue;
398
+ }
399
+ const rawMd = await fs.readFile(resolved, "utf-8");
400
+ const matterFile = matter(rawMd);
401
+ let modified = false;
402
+ for (const op of operations) {
403
+ if (DANGEROUS_FIELD_KEYS.has(op.key) || op.newKey && DANGEROUS_FIELD_KEYS.has(op.newKey))
404
+ continue;
405
+ switch (op.type) {
406
+ case "set":
407
+ matterFile.data[op.key] = op.value;
408
+ modified = true;
409
+ break;
410
+ case "delete":
411
+ if (op.key in matterFile.data) {
412
+ delete matterFile.data[op.key];
413
+ modified = true;
414
+ }
415
+ break;
416
+ case "rename":
417
+ if (op.key in matterFile.data && op.newKey) {
418
+ matterFile.data[op.newKey] = matterFile.data[op.key];
419
+ delete matterFile.data[op.key];
420
+ modified = true;
421
+ }
422
+ break;
423
+ }
424
+ }
425
+ if (modified) {
426
+ const newMd = matter.stringify(matterFile.content, matterFile.data);
427
+ await fs.writeFile(resolved, newMd);
428
+ result.updated++;
429
+ }
430
+ } catch (e) {
431
+ result.errors.push({ filePath, error: e.message || String(e) });
432
+ }
433
+ }
434
+ return result;
435
+ },
436
+ async getConfig() {
437
+ return readConfigs(userRoot);
438
+ },
439
+ async updateConfigField(configType, fieldPath, value) {
440
+ try {
441
+ await writeConfigField(userRoot, configType, fieldPath, value);
442
+ return { success: true };
443
+ } catch (e) {
444
+ return { success: false, error: e.message || String(e) };
445
+ }
446
+ },
447
+ async runMigration(filePaths, frontmatter) {
448
+ const workers = filePaths.map((path) => migration(path, frontmatter));
449
+ await Promise.all(workers);
450
+ return { success: true };
451
+ },
452
+ async createPost(options) {
453
+ try {
454
+ const { title, path: customPath, tags, categories } = options;
455
+ const postsDir = pathe.resolve(userRoot, "pages", "posts");
456
+ let filePath;
457
+ if (customPath) {
458
+ const normalized = customPath.endsWith(".md") ? customPath : `${customPath}.md`;
459
+ filePath = pathe.resolve(postsDir, normalized);
460
+ const rel = pathe.relative(postsDir, filePath);
461
+ if (rel.startsWith("..") || pathe.isAbsolute(rel))
462
+ return { success: false, error: "Invalid path: must be within posts directory" };
463
+ } else {
464
+ const slug = title.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^\w\u4E00-\u9FFF-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "") || `post-${Date.now()}`;
465
+ filePath = pathe.resolve(postsDir, `${slug}.md`);
466
+ }
467
+ await fs.ensureDir(pathe.dirname(filePath));
468
+ if (await fs.pathExists(filePath)) {
469
+ const ext = pathe.extname(filePath);
470
+ const base = filePath.slice(0, -ext.length);
471
+ let counter = 1;
472
+ while (await fs.pathExists(filePath)) {
473
+ filePath = `${base}-${counter}${ext}`;
474
+ counter++;
475
+ }
476
+ }
477
+ const now = dayjs().format("YYYY-MM-DD HH:mm:ss");
478
+ const frontmatter = {
479
+ title,
480
+ date: now,
481
+ updated: now,
482
+ draft: true
483
+ };
484
+ if (tags && tags.length > 0)
485
+ frontmatter.tags = tags;
486
+ if (categories && categories.length > 0)
487
+ frontmatter.categories = categories;
488
+ const content = matter.stringify("\n", frontmatter);
489
+ await fs.writeFile(filePath, content, "utf-8");
490
+ return { success: true, filePath };
491
+ } catch (e) {
492
+ return { success: false, error: e.message || String(e) };
493
+ }
494
+ }
495
+ };
496
+ }
497
+
498
+ const prefix = "/valaxy-devtools-api";
499
+ function isExactRouteRequest(req) {
500
+ const pathname = new URL(req.url || "/", "http://localhost").pathname;
501
+ return pathname === "/" || pathname === "";
502
+ }
503
+ function sendJson(res, data) {
504
+ res.setHeader("Content-Type", "application/json");
505
+ res.end(JSON.stringify(data));
506
+ }
507
+ function sendError(res, statusCode, error) {
508
+ res.statusCode = statusCode;
509
+ sendJson(res, { error });
510
+ }
511
+ function createRoute(handlers) {
512
+ return async (req, res, next) => {
513
+ if (!isExactRouteRequest(req)) {
514
+ next?.();
515
+ return;
516
+ }
517
+ const method = req.method?.toUpperCase();
518
+ const handler = handlers[method];
519
+ if (!handler) {
520
+ next?.();
521
+ return;
522
+ }
523
+ try {
524
+ const data = await handler(req, res);
525
+ if (!res.writableEnded)
526
+ sendJson(res, data);
527
+ } catch (e) {
528
+ sendError(res, 500, e?.message || String(e));
248
529
  }
249
530
  };
250
531
  }
532
+ function registerApi(server, _viteConfig, options = {}) {
533
+ const app = server.middlewares;
534
+ app.use(bodyParser.json());
535
+ const functions = getFunctions(server, options);
536
+ app.use(`${prefix}/options`, createRoute({
537
+ GET: () => functions.getOptions()
538
+ }));
539
+ app.use(`${prefix}/posts`, createRoute({
540
+ GET: () => functions.getPostList(),
541
+ POST: async (req, res) => {
542
+ const body = req.body;
543
+ if (!body?.title) {
544
+ sendError(res, 400, "Missing title");
545
+ return;
546
+ }
547
+ return functions.createPost(body);
548
+ }
549
+ }));
550
+ app.use(`${prefix}/pages`, createRoute({
551
+ GET: async (req, res) => {
552
+ const url = new URL(req.url || "", "http://localhost");
553
+ const pagePath = url.searchParams.get("path");
554
+ if (!pagePath) {
555
+ sendError(res, 400, 'Missing "path" query parameter');
556
+ return;
557
+ }
558
+ return functions.getPageData(pagePath);
559
+ }
560
+ }));
561
+ app.use(`${prefix}/collections`, createRoute({
562
+ GET: () => functions.getCollectionList()
563
+ }));
564
+ app.use(`${prefix}/frontmatter/batch`, createRoute({
565
+ POST: async (req, res) => {
566
+ const body = req.body;
567
+ const { filePaths, operations } = body;
568
+ if (!Array.isArray(filePaths) || !Array.isArray(operations)) {
569
+ sendError(res, 400, "Invalid request: filePaths and operations must be arrays");
570
+ return;
571
+ }
572
+ for (const op of operations) {
573
+ if (!op || typeof op.type !== "string" || typeof op.key !== "string") {
574
+ sendError(res, 400, "Invalid operation: each operation must have type and key");
575
+ return;
576
+ }
577
+ }
578
+ return functions.batchUpdateFrontmatter(filePaths, operations);
579
+ }
580
+ }));
581
+ app.use(`${prefix}/frontmatter`, createRoute({
582
+ POST: async (req) => {
583
+ return functions.updateFrontmatter(req.body);
584
+ }
585
+ }));
586
+ app.use(`${prefix}/migration`, createRoute({
587
+ POST: async (req) => {
588
+ const { filePaths, frontmatter } = req.body;
589
+ return functions.runMigration(filePaths, frontmatter);
590
+ }
591
+ }));
592
+ app.use(`${prefix}/config`, createRoute({
593
+ GET: () => functions.getConfig(),
594
+ PUT: async (req, res) => {
595
+ const body = req.body;
596
+ const { configType, fieldPath, value } = body;
597
+ if (!configType || !fieldPath) {
598
+ sendError(res, 400, "Missing configType or fieldPath");
599
+ return;
600
+ }
601
+ return functions.updateConfigField(configType, fieldPath, value);
602
+ }
603
+ }));
604
+ }
251
605
 
252
606
  function ValaxyDevtools(options = {}) {
253
607
  let config;
@@ -255,8 +609,6 @@ function ValaxyDevtools(options = {}) {
255
609
  function configureServer(server) {
256
610
  const _print = server.printUrls;
257
611
  const base = (options.base ?? server.config.base) || "/";
258
- const functions = getFunctions(server, options);
259
- createRPCServer(NAMESPACE, server.ws, functions);
260
612
  const devtoolsUrl = `${base}__valaxy_devtools__/`;
261
613
  if (!isDevDevtools) {
262
614
  server.middlewares.use(devtoolsUrl, sirv(DIR_CLIENT, {
@@ -279,7 +631,7 @@ function ValaxyDevtools(options = {}) {
279
631
  const colorUrl = (url2) => colors.green(url2.replace(/:(\d+)\//, (_, port) => `:${colors.bold(port)}/`));
280
632
  console.log(` ${colors.green("\u279C")} ${colors.bold("Inspect")}: ${colorUrl(`${host}${base}__inspect/`)}`);
281
633
  };
282
- registerApi(server);
634
+ registerApi(server, config, options);
283
635
  }
284
636
  const plugin = {
285
637
  name: NAMESPACE,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@valaxyjs/devtools",
3
3
  "type": "module",
4
- "version": "0.28.0-beta.7",
4
+ "version": "0.28.0",
5
5
  "repository": {
6
6
  "url": "https://github.com/YunYouJun/valaxy"
7
7
  },
@@ -26,25 +26,25 @@
26
26
  "axios": "^1.13.6",
27
27
  "body-parser": "^2.2.2",
28
28
  "cors": "^2.8.6",
29
+ "fs-extra": "^11.3.4",
29
30
  "http-proxy-middleware": "^3.0.5",
30
31
  "js-yaml": "^4.1.1",
32
+ "magicast": "^0.5.2",
31
33
  "pathe": "^2.0.3",
32
- "sirv": "^3.0.2",
33
- "vite-dev-rpc": "^1.1.0"
34
+ "sirv": "^3.0.2"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@iconify-json/ri": "^1.2.10",
37
38
  "@intlify/unplugin-vue-i18n": "^11.0.7",
38
- "@primevue/themes": "^4.5.4",
39
39
  "@types/body-parser": "^1.19.6",
40
40
  "@types/splitpanes": "^2.2.6",
41
41
  "@types/wicg-file-system-access": "^2023.10.7",
42
42
  "gray-matter": "^4.0.3",
43
- "primevue": "^4.5.4",
43
+ "reka-ui": "^2.9.2",
44
44
  "splitpanes": "^4.0.4",
45
45
  "typescript": "^5.9.3",
46
46
  "unbuild": "^3.6.1",
47
- "vite": "^8.0.1",
47
+ "vite": "^8.0.2",
48
48
  "vue-i18n": "^11.3.0"
49
49
  },
50
50
  "scripts": {
@@ -52,7 +52,7 @@
52
52
  "build": "rimraf dist && pnpm run build:client && npm run build:node",
53
53
  "build:client": "vite build src/client",
54
54
  "build:node": "unbuild",
55
- "dev": "npm run stub && npm run dev:client",
55
+ "dev": "pnpm run stub && pnpm run dev:client",
56
56
  "dev:client": "vite dev src/client --port 5001",
57
57
  "watch:client": "vite build src/client --watch",
58
58
  "stub": "unbuild --stub",
package/rpc.d.ts CHANGED
@@ -1,5 +1,56 @@
1
1
  import type { ClientCollectionData, ClientOptions, ClientPageData, ClientPostList } from './client/types'
2
2
 
3
+ export interface BatchFrontmatterOperation {
4
+ /**
5
+ * 操作类型
6
+ * - set: 设置/更新字段值
7
+ * - delete: 删除字段
8
+ * - rename: 重命名字段(oldKey -> newKey)
9
+ */
10
+ type: 'set' | 'delete' | 'rename'
11
+ key: string
12
+ value?: any
13
+ newKey?: string
14
+ }
15
+
16
+ export interface BatchUpdateResult {
17
+ total: number
18
+ updated: number
19
+ errors: { filePath: string, error: string }[]
20
+ }
21
+
22
+ export interface ConfigData {
23
+ siteConfig: Record<string, any>
24
+ valaxyConfig: Record<string, any>
25
+ themeConfig: Record<string, any>
26
+ siteConfigExists: boolean
27
+ valaxyConfigExists: boolean
28
+ siteConfigPath: string
29
+ valaxyConfigPath: string
30
+ }
31
+
32
+ export interface ConfigUpdateRequest {
33
+ configType: 'site' | 'valaxy' | 'theme'
34
+ fieldPath: string
35
+ value: any
36
+ }
37
+
38
+ export interface CreatePostOptions {
39
+ /** 文章标题 */
40
+ title: string
41
+ /** 文件路径(相对于 pages/posts/),如 'my-post.md' 或 'sub/my-post.md' */
42
+ path?: string
43
+ /** 标签 */
44
+ tags?: string[]
45
+ /** 分类 */
46
+ categories?: string[]
47
+ }
48
+
49
+ export interface UpdateFrontmatterRequest {
50
+ filePath: string
51
+ frontmatter: Record<string, any>
52
+ }
53
+
3
54
  export interface ServerFunctions {
4
55
  // add: (a: number, b: number) => number
5
56
  /**
@@ -18,6 +69,30 @@ export interface ServerFunctions {
18
69
  * 获取合集列表
19
70
  */
20
71
  getCollectionList: () => Promise<ClientCollectionData[]>
72
+ /**
73
+ * 更新单个页面的 frontmatter(整体覆盖)
74
+ */
75
+ updateFrontmatter: (req: UpdateFrontmatterRequest) => Promise<{ success: boolean }>
76
+ /**
77
+ * 批量修改文章的 frontmatter
78
+ */
79
+ batchUpdateFrontmatter: (filePaths: string[], operations: BatchFrontmatterOperation[]) => Promise<BatchUpdateResult>
80
+ /**
81
+ * 获取配置数据
82
+ */
83
+ getConfig: () => Promise<ConfigData>
84
+ /**
85
+ * 更新配置字段
86
+ */
87
+ updateConfigField: (configType: 'site' | 'valaxy' | 'theme', fieldPath: string, value: any) => Promise<{ success: boolean, error?: string }>
88
+ /**
89
+ * 运行 frontmatter 迁移
90
+ */
91
+ runMigration: (filePaths: string[], frontmatter: Record<string, any>) => Promise<{ success: boolean }>
92
+ /**
93
+ * 创建新文章
94
+ */
95
+ createPost: (options: CreatePostOptions) => Promise<{ success: boolean, filePath?: string, error?: string }>
21
96
  }
22
97
 
23
98
  export interface ClientFunctions {
@@ -1 +0,0 @@
1
- import{C as e,d as t}from"./vue.runtime.esm-bundler-D01s-oMJ.js";import{t as n}from"./index-B12droL0.js";var r={};function i(n,r){return e(),t(`div`,null,` About `)}var a=n(r,[[`render`,i]]);export{a as default};
@@ -1 +0,0 @@
1
- import{B as e,C as t,E as n,G as r,M as i,U as a,_ as o,b as s,d as c,g as l,h as u,j as d,k as f,l as p,s as m,w as h,x as g,y as _}from"./vue.runtime.esm-bundler-D01s-oMJ.js";var v={__name:`splitpanes`,props:{horizontal:{type:Boolean,default:!1},pushOtherPanes:{type:Boolean,default:!0},maximizePanes:{type:Boolean,default:!0},rtl:{type:Boolean,default:!1},firstSplitter:{type:Boolean,default:!1}},emits:[`ready`,`resize`,`resized`,`pane-click`,`pane-maximize`,`pane-add`,`pane-remove`,`splitter-click`,`splitter-dblclick`],setup(n,{emit:r}){let a=r,o=n,c=d(),u=e([]),v=m(()=>u.value.reduce((e,t)=>(e[~~t.id]=t)&&e,{})),y=m(()=>u.value.length),b=e(null),x=e(!1),S=e({mouseDown:!1,dragging:!1,activeSplitter:null,cursorOffset:0}),C=e({splitter:null,timeoutId:null}),w=m(()=>({[`splitpanes splitpanes--${o.horizontal?`horizontal`:`vertical`}`]:!0,"splitpanes--dragging":S.value.dragging})),T=()=>{document.addEventListener(`mousemove`,O,{passive:!1}),document.addEventListener(`mouseup`,k),`ontouchstart`in window&&(document.addEventListener(`touchmove`,O,{passive:!1}),document.addEventListener(`touchend`,k))},E=()=>{document.removeEventListener(`mousemove`,O,{passive:!1}),document.removeEventListener(`mouseup`,k),`ontouchstart`in window&&(document.removeEventListener(`touchmove`,O,{passive:!1}),document.removeEventListener(`touchend`,k))},D=(e,t)=>{let n=e.target.closest(`.splitpanes__splitter`);if(n){let{left:t,top:r}=n.getBoundingClientRect(),{clientX:i,clientY:a}=`ontouchstart`in window&&e.touches?e.touches[0]:e;S.value.cursorOffset=o.horizontal?a-r:i-t}T(),S.value.mouseDown=!0,S.value.activeSplitter=t},O=e=>{S.value.mouseDown&&(e.preventDefault(),S.value.dragging=!0,requestAnimationFrame(()=>{F(N(e)),$(`resize`,{event:e},!0)}))},k=e=>{S.value.dragging&&(window.getSelection().removeAllRanges(),$(`resized`,{event:e},!0)),S.value.mouseDown=!1,S.value.activeSplitter=null,setTimeout(()=>{S.value.dragging=!1,E()},100)},A=(e,t)=>{`ontouchstart`in window&&(e.preventDefault(),C.value.splitter===t?(clearTimeout(C.value.timeoutId),C.value.timeoutId=null,j(e,t),C.value.splitter=null):(C.value.splitter=t,C.value.timeoutId=setTimeout(()=>C.value.splitter=null,500))),S.value.dragging||$(`splitter-click`,{event:e,index:t},!0)},j=(e,t)=>{if($(`splitter-dblclick`,{event:e,index:t},!0),o.maximizePanes){let n=0;u.value=u.value.map((e,r)=>(e.size=r===t?e.max:e.min,r!==t&&(n+=e.min),e)),u.value[t].size-=n,$(`pane-maximize`,{event:e,index:t,pane:u.value[t]}),$(`resized`,{event:e,index:t},!0)}},M=(e,t)=>{$(`pane-click`,{event:e,index:v.value[t].index,pane:v.value[t]})},N=e=>{let t=b.value.getBoundingClientRect(),{clientX:n,clientY:r}=`ontouchstart`in window&&e.touches?e.touches[0]:e;return{x:n-(o.horizontal?0:S.value.cursorOffset)-t.left,y:r-(o.horizontal?S.value.cursorOffset:0)-t.top}},P=e=>{e=e[o.horizontal?`y`:`x`];let t=b.value[o.horizontal?`clientHeight`:`clientWidth`];return o.rtl&&!o.horizontal&&(e=t-e),e*100/t},F=e=>{let t=S.value.activeSplitter,n={prevPanesSize:L(t),nextPanesSize:R(t),prevReachedMinPanes:0,nextReachedMinPanes:0},r=0+(o.pushOtherPanes?0:n.prevPanesSize),i=100-(o.pushOtherPanes?0:n.nextPanesSize),a=Math.max(Math.min(P(e),i),r),s=[t,t+1],c=u.value[s[0]]||null,l=u.value[s[1]]||null,d=c.max<100&&a>=c.max+n.prevPanesSize,f=l.max<100&&a<=100-(l.max+R(t+1));if(d||f){d?(c.size=c.max,l.size=Math.max(100-c.max-n.prevPanesSize-n.nextPanesSize,0)):(c.size=Math.max(100-l.max-n.prevPanesSize-R(t+1),0),l.size=l.max);return}if(o.pushOtherPanes){let e=I(n,a);if(!e)return;({sums:n,panesToResize:s}=e),c=u.value[s[0]]||null,l=u.value[s[1]]||null}c!==null&&(c.size=Math.min(Math.max(a-n.prevPanesSize-n.prevReachedMinPanes,c.min),c.max)),l!==null&&(l.size=Math.min(Math.max(100-a-n.nextPanesSize-n.nextReachedMinPanes,l.min),l.max))},I=(e,t)=>{let n=S.value.activeSplitter,r=[n,n+1];return t<e.prevPanesSize+u.value[r[0]].min&&(r[0]=z(n).index,e.prevReachedMinPanes=0,r[0]<n&&u.value.forEach((t,i)=>{i>r[0]&&i<=n&&(t.size=t.min,e.prevReachedMinPanes+=t.min)}),e.prevPanesSize=L(r[0]),r[0]===void 0)?(e.prevReachedMinPanes=0,u.value[0].size=u.value[0].min,u.value.forEach((t,r)=>{r>0&&r<=n&&(t.size=t.min,e.prevReachedMinPanes+=t.min)}),u.value[r[1]].size=100-e.prevReachedMinPanes-u.value[0].min-e.prevPanesSize-e.nextPanesSize,null):t>100-e.nextPanesSize-u.value[r[1]].min&&(r[1]=B(n).index,e.nextReachedMinPanes=0,r[1]>n+1&&u.value.forEach((t,i)=>{i>n&&i<r[1]&&(t.size=t.min,e.nextReachedMinPanes+=t.min)}),e.nextPanesSize=R(r[1]-1),r[1]===void 0)?(e.nextReachedMinPanes=0,u.value.forEach((t,r)=>{r<y.value-1&&r>=n+1&&(t.size=t.min,e.nextReachedMinPanes+=t.min)}),u.value[r[0]].size=100-e.prevPanesSize-R(r[0]-1),null):{sums:e,panesToResize:r}},L=e=>u.value.reduce((t,n,r)=>t+(r<e?n.size:0),0),R=e=>u.value.reduce((t,n,r)=>t+(r>e+1?n.size:0),0),z=e=>[...u.value].reverse().find(t=>t.index<e&&t.size>t.min)||{},B=e=>u.value.find(t=>t.index>e+1&&t.size>t.min)||{},V=()=>{let e=Array.from(b.value?.children||[]);for(let t of e){let e=t.classList.contains(`splitpanes__pane`),n=t.classList.contains(`splitpanes__splitter`);!e&&!n&&(t.remove(),console.warn(`Splitpanes: Only <pane> elements are allowed at the root of <splitpanes>. One of your DOM nodes was removed.`))}},H=(e,t,n=!1)=>{let r=e-1,i=document.createElement(`div`);i.classList.add(`splitpanes__splitter`),n||(i.onmousedown=e=>D(e,r),typeof window<`u`&&`ontouchstart`in window&&(i.ontouchstart=e=>D(e,r)),i.onclick=e=>A(e,r+1)),i.ondblclick=e=>j(e,r+1),t.parentNode.insertBefore(i,t)},U=e=>{e.onmousedown=void 0,e.onclick=void 0,e.ondblclick=void 0,e.remove()},W=()=>{let e=Array.from(b.value?.children||[]);for(let t of e)t.className.includes(`splitpanes__splitter`)&&U(t);let t=0;for(let n of e)n.className.includes(`splitpanes__pane`)&&(!t&&o.firstSplitter?H(t,n,!0):t&&H(t,n),t++)},G=({uid:e,...t})=>{let n=v.value[e];for(let[e,r]of Object.entries(t))n[e]=r},K=e=>{let t=-1;Array.from(b.value?.children||[]).some(n=>(n.className.includes(`splitpanes__pane`)&&t++,n.isSameNode(e.el))),u.value.splice(t,0,{...e,index:t}),u.value.forEach((e,t)=>e.index=t),x.value&&_(()=>{W(),J({addedPane:u.value[t]}),$(`pane-add`,{pane:u.value[t]})})},q=e=>{let t=u.value.findIndex(t=>t.id===e);u.value[t].el=null;let n=u.value.splice(t,1)[0];u.value.forEach((e,t)=>e.index=t),_(()=>{W(),$(`pane-remove`,{pane:n}),J({removedPane:{...n}})})},J=(e={})=>{!e.addedPane&&!e.removedPane?X():u.value.some(e=>e.givenSize!==null||e.min||e.max<100)?Z(e):Y(),x.value&&$(`resized`)},Y=()=>{let e=100/y.value,t=0,n=[],r=[];for(let i of u.value)i.size=Math.max(Math.min(e,i.max),i.min),t-=i.size,i.size>=i.max&&n.push(i.id),i.size<=i.min&&r.push(i.id);t>.1&&Q(t,n,r)},X=()=>{let e=100,t=[],n=[],r=0;for(let i of u.value)e-=i.size,i.givenSize!==null&&r++,i.size>=i.max&&t.push(i.id),i.size<=i.min&&n.push(i.id);let i=100;if(e>.1){for(let t of u.value)t.givenSize===null&&(t.size=Math.max(Math.min(e/(y.value-r),t.max),t.min)),i-=t.size;i>.1&&Q(i,t,n)}},Z=({addedPane:e,removedPane:t}={})=>{let n=100/y.value,r=0,i=[],a=[];(e?.givenSize??null)!==null&&(n=(100-e.givenSize)/(y.value-1));for(let e of u.value)r-=e.size,e.size>=e.max&&i.push(e.id),e.size<=e.min&&a.push(e.id);if(!(Math.abs(r)<.1)){for(let t of u.value)e?.givenSize!==null&&e?.id===t.id||(t.size=Math.max(Math.min(n,t.max),t.min)),r-=t.size,t.size>=t.max&&i.push(t.id),t.size<=t.min&&a.push(t.id);r>.1&&Q(r,i,a)}},Q=(e,t,n)=>{let r;r=e>0?e/(y.value-t.length):e/(y.value-n.length),u.value.forEach((i,a)=>{if(e>0&&!t.includes(i.id)){let t=Math.max(Math.min(i.size+r,i.max),i.min),n=t-i.size;e-=n,i.size=t}else if(!n.includes(i.id)){let t=Math.max(Math.min(i.size+r,i.max),i.min),n=t-i.size;e-=n,i.size=t}}),Math.abs(e)>.1&&_(()=>{x.value&&console.warn(`Splitpanes: Could not resize panes correctly due to their constraints.`)})},$=(e,t=void 0,n=!1)=>{let r=t?.index??S.value.activeSplitter??null;a(e,{...t,...r!==null&&{index:r},...n&&r!==null&&{prevPane:u.value[r-(o.firstSplitter?1:0)],nextPane:u.value[r+(o.firstSplitter?0:1)]},panes:u.value.map(e=>({min:e.min,max:e.max,size:e.size}))})};i(()=>o.firstSplitter,()=>W()),g(()=>{V(),W(),J(),$(`ready`),x.value=!0}),s(()=>x.value=!1);let ee=()=>l(`div`,{ref:b,class:w.value},c.default?.call(c));return h(`panes`,u),h(`indexedPanes`,v),h(`horizontal`,m(()=>o.horizontal)),h(`requestUpdate`,G),h(`onPaneAdd`,K),h(`onPaneRemove`,q),h(`onPaneClick`,M),(e,n)=>(t(),p(f(ee)))}},y={__name:`pane`,props:{size:{type:[Number,String]},minSize:{type:[Number,String],default:0},maxSize:{type:[Number,String],default:100}},setup(l){let d=l,f=o(`requestUpdate`),p=o(`onPaneAdd`),h=o(`horizontal`),_=o(`onPaneRemove`),v=o(`onPaneClick`),y=u()?.uid,b=o(`indexedPanes`),x=m(()=>b.value[y]),S=e(null),C=m(()=>{let e=isNaN(d.size)||d.size===void 0?0:parseFloat(d.size);return Math.max(Math.min(e,T.value),w.value)}),w=m(()=>{let e=parseFloat(d.minSize);return isNaN(e)?0:e}),T=m(()=>{let e=parseFloat(d.maxSize);return isNaN(e)?100:e}),E=m(()=>`${h.value?`height`:`width`}: ${x.value?.size}%`);return i(()=>C.value,e=>f({uid:y,size:e})),i(()=>w.value,e=>f({uid:y,min:e})),i(()=>T.value,e=>f({uid:y,max:e})),g(()=>{p({id:y,el:S.value,min:w.value,max:T.value,givenSize:d.size===void 0?null:C.value,size:C.value})}),s(()=>_(y)),(e,i)=>(t(),c(`div`,{ref_key:`paneEl`,ref:S,class:`splitpanes__pane`,onClick:i[0]||=t=>a(v)(t,e._.uid),style:r(E.value)},[n(e.$slots,`default`)],4))}};e(!1),e({userRoot:``});var b=e({posts:[]}),x=e([]);e(``),e();export{y as i,b as n,v as r,x as t};
@@ -1 +0,0 @@
1
- import{C as e,d as t}from"./vue.runtime.esm-bundler-D01s-oMJ.js";import{t as n}from"./index-B12droL0.js";var r={};function i(n,r){return e(),t(`div`,null,` Categories `)}var a=n(r,[[`render`,i]]);export{a as default};
@@ -1 +0,0 @@
1
- import{B as e,C as t,K as n,N as r,T as i,U as a,W as o,a as s,c,d as l,f as u,l as d,m as f,p,u as m}from"./vue.runtime.esm-bundler-D01s-oMJ.js";import{i as h,r as g,t as _}from"./app-D2GYSWUd.js";var v={class:`h-full overflow-auto`},y=[`onClick`],b={class:`font-bold text-sm`},x={class:`text-xs op-50 mt-1`},S={key:0,class:`text-center op-50 py-8 text-sm`},C={key:0,class:`overflow-auto h-full p-4`},w={class:`flex items-start gap-4 mb-4`},T=[`src`],E={class:`text-lg font-bold`},D={class:`text-xs op-50 mt-1`},O={key:0,class:`text-sm op-70 mt-2`},k={class:`text-xs mt-2`},A={class:`font-bold text-sm mb-2 op-70`},j={class:`border rounded dark:border-gray-700`},M={class:`op-40 text-xs w-6 text-right`},N={class:`flex-1`},P=[`href`],F={key:1,class:`text-xs op-40 font-mono`},I={key:0,class:`text-center op-50 py-4 text-sm`},L={key:1,class:`flex items-center justify-center h-full op-40 text-sm`},R=f({__name:`collections`,setup(f){let R=e(null);function z(e){R.value=e}return(e,f)=>(t(),d(a(g),{class:`h-full`},{default:r(()=>[p(a(h),{"min-size":`20`,size:`30`},{default:r(()=>[c(`div`,v,[(t(!0),l(s,null,i(a(_),e=>(t(),l(`div`,{key:e.key,class:o([`cursor-pointer p-3 border-b border-gray-100 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800 transition`,{"bg-gray-100 dark:bg-gray-700":R.value?.key===e.key}]),onClick:t=>z(e)},[c(`div`,b,n(e.title||e.key),1),c(`div`,x,n(e.items.length)+` items `,1)],10,y))),128)),a(_).length?m(``,!0):(t(),l(`div`,S,` No collections found `))])]),_:1}),p(a(h),null,{default:r(()=>[R.value?(t(),l(`div`,C,[c(`div`,w,[R.value.cover?(t(),l(`img`,{key:0,src:R.value.cover,class:`max-h-30 rounded shadow`},null,8,T)):m(``,!0),c(`div`,null,[c(`h2`,E,n(R.value.title||R.value.key),1),c(`div`,D,` key: `+n(R.value.key),1),R.value.description?(t(),l(`div`,O,n(R.value.description),1)):m(``,!0),c(`div`,k,[c(`span`,{class:o([`px-1.5 py-0.5 rounded`,R.value.collapse?`bg-blue-100 text-blue-600 dark:bg-blue-900 dark:text-blue-300`:`bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300`])},n(R.value.collapse?`collapsed`:`expanded`),3)])])]),c(`h3`,A,` Items (`+n(R.value.items.length)+`) `,1),c(`div`,j,[(t(!0),l(s,null,i(R.value.items,(e,r)=>(t(),l(`div`,{key:e.key||e.link,class:`flex items-center gap-2 px-3 py-2 text-sm border-b last:border-b-0 dark:border-gray-700`},[c(`span`,M,n(r+1),1),c(`span`,N,n(e.title),1),e.link?(t(),l(`a`,{key:0,href:e.link,target:`_blank`,rel:`noopener noreferrer`,class:`text-xs text-blue-500 font-mono inline-flex items-center gap-0.5`},[u(n(e.link)+` `,1),f[0]||=c(`span`,{class:`i-ri-external-link-line text-xs`},null,-1)],8,P)):(t(),l(`span`,F,n(e.key),1))]))),128)),R.value.items.length?m(``,!0):(t(),l(`div`,I,` No items `))])])):(t(),l(`div`,L,` Select a collection to view details `))]),_:1})]),_:1}))}});export{R as default};
@@ -1 +0,0 @@
1
- var e={button:{about:{t:0,b:{t:2,i:[{t:3}],s:`About`}},back:{t:0,b:{t:2,i:[{t:3}],s:`Back`}},go:{t:0,b:{t:2,i:[{t:3}],s:`GO`}},home:{t:0,b:{t:2,i:[{t:3}],s:`Home`}},toggle_dark:{t:0,b:{t:2,i:[{t:3}],s:`Toggle dark mode`}},toggle_langs:{t:0,b:{t:2,i:[{t:3}],s:`Change languages`}},save_frontmatter:{t:0,b:{t:2,i:[{t:3}],s:`Save Frontmatter`}}},pageData:{path:{t:0,b:{t:2,i:[{t:3}],s:`Path`}},filePath:{t:0,b:{t:2,i:[{t:3}],s:`File Path`}},routePath:{t:0,b:{t:2,i:[{t:3}],s:`Route Path`}}}};export{e as default};