@supersoniks/concorde 4.9.0 → 4.9.2

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.
Files changed (123) hide show
  1. package/build-infos.json +1 -1
  2. package/concorde-core.bundle.js +195 -195
  3. package/concorde-core.es.js +3390 -3370
  4. package/dist/concorde-core.bundle.js +195 -195
  5. package/dist/concorde-core.es.js +3390 -3370
  6. package/dist/docs-mock-api-sw.js +3 -1
  7. package/dist/docs-mock-api-sw.js.map +2 -2
  8. package/dist/js/core/decorators/api.js +64 -12
  9. package/dist/js/core/decorators/api.js.map +1 -1
  10. package/dist/js/core/utils/HTML.js +4 -5
  11. package/dist/js/core/utils/HTML.js.map +1 -1
  12. package/dist/robots.txt +15 -1
  13. package/dist/types/core/decorators/api.d.ts +14 -1
  14. package/dist/types/core/decorators/api.d.ts.map +1 -1
  15. package/dist/types/core/utils/HTML.d.ts.map +1 -1
  16. package/docs/assets/{index-t0-i22oI.css → index-Bnp7gVaA.css} +1 -1
  17. package/docs/assets/{index-BEgZEIbv.js → index-hElUgV9_.js} +894 -552
  18. package/docs/crawl/core/components/functional/date/date.html +190 -0
  19. package/docs/crawl/core/components/functional/fetch/fetch.html +109 -0
  20. package/docs/crawl/core/components/functional/if/if.html +27 -0
  21. package/docs/crawl/core/components/functional/list/list.html +70 -0
  22. package/docs/crawl/core/components/functional/mix/mix.html +45 -0
  23. package/docs/crawl/core/components/functional/queue/queue.html +96 -0
  24. package/docs/crawl/core/components/functional/router/router.html +106 -0
  25. package/docs/crawl/core/components/functional/sdui/sdui.html +269 -0
  26. package/docs/crawl/core/components/functional/states/states.html +66 -0
  27. package/docs/crawl/core/components/functional/submit/submit.html +160 -0
  28. package/docs/crawl/core/components/functional/subscriber/subscriber.html +67 -0
  29. package/docs/crawl/core/components/functional/value/value.html +51 -0
  30. package/docs/crawl/core/components/ui/alert/alert.html +110 -0
  31. package/docs/crawl/core/components/ui/alert-messages/alert-messages.html +23 -0
  32. package/docs/crawl/core/components/ui/badge/badge.html +119 -0
  33. package/docs/crawl/core/components/ui/button/button.html +149 -0
  34. package/docs/crawl/core/components/ui/captcha/captcha.html +30 -0
  35. package/docs/crawl/core/components/ui/card/card.html +99 -0
  36. package/docs/crawl/core/components/ui/divider/divider.html +58 -0
  37. package/docs/crawl/core/components/ui/form/checkbox/checkbox.html +73 -0
  38. package/docs/crawl/core/components/ui/form/fieldset/fieldset.html +135 -0
  39. package/docs/crawl/core/components/ui/form/form-actions/form-actions.html +79 -0
  40. package/docs/crawl/core/components/ui/form/form-layout/form-layout.html +62 -0
  41. package/docs/crawl/core/components/ui/form/input/input.html +109 -0
  42. package/docs/crawl/core/components/ui/form/input-autocomplete/input-autocomplete.html +106 -0
  43. package/docs/crawl/core/components/ui/form/radio/radio.html +56 -0
  44. package/docs/crawl/core/components/ui/form/select/select.html +73 -0
  45. package/docs/crawl/core/components/ui/form/switch/switch.html +56 -0
  46. package/docs/crawl/core/components/ui/form/textarea/textarea.html +59 -0
  47. package/docs/crawl/core/components/ui/group/group.html +82 -0
  48. package/docs/crawl/core/components/ui/icon/icon.html +133 -0
  49. package/docs/crawl/core/components/ui/image/image.html +110 -0
  50. package/docs/crawl/core/components/ui/link/link.html +42 -0
  51. package/docs/crawl/core/components/ui/loader/loader.html +50 -0
  52. package/docs/crawl/core/components/ui/menu/menu.html +322 -0
  53. package/docs/crawl/core/components/ui/modal/modal.html +117 -0
  54. package/docs/crawl/core/components/ui/pop/pop.html +102 -0
  55. package/docs/crawl/core/components/ui/progress/progress.html +58 -0
  56. package/docs/crawl/core/components/ui/table/table.html +413 -0
  57. package/docs/crawl/core/components/ui/toast/toast.html +122 -0
  58. package/docs/crawl/core/components/ui/tooltip/tooltip.html +79 -0
  59. package/docs/crawl/docs/_core-concept/dataFlow.html +166 -0
  60. package/docs/crawl/docs/_core-concept/overview.html +66 -0
  61. package/docs/crawl/docs/_core-concept/subscriber.html +75 -0
  62. package/docs/crawl/docs/_decorators/ancestor-attribute.html +116 -0
  63. package/docs/crawl/docs/_decorators/auto-subscribe.html +202 -0
  64. package/docs/crawl/docs/_decorators/bind.html +141 -0
  65. package/docs/crawl/docs/_decorators/get.html +120 -0
  66. package/docs/crawl/docs/_decorators/handle.html +165 -0
  67. package/docs/crawl/docs/_decorators/on-assign.html +355 -0
  68. package/docs/crawl/docs/_decorators/patch.html +52 -0
  69. package/docs/crawl/docs/_decorators/post.html +102 -0
  70. package/docs/crawl/docs/_decorators/publish.html +61 -0
  71. package/docs/crawl/docs/_decorators/put.html +49 -0
  72. package/docs/crawl/docs/_decorators/subscribe.html +109 -0
  73. package/docs/crawl/docs/_decorators/wait-for-ancestors.html +137 -0
  74. package/docs/crawl/docs/_directives/sub.html +93 -0
  75. package/docs/crawl/docs/_getting-started/ai-agents.html +134 -0
  76. package/docs/crawl/docs/_getting-started/concorde-manual-install.html +100 -0
  77. package/docs/crawl/docs/_getting-started/concorde-outside.html +58 -0
  78. package/docs/crawl/docs/_getting-started/create-a-component.html +111 -0
  79. package/docs/crawl/docs/_getting-started/my-first-component.html +189 -0
  80. package/docs/crawl/docs/_getting-started/my-first-subscriber.html +110 -0
  81. package/docs/crawl/docs/_getting-started/pubsub.html +62 -0
  82. package/docs/crawl/docs/_getting-started/start.html +76 -0
  83. package/docs/crawl/docs/_getting-started/theming.html +85 -0
  84. package/docs/crawl/docs/_misc/api-configuration.html +149 -0
  85. package/docs/crawl/docs/_misc/dataProviderKey.html +135 -0
  86. package/docs/crawl/docs/_misc/docs-mock-api.html +146 -0
  87. package/docs/crawl/docs/_misc/dynamic-path.html +180 -0
  88. package/docs/crawl/docs/_misc/endpoint.html +50 -0
  89. package/docs/crawl/docs/_misc/html-integration.html +47 -0
  90. package/docs/crawl/hubs/decorators.html +467 -0
  91. package/docs/crawl/hubs/form-components.html +357 -0
  92. package/docs/crawl/hubs/remote-data.html +374 -0
  93. package/docs/crawl/index.html +99 -0
  94. package/docs/docs-mock-api-sw.js +3 -1
  95. package/docs/docs-mock-api-sw.js.map +2 -2
  96. package/docs/index.html +3 -3
  97. package/docs/llms-full.txt +5423 -0
  98. package/docs/llms.txt +102 -0
  99. package/docs/robots.txt +15 -1
  100. package/docs/sitemap.xml +231 -0
  101. package/docs/src/docs/_decorators/ancestor-attribute.md +56 -8
  102. package/docs/src/docs/_decorators/get.md +47 -1
  103. package/docs/src/docs/search/docs-search.json +92 -2
  104. package/docs/src/tsconfig.declarations.json +22 -0
  105. package/docs/src/tsconfig.emit.json +23 -0
  106. package/docs/src/tsconfig.json +3 -285
  107. package/index.html +1 -0
  108. package/package.json +1 -1
  109. package/public/docs-mock-api-sw.js +3 -1
  110. package/public/docs-mock-api-sw.js.map +2 -2
  111. package/public/robots.txt +15 -1
  112. package/scripts/generate-crawl-docs.mjs +569 -0
  113. package/scripts/post-build-docs.js +18 -14
  114. package/src/core/decorators/api.spec.ts +74 -4
  115. package/src/core/decorators/api.ts +103 -20
  116. package/src/core/decorators/subscriber/ancestorAttribute.spec.ts +42 -0
  117. package/src/core/utils/HTML.ts +4 -5
  118. package/src/docs/_decorators/get.md +47 -1
  119. package/src/docs/components/docs-demo-sources.ts +7 -2
  120. package/src/docs/example/decorators-demo-geo.ts +5 -0
  121. package/src/docs/example/decorators-demo-subscribe-publish-get-demos.ts +72 -31
  122. package/src/docs/mock-api/router.ts +3 -1
  123. package/src/docs/search/docs-search.json +92 -2
