@topik/core 0.1.0-alpha.1

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.
@@ -0,0 +1,171 @@
1
+ import { z } from "zod";
2
+ import { Guide, Wiki, WikiPage } from "@topik/schema";
3
+
4
+ //#region src/config/collection.d.ts
5
+ declare const collectionConfigSchema: z.ZodObject<{
6
+ id: z.ZodString;
7
+ title: z.ZodString;
8
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ id: string;
11
+ title: string;
12
+ tags?: string[] | undefined;
13
+ }, {
14
+ id: string;
15
+ title: string;
16
+ tags?: string[] | undefined;
17
+ }>;
18
+ type CollectionConfig = z.infer<typeof collectionConfigSchema>;
19
+ declare function parseCollectionConfig(raw: unknown): CollectionConfig;
20
+ //#endregion
21
+ //#region src/config/wiki.d.ts
22
+ type WikiNavNode = string | {
23
+ group: string;
24
+ slug?: string;
25
+ icon?: string;
26
+ children: WikiNavNode[];
27
+ } | {
28
+ tab: string;
29
+ slug?: string;
30
+ icon?: string;
31
+ children: WikiNavNode[];
32
+ } | {
33
+ href: string;
34
+ title: string;
35
+ icon?: string;
36
+ };
37
+ declare const wikiConfigSchema: z.ZodObject<{
38
+ id: z.ZodString;
39
+ title: z.ZodString;
40
+ navigation: z.ZodOptional<z.ZodArray<z.ZodType<WikiNavNode, z.ZodTypeDef, unknown>, "many">>;
41
+ theme: z.ZodOptional<z.ZodObject<{
42
+ colors: z.ZodOptional<z.ZodObject<{
43
+ primary: z.ZodString;
44
+ light: z.ZodOptional<z.ZodString>;
45
+ dark: z.ZodOptional<z.ZodString>;
46
+ }, "strip", z.ZodTypeAny, {
47
+ primary: string;
48
+ light?: string | undefined;
49
+ dark?: string | undefined;
50
+ }, {
51
+ primary: string;
52
+ light?: string | undefined;
53
+ dark?: string | undefined;
54
+ }>>;
55
+ appearance: z.ZodOptional<z.ZodObject<{
56
+ default: z.ZodOptional<z.ZodEnum<["light", "dark", "system"]>>;
57
+ }, "strip", z.ZodTypeAny, {
58
+ default?: "dark" | "light" | "system" | undefined;
59
+ }, {
60
+ default?: "dark" | "light" | "system" | undefined;
61
+ }>>;
62
+ }, "strip", z.ZodTypeAny, {
63
+ colors?: {
64
+ primary: string;
65
+ light?: string | undefined;
66
+ dark?: string | undefined;
67
+ } | undefined;
68
+ appearance?: {
69
+ default?: "dark" | "light" | "system" | undefined;
70
+ } | undefined;
71
+ }, {
72
+ colors?: {
73
+ primary: string;
74
+ light?: string | undefined;
75
+ dark?: string | undefined;
76
+ } | undefined;
77
+ appearance?: {
78
+ default?: "dark" | "light" | "system" | undefined;
79
+ } | undefined;
80
+ }>>;
81
+ }, "strip", z.ZodTypeAny, {
82
+ id: string;
83
+ title: string;
84
+ navigation?: WikiNavNode[] | undefined;
85
+ theme?: {
86
+ colors?: {
87
+ primary: string;
88
+ light?: string | undefined;
89
+ dark?: string | undefined;
90
+ } | undefined;
91
+ appearance?: {
92
+ default?: "dark" | "light" | "system" | undefined;
93
+ } | undefined;
94
+ } | undefined;
95
+ }, {
96
+ id: string;
97
+ title: string;
98
+ navigation?: unknown[] | undefined;
99
+ theme?: {
100
+ colors?: {
101
+ primary: string;
102
+ light?: string | undefined;
103
+ dark?: string | undefined;
104
+ } | undefined;
105
+ appearance?: {
106
+ default?: "dark" | "light" | "system" | undefined;
107
+ } | undefined;
108
+ } | undefined;
109
+ }>;
110
+ type WikiConfig = z.infer<typeof wikiConfigSchema>;
111
+ declare function parseWikiConfig(raw: unknown): WikiConfig;
112
+ //#endregion
113
+ //#region src/resource.d.ts
114
+ type Resource = Guide | Wiki | WikiPage;
115
+ type ResourceType = Resource["type"];
116
+ //#endregion
117
+ //#region src/compile/shared.d.ts
118
+ interface CompileResult {
119
+ resources: Resource[];
120
+ }
121
+ //#endregion
122
+ //#region src/compile/wiki.d.ts
123
+ interface CompileWikiOptions {
124
+ dir: string;
125
+ }
126
+ declare function compileWiki(options: CompileWikiOptions): Promise<CompileResult>;
127
+ //#endregion
128
+ //#region src/compile/guide.d.ts
129
+ interface CompileGuidesOptions {
130
+ dir: string;
131
+ }
132
+ declare function compileGuides(options: CompileGuidesOptions): Promise<CompileResult>;
133
+ //#endregion
134
+ //#region src/compile/index.d.ts
135
+ interface CompileOptions {
136
+ dir: string;
137
+ }
138
+ declare function compile(options: CompileOptions): Promise<CompileResult>;
139
+ //#endregion
140
+ //#region src/validate.d.ts
141
+ interface ValidationError {
142
+ resource: string;
143
+ path: string;
144
+ message: string;
145
+ }
146
+ interface ValidationResult {
147
+ valid: boolean;
148
+ errors: ValidationError[];
149
+ }
150
+ /** Validate the resources produced by `compile` and the CLI export. */
151
+ declare function validateResources(resources: readonly unknown[]): ValidationResult;
152
+ //#endregion
153
+ //#region src/watch.d.ts
154
+ interface WatchOptions {
155
+ dir: string;
156
+ signal?: AbortSignal;
157
+ }
158
+ type UpdateListener = (key: string, resource: Resource) => void;
159
+ type ErrorListener = (error: Error) => void;
160
+ interface Watcher {
161
+ /** Current compiled resources, keyed by `Type/name`. */
162
+ resources: Map<string, Resource>;
163
+ on(event: "update", listener: UpdateListener): this;
164
+ on(event: "error", listener: ErrorListener): this;
165
+ off(event: "update", listener: UpdateListener): this;
166
+ off(event: "error", listener: ErrorListener): this;
167
+ close(): Promise<void>;
168
+ }
169
+ declare function watch(options: WatchOptions): Promise<Watcher>;
170
+ //#endregion
171
+ export { type CollectionConfig, type CompileGuidesOptions, type CompileOptions, type CompileResult, type CompileWikiOptions, type Resource, type ResourceType, type ValidationError, type ValidationResult, type WatchOptions, type Watcher, type WikiConfig, compile, compileGuides, compileWiki, parseCollectionConfig, parseWikiConfig, validateResources, watch };
package/dist/index.mjs ADDED
@@ -0,0 +1,430 @@
1
+ import { z } from "zod";
2
+ import { join, resolve } from "node:path";
3
+ import { access, readFile, readdir } from "node:fs/promises";
4
+ import { parse } from "yaml";
5
+ import Ajv2020 from "ajv/dist/2020.js";
6
+ import addFormats from "ajv-formats";
7
+ import { guideSchema, wikiPageSchema, wikiSchema } from "@topik/schema";
8
+ import { EventEmitter } from "node:events";
9
+ import { watch as watch$1 } from "chokidar";
10
+ //#region src/config/collection.ts
11
+ const collectionConfigSchema = z.object({
12
+ id: z.string().min(1).max(63).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
13
+ title: z.string().min(1).max(256),
14
+ tags: z.array(z.string().min(1).max(63)).optional()
15
+ });
16
+ function parseCollectionConfig(raw) {
17
+ return collectionConfigSchema.parse(raw);
18
+ }
19
+ //#endregion
20
+ //#region src/config/wiki.ts
21
+ const wikiPagePath = z.string().min(1).max(512).regex(/^(?:[a-z0-9]+(?:-[a-z0-9]+)*)(?:\/(?:[a-z0-9]+(?:-[a-z0-9]+)*))*$/, "Wiki page paths must use lowercase DNS-style segments separated by '/'");
22
+ const navNodeSchema = z.union([
23
+ wikiPagePath,
24
+ z.object({
25
+ group: z.string().min(1).max(256),
26
+ slug: z.string().min(1).max(256).optional(),
27
+ icon: z.string().min(1).max(256).optional(),
28
+ children: z.lazy(() => z.array(navNodeSchema)).default([])
29
+ }),
30
+ z.object({
31
+ tab: z.string().min(1).max(256),
32
+ slug: z.string().min(1).max(256).optional(),
33
+ icon: z.string().min(1).max(256).optional(),
34
+ children: z.lazy(() => z.array(navNodeSchema)).default([])
35
+ }),
36
+ z.object({
37
+ href: z.string().max(2048).refine((v) => v.startsWith("https://") || v.startsWith("http://"), { message: "href must be an http or https URL" }),
38
+ title: z.string().min(1).max(256),
39
+ icon: z.string().min(1).max(256).optional()
40
+ })
41
+ ]);
42
+ const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/, "Must be a hex color (e.g. #ff73a8)");
43
+ const themeSchema = z.object({
44
+ colors: z.object({
45
+ primary: hexColor,
46
+ light: hexColor.optional(),
47
+ dark: hexColor.optional()
48
+ }).optional(),
49
+ appearance: z.object({ default: z.enum([
50
+ "light",
51
+ "dark",
52
+ "system"
53
+ ]).optional() }).optional()
54
+ });
55
+ const wikiConfigSchema = z.object({
56
+ id: z.string().min(1).max(63).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
57
+ title: z.string().min(1).max(256),
58
+ navigation: z.array(navNodeSchema).optional(),
59
+ theme: themeSchema.optional()
60
+ });
61
+ function parseWikiConfig(raw) {
62
+ return wikiConfigSchema.parse(raw);
63
+ }
64
+ //#endregion
65
+ //#region src/compile/config.ts
66
+ async function readOptionalConfigFile(dir, candidates) {
67
+ for (const name of candidates) {
68
+ const filePath = join(dir, name);
69
+ let raw;
70
+ try {
71
+ raw = await readFile(filePath, "utf-8");
72
+ } catch (error) {
73
+ if (error.code === "ENOENT") continue;
74
+ throw new Error(`Failed to read config file ${filePath}`, { cause: error });
75
+ }
76
+ try {
77
+ return name.endsWith(".json") ? JSON.parse(raw) : parse(raw);
78
+ } catch (error) {
79
+ throw new Error(`Failed to parse config file ${filePath}`, { cause: error });
80
+ }
81
+ }
82
+ }
83
+ async function findConfigFile(dir, candidates) {
84
+ for (const name of candidates) try {
85
+ await access(join(dir, name));
86
+ return name;
87
+ } catch (error) {
88
+ if (error.code === "ENOENT") continue;
89
+ throw new Error(`Failed to access config file ${join(dir, name)}`, { cause: error });
90
+ }
91
+ return null;
92
+ }
93
+ //#endregion
94
+ //#region src/compile/shared.ts
95
+ const DNS_LABEL_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
96
+ function parseMarkdownFrontmatter(raw, filePath) {
97
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
98
+ if (!match) return {
99
+ frontmatter: {},
100
+ content: raw
101
+ };
102
+ try {
103
+ const frontmatter = parse(match[1]);
104
+ if (frontmatter == null) return {
105
+ frontmatter: {},
106
+ content: match[2]
107
+ };
108
+ if (typeof frontmatter !== "object" || Array.isArray(frontmatter)) throw new Error("Frontmatter must parse to an object");
109
+ if ("title" in frontmatter && frontmatter.title != null && typeof frontmatter.title !== "string") throw new Error("Frontmatter title must be a string");
110
+ return {
111
+ frontmatter,
112
+ content: match[2]
113
+ };
114
+ } catch (error) {
115
+ const message = error instanceof Error ? error.message : String(error);
116
+ throw new Error(`Invalid frontmatter in ${filePath}: ${message}`, { cause: error });
117
+ }
118
+ }
119
+ function extractMarkdownTitle(content, fallback) {
120
+ const match = content.match(/^#\s+(.+)$/m);
121
+ if (match) return match[1].trim();
122
+ return fallback.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
123
+ }
124
+ function parseReferenceList(value, fieldName, filePath) {
125
+ if (value == null) return;
126
+ if (!Array.isArray(value)) throw new Error(`${fieldName} in ${filePath} must be an array of resource names`);
127
+ const references = value.map((entry, index) => {
128
+ if (typeof entry !== "string") throw new Error(`${fieldName}[${index}] in ${filePath} must be a string`);
129
+ if (entry.length > 63 || !DNS_LABEL_PATTERN.test(entry)) throw new Error(`${fieldName}[${index}] in ${filePath} must be a DNS-1123 resource name`);
130
+ return entry;
131
+ });
132
+ return references.length > 0 ? references : void 0;
133
+ }
134
+ //#endregion
135
+ //#region src/compile/wiki.ts
136
+ async function compileWiki(options) {
137
+ const dir = resolve(options.dir);
138
+ const raw = await readOptionalConfigFile(dir, [
139
+ "wiki.yaml",
140
+ "wiki.yml",
141
+ "wiki.json"
142
+ ]);
143
+ if (raw == null) return { resources: [] };
144
+ const config = parseWikiConfig(raw);
145
+ const pagePaths = config.navigation ? [...new Set(collectPagePaths(config.navigation))] : [];
146
+ const fileContents = await Promise.all(pagePaths.map(async (pagePath) => {
147
+ return readFile(await resolveFilePath(dir, pagePath), "utf-8");
148
+ }));
149
+ const resources = [];
150
+ for (let i = 0; i < pagePaths.length; i++) {
151
+ const pagePath = pagePaths[i];
152
+ const { frontmatter, content } = parseMarkdownFrontmatter(fileContents[i], pagePath);
153
+ const name = pagePathToName(pagePath);
154
+ const title = typeof frontmatter.title === "string" ? frontmatter.title : extractMarkdownTitle(content, name);
155
+ const pageResource = {
156
+ apiVersion: "v1",
157
+ type: "WikiPage",
158
+ name: `${config.id}-${name}`,
159
+ spec: {
160
+ wiki: config.id,
161
+ title,
162
+ content: {
163
+ format: "topik",
164
+ value: content
165
+ }
166
+ }
167
+ };
168
+ resources.push(pageResource);
169
+ }
170
+ const wikiResource = {
171
+ apiVersion: "v1",
172
+ type: "Wiki",
173
+ name: config.id,
174
+ spec: {
175
+ title: config.title,
176
+ ...config.navigation ? { navigation: resolveNavigation(config.navigation, config.id) } : {},
177
+ ...config.theme ? { theme: config.theme } : {}
178
+ }
179
+ };
180
+ resources.push(wikiResource);
181
+ return { resources };
182
+ }
183
+ function collectPagePaths(nodes) {
184
+ const paths = [];
185
+ for (const node of nodes) if (typeof node === "string") paths.push(node);
186
+ else if ("group" in node || "tab" in node) paths.push(...collectPagePaths(node.children));
187
+ return paths;
188
+ }
189
+ async function resolveFilePath(dir, pagePath) {
190
+ for (const ext of [".mdx", ".md"]) {
191
+ const filePath = join(dir, pagePath + ext);
192
+ try {
193
+ await access(filePath);
194
+ return filePath;
195
+ } catch {}
196
+ }
197
+ throw new Error(`Page not found: ${pagePath} (tried .md and .mdx in ${dir})`);
198
+ }
199
+ function pagePathToSlug(pagePath) {
200
+ if (pagePath === "index") return "";
201
+ return pagePath.replace(/\/index$/, "");
202
+ }
203
+ function pagePathToName(pagePath) {
204
+ return pagePath.replace(/^\//, "").replaceAll("/", "-");
205
+ }
206
+ function resolveNavigation(nodes, wikiId) {
207
+ return nodes.map((node) => {
208
+ if (typeof node === "string") return {
209
+ type: "page",
210
+ page: `${wikiId}-${pagePathToName(node)}`,
211
+ slug: pagePathToSlug(node)
212
+ };
213
+ if ("group" in node || "tab" in node) {
214
+ const isTab = "tab" in node;
215
+ const title = isTab ? node.tab : node.group;
216
+ return {
217
+ type: isTab ? "tab" : "group",
218
+ title,
219
+ ...node.slug ? { slug: node.slug } : {},
220
+ ...node.icon ? { icon: node.icon } : {},
221
+ children: resolveNavigation(node.children, wikiId)
222
+ };
223
+ }
224
+ return {
225
+ type: "link",
226
+ title: node.title,
227
+ href: node.href,
228
+ ...node.icon ? { icon: node.icon } : {}
229
+ };
230
+ });
231
+ }
232
+ //#endregion
233
+ //#region src/compile/guide.ts
234
+ async function compileGuides(options) {
235
+ const dir = resolve(options.dir);
236
+ const raw = await readOptionalConfigFile(dir, [
237
+ "collection.yaml",
238
+ "collection.yml",
239
+ "collection.json"
240
+ ]);
241
+ if (raw == null) return { resources: [] };
242
+ const config = parseCollectionConfig(raw);
243
+ const markdownFiles = (await readdir(dir)).filter((f) => f.endsWith(".md") || f.endsWith(".mdx")).sort();
244
+ const resources = [];
245
+ for (const file of markdownFiles) {
246
+ const { frontmatter, content } = parseMarkdownFrontmatter(await readFile(join(dir, file), "utf-8"), file);
247
+ const slug = fileToSlug(file);
248
+ const name = `${config.id}-${slug}`;
249
+ const title = typeof frontmatter.title === "string" ? frontmatter.title : extractMarkdownTitle(content, slug);
250
+ const tags = mergeTags(config.tags, frontmatter.tags);
251
+ const authors = parseReferenceList(frontmatter.authors, "authors", file);
252
+ const description = typeof frontmatter.description === "string" ? frontmatter.description : void 0;
253
+ const guide = {
254
+ apiVersion: "v1",
255
+ type: "Guide",
256
+ name,
257
+ spec: {
258
+ title,
259
+ slug,
260
+ ...description != null ? { description } : {},
261
+ ...authors != null ? { authors } : {},
262
+ ...tags.length > 0 ? { tags } : {},
263
+ content: {
264
+ format: "topik",
265
+ value: content
266
+ }
267
+ }
268
+ };
269
+ resources.push(guide);
270
+ }
271
+ return { resources };
272
+ }
273
+ function fileToSlug(filename) {
274
+ return filename.replace(/\.(mdx?|md)$/, "");
275
+ }
276
+ function mergeTags(collectionTags, frontmatterTags) {
277
+ const tags = /* @__PURE__ */ new Set();
278
+ if (collectionTags) for (const tag of collectionTags) tags.add(tag);
279
+ if (Array.isArray(frontmatterTags)) {
280
+ for (const tag of frontmatterTags) if (typeof tag === "string") tags.add(tag);
281
+ }
282
+ return [...tags];
283
+ }
284
+ //#endregion
285
+ //#region src/compile/index.ts
286
+ const WIKI_CONFIG_FILES = [
287
+ "wiki.yaml",
288
+ "wiki.yml",
289
+ "wiki.json"
290
+ ];
291
+ const COLLECTION_CONFIG_FILES = [
292
+ "collection.yaml",
293
+ "collection.yml",
294
+ "collection.json"
295
+ ];
296
+ async function compile(options) {
297
+ const dir = resolve(options.dir);
298
+ const resources = [];
299
+ if (await findConfigFile(dir, WIKI_CONFIG_FILES)) {
300
+ const result = await compileWiki({ dir });
301
+ resources.push(...result.resources);
302
+ }
303
+ if (await findConfigFile(dir, COLLECTION_CONFIG_FILES)) {
304
+ const result = await compileGuides({ dir });
305
+ resources.push(...result.resources);
306
+ }
307
+ return { resources };
308
+ }
309
+ //#endregion
310
+ //#region src/validate.ts
311
+ const ajv = new Ajv2020({
312
+ strict: true,
313
+ discriminator: true
314
+ });
315
+ addFormats(ajv);
316
+ const validators = {
317
+ Guide: ajv.compile(guideSchema),
318
+ Wiki: ajv.compile(wikiSchema),
319
+ WikiPage: ajv.compile(wikiPageSchema)
320
+ };
321
+ function isRecord(value) {
322
+ return typeof value === "object" && value !== null;
323
+ }
324
+ function getResourceLabel(resource) {
325
+ return `${typeof resource.type === "string" ? resource.type : "unknown"}/${typeof resource.name === "string" ? resource.name : "unknown"}`;
326
+ }
327
+ /** Validate the resources produced by `compile` and the CLI export. */
328
+ function validateResources(resources) {
329
+ const errors = [];
330
+ for (const resource of resources) {
331
+ if (!isRecord(resource)) {
332
+ errors.push({
333
+ resource: "unknown/unknown",
334
+ path: "/",
335
+ message: "Resource must be an object"
336
+ });
337
+ continue;
338
+ }
339
+ if (typeof resource.type !== "string") {
340
+ errors.push({
341
+ resource: getResourceLabel(resource),
342
+ path: "/type",
343
+ message: "Resource type must be a string"
344
+ });
345
+ continue;
346
+ }
347
+ const validate = validators[resource.type];
348
+ if (!validate) {
349
+ errors.push({
350
+ resource: getResourceLabel(resource),
351
+ path: "/type",
352
+ message: `Unsupported resource type: ${resource.type}`
353
+ });
354
+ continue;
355
+ }
356
+ if (!validate(resource)) for (const err of validate.errors ?? []) errors.push({
357
+ resource: getResourceLabel(resource),
358
+ path: err.instancePath || "/",
359
+ message: err.message ?? "Unknown error"
360
+ });
361
+ }
362
+ return {
363
+ valid: errors.length === 0,
364
+ errors
365
+ };
366
+ }
367
+ //#endregion
368
+ //#region src/watch.ts
369
+ function resourceKey(resource) {
370
+ return `${resource.type}/${resource.name}`;
371
+ }
372
+ async function watch(options) {
373
+ const dir = resolve(options.dir);
374
+ const emitter = new EventEmitter();
375
+ const resources = /* @__PURE__ */ new Map();
376
+ const initial = await compile({ dir });
377
+ for (const resource of initial.resources) resources.set(resourceKey(resource), resource);
378
+ let debounceTimer = null;
379
+ async function recompile() {
380
+ try {
381
+ const result = await compile({ dir });
382
+ const newKeys = /* @__PURE__ */ new Set();
383
+ for (const resource of result.resources) {
384
+ const key = resourceKey(resource);
385
+ newKeys.add(key);
386
+ const existing = resources.get(key);
387
+ const serialized = JSON.stringify(resource);
388
+ if (!existing || JSON.stringify(existing) !== serialized) {
389
+ resources.set(key, resource);
390
+ emitter.emit("update", key, resource);
391
+ }
392
+ }
393
+ for (const key of resources.keys()) if (!newKeys.has(key)) resources.delete(key);
394
+ } catch (error) {
395
+ emitter.emit("error", error instanceof Error ? error : new Error(String(error)));
396
+ }
397
+ }
398
+ function scheduleRecompile() {
399
+ if (debounceTimer) clearTimeout(debounceTimer);
400
+ debounceTimer = setTimeout(recompile, 100);
401
+ }
402
+ const fsWatcher = watch$1(dir, {
403
+ ignoreInitial: true,
404
+ ignored: [/(^|[/\\])\../, "**/node_modules/**"]
405
+ });
406
+ fsWatcher.on("add", scheduleRecompile);
407
+ fsWatcher.on("change", scheduleRecompile);
408
+ fsWatcher.on("unlink", scheduleRecompile);
409
+ if (options.signal) options.signal.addEventListener("abort", () => {
410
+ fsWatcher.close();
411
+ });
412
+ const watcher = {
413
+ resources,
414
+ on(event, listener) {
415
+ emitter.on(event, listener);
416
+ return watcher;
417
+ },
418
+ off(event, listener) {
419
+ emitter.off(event, listener);
420
+ return watcher;
421
+ },
422
+ close: async () => {
423
+ if (debounceTimer) clearTimeout(debounceTimer);
424
+ await fsWatcher.close();
425
+ }
426
+ };
427
+ return watcher;
428
+ }
429
+ //#endregion
430
+ export { compile, compileGuides, compileWiki, parseCollectionConfig, parseWikiConfig, validateResources, watch };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@topik/core",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "Topik core library",
5
+ "license": "MIT",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "exports": {
11
+ ".": "./dist/index.mjs",
12
+ "./package.json": "./package.json"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "scripts": {
18
+ "build": "vp pack",
19
+ "dev": "vp pack --watch",
20
+ "test": "vp test",
21
+ "check": "vp check",
22
+ "prepublishOnly": "vp run build"
23
+ },
24
+ "dependencies": {
25
+ "@topik/schema": "workspace:*",
26
+ "ajv": "^8.18.0",
27
+ "ajv-formats": "^3.0.1",
28
+ "chokidar": "^4.0.0",
29
+ "yaml": "^2.7.0",
30
+ "zod": "^3.25.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "catalog:",
34
+ "@typescript/native-preview": "catalog:",
35
+ "typescript": "catalog:",
36
+ "vite-plus": "catalog:",
37
+ "vitest": "catalog:"
38
+ }
39
+ }