1use std::path::Path;
11
12use dmc_diagnostic::{Code, DiagResult};
13use duck_diagnostic::diag;
14
15use crate::engine::collection::Collection;
16use crate::engine::schema_ts::schema_to_ts_object;
17
18use crate::engine::utils::{pascal_case, relative_from};
19
20pub fn write_index(out_dir: &Path, collections: &[Collection], format: &str, config_path: Option<&Path>) -> DiagResult {
22 let names: Vec<&str> = collections.iter().map(|c| c.name.as_str()).collect();
23 write_index_js(out_dir, &names, format)?;
24
25 let ts_cfg =
26 config_path.filter(|p| matches!(p.extension().and_then(|s| s.to_str()), Some("ts") | Some("js") | Some("mjs"),));
27
28 match ts_cfg {
29 Some(p) => {
30 let rel = relative_from(out_dir, p);
31 write_dts_velite_style(out_dir, &names, &rel)?;
32 },
33 None => write_dts_self_contained(out_dir, collections)?,
34 }
35 Ok(())
36}
37
38fn write_index_js(out_dir: &Path, names: &[&str], format: &str) -> DiagResult {
39 let mut js = String::from("// This file is generated by dmc. DO NOT EDIT.\n\n");
40 if format == "cjs" {
41 for name in names {
42 js.push_str(&format!("exports.{name} = require('./{name}.json')\n"));
43 }
44 } else {
45 for name in names {
46 js.push_str(&format!("export {{ default as {name} }} from './{name}.json' with {{ type: 'json' }}\n",));
47 }
48 }
49 let path = out_dir.join("index.js");
50 std::fs::write(&path, js).map_err(|e| diag!(Code::IoWrite, format!("write {}: {}", path.display(), e)))
51}
52
53fn write_dts_self_contained(out_dir: &Path, collections: &[Collection]) -> DiagResult {
54 let mut dts = String::from(
55 "// This file is generated by dmc. DO NOT EDIT.
56
57export interface TocItem { title: string; url: string; items: TocItem[] }
58export interface Metadata { readingTime: number; wordCount: number }
59export interface BaseCollection {
60 body: string
61 content: string
62 excerpt: string
63 metadata: Metadata
64 toc: TocItem[]
65 contentType: string
66 flattenedPath: string
67 permalink: string
68 slug: string
69 sourceFileDir: string
70 sourceFileName: string
71 sourceFilePath: string
72 html?: string
73}
74export interface DocCollection extends BaseCollection { [frontmatterField: string]: unknown }
75
76",
77 );
78
79 for c in collections {
80 let iface = pascal_case(&c.name) + "Collection";
81 match &c.schema {
82 Some(schema) => {
83 dts.push_str(&format!("export interface {iface} extends BaseCollection "));
84 dts.push_str(&schema_to_ts_object(schema));
85 dts.push('\n');
86 dts.push_str(&format!("export declare const {}: {iface}[]\n\n", c.name));
87 },
88 None => {
89 dts.push_str(&format!("export declare const {}: DocCollection[]\n\n", c.name));
90 },
91 }
92 }
93
94 let path = out_dir.join("index.d.ts");
95 std::fs::write(&path, dts).map_err(|e| diag!(Code::IoWrite, format!("write {}: {}", path.display(), e)))
96}
97
98fn write_dts_velite_style(out_dir: &Path, names: &[&str], cfg_rel: &str) -> DiagResult {
99 let mut dts = String::from("// This file is generated by dmc. DO NOT EDIT.\n\n");
100 dts.push_str(&format!("import type __dmc from '{cfg_rel}'\n\n"));
101 dts.push_str("type Collections = typeof __dmc['collections']\n");
102 dts.push_str("type CollectionName = keyof Collections\n");
103 dts.push_str("type RecordOf<K extends CollectionName> =\n");
104 dts.push_str(" Collections[K] extends { schema: { _output: infer T } } ? T : unknown\n\n");
105 for name in names {
106 dts.push_str(&format!("export declare const {name}: RecordOf<'{name}'>[]\n"));
107 }
108 let path = out_dir.join("index.d.ts");
109 std::fs::write(&path, dts).map_err(|e| diag!(Code::IoWrite, format!("write {}: {}", path.display(), e)))
110}