radiant-docs 0.1.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 (61) hide show
  1. package/dist/index.js +312 -0
  2. package/package.json +38 -0
  3. package/template/.vscode/extensions.json +4 -0
  4. package/template/.vscode/launch.json +11 -0
  5. package/template/astro.config.mjs +216 -0
  6. package/template/ec.config.mjs +51 -0
  7. package/template/package-lock.json +12546 -0
  8. package/template/package.json +51 -0
  9. package/template/public/favicon.svg +9 -0
  10. package/template/src/assets/icons/check.svg +33 -0
  11. package/template/src/assets/icons/danger.svg +37 -0
  12. package/template/src/assets/icons/info.svg +36 -0
  13. package/template/src/assets/icons/lightbulb.svg +74 -0
  14. package/template/src/assets/icons/warning.svg +37 -0
  15. package/template/src/components/Header.astro +176 -0
  16. package/template/src/components/MdxPage.astro +49 -0
  17. package/template/src/components/OpenApiPage.astro +270 -0
  18. package/template/src/components/Search.astro +362 -0
  19. package/template/src/components/Sidebar.astro +19 -0
  20. package/template/src/components/SidebarDropdown.astro +149 -0
  21. package/template/src/components/SidebarGroup.astro +51 -0
  22. package/template/src/components/SidebarLink.astro +56 -0
  23. package/template/src/components/SidebarMenu.astro +46 -0
  24. package/template/src/components/SidebarSubgroup.astro +136 -0
  25. package/template/src/components/TableOfContents.astro +480 -0
  26. package/template/src/components/ThemeSwitcher.astro +84 -0
  27. package/template/src/components/endpoint/PlaygroundBar.astro +68 -0
  28. package/template/src/components/endpoint/PlaygroundButton.astro +44 -0
  29. package/template/src/components/endpoint/PlaygroundField.astro +54 -0
  30. package/template/src/components/endpoint/PlaygroundForm.astro +203 -0
  31. package/template/src/components/endpoint/RequestSnippets.astro +308 -0
  32. package/template/src/components/endpoint/ResponseDisplay.astro +177 -0
  33. package/template/src/components/endpoint/ResponseFields.astro +224 -0
  34. package/template/src/components/endpoint/ResponseSnippets.astro +247 -0
  35. package/template/src/components/sidebar/SidebarEndpointLink.astro +51 -0
  36. package/template/src/components/sidebar/SidebarOpenApi.astro +207 -0
  37. package/template/src/components/ui/Field.astro +69 -0
  38. package/template/src/components/ui/Tag.astro +5 -0
  39. package/template/src/components/ui/demo/CodeDemo.astro +15 -0
  40. package/template/src/components/ui/demo/Demo.astro +3 -0
  41. package/template/src/components/ui/demo/UiDisplay.astro +13 -0
  42. package/template/src/components/user/Accordian.astro +69 -0
  43. package/template/src/components/user/AccordianGroup.astro +13 -0
  44. package/template/src/components/user/Callout.astro +101 -0
  45. package/template/src/components/user/Step.astro +51 -0
  46. package/template/src/components/user/Steps.astro +9 -0
  47. package/template/src/components/user/Tab.astro +25 -0
  48. package/template/src/components/user/Tabs.astro +122 -0
  49. package/template/src/content.config.ts +11 -0
  50. package/template/src/entrypoint.ts +9 -0
  51. package/template/src/layouts/Layout.astro +92 -0
  52. package/template/src/lib/component-error.ts +163 -0
  53. package/template/src/lib/frontmatter-schema.ts +9 -0
  54. package/template/src/lib/oas.ts +24 -0
  55. package/template/src/lib/pagefind.ts +88 -0
  56. package/template/src/lib/routes.ts +316 -0
  57. package/template/src/lib/utils.ts +59 -0
  58. package/template/src/lib/validation.ts +1097 -0
  59. package/template/src/pages/[...slug].astro +77 -0
  60. package/template/src/styles/global.css +209 -0
  61. package/template/tsconfig.json +5 -0