@@ -0,0 +1,569 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { marked } from "marked";
5
+ import { gfmHeadingId } from "marked-gfm-heading-id";
6
+
7
+ const SITE_ORIGIN = "https://concorde.supersoniks.org";
8
+ const SRC_DIR = path.resolve("src");
9
+ const DOCS_OUT = path.resolve("docs");
10
+ const CRAWL_DIR = path.join(DOCS_OUT, "crawl");
11
+
12
+ const SONIC_CODE_BLOCK_RE = /<sonic-code\b[\s\S]*?<\/sonic-code>/gi;
13
+ const DOC_HASH_LINK_RE = /#([^)\s"']+)\.md\/([^)\s"'#]+)/g;
14
+
15
+ marked.use(gfmHeadingId());
16
+
17
+ function listMdFiles(dir, files = []) {
18
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
19
+ const filePath = path.join(dir, entry.name);
20
+ if (entry.isDirectory()) {
21
+ listMdFiles(filePath, files);
22
+ } else if (entry.name.endsWith(".md") || entry.name.endsWith(".mdx")) {
23
+ files.push(filePath);
24
+ }
25
+ }
26
+ return files;
27
+ }
28
+
29
+ function escapeHtml(text) {
30
+ return text
31
+ .replace(/&/g, "&amp;")
32
+ .replace(/</g, "&lt;")
33
+ .replace(/>/g, "&gt;")
34
+ .replace(/"/g, "&quot;");
35
+ }
36
+
37
+ function preprocessSonicCode(markdown) {
38
+ return markdown.replace(SONIC_CODE_BLOCK_RE, (block) => {
39
+ const langMatch = block.match(/language="([^"]+)"/);
40
+ const lang = langMatch ? langMatch[1] : "";
41
+ const templateMatch = block.match(/<template>([\s\S]*?)<\/template>/);
42
+ const code = templateMatch ? templateMatch[1].trim() : "";
43
+ return `\n\n\`\`\`${lang}\n${code}\n\`\`\`\n\n`;
44
+ });
45
+ }
46
+
47
+ function mdRelativePath(absPath) {
48
+ return path.relative(SRC_DIR, absPath).replace(/\\/g, "/");
49
+ }
50
+
51
+ function crawlHtmlPath(mdRel) {
52
+ return path.join("crawl", mdRel.replace(/\.mdx?$/, ".html")).replace(/\\/g, "/");
53
+ }
54
+
55
+ function docId(mdRel) {
56
+ return mdRel.replace(/\.mdx?$/, "");
57
+ }
58
+
59
+ function relativeCrawlLink(fromMdRel, toMdPath, anchor) {
60
+ const fromHtml = crawlHtmlPath(fromMdRel);
61
+ const toHtml = crawlHtmlPath(`${toMdPath}.md`);
62
+ const rel = path
63
+ .relative(path.dirname(fromHtml), toHtml)
64
+ .replace(/\\/g, "/");
65
+ const href = rel.startsWith(".") ? rel : `./${rel}`;
66
+ return `${href}#${anchor}`;
67
+ }
68
+
69
+ function rewriteDocLinks(text, fromMdRel) {
70
+ return text.replace(DOC_HASH_LINK_RE, (match, mdPath, anchor) => {
71
+ return relativeCrawlLink(fromMdRel, mdPath, anchor);
72
+ });
73
+ }
74
+
75
+ function extractTitle(markdown) {
76
+ const match = markdown.match(/^#\s+(.+)$/m);
77
+ return match ? match[1].trim() : "Concorde";
78
+ }
79
+
80
+ function markdownToPlainText(markdown) {
81
+ let text = preprocessSonicCode(markdown);
82
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
83
+ text = text.replace(/^#+\s+/gm, "");
84
+ text = text.replace(/```[\s\S]*?```/g, (block) =>
85
+ block.replace(/```\w*\n?/g, "").replace(/```/g, "\n"),
86
+ );
87
+ text = text.replace(/[*_`]/g, "");
88
+ text = text.replace(/<[^>]+>/g, " ");
89
+ return text.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
90
+ }
91
+
92
+ function cleanDescriptionLine(text) {
93
+ return text
94
+ .replace(/\*\*/g, "")
95
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
96
+ .replace(/`([^`]+)`/g, "$1")
97
+ .replace(/\s+/g, " ")
98
+ .trim();
99
+ }
100
+
101
+ function extractDescription(markdown, title) {
102
+ const lines = markdown.split("\n");
103
+ let pastTitle = false;
104
+ for (const line of lines) {
105
+ if (/^#\s+/.test(line)) {
106
+ pastTitle = true;
107
+ continue;
108
+ }
109
+ if (!pastTitle) continue;
110
+ const trimmed = line.trim();
111
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("|")) continue;
112
+ if (trimmed.startsWith("<")) continue;
113
+ const cleaned = cleanDescriptionLine(trimmed);
114
+ if (cleaned) return cleaned.slice(0, 200);
115
+ }
116
+
117
+ const templateMatch = markdown.match(/<template>([\s\S]*?)<\/template>/);
118
+ if (templateMatch) {
119
+ const fromTemplate = cleanDescriptionLine(
120
+ templateMatch[1].replace(/<[^>]+>/g, " "),
121
+ );
122
+ if (fromTemplate.length > 15) {
123
+ return `${title}: ${fromTemplate}`.slice(0, 200);
124
+ }
125
+ }
126
+
127
+ const stripped = markdownToPlainText(markdown.replace(/^#\s+.+$/m, ""));
128
+ if (stripped.length > 15) {
129
+ return stripped.slice(0, 200);
130
+ }
131
+
132
+ return `Concorde documentation: ${title}`;
133
+ }
134
+
135
+ function docKind(mdRel) {
136
+ if (mdRel.includes("/_decorators/")) return "decorator";
137
+ if (mdRel.includes("/_directives/")) return "directive";
138
+ if (mdRel.includes("/_getting-started/")) return "guide";
139
+ if (mdRel.includes("/_core-concept/")) return "concept";
140
+ if (mdRel.includes("/_misc/")) return "reference";
141
+ if (mdRel.includes("/components/functional/")) return "functional component";
142
+ if (mdRel.includes("/components/ui/")) return "UI component";
143
+ return "documentation";
144
+ }
145
+
146
+ function extractKeywords(mdRel, title, raw) {
147
+ const keywords = new Set(["Concorde", "supersoniks"]);
148
+ const baseName = path.basename(mdRel, path.extname(mdRel));
149
+ const id = docId(mdRel);
150
+
151
+ keywords.add(id);
152
+ keywords.add(baseName);
153
+ keywords.add(title);
154
+
155
+ if (title.startsWith("@")) {
156
+ keywords.add("decorator");
157
+ keywords.add(title.slice(1));
158
+ }
159
+
160
+ const kind = docKind(mdRel);
161
+ keywords.add(kind);
162
+
163
+ if (mdRel.includes("/components/ui/form/")) {
164
+ keywords.add("form", "FormElement", "formDataProvider");
165
+ }
166
+ if (mdRel.includes("/components/")) {
167
+ keywords.add(`sonic-${baseName}`);
168
+ keywords.add("web component", "Lit");
169
+ }
170
+ if (mdRel.includes("/_decorators/")) {
171
+ keywords.add("decorator", "DataProvider", "ApiResult", "Endpoint");
172
+ }
173
+ if (mdRel.includes("/_core-concept/")) {
174
+ keywords.add("DataProvider", "data flow");
175
+ }
176
+
177
+ const tickMatches = raw.slice(0, 3000).match(/`([^`]{2,40})`/g);
178
+ if (tickMatches) {
179
+ for (const tick of tickMatches.slice(0, 10)) {
180
+ keywords.add(tick.replace(/`/g, "").replace(/<[^>]+>/g, ""));
181
+ }
182
+ }
183
+
184
+ return [...keywords].join(", ");
185
+ }
186
+
187
+ function pageHash(mdRel) {
188
+ const base = mdRel.replace(/\.mdx?$/, "");
189
+ const anchor = base.split("/").pop() || "page";
190
+ return `#${base}.md/${anchor}`;
191
+ }
192
+
193
+ function crawlPageUrl(crawlRel) {
194
+ return `${SITE_ORIGIN}/${crawlRel}`;
195
+ }
196
+
197
+ function buildLeadParagraph({ title, mdRel, description, canonical, keywords }) {
198
+ const id = docId(mdRel);
199
+ const kind = docKind(mdRel);
200
+ return `<p class="doc-lead" id="doc-summary"><strong>Concorde ${escapeHtml(kind)} — ${escapeHtml(title)}</strong>. ${escapeHtml(description)} <span>Doc ID: <code>${escapeHtml(id)}</code>. Keywords: ${escapeHtml(keywords)}. URL: <a href="${escapeHtml(canonical)}">${escapeHtml(canonical)}</a>.</span></p>`;
201
+ }
202
+
203
+ function renderPage({ mdRel, title, description, keywords, bodyHtml }) {
204
+ const crawlRel = crawlHtmlPath(mdRel);
205
+ const canonical = crawlPageUrl(crawlRel);
206
+ const interactiveDoc = `${SITE_ORIGIN}/${pageHash(mdRel)}`;
207
+ const depth = path.dirname(crawlRel).split("/").length - 1;
208
+ const indexHref = `${"../".repeat(depth)}index.html`;
209
+ const id = docId(mdRel);
210
+ const leadHtml = buildLeadParagraph({
211
+ title,
212
+ mdRel,
213
+ description,
214
+ canonical,
215
+ keywords,
216
+ });
217
+
218
+ return `<!DOCTYPE html>
219
+ <html lang="fr" data-crawl-url="${escapeHtml(canonical)}" data-doc-id="${escapeHtml(id)}">
220
+ <head>
221
+ <meta charset="UTF-8">
222
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
223
+ <title>${escapeHtml(title)} — Concorde</title>
224
+ <meta name="description" content="${escapeHtml(description)}">
225
+ <meta name="keywords" content="${escapeHtml(keywords)}">
226
+ <meta name="concorde-doc-id" content="${escapeHtml(id)}">
227
+ <link rel="canonical" href="${escapeHtml(canonical)}">
228
+ </head>
229
+ <body>
230
+ <header>
231
+ <p><a href="${indexHref}">Concorde documentation (crawl)</a> · <a href="${escapeHtml(interactiveDoc)}" rel="nofollow">interactive version</a></p>
232
+ </header>
233
+ <main>
234
+ <article>
235
+ ${leadHtml}
236
+ ${bodyHtml}
237
+ </article>
238
+ </main>
239
+ </body>
240
+ </html>
241
+ `;
242
+ }
243
+
244
+ function renderIndex(pages) {
245
+ const items = pages
246
+ .map(
247
+ (p) =>
248
+ ` <li><a href="${escapeHtml(p.crawlHref)}">${escapeHtml(p.title)}</a> <small>(${escapeHtml(p.docId)})</small></li>`,
249
+ )
250
+ .join("\n");
251
+
252
+ return `<!DOCTYPE html>
253
+ <html lang="fr" data-doc-id="crawl-index">
254
+ <head>
255
+ <meta charset="UTF-8">
256
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
257
+ <title>Concorde documentation index</title>
258
+ <meta name="description" content="Static crawl index for Concorde framework documentation. All pages use .html under /crawl/.">
259
+ <meta name="keywords" content="Concorde, documentation index, web components, Lit, DataProvider">
260
+ <link rel="canonical" href="${SITE_ORIGIN}/crawl/index.html">
261
+ </head>
262
+ <body>
263
+ <header>
264
+ <h1>Concorde documentation</h1>
265
+ <p>Static pages for crawlers and AI indexing. Interactive docs: <a href="${SITE_ORIGIN}/">${SITE_ORIGIN}/</a></p>
266
+ <p>Full text mirror: <a href="${SITE_ORIGIN}/llms-full.txt">llms-full.txt</a></p>
267
+ <ul>
268
+ <li><a href="hubs/decorators.html">Decorators hub (@get, @post, …)</a></li>
269
+ <li><a href="hubs/remote-data.html">Remote data hub (@get, List, Queue)</a></li>
270
+ <li><a href="hubs/form-components.html">Form components hub</a></li>
271
+ </ul>
272
+ </header>
273
+ <main>
274
+ <ul>
275
+ ${items}
276
+ </ul>
277
+ </main>
278
+ </body>
279
+ </html>
280
+ `;
281
+ }
282
+
283
+ function writeFileEnsuringDir(filePath, content) {
284
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
285
+ fs.writeFileSync(filePath, content);
286
+ }
287
+
288
+ function generateLlmsTxt(pages, hubs = []) {
289
+ const lines = [
290
+ "# Concorde",
291
+ "",
292
+ "> Web components framework (Lit, DataProvider, decorators).",
293
+ "",
294
+ "Documentation for AI indexers lives under `/crawl/` as **HTML** (`.html`), not Markdown.",
295
+ "Example: https://concorde.supersoniks.org/crawl/docs/_decorators/post.html",
296
+ "Do not fetch `/crawl/**/*.md` (does not exist) nor `/src/docs/**/*.md` (SPA source only).",
297
+ "",
298
+ `Full text: ${SITE_ORIGIN}/llms-full.txt`,
299
+ "",
300
+ "## Topic hubs (start here for RAG)",
301
+ "",
302
+ ];
303
+
304
+ for (const h of hubs) {
305
+ lines.push(
306
+ `- [${h.title}](${SITE_ORIGIN}/${h.crawlPath}) (${h.docId}, ${h.sectionCount} pages)`,
307
+ );
308
+ }
309
+
310
+ lines.push("", "## Docs", "");
311
+
312
+ for (const p of pages) {
313
+ lines.push(
314
+ `- [${p.title}](${SITE_ORIGIN}/${p.crawlPath}) (${p.docId}): ${p.description}`,
315
+ );
316
+ }
317
+
318
+ lines.push("", "## Optional", "", `- [Full index](${SITE_ORIGIN}/crawl/index.html)`);
319
+ return lines.join("\n");
320
+ }
321
+
322
+ function generateLlmsFullTxt(pages, hubs = []) {
323
+ const lines = [
324
+ "# Concorde — full documentation (plain text)",
325
+ "",
326
+ "> Mirror for AI indexers. Each page is a section below.",
327
+ "> HTML URLs use /crawl/**/*.html — never .md under /crawl/.",
328
+ "",
329
+ "## Topic hubs",
330
+ "",
331
+ ];
332
+
333
+ for (const h of hubs) {
334
+ lines.push(`- ${h.title}: ${SITE_ORIGIN}/${h.crawlPath} (${h.docId})`);
335
+ }
336
+
337
+ lines.push("");
338
+
339
+ for (const p of pages) {
340
+ lines.push(
341
+ "---",
342
+ "",
343
+ `## ${p.title}`,
344
+ "",
345
+ `- URL: ${SITE_ORIGIN}/${p.crawlPath}`,
346
+ `- Doc ID: ${p.docId}`,
347
+ `- Keywords: ${p.keywords}`,
348
+ "",
349
+ p.plainText,
350
+ "",
351
+ );
352
+ }
353
+
354
+ return lines.join("\n");
355
+ }
356
+
357
+ const TOPIC_HUBS = [
358
+ {
359
+ slug: "decorators",
360
+ title: "Concorde decorators reference",
361
+ docId: "hubs/decorators",
362
+ intro:
363
+ "Hub for all Concorde Lit decorators: @get @post @put @patch @subscribe @publish @bind @handle and related. Use this page when asking about any decorator.",
364
+ keywords:
365
+ "decorator, @get, @post, @put, @patch, @subscribe, @publish, @bind, @handle, ApiResult, Endpoint, DataProviderKey",
366
+ filter: (p) => p.docId.startsWith("docs/_decorators/"),
367
+ },
368
+ {
369
+ slug: "remote-data",
370
+ title: "Fetching remote data in Concorde",
371
+ docId: "hubs/remote-data",
372
+ intro:
373
+ "Hub for loading online data: @get decorator, Endpoint, API configuration, dynamic paths, sonic-list fetch, sonic-queue, legacy sonic-fetch.",
374
+ keywords:
375
+ "@get, fetch, remote data, API, Endpoint, ApiResult, serviceURL, sonic-list, sonic-queue, HTTP GET",
376
+ filter: (p) =>
377
+ [
378
+ "docs/_decorators/get",
379
+ "docs/_decorators/post",
380
+ "docs/_decorators/put",
381
+ "docs/_decorators/patch",
382
+ "docs/_misc/endpoint",
383
+ "docs/_misc/api-configuration",
384
+ "docs/_misc/dynamic-path",
385
+ "docs/_core-concept/dataFlow",
386
+ "core/components/functional/fetch/fetch",
387
+ "core/components/functional/list/list",
388
+ "core/components/functional/queue/queue",
389
+ ].includes(p.docId),
390
+ },
391
+ {
392
+ slug: "form-components",
393
+ title: "Concorde form components",
394
+ docId: "hubs/form-components",
395
+ intro:
396
+ "Hub for form UI: sonic-input, sonic-select, sonic-checkbox, sonic-submit, formDataProvider, FormElement.",
397
+ keywords:
398
+ "form, FormElement, formDataProvider, sonic-input, sonic-select, sonic-checkbox, sonic-submit",
399
+ filter: (p) => p.docId.includes("/components/ui/form/"),
400
+ },
401
+ ];
402
+
403
+ function hubCrawlPath(slug) {
404
+ return `crawl/hubs/${slug}.html`;
405
+ }
406
+
407
+ function relativeHubLink(fromHubSlug, targetCrawlPath) {
408
+ const from = hubCrawlPath(fromHubSlug);
409
+ const rel = path.relative(path.dirname(from), targetCrawlPath).replace(/\\/g, "/");
410
+ return rel.startsWith(".") ? rel : `./${rel}`;
411
+ }
412
+
413
+ function renderHubPage(hub, sections) {
414
+ const crawlRel = hubCrawlPath(hub.slug);
415
+ const canonical = crawlPageUrl(crawlRel);
416
+ const sectionHtml = sections
417
+ .map((p) => {
418
+ const excerpt = p.plainText.slice(0, 1200);
419
+ const href = relativeHubLink(hub.slug, p.crawlPath);
420
+ return `<section id="${escapeHtml(p.docId.replace(/\//g, "-"))}" data-doc-id="${escapeHtml(p.docId)}">
421
+ <h2><a href="${escapeHtml(href)}">${escapeHtml(p.title)}</a></h2>
422
+ <p><strong>Doc ID:</strong> <code>${escapeHtml(p.docId)}</code> · <a href="${escapeHtml(href)}">full page</a></p>
423
+ <p>${escapeHtml(p.description)}</p>
424
+ <pre class="doc-excerpt">${escapeHtml(excerpt)}</pre>
425
+ </section>`;
426
+ })
427
+ .join("\n");
428
+
429
+ const leadHtml = buildLeadParagraph({
430
+ title: hub.title,
431
+ mdRel: `${hub.docId}.md`,
432
+ description: hub.intro,
433
+ canonical,
434
+ keywords: hub.keywords,
435
+ });
436
+
437
+ return `<!DOCTYPE html>
438
+ <html lang="fr" data-crawl-url="${escapeHtml(canonical)}" data-doc-id="${escapeHtml(hub.docId)}">
439
+ <head>
440
+ <meta charset="UTF-8">
441
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
442
+ <title>${escapeHtml(hub.title)} — Concorde</title>
443
+ <meta name="description" content="${escapeHtml(hub.intro)}">
444
+ <meta name="keywords" content="${escapeHtml(hub.keywords)}">
445
+ <meta name="concorde-doc-id" content="${escapeHtml(hub.docId)}">
446
+ <link rel="canonical" href="${escapeHtml(canonical)}">
447
+ </head>
448
+ <body>
449
+ <header>
450
+ <p><a href="../index.html">Concorde documentation (crawl)</a></p>
451
+ </header>
452
+ <main>
453
+ <article>
454
+ ${leadHtml}
455
+ ${sectionHtml}
456
+ </article>
457
+ </main>
458
+ </body>
459
+ </html>
460
+ `;
461
+ }
462
+
463
+ function generateTopicHubs(pages) {
464
+ const hubs = [];
465
+ for (const hub of TOPIC_HUBS) {
466
+ const sections = pages.filter(hub.filter);
467
+ if (sections.length === 0) continue;
468
+ const crawlRel = hubCrawlPath(hub.slug);
469
+ writeFileEnsuringDir(
470
+ path.join(DOCS_OUT, crawlRel),
471
+ renderHubPage(hub, sections),
472
+ );
473
+ hubs.push({
474
+ ...hub,
475
+ crawlPath: crawlRel,
476
+ sectionCount: sections.length,
477
+ });
478
+ }
479
+ return hubs;
480
+ }
481
+
482
+ function generateSitemap(pages, hubs = []) {
483
+ const indexUrl = ` <url>\n <loc>${SITE_ORIGIN}/crawl/index.html</loc>\n </url>`;
484
+ const hubUrls = hubs
485
+ .map((h) => ` <url>\n <loc>${SITE_ORIGIN}/${h.crawlPath}</loc>\n </url>`)
486
+ .join("\n");
487
+ const urls = pages
488
+ .map((p) => ` <url>\n <loc>${SITE_ORIGIN}/${p.crawlPath}</loc>\n </url>`)
489
+ .join("\n");
490
+
491
+ return `<?xml version="1.0" encoding="UTF-8"?>
492
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
493
+ ${indexUrl}
494
+ ${hubUrls}
495
+ ${urls}
496
+ </urlset>
497
+ `;
498
+ }
499
+
500
+ export function generateCrawlDocs() {
501
+ if (!fs.existsSync(DOCS_OUT)) {
502
+ console.warn("generate-crawl-docs: docs/ not found, skipping");
503
+ return;
504
+ }
505
+
506
+ if (fs.existsSync(CRAWL_DIR)) {
507
+ fs.rmSync(CRAWL_DIR, { recursive: true, force: true });
508
+ }
509
+
510
+ const mdFiles = listMdFiles(SRC_DIR).sort();
511
+ const pages = [];
512
+
513
+ for (const absPath of mdFiles) {
514
+ const mdRel = mdRelativePath(absPath);
515
+ const raw = fs.readFileSync(absPath, "utf8");
516
+ const title = extractTitle(raw);
517
+ const description = extractDescription(raw, title);
518
+ const keywords = extractKeywords(mdRel, title, raw);
519
+ const plainText = markdownToPlainText(raw);
520
+ const markdown = rewriteDocLinks(preprocessSonicCode(raw), mdRel);
521
+ const bodyHtml = marked.parse(markdown);
522
+ const crawlRel = crawlHtmlPath(mdRel);
523
+ const outPath = path.join(DOCS_OUT, crawlRel);
524
+ const id = docId(mdRel);
525
+
526
+ writeFileEnsuringDir(
527
+ outPath,
528
+ renderPage({ mdRel, title, description, keywords, bodyHtml }),
529
+ );
530
+
531
+ pages.push({
532
+ mdRel,
533
+ docId: id,
534
+ title,
535
+ description,
536
+ keywords,
537
+ plainText,
538
+ crawlPath: crawlRel,
539
+ crawlHref: crawlRel.replace(/^crawl\//, ""),
540
+ });
541
+ }
542
+
543
+ writeFileEnsuringDir(
544
+ path.join(CRAWL_DIR, "index.html"),
545
+ renderIndex(
546
+ pages.map((p) => ({
547
+ ...p,
548
+ crawlHref: p.mdRel.replace(/\.mdx?$/, ".html"),
549
+ })),
550
+ ),
551
+ );
552
+
553
+ const hubs = generateTopicHubs(pages);
554
+
555
+ fs.writeFileSync(path.join(DOCS_OUT, "sitemap.xml"), generateSitemap(pages, hubs));
556
+ fs.writeFileSync(path.join(DOCS_OUT, "llms.txt"), generateLlmsTxt(pages, hubs));
557
+ fs.writeFileSync(
558
+ path.join(DOCS_OUT, "llms-full.txt"),
559
+ generateLlmsFullTxt(pages, hubs),
560
+ );
561
+
562
+ console.info(
563
+ `generate-crawl-docs: ${pages.length} pages, ${hubs.length} hubs → docs/crawl/, sitemap.xml, llms.txt, llms-full.txt`,
564
+ );
565
+ }
566
+
567
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
568
+ generateCrawlDocs();
569
+ }
@@ -1,6 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { exec } from "child_process";
3
+ import { generateCrawlDocs } from "./generate-crawl-docs.mjs";
4
4
 
5
5
  const src = path.resolve("src");
6
6
  const docs = path.resolve("docs");
@@ -46,34 +46,32 @@ function copyDocs() {
46
46
  }
47
47
  }
