@eclipsa/content 0.0.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/.turbo/turbo-build.log +34 -0
- package/.turbo/turbo-test.log +13 -0
- package/.turbo/turbo-typecheck.log +1 -0
- package/dist/internal-h0upzIHm.mjs +644 -0
- package/dist/internal-h0upzIHm.mjs.map +1 -0
- package/dist/internal.d.mts +47 -0
- package/dist/internal.mjs +2 -0
- package/dist/mod-P8gKoDsz.d.mts +151 -0
- package/dist/mod.d.mts +2 -0
- package/dist/mod.mjs +34 -0
- package/dist/mod.mjs.map +1 -0
- package/dist/package.json +40 -0
- package/dist/types-rZ-wc23p.mjs +6 -0
- package/dist/types-rZ-wc23p.mjs.map +1 -0
- package/dist/virtual-runtime.d.ts +24 -0
- package/dist/vite.d.mts +7 -0
- package/dist/vite.mjs +195 -0
- package/dist/vite.mjs.map +1 -0
- package/highlight.ts +125 -0
- package/internal.test.ts +263 -0
- package/internal.ts +514 -0
- package/mod.ts +124 -0
- package/package.json +62 -0
- package/search.test.ts +56 -0
- package/search.ts +450 -0
- package/typecheck.ts +103 -0
- package/types.ts +172 -0
- package/virtual-runtime.d.ts +24 -0
- package/vite-config.test.ts +15 -0
- package/vite.config.ts +16 -0
- package/vite.test.ts +283 -0
- package/vite.ts +276 -0
package/types.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { InferStandardSchemaOutput, StandardSchemaV1 } from 'eclipsa'
|
|
2
|
+
|
|
3
|
+
export const CONTENT_COLLECTION_MARKER = '__eclipsa_content_collection__'
|
|
4
|
+
|
|
5
|
+
export interface ContentSourceEntry {
|
|
6
|
+
body: string
|
|
7
|
+
data?: Record<string, unknown>
|
|
8
|
+
filePath?: string
|
|
9
|
+
id?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ContentLoaderContext {
|
|
13
|
+
collection: string
|
|
14
|
+
configPath: string
|
|
15
|
+
root: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ContentLoaderObject {
|
|
19
|
+
load(
|
|
20
|
+
context: ContentLoaderContext,
|
|
21
|
+
): ContentSourceEntry[] | Promise<ContentSourceEntry[]> | readonly ContentSourceEntry[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ContentHighlightOptions {
|
|
25
|
+
theme?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ContentMarkdownOptions {
|
|
29
|
+
highlight?: boolean | ContentHighlightOptions
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ContentSearchOptions {
|
|
33
|
+
enabled?: boolean
|
|
34
|
+
hotkey?: string
|
|
35
|
+
limit?: number
|
|
36
|
+
placeholder?: string
|
|
37
|
+
prefix?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ResolvedContentSearchOptions {
|
|
41
|
+
enabled: boolean
|
|
42
|
+
hotkey: string
|
|
43
|
+
limit: number
|
|
44
|
+
placeholder: string
|
|
45
|
+
prefix: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type ContentSearchField = 'body' | 'code' | 'heading' | 'title'
|
|
49
|
+
|
|
50
|
+
export interface ContentSearchDocument {
|
|
51
|
+
body: string
|
|
52
|
+
code: string[]
|
|
53
|
+
collection: string
|
|
54
|
+
headings: string[]
|
|
55
|
+
id: string
|
|
56
|
+
title: string
|
|
57
|
+
url: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ContentSearchPosting {
|
|
61
|
+
docIdx: number
|
|
62
|
+
field: ContentSearchField
|
|
63
|
+
tf: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ContentSearchIndex {
|
|
67
|
+
avgDl: number
|
|
68
|
+
df: Record<string, number>
|
|
69
|
+
docCount: number
|
|
70
|
+
documents: ContentSearchDocument[]
|
|
71
|
+
index: Record<string, ContentSearchPosting[]>
|
|
72
|
+
options: ResolvedContentSearchOptions
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ContentSearchQueryOptions {
|
|
76
|
+
limit?: number
|
|
77
|
+
prefix?: boolean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ContentSearchResult {
|
|
81
|
+
collection: string
|
|
82
|
+
id: string
|
|
83
|
+
matches: string[]
|
|
84
|
+
score: number
|
|
85
|
+
snippet: string
|
|
86
|
+
title: string
|
|
87
|
+
url: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface GlobLoaderOptions {
|
|
91
|
+
base: string
|
|
92
|
+
pattern: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface GlobLoader {
|
|
96
|
+
readonly base: string
|
|
97
|
+
readonly kind: 'glob'
|
|
98
|
+
readonly pattern: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type ContentLoader = GlobLoader | ContentLoaderObject
|
|
102
|
+
|
|
103
|
+
export interface ContentCollectionDefinition<
|
|
104
|
+
Schema extends StandardSchemaV1<any, any> | undefined,
|
|
105
|
+
> {
|
|
106
|
+
loader: ContentLoader
|
|
107
|
+
markdown?: ContentMarkdownOptions
|
|
108
|
+
search?: boolean | ContentSearchOptions
|
|
109
|
+
schema?: Schema
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface DefinedCollection<
|
|
113
|
+
Schema extends StandardSchemaV1<any, any> | undefined = StandardSchemaV1<any, any> | undefined,
|
|
114
|
+
> extends ContentCollectionDefinition<Schema> {
|
|
115
|
+
readonly [CONTENT_COLLECTION_MARKER]: true
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type AnyCollection = DefinedCollection<StandardSchemaV1<any, any> | undefined>
|
|
119
|
+
|
|
120
|
+
export type InferCollectionData<Collection extends AnyCollection> =
|
|
121
|
+
Collection extends DefinedCollection<infer Schema>
|
|
122
|
+
? Schema extends StandardSchemaV1<any, any>
|
|
123
|
+
? InferStandardSchemaOutput<Schema>
|
|
124
|
+
: Record<string, unknown>
|
|
125
|
+
: Record<string, unknown>
|
|
126
|
+
|
|
127
|
+
export interface BaseContentEntry<
|
|
128
|
+
Data = Record<string, unknown>,
|
|
129
|
+
Collection extends string = string,
|
|
130
|
+
> {
|
|
131
|
+
body: string
|
|
132
|
+
collection: Collection
|
|
133
|
+
data: Data
|
|
134
|
+
filePath: string
|
|
135
|
+
id: string
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface ContentHeading {
|
|
139
|
+
depth: number
|
|
140
|
+
slug: string
|
|
141
|
+
text: string
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface ContentComponentProps extends Record<string, unknown> {
|
|
145
|
+
as?: string
|
|
146
|
+
html: string
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface RenderedContent {
|
|
150
|
+
Content: (props?: Omit<ContentComponentProps, 'html'>) => any
|
|
151
|
+
headings: ContentHeading[]
|
|
152
|
+
html: string
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export type CollectionEntry<Collection extends AnyCollection = AnyCollection> = BaseContentEntry<
|
|
156
|
+
InferCollectionData<Collection>
|
|
157
|
+
>
|
|
158
|
+
|
|
159
|
+
export type ContentFilter<Collection extends AnyCollection> = (
|
|
160
|
+
entry: CollectionEntry<Collection>,
|
|
161
|
+
) => boolean | Promise<boolean>
|
|
162
|
+
|
|
163
|
+
export interface ContentEntryReference<Collection extends AnyCollection = AnyCollection> {
|
|
164
|
+
collection: Collection
|
|
165
|
+
id: string
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export type ResolvedContentEntries<Entries extends readonly ContentEntryReference<any>[]> = {
|
|
169
|
+
[Index in keyof Entries]: Entries[Index] extends ContentEntryReference<infer Collection>
|
|
170
|
+
? CollectionEntry<Collection> | undefined
|
|
171
|
+
: never
|
|
172
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
declare module 'virtual:eclipsa-content:runtime' {
|
|
2
|
+
export function getCollection<Collection extends import('./types.ts').AnyCollection>(
|
|
3
|
+
collection: Collection,
|
|
4
|
+
filter?: import('./mod.ts').ContentFilter<Collection>,
|
|
5
|
+
): Promise<import('./mod.ts').CollectionEntry<Collection>[]>
|
|
6
|
+
export function getEntries<
|
|
7
|
+
Entries extends readonly import('./types.ts').ContentEntryReference<any>[],
|
|
8
|
+
>(entries: Entries): Promise<import('./types.ts').ResolvedContentEntries<Entries>>
|
|
9
|
+
export function getEntry<Collection extends import('./types.ts').AnyCollection>(
|
|
10
|
+
collection: Collection,
|
|
11
|
+
id: string,
|
|
12
|
+
): Promise<import('./mod.ts').CollectionEntry<Collection> | undefined>
|
|
13
|
+
export function render<Collection extends import('./types.ts').AnyCollection>(
|
|
14
|
+
entry: import('./mod.ts').CollectionEntry<Collection>,
|
|
15
|
+
): Promise<import('./types.ts').RenderedContent>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare module 'virtual:eclipsa-content:search' {
|
|
19
|
+
export const searchOptions: import('./types.ts').ResolvedContentSearchOptions
|
|
20
|
+
export function search(
|
|
21
|
+
query: string,
|
|
22
|
+
options?: import('./types.ts').ContentSearchQueryOptions,
|
|
23
|
+
): Promise<import('./types.ts').ContentSearchResult[]>
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import config from './vite.config.ts'
|
|
3
|
+
|
|
4
|
+
describe('@eclipsa/content vite pack config', () => {
|
|
5
|
+
it('builds every published entrypoint with declarations', () => {
|
|
6
|
+
expect(config.pack).toMatchObject({
|
|
7
|
+
clean: true,
|
|
8
|
+
copy: ['virtual-runtime.d.ts'],
|
|
9
|
+
dts: true,
|
|
10
|
+
entry: ['mod.ts', 'vite.ts', 'internal.ts'],
|
|
11
|
+
format: ['esm'],
|
|
12
|
+
sourcemap: true,
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
})
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineConfig } from 'vite-plus'
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
include: ['*.test.ts', '*.test.tsx'],
|
|
6
|
+
environment: 'node',
|
|
7
|
+
},
|
|
8
|
+
pack: {
|
|
9
|
+
copy: ['virtual-runtime.d.ts'],
|
|
10
|
+
entry: ['mod.ts', 'vite.ts', 'internal.ts'],
|
|
11
|
+
dts: true,
|
|
12
|
+
format: ['esm'],
|
|
13
|
+
clean: true,
|
|
14
|
+
sourcemap: true,
|
|
15
|
+
},
|
|
16
|
+
})
|
package/vite.test.ts
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises'
|
|
2
|
+
import * as os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import type { Plugin } from 'vite'
|
|
6
|
+
import { eclipsaContent } from './vite.ts'
|
|
7
|
+
|
|
8
|
+
const DEV_APP_INVALIDATORS_KEY = Symbol.for('eclipsa.dev-app-invalidators')
|
|
9
|
+
const createdRoots: string[] = []
|
|
10
|
+
const contentEntryImportPath = JSON.stringify(path.resolve(__dirname, 'mod.ts'))
|
|
11
|
+
|
|
12
|
+
const createTempRoot = async () => {
|
|
13
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'eclipsa-content-vite-'))
|
|
14
|
+
createdRoots.push(root)
|
|
15
|
+
await fs.mkdir(path.join(root, 'app'), {
|
|
16
|
+
recursive: true,
|
|
17
|
+
})
|
|
18
|
+
return root
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const getPlugin = (): Plugin => {
|
|
22
|
+
const plugin = eclipsaContent()
|
|
23
|
+
if (!Array.isArray(plugin)) {
|
|
24
|
+
throw new Error('Expected eclipsaContent() to return a plugin array')
|
|
25
|
+
}
|
|
26
|
+
return plugin[0] as Plugin
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const getHotUpdate = (plugin: Plugin) => {
|
|
30
|
+
const hook = plugin.hotUpdate
|
|
31
|
+
if (typeof hook === 'function') {
|
|
32
|
+
return hook
|
|
33
|
+
}
|
|
34
|
+
return hook?.handler
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
afterEach(async () => {
|
|
38
|
+
await Promise.all(
|
|
39
|
+
createdRoots.splice(0).map((root) => fs.rm(root, { force: true, recursive: true })),
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('@eclipsa/content vite plugin', () => {
|
|
44
|
+
it('returns a failing runtime module when content config is missing', async () => {
|
|
45
|
+
const root = await createTempRoot()
|
|
46
|
+
const plugin = getPlugin()
|
|
47
|
+
const configResolved =
|
|
48
|
+
typeof plugin.configResolved === 'function'
|
|
49
|
+
? plugin.configResolved
|
|
50
|
+
: plugin.configResolved?.handler
|
|
51
|
+
await configResolved?.call(
|
|
52
|
+
{} as any,
|
|
53
|
+
{
|
|
54
|
+
root,
|
|
55
|
+
} as any,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const load = typeof plugin.load === 'function' ? plugin.load : plugin.load?.handler
|
|
59
|
+
const code = await load?.call({} as any, '\0eclipsa-content:runtime')
|
|
60
|
+
|
|
61
|
+
expect(code).toContain('Missing app/content.config.ts')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('builds a runtime module that imports the app content config', async () => {
|
|
65
|
+
const root = await createTempRoot()
|
|
66
|
+
await fs.writeFile(path.join(root, 'app', 'content.config.ts'), 'export const docs = {}')
|
|
67
|
+
const plugin = getPlugin()
|
|
68
|
+
const configResolved =
|
|
69
|
+
typeof plugin.configResolved === 'function'
|
|
70
|
+
? plugin.configResolved
|
|
71
|
+
: plugin.configResolved?.handler
|
|
72
|
+
await configResolved?.call(
|
|
73
|
+
{} as any,
|
|
74
|
+
{
|
|
75
|
+
root,
|
|
76
|
+
} as any,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
const load = typeof plugin.load === 'function' ? plugin.load : plugin.load?.handler
|
|
80
|
+
const code = await load?.call({} as any, '\0eclipsa-content:runtime')
|
|
81
|
+
|
|
82
|
+
expect(code).toContain('@eclipsa/content/internal')
|
|
83
|
+
expect(code).toContain('app/content.config.ts')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('builds a search runtime module when search is enabled', async () => {
|
|
87
|
+
const root = await createTempRoot()
|
|
88
|
+
await fs.mkdir(path.join(root, 'app', 'content', 'docs'), {
|
|
89
|
+
recursive: true,
|
|
90
|
+
})
|
|
91
|
+
await fs.writeFile(
|
|
92
|
+
path.join(root, 'app', 'content.config.ts'),
|
|
93
|
+
`
|
|
94
|
+
import { defineCollection, glob } from ${contentEntryImportPath}
|
|
95
|
+
|
|
96
|
+
export const docs = defineCollection({
|
|
97
|
+
loader: glob({
|
|
98
|
+
base: './content/docs',
|
|
99
|
+
pattern: '**/*.md',
|
|
100
|
+
}),
|
|
101
|
+
search: {
|
|
102
|
+
placeholder: 'Search docs',
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
`,
|
|
106
|
+
)
|
|
107
|
+
await fs.writeFile(
|
|
108
|
+
path.join(root, 'app', 'content', 'docs', 'page.md'),
|
|
109
|
+
`---
|
|
110
|
+
title: Search Page
|
|
111
|
+
---
|
|
112
|
+
# Search Page
|
|
113
|
+
|
|
114
|
+
Find the comet needle.
|
|
115
|
+
`,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
const plugin = getPlugin()
|
|
119
|
+
const configResolved =
|
|
120
|
+
typeof plugin.configResolved === 'function'
|
|
121
|
+
? plugin.configResolved
|
|
122
|
+
: plugin.configResolved?.handler
|
|
123
|
+
await configResolved?.call(
|
|
124
|
+
{} as any,
|
|
125
|
+
{
|
|
126
|
+
base: '/',
|
|
127
|
+
root,
|
|
128
|
+
} as any,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const load = typeof plugin.load === 'function' ? plugin.load : plugin.load?.handler
|
|
132
|
+
const code = await load?.call(
|
|
133
|
+
{ environment: { name: 'client' } } as any,
|
|
134
|
+
'\0eclipsa-content:search',
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
expect(code).toContain('__eclipsa_content_search__.json')
|
|
138
|
+
expect(code).toContain('searchOptions')
|
|
139
|
+
expect(code).not.toContain('import type')
|
|
140
|
+
expect(code).not.toContain(': Promise<')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('stubs content config collections in the client environment', async () => {
|
|
144
|
+
const root = await createTempRoot()
|
|
145
|
+
const configPath = path.join(root, 'app', 'content.config.ts')
|
|
146
|
+
await fs.writeFile(
|
|
147
|
+
configPath,
|
|
148
|
+
`
|
|
149
|
+
import { defineCollection } from '@eclipsa/content'
|
|
150
|
+
import { z } from 'zod'
|
|
151
|
+
|
|
152
|
+
export const docs = defineCollection({ loader: { load: () => [] }, schema: z.object({ title: z.string() }) })
|
|
153
|
+
export const posts = defineCollection({ loader: { load: () => [] } })
|
|
154
|
+
`,
|
|
155
|
+
)
|
|
156
|
+
const plugin = getPlugin()
|
|
157
|
+
const configResolved =
|
|
158
|
+
typeof plugin.configResolved === 'function'
|
|
159
|
+
? plugin.configResolved
|
|
160
|
+
: plugin.configResolved?.handler
|
|
161
|
+
await configResolved?.call(
|
|
162
|
+
{} as any,
|
|
163
|
+
{
|
|
164
|
+
root,
|
|
165
|
+
} as any,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const load = typeof plugin.load === 'function' ? plugin.load : plugin.load?.handler
|
|
169
|
+
const code = await load?.call(
|
|
170
|
+
{
|
|
171
|
+
environment: {
|
|
172
|
+
name: 'client',
|
|
173
|
+
},
|
|
174
|
+
} as any,
|
|
175
|
+
configPath,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
expect(code).toContain('export const docs = Object.freeze')
|
|
179
|
+
expect(code).toContain('export const posts = Object.freeze')
|
|
180
|
+
expect(code).not.toContain('zod')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('invalidates registered dev apps and emits a content HMR event for markdown changes', async () => {
|
|
184
|
+
const root = await createTempRoot()
|
|
185
|
+
const plugin = getPlugin()
|
|
186
|
+
const configResolved =
|
|
187
|
+
typeof plugin.configResolved === 'function'
|
|
188
|
+
? plugin.configResolved
|
|
189
|
+
: plugin.configResolved?.handler
|
|
190
|
+
await configResolved?.call(
|
|
191
|
+
{} as any,
|
|
192
|
+
{
|
|
193
|
+
root,
|
|
194
|
+
} as any,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
const invalidate = vi.fn()
|
|
198
|
+
const send = vi.fn()
|
|
199
|
+
const hotUpdate = getHotUpdate(plugin)
|
|
200
|
+
|
|
201
|
+
const result = await hotUpdate?.call(
|
|
202
|
+
{} as any,
|
|
203
|
+
{
|
|
204
|
+
file: path.join(root, 'app', 'content', 'docs', 'guide', 'page.md'),
|
|
205
|
+
modules: [],
|
|
206
|
+
read: () => '',
|
|
207
|
+
server: {
|
|
208
|
+
[DEV_APP_INVALIDATORS_KEY]: new Set([invalidate]),
|
|
209
|
+
environments: {
|
|
210
|
+
ssr: {
|
|
211
|
+
moduleGraph: {
|
|
212
|
+
getModuleById: vi.fn(),
|
|
213
|
+
invalidateModule: vi.fn(),
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
ws: {
|
|
218
|
+
send,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
timestamp: Date.now(),
|
|
222
|
+
type: 'update',
|
|
223
|
+
} as any,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
expect(invalidate).toHaveBeenCalledTimes(1)
|
|
227
|
+
expect(send).toHaveBeenCalledWith('eclipsa:content-update')
|
|
228
|
+
expect(result).toEqual([])
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('emits a search index asset during bundle generation when search is enabled', async () => {
|
|
232
|
+
const root = await createTempRoot()
|
|
233
|
+
await fs.mkdir(path.join(root, 'app', 'content', 'docs'), {
|
|
234
|
+
recursive: true,
|
|
235
|
+
})
|
|
236
|
+
await fs.writeFile(
|
|
237
|
+
path.join(root, 'app', 'content.config.ts'),
|
|
238
|
+
`
|
|
239
|
+
import { defineCollection, glob } from ${contentEntryImportPath}
|
|
240
|
+
|
|
241
|
+
export const docs = defineCollection({
|
|
242
|
+
loader: glob({
|
|
243
|
+
base: './content/docs',
|
|
244
|
+
pattern: '**/*.md',
|
|
245
|
+
}),
|
|
246
|
+
search: true,
|
|
247
|
+
})
|
|
248
|
+
`,
|
|
249
|
+
)
|
|
250
|
+
await fs.writeFile(
|
|
251
|
+
path.join(root, 'app', 'content', 'docs', 'page.md'),
|
|
252
|
+
'# Search Asset\n\nMeteor shard token.',
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
const plugin = getPlugin()
|
|
256
|
+
const configResolved =
|
|
257
|
+
typeof plugin.configResolved === 'function'
|
|
258
|
+
? plugin.configResolved
|
|
259
|
+
: plugin.configResolved?.handler
|
|
260
|
+
await configResolved?.call(
|
|
261
|
+
{} as any,
|
|
262
|
+
{
|
|
263
|
+
base: '/',
|
|
264
|
+
root,
|
|
265
|
+
} as any,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
const emitFile = vi.fn()
|
|
269
|
+
const generateBundle =
|
|
270
|
+
typeof plugin.generateBundle === 'function'
|
|
271
|
+
? plugin.generateBundle
|
|
272
|
+
: plugin.generateBundle?.handler
|
|
273
|
+
|
|
274
|
+
await generateBundle?.call({ emitFile } as any, {} as any, {} as any, false)
|
|
275
|
+
|
|
276
|
+
expect(emitFile).toHaveBeenCalledWith(
|
|
277
|
+
expect.objectContaining({
|
|
278
|
+
fileName: '__eclipsa_content_search__.json',
|
|
279
|
+
type: 'asset',
|
|
280
|
+
}),
|
|
281
|
+
)
|
|
282
|
+
})
|
|
283
|
+
})
|