package/dist/index.js ADDED
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { spawn, execSync } from "child_process";
5
+ import path from "path";
6
+ import fs from "fs-extra";
7
+ import chokidar from "chokidar";
8
+ import { fileURLToPath } from "url";
9
+ import envPaths from "env-paths";
10
+ import crypto from "crypto";
11
+ var __filename = fileURLToPath(import.meta.url);
12
+ var __dirname = path.dirname(__filename);
13
+ var paths = envPaths("radiant", { suffix: "" });
14
+ var VERSION = "0.1.0";
15
+ var lastErrorMessage = "";
16
+ var lastErrorTime = 0;
17
+ var ERROR_DEDUPE_MS = 2e3;
18
+ var colors = {
19
+ reset: "\x1B[0m",
20
+ bright: "\x1B[1m",
21
+ dim: "\x1B[2m",
22
+ red: "\x1B[31m",
23
+ green: "\x1B[32m",
24
+ yellow: "\x1B[33m",
25
+ blue: "\x1B[34m",
26
+ cyan: "\x1B[36m",
27
+ magenta: "\x1B[35m"
28
+ };
29
+ var log = {
30
+ info: (msg) => console.log(`${colors.cyan}\u25CF${colors.reset} ${msg}`),
31
+ success: (msg) => console.log(`${colors.green}\u2713${colors.reset} ${msg}`),
32
+ error: (msg) => console.log(`${colors.red}\u2717${colors.reset} ${msg}`),
33
+ warn: (msg) => console.log(`${colors.yellow}\u26A0${colors.reset} ${msg}`),
34
+ file: (msg) => console.log(
35
+ `${colors.dim}\u21BB${colors.reset} ${colors.dim}${msg}${colors.reset}`
36
+ ),
37
+ blank: () => console.log("")
38
+ };
39
+ function getUserDocsDir() {
40
+ return process.cwd();
41
+ }
42
+ function getProjectHash(docsDir) {
43
+ return crypto.createHash("md5").update(docsDir).digest("hex").slice(0, 8);
44
+ }
45
+ function getCacheDir(docsDir) {
46
+ const projectHash = getProjectHash(docsDir);
47
+ return path.join(paths.cache, projectHash);
48
+ }
49
+ function getTemplateDir() {
50
+ return path.join(__dirname, "../template");
51
+ }
52
+ function getContentDir(cacheDir) {
53
+ return path.join(cacheDir, "src/content/docs");
54
+ }
55
+ async function validateUserDocs(docsDir) {
56
+ const docsJsonPath = path.join(docsDir, "docs.json");
57
+ if (!fs.existsSync(docsJsonPath)) {
58
+ log.error("No docs.json found in current directory.");
59
+ log.info("Run this command from your documentation root folder.");
60
+ log.blank();
61
+ log.info(`Expected location: ${colors.dim}${docsJsonPath}${colors.reset}`);
62
+ process.exit(1);
63
+ }
64
+ try {
65
+ const content = await fs.readFile(docsJsonPath, "utf-8");
66
+ JSON.parse(content);
67
+ } catch {
68
+ log.error("Invalid JSON in docs.json");
69
+ process.exit(1);
70
+ }
71
+ }
72
+ async function setupCache(cacheDir) {
73
+ const templateDir = getTemplateDir();
74
+ const nodeModulesExists = fs.existsSync(path.join(cacheDir, "node_modules"));
75
+ const cacheExists = fs.existsSync(cacheDir);
76
+ if (!fs.existsSync(templateDir)) {
77
+ log.error(
78
+ "Framework template not found. The CLI package may be corrupted."
79
+ );
80
+ log.info(`Expected at: ${templateDir}`);
81
+ process.exit(1);
82
+ }
83
+ let needsUpdate = !cacheExists;
84
+ if (cacheExists) {
85
+ try {
86
+ const templatePkg = await fs.readJson(
87
+ path.join(templateDir, "package.json")
88
+ );
89
+ const cachePkg = await fs.readJson(path.join(cacheDir, "package.json"));
90
+ if (templatePkg.version !== cachePkg.version) {
91
+ needsUpdate = true;
92
+ log.info("Framework update detected, refreshing cache...");
93
+ }
94
+ } catch {
95
+ needsUpdate = true;
96
+ }
97
+ }
98
+ if (needsUpdate) {
99
+ log.info("Setting up Radiant framework...");
100
+ if (cacheExists) {
101
+ await fs.remove(cacheDir);
102
+ }
103
+ await fs.copy(templateDir, cacheDir, {
104
+ filter: (src) => !src.includes("node_modules")
105
+ });
106
+ log.success("Framework copied to cache.");
107
+ return true;
108
+ }
109
+ return !nodeModulesExists;
110
+ }
111
+ async function installDependencies(cacheDir) {
112
+ log.info("Installing dependencies (first run only)...");
113
+ try {
114
+ execSync("npm install --silent --no-fund --no-audit", {
115
+ cwd: cacheDir,
116
+ stdio: "pipe"
117
+ });
118
+ log.success("Dependencies installed.");
119
+ } catch (error) {
120
+ log.error("Failed to install dependencies.");
121
+ throw error;
122
+ }
123
+ }
124
+ async function syncContent(docsDir, contentDir) {
125
+ await fs.emptyDir(contentDir);
126
+ await fs.copy(docsDir, contentDir, {
127
+ filter: (src) => {
128
+ const basename = path.basename(src);
129
+ const relativePath = path.relative(docsDir, src);
130
+ if (basename.startsWith(".")) return false;
131
+ if (relativePath.includes("node_modules")) return false;
132
+ if (basename === "package.json" || basename === "package-lock.json")
133
+ return false;
134
+ if (basename.toLowerCase().startsWith("readme")) return false;
135
+ return true;
136
+ }
137
+ });
138
+ }
139
+ async function runAstroSync(cacheDir) {
140
+ try {
141
+ execSync("npx astro sync", {
142
+ cwd: cacheDir,
143
+ stdio: "pipe"
144
+ });
145
+ } catch {
146
+ }
147
+ }
148
+ function startDevServer(cacheDir) {
149
+ const child = spawn("npm", ["run", "dev"], {
150
+ cwd: cacheDir,
151
+ shell: true,
152
+ env: {
153
+ ...process.env,
154
+ FORCE_COLOR: "1"
155
+ }
156
+ });
157
+ let serverStarted = false;
158
+ let serverUrl = "http://localhost:4321";
159
+ const processOutput = (data, isError = false) => {
160
+ const output = data.toString();
161
+ const lines = output.split("\n");
162
+ for (const line of lines) {
163
+ const trimmedLine = line.trim();
164
+ if (!trimmedLine) continue;
165
+ const urlMatch = line.match(/http:\/\/localhost:\d+/);
166
+ if (urlMatch) {
167
+ serverUrl = urlMatch[0];
168
+ }
169
+ if (!serverStarted && (line.includes("watching for file changes") || line.includes("Local") && line.includes("localhost"))) {
170
+ serverStarted = true;
171
+ log.blank();
172
+ log.success("Dev server ready!");
173
+ console.log(` ${colors.bright}Local:${colors.reset} ${serverUrl}`);
174
+ log.blank();
175
+ log.info("Watching for changes...");
176
+ log.blank();
177
+ continue;
178
+ }
179
+ if (line.includes("[USER_ERROR]")) {
180
+ const errorMsg = line.split("[USER_ERROR]:")[1]?.trim() || line;
181
+ const now = Date.now();
182
+ if (errorMsg === lastErrorMessage && now - lastErrorTime < ERROR_DEDUPE_MS) {
183
+ continue;
184
+ }
185
+ lastErrorMessage = errorMsg;
186
+ lastErrorTime = now;
187
+ log.blank();
188
+ log.error(errorMsg);
189
+ log.blank();
190
+ continue;
191
+ }
192
+ }
193
+ };
194
+ child.stdout?.on("data", (data) => processOutput(data, false));
195
+ child.stderr?.on("data", (data) => processOutput(data, true));
196
+ child.on("exit", (code) => {
197
+ if (code !== 0 && code !== null) {
198
+ log.blank();
199
+ log.error(`Dev server exited with code ${code}`);
200
+ process.exit(code);
201
+ }
202
+ });
203
+ return child;
204
+ }
205
+ function watchUserContent(docsDir, contentDir, _onSync) {
206
+ const watcher = chokidar.watch(docsDir, {
207
+ ignored: [
208
+ "**/node_modules/**",
209
+ "**/.git/**",
210
+ "**/package.json",
211
+ "**/package-lock.json",
212
+ /^\./
213
+ ],
214
+ persistent: true,
215
+ ignoreInitial: true,
216
+ awaitWriteFinish: {
217
+ stabilityThreshold: 100,
218
+ pollInterval: 50
219
+ }
220
+ });
221
+ let debounceTimer = null;
222
+ const pendingChanges = /* @__PURE__ */ new Set();
223
+ const processChanges = async () => {
224
+ if (pendingChanges.size === 0) return;
225
+ for (const filepath of pendingChanges) {
226
+ const relativePath = path.relative(docsDir, filepath);
227
+ log.file(relativePath);
228
+ }
229
+ try {
230
+ await syncContent(docsDir, contentDir);
231
+ await _onSync();
232
+ } catch (error) {
233
+ log.error(`Sync failed: ${error}`);
234
+ }
235
+ pendingChanges.clear();
236
+ };
237
+ watcher.on("all", (event, filepath) => {
238
+ if (event === "change" || event === "add" || event === "unlink") {
239
+ pendingChanges.add(filepath);
240
+ if (debounceTimer) clearTimeout(debounceTimer);
241
+ debounceTimer = setTimeout(processChanges, 300);
242
+ }
243
+ });
244
+ return watcher;
245
+ }
246
+ function showHelp() {
247
+ console.log(`
248
+ ${colors.bright}${colors.cyan}\u25C6 Radiant${colors.reset} v${VERSION}
249
+
250
+ ${colors.bright}Usage:${colors.reset}
251
+ radiant Start the dev server
252
+ radiant dev Start the dev server
253
+ radiant --help Show this help message
254
+ radiant --version Show version
255
+
256
+ ${colors.bright}Description:${colors.reset}
257
+ Run this command from your documentation folder (containing docs.json)
258
+ to preview your docs locally with hot reload.
259
+ `);
260
+ }
261
+ async function run() {
262
+ const args = process.argv.slice(2);
263
+ if (args.includes("--help") || args.includes("-h")) {
264
+ showHelp();
265
+ process.exit(0);
266
+ }
267
+ if (args.includes("--version") || args.includes("-v")) {
268
+ console.log(VERSION);
269
+ process.exit(0);
270
+ }
271
+ const command = args[0];
272
+ if (command && command !== "dev") {
273
+ log.error(`Unknown command: ${command}`);
274
+ showHelp();
275
+ process.exit(1);
276
+ }
277
+ log.blank();
278
+ console.log(
279
+ `${colors.bright}${colors.cyan}\u25C6 Radiant${colors.reset} ${colors.dim}v${VERSION}${colors.reset}`
280
+ );
281
+ log.blank();
282
+ const docsDir = getUserDocsDir();
283
+ const cacheDir = getCacheDir(docsDir);
284
+ const contentDir = getContentDir(cacheDir);
285
+ await validateUserDocs(docsDir);
286
+ const needsInstall = await setupCache(cacheDir);
287
+ if (needsInstall) {
288
+ await installDependencies(cacheDir);
289
+ }
290
+ log.info("Syncing your documentation...");
291
+ await syncContent(docsDir, contentDir);
292
+ await runAstroSync(cacheDir);
293
+ log.success("Content synced.");
294
+ log.info("Starting dev server...");
295
+ const devServer = startDevServer(cacheDir);
296
+ const watcher = watchUserContent(docsDir, contentDir, async () => {
297
+ await runAstroSync(cacheDir);
298
+ });
299
+ const cleanup = () => {
300
+ log.blank();
301
+ log.info("Shutting down...");
302
+ watcher.close();
303
+ devServer.kill();
304
+ process.exit(0);
305
+ };
306
+ process.on("SIGINT", cleanup);
307
+ process.on("SIGTERM", cleanup);
308
+ }
309
+ run().catch((error) => {
310
+ log.error(error.message || String(error));
311
+ process.exit(1);
312
+ });
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "radiant-docs",
3
+ "version": "0.1.0",
4
+ "description": "CLI tool for previewing Radiant documentation locally",
5
+ "type": "module",
6
+ "bin": {
7
+ "radiant": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsup src/index.ts --format esm --clean",
11
+ "dev": "tsup src/index.ts --format esm --watch",
12
+ "prepublishOnly": "npm run build && npm run bundle-template",
13
+ "bundle-template": "node scripts/bundle-template.js"
14
+ },
15
+ "dependencies": {
16
+ "chokidar": "^4.0.3",
17
+ "env-paths": "^3.0.0",
18
+ "fs-extra": "^11.2.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/fs-extra": "^11.0.4",
22
+ "@types/node": "^22.0.0",
23
+ "tsup": "^8.0.0",
24
+ "typescript": "^5.0.0"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "template"
29
+ ],
30
+ "keywords": [
31
+ "documentation",
32
+ "docs",
33
+ "cli",
34
+ "radiant",
35
+ "astro"
36
+ ],
37
+ "license": "MIT"
38
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "recommendations": ["astro-build.astro-vscode"],
3
+ "unwantedRecommendations": []
4
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "command": "./node_modules/.bin/astro dev",
6
+ "name": "Development server",
7
+ "request": "launch",
8
+ "type": "node-terminal"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,216 @@
1
+ // @ts-check
2
+ import { defineConfig } from "astro/config";
3
+ import tailwindcss from "@tailwindcss/vite";
4
+ import mdx from "@astrojs/mdx";
5
+ import alpine from "@astrojs/alpinejs";
6
+ import icon from "astro-icon";
7
+ import expressiveCode from "astro-expressive-code";
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { getConfig, validateMdxContent } from "./src/lib/validation";
11
+
12
+ if (process.env.npm_lifecycle_event === "build") {
13
+ // Run validation for build before Astro's internal validation
14
+ try {
15
+ await getConfig();
16
+ await validateMdxContent();
17
+ } catch (error) {
18
+ console.error(error); // Log our [USER_ERROR] message
19
+ process.exit(1);
20
+ }
21
+ }
22
+
23
+ // Recursively copy all image files from src/content/docs to public
24
+ // This runs in both dev and prod to ensure user assets are always available
25
+ function copyContentAssets() {
26
+ const CWD = process.cwd();
27
+ const DOCS_DIR = path.join(CWD, "src/content/docs");
28
+ const PUBLIC_DIR = path.join(CWD, "public");
29
+
30
+ const imageExtensions = [
31
+ ".svg",
32
+ ".png",
33
+ ".jpg",
34
+ ".jpeg",
35
+ ".webp",
36
+ ".gif",
37
+ ".avif",
38
+ ".ico",
39
+ ];
40
+
41
+ function shouldCopyFile(filePath) {
42
+ const ext = path.extname(filePath).toLowerCase();
43
+ return imageExtensions.includes(ext);
44
+ }
45
+
46
+ // Build a set of all image file paths in src/content/docs
47
+ function collectImagePaths(dir, baseDir, pathSet) {
48
+ if (!fs.existsSync(dir)) return;
49
+
50
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
51
+
52
+ for (const entry of entries) {
53
+ const entryPath = path.join(dir, entry.name);
54
+
55
+ // Skip certain files/directories
56
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
57
+
58
+ if (entry.isDirectory()) {
59
+ collectImagePaths(entryPath, baseDir, pathSet);
60
+ } else if (entry.isFile() && shouldCopyFile(entryPath)) {
61
+ // Store relative path from baseDir
62
+ const relativePath = path.relative(baseDir, entryPath);
63
+ pathSet.add(relativePath.replace(/\\/g, "/")); // Normalize path separators
64
+ }
65
+ }
66
+ }
67
+
68
+ // Single-pass copy: returns true if any files were copied (or subdirectories contain files)
69
+ function copyDirectory(srcDir, destDir) {
70
+ if (!fs.existsSync(srcDir)) return false;
71
+
72
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
73
+ let hasCopiedFiles = false;
74
+
75
+ for (const entry of entries) {
76
+ const srcPath = path.join(srcDir, entry.name);
77
+ const destPath = path.join(destDir, entry.name);
78
+
79
+ // Skip certain files/directories
80
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
81
+
82
+ if (entry.isDirectory()) {
83
+ // Recursively copy subdirectory - only create if it contains images
84
+ const subDirHasFiles = copyDirectory(srcPath, destPath);
85
+ if (subDirHasFiles) {
86
+ hasCopiedFiles = true;
87
+ // Create directory only if we actually copied something into it
88
+ if (!fs.existsSync(destPath)) {
89
+ fs.mkdirSync(destPath, { recursive: true });
90
+ }
91
+ }
92
+ } else if (entry.isFile() && shouldCopyFile(srcPath)) {
93
+ // Ensure destination directory exists before copying
94
+ if (!fs.existsSync(destDir)) {
95
+ fs.mkdirSync(destDir, { recursive: true });
96
+ }
97
+ fs.copyFileSync(srcPath, destPath);
98
+ hasCopiedFiles = true;
99
+ }
100
+ }
101
+
102
+ return hasCopiedFiles;
103
+ }
104
+
105
+ // Delete files in public that don't exist in the source set
106
+ function deleteOrphanedFiles(publicDir, baseDir, validPaths) {
107
+ if (!fs.existsSync(publicDir)) return;
108
+
109
+ const entries = fs.readdirSync(publicDir, { withFileTypes: true });
110
+
111
+ for (const entry of entries) {
112
+ const entryPath = path.join(publicDir, entry.name);
113
+ const relativePath = path
114
+ .relative(baseDir, entryPath)
115
+ .replace(/\\/g, "/");
116
+
117
+ if (entry.isDirectory()) {
118
+ // Recursively check subdirectories
119
+ deleteOrphanedFiles(entryPath, baseDir, validPaths);
120
+ } else if (entry.isFile() && shouldCopyFile(entryPath)) {
121
+ // Delete if not in valid paths set
122
+ if (!validPaths.has(relativePath)) {
123
+ fs.unlinkSync(entryPath);
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ function cleanupEmptyDirs(dir, baseDir) {
130
+ if (!fs.existsSync(dir)) return;
131
+
132
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
133
+
134
+ for (const entry of entries) {
135
+ const entryPath = path.join(dir, entry.name);
136
+ if (entry.isDirectory()) {
137
+ cleanupEmptyDirs(entryPath, baseDir);
138
+ }
139
+ }
140
+
141
+ // After processing children, check if this directory is empty
142
+ const remainingEntries = fs.readdirSync(dir);
143
+ if (remainingEntries.length === 0 && dir !== baseDir) {
144
+ fs.rmdirSync(dir);
145
+ }
146
+ }
147
+
148
+ // Main sync function
149
+ function syncAssets() {
150
+ if (!fs.existsSync(DOCS_DIR)) return;
151
+
152
+ // Step 1: Collect all valid image paths from source
153
+ const validImagePaths = new Set();
154
+ collectImagePaths(DOCS_DIR, DOCS_DIR, validImagePaths);
155
+
156
+ // Step 2: Copy all images from source to public
157
+ copyDirectory(DOCS_DIR, PUBLIC_DIR);
158
+
159
+ // Step 3: Delete orphaned files in public
160
+ deleteOrphanedFiles(PUBLIC_DIR, PUBLIC_DIR, validImagePaths);
161
+
162
+ // Step 4: Clean up empty directories
163
+ cleanupEmptyDirs(PUBLIC_DIR, PUBLIC_DIR);
164
+ }
165
+
166
+ return {
167
+ name: "copy-content-assets",
168
+ buildStart() {
169
+ // Always copy in both dev and prod
170
+ try {
171
+ if (fs.existsSync(DOCS_DIR)) {
172
+ syncAssets();
173
+ console.log("✅ Copied content assets to public folder");
174
+ }
175
+ } catch (error) {
176
+ console.warn("Failed to copy content assets:", error);
177
+ }
178
+ },
179
+ // Also watch for changes in dev mode
180
+ configureServer(server) {
181
+ const watcher = server.watcher;
182
+ watcher.on("change", (file) => {
183
+ if (file.includes("src/content/docs") && shouldCopyFile(file)) {
184
+ const relativePath = path.relative(
185
+ path.join(CWD, "src/content/docs"),
186
+ file
187
+ );
188
+ const destPath = path.join(PUBLIC_DIR, relativePath);
189
+ const destDir = path.dirname(destPath);
190
+
191
+ if (!fs.existsSync(destDir)) {
192
+ fs.mkdirSync(destDir, { recursive: true });
193
+ }
194
+ fs.copyFileSync(file, destPath);
195
+ }
196
+ });
197
+ },
198
+ };
199
+ }
200
+
201
+ // https://astro.build/config
202
+ export default defineConfig({
203
+ vite: {
204
+ plugins: [tailwindcss(), copyContentAssets()],
205
+ },
206
+ output: "static",
207
+ build: {
208
+ format: "directory",
209
+ },
210
+ integrations: [
211
+ expressiveCode(),
212
+ mdx(),
213
+ alpine({ entrypoint: "/src/entrypoint" }),
214
+ icon(),
215
+ ],
216
+ });
@@ -0,0 +1,51 @@
1
+ import { defineEcConfig } from "astro-expressive-code";
2
+ import { pluginLineNumbers } from "@expressive-code/plugin-line-numbers";
3
+ import { pluginCollapsibleSections } from "@expressive-code/plugin-collapsible-sections";
4
+ import { pluginFileIcons } from "@xt0rted/expressive-code-file-icons";
5
+
6
+ export default defineEcConfig({
7
+ themes: ["min-dark", "min-light"],
8
+ // Customize as needed:
9
+ styleOverrides: {
10
+ borderRadius: "12px",
11
+ borderColor: "var(--color-neutral-200)",
12
+ borderWidth: "1px",
13
+ codeFontSize: "13px",
14
+ frames: {
15
+ frameBoxShadowCssValue: "var(--shadow-xs)",
16
+ inlineButtonBorder: "var(--color-neutral-200)",
17
+ inlineButtonBorderOpacity: "1",
18
+ copyIcon: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect width='14' height='14' x='8' y='8' rx='2' ry='2'/%3E%3Cpath d='M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2'/%3E%3C/svg%3E")`,
19
+ },
20
+ lineNumbers: {
21
+ foreground: "var(--color-neutral-400)",
22
+ },
23
+ textMarkers: {
24
+ markBackground: "var(--color-neutral-100)",
25
+ },
26
+ collapsibleSections: {
27
+ closedBackgroundColor: "var(--color-neutral-200)",
28
+ openBackgroundColor: "var(--color-neutral-100)",
29
+ openBackgroundColorCollapsible: "var(--color-neutral-100)",
30
+ collapseIcon: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 22v-6'/%3E%3Cpath d='M12 8V2'/%3E%3Cpath d='M4 12H2'/%3E%3Cpath d='M10 12H8'/%3E%3Cpath d='M16 12h-2'/%3E%3Cpath d='M22 12h-2'/%3E%3Cpath d='m15 19-3-3-3 3'/%3E%3Cpath d='m15 5-3 3-3-3'/%3E%3C/svg%3E")`,
31
+ expandIcon: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 22v-6'/%3E%3Cpath d='M12 8V2'/%3E%3Cpath d='M4 12H2'/%3E%3Cpath d='M10 12H8'/%3E%3Cpath d='M16 12h-2'/%3E%3Cpath d='M22 12h-2'/%3E%3Cpath d='m15 19-3 3-3-3'/%3E%3Cpath d='m15 5-3-3-3 3'/%3E%3C/svg%3E")`,
32
+ closedTextColor: "var(--color-neutral-500)",
33
+ },
34
+ // codeFontFamily: '"JetBrains Mono", monospace',
35
+ },
36
+
37
+ plugins: [
38
+ pluginLineNumbers(),
39
+ pluginCollapsibleSections(),
40
+ // @ts-ignore - plugin types not fully compatible
41
+ pluginFileIcons({
42
+ iconClass: "size-4",
43
+ titleClass: "!flex !items-center !gap-1",
44
+ }),
45
+ ],
46
+ defaultProps: {
47
+ wrap: false,
48
+ collapseStyle: "collapsible-auto",
49
+ showLineNumbers: false,
50
+ },
51
+ });