create-specra 0.1.7 → 0.2.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.
- package/LICENSE.MD +16 -4
- package/README.md +53 -17
- package/dist/chunk-3DKWECRK.js +45 -0
- package/dist/chunk-3DKWECRK.js.map +1 -0
- package/dist/chunk-MA7QG54W.js +74 -0
- package/dist/chunk-MA7QG54W.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +33 -0
- package/dist/cli.js.map +1 -0
- package/dist/deploy-SCEMUQNS.js +141 -0
- package/dist/deploy-SCEMUQNS.js.map +1 -0
- package/dist/index.js +11 -11
- package/dist/index.js.map +1 -1
- package/dist/login-NKDRQXRE.js +71 -0
- package/dist/login-NKDRQXRE.js.map +1 -0
- package/dist/logout-H543QEKU.js +20 -0
- package/dist/logout-H543QEKU.js.map +1 -0
- package/dist/logs-YDAUCMAV.js +71 -0
- package/dist/logs-YDAUCMAV.js.map +1 -0
- package/dist/projects-3TAY7EDJ.js +42 -0
- package/dist/projects-3TAY7EDJ.js.map +1 -0
- package/package.json +7 -3
- package/templates/book-docs/docs/v1.0.0/concepts.mdx +89 -0
- package/templates/book-docs/docs/v1.0.0/content/_category_.json +7 -0
- package/templates/book-docs/docs/v1.0.0/content/formatting.mdx +128 -0
- package/templates/book-docs/docs/v1.0.0/content/reusable-content.mdx +116 -0
- package/templates/book-docs/docs/v1.0.0/content/structure.mdx +92 -0
- package/templates/book-docs/docs/v1.0.0/customization/_category_.json +7 -0
- package/templates/book-docs/docs/v1.0.0/customization/branding.mdx +115 -0
- package/templates/book-docs/docs/v1.0.0/customization/themes.mdx +81 -0
- package/templates/book-docs/docs/v1.0.0/introduction.mdx +38 -0
- package/templates/book-docs/docs/v1.0.0/quickstart.mdx +112 -0
- package/templates/book-docs/docs/v2.0.0/concepts.mdx +89 -0
- package/templates/book-docs/docs/v2.0.0/content/_category_.json +7 -0
- package/templates/book-docs/docs/v2.0.0/content/formatting.mdx +128 -0
- package/templates/book-docs/docs/v2.0.0/content/reusable-content.mdx +116 -0
- package/templates/book-docs/docs/v2.0.0/content/structure.mdx +92 -0
- package/templates/book-docs/docs/v2.0.0/customization/_category_.json +7 -0
- package/templates/book-docs/docs/v2.0.0/customization/branding.mdx +115 -0
- package/templates/book-docs/docs/v2.0.0/customization/themes.mdx +81 -0
- package/templates/book-docs/docs/v2.0.0/introduction.mdx +39 -0
- package/templates/book-docs/docs/v2.0.0/quickstart.mdx +112 -0
- package/templates/book-docs/gitignore +7 -0
- package/templates/book-docs/package.json +28 -0
- package/templates/book-docs/postcss.config.mjs +8 -0
- package/templates/book-docs/public/api-specs/openapi-example.json +259 -0
- package/templates/book-docs/public/api-specs/postman-example.json +205 -0
- package/templates/book-docs/public/api-specs/test-api.json +256 -0
- package/templates/book-docs/public/api-specs/users-api.json +264 -0
- package/templates/book-docs/specra.config.json +77 -0
- package/templates/book-docs/src/app.css +2 -0
- package/templates/book-docs/src/app.html +12 -0
- package/templates/book-docs/src/routes/+layout.server.ts +11 -0
- package/templates/book-docs/src/routes/+layout.svelte +21 -0
- package/templates/book-docs/src/routes/+page.server.ts +9 -0
- package/templates/book-docs/src/routes/docs/[version]/[...slug]/+page.server.ts +119 -0
- package/templates/book-docs/src/routes/docs/[version]/[...slug]/+page.svelte +129 -0
- package/templates/book-docs/svelte.config.js +8 -0
- package/templates/book-docs/tsconfig.json +12 -0
- package/templates/book-docs/vite.config.ts +6 -0
- package/templates/jbrains-docs/docs/v1.0.0/advanced/_category_.json +8 -0
- package/templates/jbrains-docs/docs/v1.0.0/advanced/async.mdx +95 -0
- package/templates/jbrains-docs/docs/v1.0.0/advanced/generics.mdx +126 -0
- package/templates/jbrains-docs/docs/v1.0.0/basics/_category_.json +8 -0
- package/templates/jbrains-docs/docs/v1.0.0/basics/control-flow.mdx +106 -0
- package/templates/jbrains-docs/docs/v1.0.0/basics/syntax.mdx +129 -0
- package/templates/jbrains-docs/docs/v1.0.0/basics/types.mdx +135 -0
- package/templates/jbrains-docs/docs/v1.0.0/getting-started.mdx +111 -0
- package/templates/jbrains-docs/docs/v1.0.0/home.mdx +37 -0
- package/templates/jbrains-docs/docs/v1.0.0/tools/_category_.json +8 -0
- package/templates/jbrains-docs/docs/v1.0.0/tools/build-tools.mdx +165 -0
- package/templates/jbrains-docs/docs/v1.0.0/tools/testing.mdx +112 -0
- package/templates/jbrains-docs/docs/v2.0.0/advanced/_category_.json +8 -0
- package/templates/jbrains-docs/docs/v2.0.0/advanced/async.mdx +95 -0
- package/templates/jbrains-docs/docs/v2.0.0/advanced/generics.mdx +126 -0
- package/templates/jbrains-docs/docs/v2.0.0/basics/_category_.json +8 -0
- package/templates/jbrains-docs/docs/v2.0.0/basics/control-flow.mdx +106 -0
- package/templates/jbrains-docs/docs/v2.0.0/basics/syntax.mdx +129 -0
- package/templates/jbrains-docs/docs/v2.0.0/basics/types.mdx +135 -0
- package/templates/jbrains-docs/docs/v2.0.0/getting-started.mdx +111 -0
- package/templates/jbrains-docs/docs/v2.0.0/home.mdx +37 -0
- package/templates/jbrains-docs/docs/v2.0.0/tools/_category_.json +8 -0
- package/templates/jbrains-docs/docs/v2.0.0/tools/build-tools.mdx +165 -0
- package/templates/jbrains-docs/docs/v2.0.0/tools/testing.mdx +112 -0
- package/templates/jbrains-docs/gitignore +7 -0
- package/templates/jbrains-docs/package.json +28 -0
- package/templates/jbrains-docs/postcss.config.mjs +8 -0
- package/templates/jbrains-docs/public/api-specs/openapi-example.json +259 -0
- package/templates/jbrains-docs/public/api-specs/postman-example.json +205 -0
- package/templates/jbrains-docs/public/api-specs/test-api.json +256 -0
- package/templates/jbrains-docs/public/api-specs/users-api.json +264 -0
- package/templates/jbrains-docs/specra.config.json +80 -0
- package/templates/jbrains-docs/src/app.css +2 -0
- package/templates/jbrains-docs/src/app.html +12 -0
- package/templates/jbrains-docs/src/routes/+layout.server.ts +11 -0
- package/templates/jbrains-docs/src/routes/+layout.svelte +21 -0
- package/templates/jbrains-docs/src/routes/+page.server.ts +9 -0
- package/templates/jbrains-docs/src/routes/docs/[version]/[...slug]/+page.server.ts +119 -0
- package/templates/jbrains-docs/src/routes/docs/[version]/[...slug]/+page.svelte +129 -0
- package/templates/jbrains-docs/svelte.config.js +8 -0
- package/templates/jbrains-docs/tsconfig.json +12 -0
- package/templates/jbrains-docs/vite.config.ts +6 -0
- package/templates/minimal/docs/v1.0.0/about.mdx +3 -3
- package/templates/minimal/docs/v2.0.0/about.mdx +3 -3
- package/templates/minimal/gitignore +7 -0
- package/templates/minimal/package.json +18 -24
- package/templates/minimal/specra.config.json +12 -63
- package/templates/minimal/src/app.css +2 -0
- package/templates/minimal/src/app.html +12 -0
- package/templates/minimal/src/routes/+layout.server.ts +11 -0
- package/templates/minimal/src/routes/+layout.svelte +21 -0
- package/templates/minimal/src/routes/+page.server.ts +9 -0
- package/templates/minimal/src/routes/docs/[version]/[...slug]/+page.server.ts +119 -0
- package/templates/minimal/src/routes/docs/[version]/[...slug]/+page.svelte +129 -0
- package/templates/minimal/svelte.config.js +8 -0
- package/templates/minimal/tsconfig.json +7 -36
- package/templates/minimal/vite.config.ts +6 -0
- package/templates/minimal/README.md +0 -132
- package/templates/minimal/app/api/mdx-watch/route.ts +0 -6
- package/templates/minimal/app/api/search/route.ts +0 -75
- package/templates/minimal/app/docs/[version]/[...slug]/loading.tsx +0 -7
- package/templates/minimal/app/docs/[version]/[...slug]/page.tsx +0 -205
- package/templates/minimal/app/docs/[version]/[...slug]/page.tsx.bak +0 -203
- package/templates/minimal/app/docs/[version]/not-found.tsx +0 -10
- package/templates/minimal/app/docs/[version]/page.tsx +0 -27
- package/templates/minimal/app/globals.css +0 -9
- package/templates/minimal/app/layout.tsx +0 -62
- package/templates/minimal/app/not-found.tsx +0 -10
- package/templates/minimal/app/page.tsx +0 -179
- package/templates/minimal/next.config.mjs +0 -1
- package/templates/minimal/package-lock.json +0 -7881
- package/templates/minimal/proxy_.ts +0 -22
- package/templates/minimal/scripts/generate-redirects.mjs +0 -128
- package/templates/minimal/scripts/generate-static-redirects.mjs +0 -115
- package/templates/minimal/scripts/index-search.ts +0 -182
- package/templates/minimal/scripts/test-search.ts +0 -83
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { createSecurityProxy } from "specra/middleware/security"
|
|
2
|
-
|
|
3
|
-
// Note: Redirects from frontmatter are handled at build time via next.config.js
|
|
4
|
-
// This proxy handles runtime security headers, path validation, and dynamic logic
|
|
5
|
-
|
|
6
|
-
export const proxy = createSecurityProxy({
|
|
7
|
-
production: process.env.NODE_ENV === "production",
|
|
8
|
-
strictPathValidation: true,
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
export const config = {
|
|
12
|
-
matcher: [
|
|
13
|
-
/*
|
|
14
|
-
* Match all request paths except for the ones starting with:
|
|
15
|
-
* - api (API routes)
|
|
16
|
-
* - _next/static (static files)
|
|
17
|
-
* - _next/image (image optimization files)
|
|
18
|
-
* - favicon.ico (favicon file)
|
|
19
|
-
*/
|
|
20
|
-
"/((?!api|_next/static|_next/image|favicon.ico).*)",
|
|
21
|
-
],
|
|
22
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import fs from "fs"
|
|
2
|
-
import path from "path"
|
|
3
|
-
import matter from "gray-matter"
|
|
4
|
-
import { fileURLToPath } from "url"
|
|
5
|
-
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
7
|
-
const __dirname = path.dirname(__filename)
|
|
8
|
-
|
|
9
|
-
const DOCS_DIR = path.join(__dirname, "..", "docs")
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Recursively find all MDX files
|
|
13
|
-
*/
|
|
14
|
-
function findMdxFiles(dir, baseDir = dir) {
|
|
15
|
-
const files = []
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
19
|
-
|
|
20
|
-
for (const entry of entries) {
|
|
21
|
-
const fullPath = path.join(dir, entry.name)
|
|
22
|
-
|
|
23
|
-
if (entry.isDirectory()) {
|
|
24
|
-
files.push(...findMdxFiles(fullPath, baseDir))
|
|
25
|
-
} else if (entry.isFile() && entry.name.endsWith(".mdx")) {
|
|
26
|
-
const relativePath = path.relative(baseDir, fullPath)
|
|
27
|
-
files.push(relativePath)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
} catch (error) {
|
|
31
|
-
console.error(`Error reading directory ${dir}:`, error)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return files
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Get all folders (categories) that need redirect pages
|
|
39
|
-
*/
|
|
40
|
-
function getFolderRedirects(mdxFiles, version) {
|
|
41
|
-
const folders = new Map() // folder path -> first doc in folder
|
|
42
|
-
|
|
43
|
-
for (const file of mdxFiles) {
|
|
44
|
-
const normalized = file.replace(/\\/g, '/')
|
|
45
|
-
const parts = normalized.split('/')
|
|
46
|
-
|
|
47
|
-
// For each level of nesting, track the folder
|
|
48
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
49
|
-
const folderPath = parts.slice(0, i + 1).join('/')
|
|
50
|
-
|
|
51
|
-
if (!folders.has(folderPath)) {
|
|
52
|
-
// This is the first doc we've seen in this folder
|
|
53
|
-
const docSlug = normalized.replace(/\.mdx$/, '').replace(/\/index$/, '')
|
|
54
|
-
folders.set(folderPath, `/docs/${version}/${docSlug}`)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return folders
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Build redirect mappings from frontmatter
|
|
64
|
-
*/
|
|
65
|
-
async function buildRedirects() {
|
|
66
|
-
const versions = fs.readdirSync(DOCS_DIR).filter((v) => {
|
|
67
|
-
const stat = fs.statSync(path.join(DOCS_DIR, v))
|
|
68
|
-
return stat.isDirectory()
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
const redirects = []
|
|
72
|
-
const folderRedirects = []
|
|
73
|
-
|
|
74
|
-
for (const version of versions) {
|
|
75
|
-
const versionDir = path.join(DOCS_DIR, version)
|
|
76
|
-
const mdxFiles = findMdxFiles(versionDir)
|
|
77
|
-
|
|
78
|
-
// Build folder redirects for static export
|
|
79
|
-
const folders = getFolderRedirects(mdxFiles, version)
|
|
80
|
-
for (const [folderPath, destination] of folders.entries()) {
|
|
81
|
-
const source = `/docs/${version}/${folderPath}`
|
|
82
|
-
folderRedirects.push({
|
|
83
|
-
source,
|
|
84
|
-
destination,
|
|
85
|
-
permanent: false, // Temporary redirect for folder access
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
for (const file of mdxFiles) {
|
|
90
|
-
const filePath = path.join(versionDir, file)
|
|
91
|
-
const fileContents = fs.readFileSync(filePath, "utf8")
|
|
92
|
-
const { data } = matter(fileContents)
|
|
93
|
-
|
|
94
|
-
if (data.redirect_from && Array.isArray(data.redirect_from)) {
|
|
95
|
-
const slug = file.replace(/\.mdx$/, "").replace(/\\/g, '/')
|
|
96
|
-
const destination = `/docs/${version}/${slug}`
|
|
97
|
-
|
|
98
|
-
for (const source of data.redirect_from) {
|
|
99
|
-
redirects.push({
|
|
100
|
-
source,
|
|
101
|
-
destination,
|
|
102
|
-
permanent: true,
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return { redirects, folderRedirects }
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Generate redirects and write to a JSON file
|
|
114
|
-
*/
|
|
115
|
-
async function main() {
|
|
116
|
-
const { redirects, folderRedirects } = await buildRedirects()
|
|
117
|
-
|
|
118
|
-
const allRedirects = [...redirects, ...folderRedirects]
|
|
119
|
-
|
|
120
|
-
const outputPath = path.join(__dirname, "..", "redirects.json")
|
|
121
|
-
fs.writeFileSync(outputPath, JSON.stringify(allRedirects, null, 2))
|
|
122
|
-
|
|
123
|
-
console.log(`✅ Generated ${redirects.length} frontmatter redirects`)
|
|
124
|
-
console.log(`✅ Generated ${folderRedirects.length} folder redirects`)
|
|
125
|
-
console.log(`📝 Saved to: ${outputPath}`)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
main().catch(console.error)
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import fs from "fs"
|
|
2
|
-
import path from "path"
|
|
3
|
-
import { fileURLToPath } from "url"
|
|
4
|
-
|
|
5
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
6
|
-
const __dirname = path.dirname(__filename)
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Generate an HTML redirect page
|
|
10
|
-
*/
|
|
11
|
-
function generateRedirectHTML(destination, basePath = "") {
|
|
12
|
-
const fullDestination = basePath + destination
|
|
13
|
-
|
|
14
|
-
return `<!DOCTYPE html>
|
|
15
|
-
<html lang="en">
|
|
16
|
-
<head>
|
|
17
|
-
<meta charset="UTF-8">
|
|
18
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
-
<meta http-equiv="refresh" content="0;url=${fullDestination}">
|
|
20
|
-
<link rel="canonical" href="${fullDestination}">
|
|
21
|
-
<title>Redirecting...</title>
|
|
22
|
-
<script>
|
|
23
|
-
window.location.href = "${fullDestination}";
|
|
24
|
-
</script>
|
|
25
|
-
</head>
|
|
26
|
-
<body>
|
|
27
|
-
<p>Redirecting to <a href="${fullDestination}">${fullDestination}</a>...</p>
|
|
28
|
-
</body>
|
|
29
|
-
</html>`
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Create redirect HTML files in the out directory
|
|
34
|
-
*/
|
|
35
|
-
async function createStaticRedirects() {
|
|
36
|
-
const outDir = path.join(__dirname, "..", "out")
|
|
37
|
-
const redirectsPath = path.join(__dirname, "..", "redirects.json")
|
|
38
|
-
|
|
39
|
-
// Load basePath from specra.config.json
|
|
40
|
-
let basePath = ""
|
|
41
|
-
try {
|
|
42
|
-
const configPath = path.join(__dirname, "..", "specra.config.json")
|
|
43
|
-
if (fs.existsSync(configPath)) {
|
|
44
|
-
const config = JSON.parse(fs.readFileSync(configPath, "utf8"))
|
|
45
|
-
const deployment = config.deployment || {}
|
|
46
|
-
|
|
47
|
-
// Only apply basePath for GitHub Pages without custom domain
|
|
48
|
-
if (deployment.target === "github-pages" && !deployment.customDomain && deployment.basePath) {
|
|
49
|
-
basePath = deployment.basePath.startsWith("/")
|
|
50
|
-
? deployment.basePath
|
|
51
|
-
: `/${deployment.basePath}`
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
} catch (error) {
|
|
55
|
-
console.warn("Could not load deployment config:", error.message)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Check if out directory exists
|
|
59
|
-
if (!fs.existsSync(outDir)) {
|
|
60
|
-
console.warn("⚠️ Output directory 'out' not found. Run 'npm run build:export' first.")
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Load redirects
|
|
65
|
-
let redirects = []
|
|
66
|
-
try {
|
|
67
|
-
if (fs.existsSync(redirectsPath)) {
|
|
68
|
-
redirects = JSON.parse(fs.readFileSync(redirectsPath, "utf8"))
|
|
69
|
-
}
|
|
70
|
-
} catch (error) {
|
|
71
|
-
console.error("Could not load redirects.json:", error.message)
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
let created = 0
|
|
76
|
-
|
|
77
|
-
for (const redirect of redirects) {
|
|
78
|
-
const { source, destination } = redirect
|
|
79
|
-
|
|
80
|
-
// Remove basePath from source if present (for file system path)
|
|
81
|
-
let sourcePath = source
|
|
82
|
-
if (basePath && sourcePath.startsWith(basePath)) {
|
|
83
|
-
sourcePath = sourcePath.substring(basePath.length)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Ensure source starts with /
|
|
87
|
-
if (!sourcePath.startsWith('/')) {
|
|
88
|
-
sourcePath = '/' + sourcePath
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Convert URL path to file system path
|
|
92
|
-
const targetDir = path.join(outDir, sourcePath.substring(1))
|
|
93
|
-
const indexPath = path.join(targetDir, "index.html")
|
|
94
|
-
|
|
95
|
-
// Create directory if it doesn't exist
|
|
96
|
-
if (!fs.existsSync(targetDir)) {
|
|
97
|
-
fs.mkdirSync(targetDir, { recursive: true })
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Don't overwrite if index.html already exists (real content page)
|
|
101
|
-
if (!fs.existsSync(indexPath)) {
|
|
102
|
-
// Generate and write redirect HTML
|
|
103
|
-
const html = generateRedirectHTML(destination, basePath)
|
|
104
|
-
fs.writeFileSync(indexPath, html)
|
|
105
|
-
created++
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
console.log(`✅ Created ${created} static redirect HTML files`)
|
|
110
|
-
if (basePath) {
|
|
111
|
-
console.log(`🔗 Using basePath: ${basePath}`)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
createStaticRedirects().catch(console.error)
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { MeiliSearch } from "meilisearch"
|
|
2
|
-
import fs from "fs"
|
|
3
|
-
import path from "path"
|
|
4
|
-
import matter from "gray-matter"
|
|
5
|
-
import { extractSearchText, getConfig, initConfig } from "specra"
|
|
6
|
-
import specraConfig from "../specra.config.json"
|
|
7
|
-
// import { extractSearchText } from "specra/components"
|
|
8
|
-
// import { extractSearchText } from "@/components/docs/componentTextProps"
|
|
9
|
-
|
|
10
|
-
interface SearchDocument {
|
|
11
|
-
id: string
|
|
12
|
-
title: string
|
|
13
|
-
content: string
|
|
14
|
-
slug: string
|
|
15
|
-
version: string
|
|
16
|
-
category?: string
|
|
17
|
-
tags?: string[]
|
|
18
|
-
tab_group?: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
async function indexDocuments() {
|
|
23
|
-
initConfig(specraConfig as any)
|
|
24
|
-
const config = getConfig()
|
|
25
|
-
console.log(config)
|
|
26
|
-
const searchConfig = config.search
|
|
27
|
-
|
|
28
|
-
if (!searchConfig?.enabled || searchConfig.provider !== "meilisearch") {
|
|
29
|
-
console.error("Meilisearch is not enabled in config")
|
|
30
|
-
process.exit(1)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const meilisearchConfig = searchConfig.meilisearch
|
|
34
|
-
if (!meilisearchConfig) {
|
|
35
|
-
console.error("Meilisearch configuration is missing")
|
|
36
|
-
process.exit(1)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
console.log("Connecting to Meilisearch at:", meilisearchConfig.host)
|
|
40
|
-
|
|
41
|
-
const client = new MeiliSearch({
|
|
42
|
-
host: meilisearchConfig.host,
|
|
43
|
-
apiKey: meilisearchConfig.apiKey || "",
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const index = client.index(meilisearchConfig.indexName)
|
|
47
|
-
|
|
48
|
-
// Get all MDX files
|
|
49
|
-
const docsDir = path.join(process.cwd(), "docs")
|
|
50
|
-
const documents: SearchDocument[] = []
|
|
51
|
-
|
|
52
|
-
function processDirectory(dir: string, version: string) {
|
|
53
|
-
const files = fs.readdirSync(dir)
|
|
54
|
-
|
|
55
|
-
for (const file of files) {
|
|
56
|
-
const filePath = path.join(dir, file)
|
|
57
|
-
const stat = fs.statSync(filePath)
|
|
58
|
-
|
|
59
|
-
if (stat.isDirectory()) {
|
|
60
|
-
processDirectory(filePath, version)
|
|
61
|
-
} else if (file.endsWith(".mdx") || file.endsWith(".md")) {
|
|
62
|
-
const content = fs.readFileSync(filePath, "utf-8")
|
|
63
|
-
const { data, content: mdxContent } = matter(content)
|
|
64
|
-
|
|
65
|
-
// Generate slug from file path
|
|
66
|
-
const relativePath = path.relative(path.join(docsDir, version), filePath)
|
|
67
|
-
const slug = relativePath
|
|
68
|
-
.replace(/\.(mdx|md)$/, "")
|
|
69
|
-
.replace(/\\/g, "/")
|
|
70
|
-
|
|
71
|
-
// Extract category from path
|
|
72
|
-
const pathParts = slug.split("/")
|
|
73
|
-
const category = pathParts.length > 1 ? pathParts[0] : undefined
|
|
74
|
-
|
|
75
|
-
// Get tab_group from frontmatter or from parent _category_.json
|
|
76
|
-
let tabGroup = data.tab_group
|
|
77
|
-
|
|
78
|
-
// If not in frontmatter, check parent directory's _category_.json
|
|
79
|
-
if (!tabGroup && pathParts.length > 1) {
|
|
80
|
-
const folderPath = pathParts.slice(0, -1).join("/")
|
|
81
|
-
const categoryPath = path.join(docsDir, version, folderPath, "_category_.json")
|
|
82
|
-
|
|
83
|
-
if (fs.existsSync(categoryPath)) {
|
|
84
|
-
try {
|
|
85
|
-
const categoryData = JSON.parse(fs.readFileSync(categoryPath, "utf-8"))
|
|
86
|
-
tabGroup = categoryData.tab_group
|
|
87
|
-
} catch (e) {
|
|
88
|
-
// Ignore JSON parse errors
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Clean content (remove code blocks and special chars for better search)
|
|
94
|
-
// const cleanContent = mdxContent
|
|
95
|
-
// .replace(/```[\s\S]*?```/g, "") // Remove code blocks
|
|
96
|
-
// .replace(/`[^`]+`/g, "") // Remove inline code
|
|
97
|
-
// .replace(/[#*_~]/g, "") // Remove markdown symbols
|
|
98
|
-
// .replace(/\n+/g, " ") // Replace newlines with spaces
|
|
99
|
-
// .trim()
|
|
100
|
-
// .slice(0, 1000) // Limit content length
|
|
101
|
-
|
|
102
|
-
const cleanContent = extractSearchText(mdxContent);
|
|
103
|
-
|
|
104
|
-
// console.log("------");
|
|
105
|
-
// console.log("Cleaned content: ");
|
|
106
|
-
// console.log(cleanContent);
|
|
107
|
-
// console.log("------");
|
|
108
|
-
// Create a valid document ID (replace periods with underscores)
|
|
109
|
-
// const docId = `${version.replace(/\./g, "_")}-${slug.replace(/\//g, "-")}`
|
|
110
|
-
const docId = slug.replace(/\//g, "-")
|
|
111
|
-
|
|
112
|
-
documents.push({
|
|
113
|
-
id: docId,
|
|
114
|
-
title: data.title || slug,
|
|
115
|
-
content: cleanContent,
|
|
116
|
-
slug: slug,
|
|
117
|
-
version: version,
|
|
118
|
-
category: category,
|
|
119
|
-
tags: data.tags || [],
|
|
120
|
-
tab_group: tabGroup,
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Process all version directories
|
|
127
|
-
const versions = fs.readdirSync(docsDir).filter((item) => {
|
|
128
|
-
const itemPath = path.join(docsDir, item)
|
|
129
|
-
return fs.statSync(itemPath).isDirectory()
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
console.log(`Found ${versions.length} version(s):`, versions.join(", "))
|
|
133
|
-
|
|
134
|
-
for (const version of versions) {
|
|
135
|
-
const versionPath = path.join(docsDir, version)
|
|
136
|
-
processDirectory(versionPath, version)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
console.log(`Indexing ${documents.length} documents...`)
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
// Configure searchable attributes first
|
|
143
|
-
console.log("Configuring search settings...")
|
|
144
|
-
await index.updateSearchableAttributes(["title", "content", "tags"])
|
|
145
|
-
await index.updateFilterableAttributes(["version", "category", "tags"])
|
|
146
|
-
await index.updateSortableAttributes(["title"])
|
|
147
|
-
await index.updateDistinctAttribute("id")
|
|
148
|
-
await index.updateSettings({
|
|
149
|
-
rankingRules: [
|
|
150
|
-
"words",
|
|
151
|
-
"typo",
|
|
152
|
-
"proximity",
|
|
153
|
-
"attribute",
|
|
154
|
-
"sort",
|
|
155
|
-
"exactness"
|
|
156
|
-
]
|
|
157
|
-
})
|
|
158
|
-
console.log("✅ Search configuration updated!")
|
|
159
|
-
|
|
160
|
-
// Delete existing documents
|
|
161
|
-
console.log("Clearing old documents...")
|
|
162
|
-
const deleteTask = await index.deleteAllDocuments()
|
|
163
|
-
console.log("Delete task created:", deleteTask.taskUid)
|
|
164
|
-
|
|
165
|
-
// Add new documents
|
|
166
|
-
console.log("Adding documents...")
|
|
167
|
-
const addTask = await index.addDocuments(documents, { primaryKey: "id" })
|
|
168
|
-
console.log("Add task created:", addTask.taskUid)
|
|
169
|
-
console.log("✅ Documents sent for indexing!")
|
|
170
|
-
|
|
171
|
-
console.log("\n⏳ Indexing is processing in the background...")
|
|
172
|
-
console.log("Wait a few seconds, then run 'npm run test:search' to verify!")
|
|
173
|
-
} catch (error) {
|
|
174
|
-
console.error("Error indexing documents:", error)
|
|
175
|
-
if (error instanceof Error) {
|
|
176
|
-
console.error("Error message:", error.message)
|
|
177
|
-
}
|
|
178
|
-
process.exit(1)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
indexDocuments()
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { MeiliSearch } from "meilisearch"
|
|
2
|
-
import { getConfig } from "specra"
|
|
3
|
-
|
|
4
|
-
async function testSearch() {
|
|
5
|
-
const config = getConfig()
|
|
6
|
-
const searchConfig = config.search
|
|
7
|
-
|
|
8
|
-
if (!searchConfig?.enabled || searchConfig.provider !== "meilisearch") {
|
|
9
|
-
console.error("Meilisearch is not enabled in config")
|
|
10
|
-
process.exit(1)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const meilisearchConfig = searchConfig.meilisearch
|
|
14
|
-
if (!meilisearchConfig) {
|
|
15
|
-
console.error("Meilisearch configuration is missing")
|
|
16
|
-
process.exit(1)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
console.log("Connecting to Meilisearch at:", meilisearchConfig.host)
|
|
20
|
-
console.log("Using API Key:", meilisearchConfig.apiKey ? "Yes" : "No")
|
|
21
|
-
|
|
22
|
-
const client = new MeiliSearch({
|
|
23
|
-
host: meilisearchConfig.host,
|
|
24
|
-
apiKey: meilisearchConfig.apiKey || "",
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
const index = client.index(meilisearchConfig.indexName)
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
// Get index stats
|
|
31
|
-
console.log("\n📊 Index Stats:")
|
|
32
|
-
const stats = await index.getStats()
|
|
33
|
-
console.log("- Number of documents:", stats.numberOfDocuments)
|
|
34
|
-
console.log("- Is indexing:", stats.isIndexing)
|
|
35
|
-
console.log("- Field distribution:", JSON.stringify(stats.fieldDistribution, null, 2))
|
|
36
|
-
|
|
37
|
-
// Get some documents
|
|
38
|
-
console.log("\n📄 Sample Documents:")
|
|
39
|
-
const documents = await index.getDocuments({ limit: 3 })
|
|
40
|
-
console.log("Total documents:", documents.results.length)
|
|
41
|
-
documents.results.forEach((doc: any, i: number) => {
|
|
42
|
-
console.log(`\nDocument ${i + 1}:`)
|
|
43
|
-
console.log("- ID:", doc.id)
|
|
44
|
-
console.log("- Title:", doc.title)
|
|
45
|
-
console.log("- Version:", doc.version)
|
|
46
|
-
console.log("- Slug:", doc.slug)
|
|
47
|
-
console.log("- Category:", doc.category)
|
|
48
|
-
console.log("- Content preview:", doc.content?.substring(0, 100) + "...")
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
// Test search
|
|
52
|
-
console.log("\n🔍 Testing Search:")
|
|
53
|
-
const searchQueries = ["documentation", "getting", "guide", "intro"]
|
|
54
|
-
|
|
55
|
-
for (const query of searchQueries) {
|
|
56
|
-
const results = await index.search(query, {
|
|
57
|
-
limit: 5,
|
|
58
|
-
})
|
|
59
|
-
console.log(`\nQuery: "${query}"`)
|
|
60
|
-
console.log(`- Results found: ${results.hits.length}`)
|
|
61
|
-
console.log(`- Processing time: ${results.processingTimeMs}ms`)
|
|
62
|
-
if (results.hits.length > 0) {
|
|
63
|
-
console.log("- First result:", (results.hits[0] as any).title)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Get searchable attributes
|
|
68
|
-
console.log("\n⚙️ Index Settings:")
|
|
69
|
-
const searchableAttrs = await index.getSearchableAttributes()
|
|
70
|
-
console.log("- Searchable attributes:", searchableAttrs)
|
|
71
|
-
|
|
72
|
-
const filterableAttrs = await index.getFilterableAttributes()
|
|
73
|
-
console.log("- Filterable attributes:", filterableAttrs)
|
|
74
|
-
|
|
75
|
-
} catch (error) {
|
|
76
|
-
console.error("\n❌ Error:", error)
|
|
77
|
-
if (error instanceof Error) {
|
|
78
|
-
console.error("Message:", error.message)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
testSearch()
|