config.mts 9.9 KB

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