config.mts 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import "dotenv/config";
  2. import { fileURLToPath, URL } from "node:url";
  3. import { defineConfig } from "vitepress";
  4. import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
  5. import Icons from "unplugin-icons/vite";
  6. import { FileSystemIconLoader } from "unplugin-icons/loaders";
  7. import { SVG, cleanupSVG, parseColors, runSVGO } from "@iconify/tools";
  8. import ImageFiguresPlugin from "markdown-it-image-figures";
  9. import {
  10. S3Client,
  11. ListObjectsCommand,
  12. GetObjectCommand,
  13. } from "@aws-sdk/client-s3";
  14. import fs from "node:fs";
  15. import path from "node:path";
  16. import { exec } from "child_process";
  17. import { buildSideBar } from "../utils/sideBar";
  18. const DOC_BASE_PATH = "pages";
  19. if (process.env.NODE_ENV === "production") {
  20. // 执行清理pages文件夹命令
  21. await new Promise((resolve, reject) =>
  22. exec(
  23. `git checkout -- ${DOC_BASE_PATH} & git clean -df ${DOC_BASE_PATH}`,
  24. (error, stdout, stderr) => {
  25. if (error) {
  26. console.error(`清理pages时出错: ${error.message}`);
  27. reject(stderr);
  28. }
  29. console.log(`清理pages结果: ${stdout}`);
  30. resolve(stdout);
  31. }
  32. )
  33. );
  34. }
  35. // 从S3构建pages里面的markdown文件
  36. const s3 = new S3Client({
  37. credentials: {
  38. accessKeyId: process.env.VITE_AWS_S3_ACCESS_KEY_ID!,
  39. secretAccessKey: process.env.VITE_AWS_S3_SECRET_ACCESS_KEY!,
  40. },
  41. region: process.env.VITE_AWS_S3_REGION!,
  42. });
  43. const command = new ListObjectsCommand({
  44. Bucket: process.env.VITE_DOCS_LIST_BUCKET,
  45. });
  46. const { Contents: contents } = await s3.send(command);
  47. await Promise.all(
  48. contents!.map((content) => {
  49. return new Promise(async (resolve, reject) => {
  50. try {
  51. const command = new GetObjectCommand({
  52. Bucket: process.env.VITE_DOCS_LIST_BUCKET,
  53. Key: content.Key,
  54. ResponseCacheControl: "no-cache",
  55. });
  56. const file = await s3.send(command);
  57. const writePath = path.join(DOC_BASE_PATH, content.Key!);
  58. const directory = path.dirname(writePath);
  59. fs.mkdirSync(directory, { recursive: true });
  60. fs.writeFileSync(writePath, await file.Body!.transformToString());
  61. resolve(content.Key);
  62. } catch (e) {
  63. reject(e);
  64. }
  65. fs.writeFile;
  66. });
  67. })
  68. );
  69. // 构建sideBar数据
  70. function readJsonFile(filePath) {
  71. try {
  72. // 同步读取文件内容,如果文件不存在会抛出异常
  73. const data = fs.readFileSync(filePath, "utf8");
  74. return JSON.parse(data); // 解析 JSON 数据
  75. } catch (err) {
  76. if (err.code === "ENOENT") {
  77. console.error("File not found:", filePath);
  78. // 可以返回默认值或者进行其他处理
  79. return {}; // 返回 null 或者其他默认值
  80. } else {
  81. throw err; // 抛出其他异常
  82. }
  83. }
  84. }
  85. const sideBarSortMap = {
  86. "zh-CN": readJsonFile("pages/zh-CN::SIDEBAR_SORTED_MAP.json"),
  87. "zh-HK": readJsonFile("pages/zh-HK::SIDEBAR_SORTED_MAP.json"),
  88. "en-US": readJsonFile("pages/en-US::SIDEBAR_SORTED_MAP.json"),
  89. };
  90. let { rootSideBar, zhHKSideBar, enUSSideBar } = buildSideBar(
  91. contents,
  92. sideBarSortMap
  93. );
  94. // import util from "util";
  95. // console.log(
  96. // util.inspect(sideBarSortMap, {
  97. // showHidden: false,
  98. // depth: null,
  99. // colors: true,
  100. // }),
  101. // util.inspect(rootSideBar, { showHidden: false, depth: null, colors: true })
  102. // );
  103. // https://vitepress.dev/reference/site-config
  104. export default defineConfig({
  105. title: "可可智慧教育平台",
  106. description: "可可智慧教育平台",
  107. base: "/help/",
  108. srcDir: DOC_BASE_PATH,
  109. ignoreDeadLinks: true,
  110. lastUpdated: true,
  111. themeConfig: {
  112. i18nRouting: false,
  113. search: {
  114. provider: "local",
  115. options: {
  116. locales: {
  117. root: {
  118. translations: {
  119. button: {
  120. buttonText: "搜索文档",
  121. buttonAriaLabel: "搜索文档",
  122. },
  123. modal: {
  124. noResultsText: "无法找到相关结果",
  125. resetButtonTitle: "清除查询条件",
  126. footer: {
  127. selectText: "选择",
  128. navigateText: "切换",
  129. closeText: "关闭",
  130. },
  131. },
  132. },
  133. },
  134. "zh-HK": {
  135. translations: {
  136. button: {
  137. buttonText: "搜索文档hk",
  138. buttonAriaLabel: "搜索文档hk",
  139. },
  140. modal: {
  141. noResultsText: "无法找到相关结果",
  142. resetButtonTitle: "清除查询条件",
  143. footer: {
  144. selectText: "选择",
  145. navigateText: "切换",
  146. closeText: "关闭",
  147. },
  148. },
  149. },
  150. },
  151. },
  152. },
  153. },
  154. },
  155. appearance: false,
  156. markdown: {
  157. breaks: true,
  158. config: (md) => {
  159. md.use(ImageFiguresPlugin, { figcaption: true });
  160. },
  161. },
  162. vite: {
  163. publicDir: "../public",
  164. envDir: "../",
  165. ssr: {
  166. // SSG Vue-i18n workaround
  167. noExternal: ["vue-i18n", "mark.js"],
  168. },
  169. plugins: [
  170. VueI18nPlugin({}),
  171. Icons({
  172. compiler: "vue3",
  173. customCollections: {
  174. "ccrbi-plain": FileSystemIconLoader(
  175. "assets/icons/plain",
  176. async (svgStr) => {
  177. const svg = new SVG(svgStr);
  178. cleanupSVG(svg);
  179. parseColors(svg, {
  180. defaultColor: "currentColor",
  181. callback: (attr, colorStr, color) => {
  182. // console.log('Color:', colorStr, color);
  183. // 普通图标
  184. return "currentColor";
  185. // Change black to 'currentColor'
  186. // const blackColor = stringToColor("black");
  187. // if (color && compareColors(color, blackColor!)) {
  188. // return "currentColor";
  189. // }
  190. // switch (color?.type) {
  191. // case "none":
  192. // case "current":
  193. // return color;
  194. // }
  195. // throw new Error(
  196. // `Unexpected color "${colorStr}" in attribute ${attr}`
  197. // );
  198. },
  199. });
  200. // Optimise, but do not change shapes because they are animated
  201. runSVGO(svg, {
  202. keepShapes: true,
  203. });
  204. return svg.toMinifiedString({});
  205. }
  206. ),
  207. "ccrbi-colored": FileSystemIconLoader(
  208. "assets/icons/colored",
  209. async (svgStr) => {
  210. const svg = new SVG(svgStr);
  211. cleanupSVG(svg);
  212. // Optimise, but do not change shapes because they are animated
  213. runSVGO(svg, {
  214. keepShapes: true,
  215. });
  216. return svg.toMinifiedString({});
  217. }
  218. ),
  219. },
  220. }),
  221. ],
  222. resolve: {
  223. alias: [
  224. {
  225. find: /^.*\/VPNavBar\.vue$/,
  226. replacement: fileURLToPath(
  227. new URL("../components/CustomNavBar.vue", import.meta.url)
  228. ),
  229. },
  230. {
  231. find: /^.*\/VPHome\.vue$/,
  232. replacement: fileURLToPath(
  233. new URL("../components/CustomVPHome.vue", import.meta.url)
  234. ),
  235. },
  236. {
  237. find: "@/",
  238. replacement: fileURLToPath(new URL("../", import.meta.url)),
  239. },
  240. ],
  241. },
  242. },
  243. locales: {
  244. root: {
  245. label: "简体中文",
  246. lang: "zh-CN",
  247. themeConfig: {
  248. i18nRouting: false,
  249. outline: { label: "本页目录", level: "deep" },
  250. logo: "/logo.png",
  251. siteTitle: false,
  252. sidebar: rootSideBar,
  253. },
  254. },
  255. "zh-HK": {
  256. label: "繁体中文",
  257. lang: "zh-HK",
  258. themeConfig: {
  259. i18nRouting: false,
  260. outline: { label: "本頁目錄", level: "deep" },
  261. logo: "/logo.png",
  262. siteTitle: false,
  263. sidebar: zhHKSideBar,
  264. },
  265. },
  266. "en-US": {
  267. label: "English",
  268. lang: "en-US",
  269. themeConfig: {
  270. i18nRouting: false,
  271. outline: { label: "On this page", level: "deep" },
  272. logo: "/logo.png",
  273. siteTitle: false,
  274. sidebar: enUSSideBar,
  275. },
  276. },
  277. },
  278. });