48
48
  copyDocs();
49
+ generateCrawlDocs();
49
50
 
50
51
  /**
51
52
  * Par rapport au projet, regarder si ../concorde_doc existe et si oui, remplacer le contenu de /concorde_doc par celui de contenu dans docs/ en dehors des fichiers spécifiques à git .git .gitignore .gitattributes)
52
53
  */
53
54
 
54
55
  function copyToConcordeDoc() {
55
-
56
-
57
56
  let concordeDoc = path.resolve("../concorde_doc");
58
-
59
-
60
-
61
- if (fs.existsSync(concordeDoc)) {
57
+
58
+ if (!fs.existsSync(concordeDoc)) {
59
+ return;
60
+ }
61
+
62
+ try {
62
63
  /* first remove all files/foilders in concorde_doc except .git folder and .gitignore file */
63
64
  let files = listFiles(concordeDoc);
64
65
  for (const file of files) {
65
66
  if (file.indexOf(".git") == -1) {
66
67
  fs.unlinkSync(file);
67
68
  }
68
-
69
69
  }
70
70
 
71
-
72
71
  /*then copy the new files*/
73
72
  files = listFiles(docs);
74
73
  for (const file of files) {
75
-
76
- let idx = file.indexOf("docs/")+5;
74
+ let idx = file.indexOf("docs/") + 5;
77
75
  let dest = file.substring(idx);
78
76
  let split = dest.split("/");
79
77
  let dir = path.resolve(concordeDoc);
@@ -86,13 +84,19 @@ function copyToConcordeDoc() {
86
84
  dir += "/" + split[split.length - 1];
87
85
  fs.copyFileSync(file, dir);
88
86
  }
89
- //log an Emoji finger showing the user what to do next
90
87
 
91
-
92
88
  console.info("---");
93
89
  console.info("NOW run the following command to publish the docs");
94
- console.info("👉 cd ../concorde_doc && git add . && git commit -m 'update doc' && git push && cd ../concorde");
90
+ console.info(
91
+ "👉 cd ../concorde_doc && git add . && git commit -m 'update doc' && git push && cd ../concorde",
92
+ );
95
93
  console.info("---");
94
+ } catch (err) {
95
+ console.warn(
96
+ "copyToConcordeDoc: skipped (",
97
+ err.code || err.message,
98
+ ") — docs/ and docs/crawl/ were still generated.",
99
+ );
96
100
  }
97
101
  }
98
102