create-specra 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.
- package/LICENSE.MD +21 -0
- package/README.md +137 -0
- package/package.json +42 -0
- package/templates/minimal/README.md +132 -0
- package/templates/minimal/app/api/mdx-watch/route.ts +80 -0
- package/templates/minimal/app/docs/[version]/[...slug]/loading.tsx +7 -0
- package/templates/minimal/app/docs/[version]/[...slug]/page.tsx +212 -0
- package/templates/minimal/app/docs/[version]/not-found.tsx +10 -0
- package/templates/minimal/app/docs/[version]/page.tsx +27 -0
- package/templates/minimal/app/globals.css +1 -0
- package/templates/minimal/app/layout.tsx +89 -0
- package/templates/minimal/app/page.tsx +185 -0
- package/templates/minimal/docs/v1.0.0/about.mdx +57 -0
- package/templates/minimal/docs/v1.0.0/components/_category_.json +8 -0
- package/templates/minimal/docs/v1.0.0/components/callout.mdx +83 -0
- package/templates/minimal/docs/v1.0.0/components/code-block.mdx +103 -0
- package/templates/minimal/docs/v1.0.0/components/index.mdx +8 -0
- package/templates/minimal/docs/v1.0.0/components/tabs.mdx +92 -0
- package/templates/minimal/docs/v1.0.0/configuration.mdx +322 -0
- package/templates/minimal/docs/v1.0.0/features.mdx +197 -0
- package/templates/minimal/docs/v1.0.0/getting-started.mdx +183 -0
- package/templates/minimal/docs/v1.0.0/index.mdx +29 -0
- package/templates/minimal/middleware.ts +23 -0
- package/templates/minimal/next.config.default.mjs +36 -0
- package/templates/minimal/next.config.export.mjs +62 -0
- package/templates/minimal/next.config.mjs +18 -0
- package/templates/minimal/package-lock.json +7338 -0
- package/templates/minimal/package.json +32 -0
- package/templates/minimal/postcss.config.mjs +8 -0
- package/templates/minimal/public/api-specs/openapi-example.json +259 -0
- package/templates/minimal/public/api-specs/postman-example.json +205 -0
- package/templates/minimal/public/api-specs/test-api.json +256 -0
- package/templates/minimal/public/api-specs/users-api.json +264 -0
- package/templates/minimal/scripts/generate-redirects.mjs +88 -0
- package/templates/minimal/scripts/index-search.ts +159 -0
- package/templates/minimal/scripts/test-search.ts +83 -0
- package/templates/minimal/specra.config.json +124 -0
- package/templates/minimal/tsconfig.json +41 -0
- package/templates/minimal/yarn.lock +3909 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { MeiliSearch } from "meilisearch"
|
|
2
|
+
import fs from "fs"
|
|
3
|
+
import path from "path"
|
|
4
|
+
import matter from "gray-matter"
|
|
5
|
+
import { getConfig } from "specra"
|
|
6
|
+
import { extractSearchText } from "specra/components"
|
|
7
|
+
// import { extractSearchText } from "@/components/docs/componentTextProps"
|
|
8
|
+
|
|
9
|
+
interface SearchDocument {
|
|
10
|
+
id: string
|
|
11
|
+
title: string
|
|
12
|
+
content: string
|
|
13
|
+
slug: string
|
|
14
|
+
version: string
|
|
15
|
+
category?: string
|
|
16
|
+
tags?: string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async function indexDocuments() {
|
|
21
|
+
const config = getConfig()
|
|
22
|
+
const searchConfig = config.search
|
|
23
|
+
|
|
24
|
+
if (!searchConfig?.enabled || searchConfig.provider !== "meilisearch") {
|
|
25
|
+
console.error("Meilisearch is not enabled in config")
|
|
26
|
+
process.exit(1)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const meilisearchConfig = searchConfig.meilisearch
|
|
30
|
+
if (!meilisearchConfig) {
|
|
31
|
+
console.error("Meilisearch configuration is missing")
|
|
32
|
+
process.exit(1)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log("Connecting to Meilisearch at:", meilisearchConfig.host)
|
|
36
|
+
|
|
37
|
+
const client = new MeiliSearch({
|
|
38
|
+
host: meilisearchConfig.host,
|
|
39
|
+
apiKey: meilisearchConfig.apiKey || "",
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const index = client.index(meilisearchConfig.indexName)
|
|
43
|
+
|
|
44
|
+
// Get all MDX files
|
|
45
|
+
const docsDir = path.join(process.cwd(), "docs")
|
|
46
|
+
const documents: SearchDocument[] = []
|
|
47
|
+
|
|
48
|
+
function processDirectory(dir: string, version: string) {
|
|
49
|
+
const files = fs.readdirSync(dir)
|
|
50
|
+
|
|
51
|
+
for (const file of files) {
|
|
52
|
+
const filePath = path.join(dir, file)
|
|
53
|
+
const stat = fs.statSync(filePath)
|
|
54
|
+
|
|
55
|
+
if (stat.isDirectory()) {
|
|
56
|
+
processDirectory(filePath, version)
|
|
57
|
+
} else if (file.endsWith(".mdx") || file.endsWith(".md")) {
|
|
58
|
+
const content = fs.readFileSync(filePath, "utf-8")
|
|
59
|
+
const { data, content: mdxContent } = matter(content)
|
|
60
|
+
|
|
61
|
+
// Generate slug from file path
|
|
62
|
+
const relativePath = path.relative(path.join(docsDir, version), filePath)
|
|
63
|
+
const slug = relativePath
|
|
64
|
+
.replace(/\.(mdx|md)$/, "")
|
|
65
|
+
.replace(/\\/g, "/")
|
|
66
|
+
|
|
67
|
+
// Extract category from path
|
|
68
|
+
const pathParts = slug.split("/")
|
|
69
|
+
const category = pathParts.length > 1 ? pathParts[0] : undefined
|
|
70
|
+
|
|
71
|
+
// Clean content (remove code blocks and special chars for better search)
|
|
72
|
+
// const cleanContent = mdxContent
|
|
73
|
+
// .replace(/```[\s\S]*?```/g, "") // Remove code blocks
|
|
74
|
+
// .replace(/`[^`]+`/g, "") // Remove inline code
|
|
75
|
+
// .replace(/[#*_~]/g, "") // Remove markdown symbols
|
|
76
|
+
// .replace(/\n+/g, " ") // Replace newlines with spaces
|
|
77
|
+
// .trim()
|
|
78
|
+
// .slice(0, 1000) // Limit content length
|
|
79
|
+
|
|
80
|
+
const cleanContent = extractSearchText(mdxContent);
|
|
81
|
+
|
|
82
|
+
// console.log("------");
|
|
83
|
+
// console.log("Cleaned content: ");
|
|
84
|
+
// console.log(cleanContent);
|
|
85
|
+
// console.log("------");
|
|
86
|
+
// Create a valid document ID (replace periods with underscores)
|
|
87
|
+
// const docId = `${version.replace(/\./g, "_")}-${slug.replace(/\//g, "-")}`
|
|
88
|
+
const docId = slug.replace(/\//g, "-")
|
|
89
|
+
|
|
90
|
+
documents.push({
|
|
91
|
+
id: docId,
|
|
92
|
+
title: data.title || slug,
|
|
93
|
+
content: cleanContent,
|
|
94
|
+
slug: slug,
|
|
95
|
+
version: version,
|
|
96
|
+
category: category,
|
|
97
|
+
tags: data.tags || [],
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Process all version directories
|
|
104
|
+
const versions = fs.readdirSync(docsDir).filter((item) => {
|
|
105
|
+
const itemPath = path.join(docsDir, item)
|
|
106
|
+
return fs.statSync(itemPath).isDirectory()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
console.log(`Found ${versions.length} version(s):`, versions.join(", "))
|
|
110
|
+
|
|
111
|
+
for (const version of versions) {
|
|
112
|
+
const versionPath = path.join(docsDir, version)
|
|
113
|
+
processDirectory(versionPath, version)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log(`Indexing ${documents.length} documents...`)
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
// Configure searchable attributes first
|
|
120
|
+
console.log("Configuring search settings...")
|
|
121
|
+
await index.updateSearchableAttributes(["title", "content", "tags"])
|
|
122
|
+
await index.updateFilterableAttributes(["version", "category", "tags"])
|
|
123
|
+
await index.updateSortableAttributes(["title"])
|
|
124
|
+
await index.updateDistinctAttribute("id")
|
|
125
|
+
await index.updateSettings({
|
|
126
|
+
rankingRules: [
|
|
127
|
+
"words",
|
|
128
|
+
"typo",
|
|
129
|
+
"proximity",
|
|
130
|
+
"attribute",
|
|
131
|
+
"sort",
|
|
132
|
+
"exactness"
|
|
133
|
+
]
|
|
134
|
+
})
|
|
135
|
+
console.log("✅ Search configuration updated!")
|
|
136
|
+
|
|
137
|
+
// Delete existing documents
|
|
138
|
+
console.log("Clearing old documents...")
|
|
139
|
+
const deleteTask = await index.deleteAllDocuments()
|
|
140
|
+
console.log("Delete task created:", deleteTask.taskUid)
|
|
141
|
+
|
|
142
|
+
// Add new documents
|
|
143
|
+
console.log("Adding documents...")
|
|
144
|
+
const addTask = await index.addDocuments(documents, { primaryKey: "id" })
|
|
145
|
+
console.log("Add task created:", addTask.taskUid)
|
|
146
|
+
console.log("✅ Documents sent for indexing!")
|
|
147
|
+
|
|
148
|
+
console.log("\n⏳ Indexing is processing in the background...")
|
|
149
|
+
console.log("Wait a few seconds, then run 'npm run test:search' to verify!")
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error("Error indexing documents:", error)
|
|
152
|
+
if (error instanceof Error) {
|
|
153
|
+
console.error("Error message:", error.message)
|
|
154
|
+
}
|
|
155
|
+
process.exit(1)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
indexDocuments()
|
|
@@ -0,0 +1,83 @@
|
|
|
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()
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./lib/config.types.ts",
|
|
3
|
+
"site": {
|
|
4
|
+
"title": "Documentation Library",
|
|
5
|
+
"description": "Comprehensive documentation for your project",
|
|
6
|
+
"url": "https://vercel.com/dalmasontos-projects/v0-documentation-library",
|
|
7
|
+
"baseUrl": "/",
|
|
8
|
+
"language": "en",
|
|
9
|
+
"organizationName": "Your Organization",
|
|
10
|
+
"projectName": "v0-documentation-library",
|
|
11
|
+
"activeVersion": "v1.0.0",
|
|
12
|
+
"favicon": "/icon-light-32x32.png"
|
|
13
|
+
},
|
|
14
|
+
"theme": {
|
|
15
|
+
"defaultMode": "system",
|
|
16
|
+
"respectPrefersColorScheme": true
|
|
17
|
+
},
|
|
18
|
+
"navigation": {
|
|
19
|
+
"showSidebar": true,
|
|
20
|
+
"collapsibleSidebar": true,
|
|
21
|
+
"showBreadcrumbs": true,
|
|
22
|
+
"showTableOfContents": true,
|
|
23
|
+
"tocPosition": "right",
|
|
24
|
+
"tocMaxDepth": 3,
|
|
25
|
+
"tabGroups": [
|
|
26
|
+
{
|
|
27
|
+
"id": "guides",
|
|
28
|
+
"label": "Guides",
|
|
29
|
+
"icon": "book-open"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "api",
|
|
33
|
+
"label": "API Reference",
|
|
34
|
+
"icon": "zap"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "components",
|
|
38
|
+
"label": "Components",
|
|
39
|
+
"icon": "layers"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"social": {
|
|
44
|
+
"github": "https://github.com/dalmasonto",
|
|
45
|
+
"twitter": "https://twitter.com/dalmasonto",
|
|
46
|
+
"discord": "https://discord.com/invite/dalmasonto",
|
|
47
|
+
"custom": [
|
|
48
|
+
{
|
|
49
|
+
"label": "Website",
|
|
50
|
+
"href": "https://craftfolio.com/dalmasonto"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"search": {
|
|
55
|
+
"enabled": false,
|
|
56
|
+
"placeholder": "Search documentation...",
|
|
57
|
+
"provider": "meilisearch",
|
|
58
|
+
"meilisearch": {
|
|
59
|
+
"host": "http://localhost:7700",
|
|
60
|
+
"apiKey": "aSampleMasterKey",
|
|
61
|
+
"indexName": "docs"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"analytics": {
|
|
65
|
+
"googleAnalytics": "",
|
|
66
|
+
"plausible": ""
|
|
67
|
+
},
|
|
68
|
+
"footer": {
|
|
69
|
+
"copyright": "Copyright © 2024 Specra. All rights reserved.",
|
|
70
|
+
"links": [
|
|
71
|
+
{
|
|
72
|
+
"title": "Documentation",
|
|
73
|
+
"items": [
|
|
74
|
+
{
|
|
75
|
+
"label": "Getting Started",
|
|
76
|
+
"href": "/docs/v1.0.0/getting-started"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"label": "API Reference",
|
|
80
|
+
"href": "/docs/v1.0.0/api"
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"title": "Community",
|
|
86
|
+
"items": [
|
|
87
|
+
{
|
|
88
|
+
"label": "GitHub",
|
|
89
|
+
"href": "https://github.com/yourusername/your-repo"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"label": "Discord",
|
|
93
|
+
"href": "#"
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
"banner": {
|
|
100
|
+
"enabled": false,
|
|
101
|
+
"message": "🎉 This is a development version. Some features may not work as expected.",
|
|
102
|
+
"type": "error",
|
|
103
|
+
"dismissible": true
|
|
104
|
+
},
|
|
105
|
+
"features": {
|
|
106
|
+
"editUrl": "https://github.com/yourusername/your-repo/edit/main/docs",
|
|
107
|
+
"showLastUpdated": true,
|
|
108
|
+
"showReadingTime": true,
|
|
109
|
+
"showAuthors": false,
|
|
110
|
+
"showTags": true,
|
|
111
|
+
"versioning": true,
|
|
112
|
+
"i18n": false
|
|
113
|
+
},
|
|
114
|
+
"env": {
|
|
115
|
+
"API_BASE_URL": "https://api.example.com",
|
|
116
|
+
"API_VERSION": "v1",
|
|
117
|
+
"CDN_URL": "https://cdn.example.com"
|
|
118
|
+
},
|
|
119
|
+
"deployment": {
|
|
120
|
+
"target": "github-pages",
|
|
121
|
+
"basePath": "",
|
|
122
|
+
"customDomain": false
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": [
|
|
4
|
+
"dom",
|
|
5
|
+
"dom.iterable",
|
|
6
|
+
"esnext"
|
|
7
|
+
],
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"target": "ES6",
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"module": "esnext",
|
|
15
|
+
"moduleResolution": "bundler",
|
|
16
|
+
"resolveJsonModule": true,
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"jsx": "react-jsx",
|
|
19
|
+
"incremental": true,
|
|
20
|
+
"plugins": [
|
|
21
|
+
{
|
|
22
|
+
"name": "next"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"paths": {
|
|
26
|
+
"@/*": [
|
|
27
|
+
"./*"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"include": [
|
|
32
|
+
"next-env.d.ts",
|
|
33
|
+
"**/*.ts",
|
|
34
|
+
"**/*.tsx",
|
|
35
|
+
".next/types/**/*.ts",
|
|
36
|
+
".next/dev/types/**/*.ts"
|
|
37
|
+
],
|
|
38
|
+
"exclude": [
|
|
39
|
+
"node_modules"
|
|
40
|
+
]
|
|
41
|
+
}
|