@dualmark/astro 0.2.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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/dist/endpoints/collection.cjs +24 -0
  4. package/dist/endpoints/collection.cjs.map +1 -0
  5. package/dist/endpoints/collection.d.cts +27 -0
  6. package/dist/endpoints/collection.d.ts +27 -0
  7. package/dist/endpoints/collection.js +22 -0
  8. package/dist/endpoints/collection.js.map +1 -0
  9. package/dist/endpoints/listing.cjs +34 -0
  10. package/dist/endpoints/listing.cjs.map +1 -0
  11. package/dist/endpoints/listing.d.cts +20 -0
  12. package/dist/endpoints/listing.d.ts +20 -0
  13. package/dist/endpoints/listing.js +32 -0
  14. package/dist/endpoints/listing.js.map +1 -0
  15. package/dist/endpoints/llms-txt.cjs +23 -0
  16. package/dist/endpoints/llms-txt.cjs.map +1 -0
  17. package/dist/endpoints/llms-txt.d.cts +12 -0
  18. package/dist/endpoints/llms-txt.d.ts +12 -0
  19. package/dist/endpoints/llms-txt.js +21 -0
  20. package/dist/endpoints/llms-txt.js.map +1 -0
  21. package/dist/endpoints/parameterized.cjs +18 -0
  22. package/dist/endpoints/parameterized.cjs.map +1 -0
  23. package/dist/endpoints/parameterized.d.cts +25 -0
  24. package/dist/endpoints/parameterized.d.ts +25 -0
  25. package/dist/endpoints/parameterized.js +16 -0
  26. package/dist/endpoints/parameterized.js.map +1 -0
  27. package/dist/endpoints/static.cjs +17 -0
  28. package/dist/endpoints/static.cjs.map +1 -0
  29. package/dist/endpoints/static.d.cts +11 -0
  30. package/dist/endpoints/static.d.ts +11 -0
  31. package/dist/endpoints/static.js +15 -0
  32. package/dist/endpoints/static.js.map +1 -0
  33. package/dist/index.cjs +316 -0
  34. package/dist/index.cjs.map +1 -0
  35. package/dist/index.d.cts +54 -0
  36. package/dist/index.d.ts +54 -0
  37. package/dist/index.js +307 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/middleware.cjs +22 -0
  40. package/dist/middleware.cjs.map +1 -0
  41. package/dist/middleware.d.cts +9 -0
  42. package/dist/middleware.d.ts +9 -0
  43. package/dist/middleware.js +19 -0
  44. package/dist/middleware.js.map +1 -0
  45. package/dist/types-BPk2cf2p.d.cts +64 -0
  46. package/dist/types-BPk2cf2p.d.ts +64 -0
  47. package/dist/virtual.cjs +10 -0
  48. package/dist/virtual.cjs.map +1 -0
  49. package/dist/virtual.d.cts +7 -0
  50. package/dist/virtual.d.ts +7 -0
  51. package/dist/virtual.js +8 -0
  52. package/dist/virtual.js.map +1 -0
  53. package/package.json +102 -0
package/dist/index.js ADDED
@@ -0,0 +1,307 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
2
+ import { fileURLToPath } from 'url';
3
+ import { join, relative, sep } from 'path';
4
+ import { videoConverter, toolConverter, pseoConverter, pricingConverter, legalConverter, glossaryConverter, featureConverter, docsConverter, compareConverter, changelogConverter, caseStudyConverter, blogConverter } from '@dualmark/converters';
5
+
6
+ // src/integration.ts
7
+
8
+ // src/config-validation.ts
9
+ var DualmarkConfigError = class extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = "DualmarkConfigError";
13
+ }
14
+ };
15
+ function resolveConfig(input) {
16
+ if (!input || typeof input !== "object") {
17
+ throw new DualmarkConfigError("Dualmark config must be an object");
18
+ }
19
+ if (typeof input.siteUrl !== "string" || !input.siteUrl) {
20
+ throw new DualmarkConfigError("Dualmark config: siteUrl is required (e.g. 'https://example.com')");
21
+ }
22
+ try {
23
+ new URL(input.siteUrl);
24
+ } catch {
25
+ throw new DualmarkConfigError(`Dualmark config: siteUrl is not a valid URL: ${input.siteUrl}`);
26
+ }
27
+ if (input.siteUrl.endsWith("/")) {
28
+ throw new DualmarkConfigError(`Dualmark config: siteUrl must not end with '/': ${input.siteUrl}`);
29
+ }
30
+ const collections = input.collections ?? {};
31
+ for (const [name, c] of Object.entries(collections)) {
32
+ if (!c.converter) {
33
+ throw new DualmarkConfigError(`Dualmark config: collection '${name}' is missing 'converter'`);
34
+ }
35
+ if (c.route && c.route.startsWith("/")) {
36
+ throw new DualmarkConfigError(
37
+ `Dualmark config: collection '${name}' route should not start with '/' (got '${c.route}')`
38
+ );
39
+ }
40
+ }
41
+ const staticPages = input.staticPages ?? [];
42
+ for (const sp of staticPages) {
43
+ if (!sp.pattern.startsWith("/")) {
44
+ throw new DualmarkConfigError(
45
+ `Dualmark config: staticPages.pattern must start with '/' (got '${sp.pattern}')`
46
+ );
47
+ }
48
+ if (typeof sp.render !== "function") {
49
+ throw new DualmarkConfigError(
50
+ `Dualmark config: staticPages.render for '${sp.pattern}' must be a function`
51
+ );
52
+ }
53
+ }
54
+ const parameterizedRoutes = input.parameterizedRoutes ?? [];
55
+ for (const pr of parameterizedRoutes) {
56
+ if (!pr.pattern.includes("[")) {
57
+ throw new DualmarkConfigError(
58
+ `Dualmark config: parameterizedRoutes.pattern must contain at least one [param] (got '${pr.pattern}')`
59
+ );
60
+ }
61
+ }
62
+ return {
63
+ siteUrl: input.siteUrl,
64
+ collections,
65
+ staticPages,
66
+ parameterizedRoutes,
67
+ llmsTxt: input.llmsTxt,
68
+ middleware: {
69
+ injectLinkHeader: input.middleware?.injectLinkHeader !== false
70
+ },
71
+ headers: {
72
+ cacheControl: input.headers?.cacheControl ?? "public, max-age=3600",
73
+ noindex: input.headers?.noindex !== false
74
+ }
75
+ };
76
+ }
77
+
78
+ // src/integration.ts
79
+ var GENERATED_DIR_NAME = ".dualmark-generated";
80
+ function rel(from, to) {
81
+ const r = relative(from, to);
82
+ return r.split(sep).join("/");
83
+ }
84
+ function createDualmarkIntegration(input) {
85
+ let resolved;
86
+ try {
87
+ resolved = resolveConfig(input);
88
+ } catch (e) {
89
+ if (e instanceof DualmarkConfigError) throw e;
90
+ throw e;
91
+ }
92
+ return {
93
+ name: "@dualmark/astro",
94
+ hooks: {
95
+ "astro:config:setup"(opts) {
96
+ const root = fileURLToPath(opts.config.root);
97
+ const generatedDir = join(root, "node_modules", GENERATED_DIR_NAME);
98
+ if (!existsSync(generatedDir)) mkdirSync(generatedDir, { recursive: true });
99
+ writeFileSync(
100
+ join(generatedDir, "config.mjs"),
101
+ `export default ${JSON.stringify(
102
+ {
103
+ siteUrl: resolved.siteUrl,
104
+ cacheControl: resolved.headers.cacheControl,
105
+ noindex: resolved.headers.noindex
106
+ },
107
+ null,
108
+ 2
109
+ )};
110
+ `,
111
+ "utf8"
112
+ );
113
+ const routes = [];
114
+ for (const [collectionName, c] of Object.entries(resolved.collections)) {
115
+ const route = c.route ?? collectionName;
116
+ const slugSeg = c.slugStrategy === "single" ? "[slug]" : "[...slug]";
117
+ const detailPattern = `/${route}/${slugSeg}.md`;
118
+ const listingPattern = `/${route}.md`;
119
+ if (typeof c.converter !== "string") {
120
+ opts.logger.warn(
121
+ `[@dualmark/astro] Collection '${collectionName}' uses an inline converter function \u2014 this isn't yet serializable into a generated route. Use a built-in converter name (e.g. 'blog') for now.`
122
+ );
123
+ continue;
124
+ }
125
+ const converterImport = `import { resolveBuiltInConverter } from "@dualmark/astro";`;
126
+ const detailSource = `${converterImport}
127
+ import { makeCollectionDetailEndpoint } from "@dualmark/astro/endpoints/collection";
128
+ import { getCollection } from "astro:content";
129
+ import dualmarkConfig from "./config.mjs";
130
+
131
+ const converter = resolveBuiltInConverter({
132
+ name: ${JSON.stringify(c.converter)},
133
+ collectionName: ${JSON.stringify(collectionName)},
134
+ baseConfig: { siteUrl: dualmarkConfig.siteUrl },
135
+ });
136
+
137
+ const endpoint = makeCollectionDetailEndpoint({
138
+ collectionName: ${JSON.stringify(collectionName)},
139
+ converter,
140
+ getCollection: (name, filter) => getCollection(name, filter),
141
+ responseOptions: { cacheControl: dualmarkConfig.cacheControl, noindex: dualmarkConfig.noindex },
142
+ });
143
+
144
+ export const getStaticPaths = endpoint.getStaticPaths;
145
+ export const GET = endpoint.GET;
146
+ `;
147
+ const listingSource = `import { makeListingEndpoint } from "@dualmark/astro/endpoints/listing";
148
+ import { getCollection } from "astro:content";
149
+ import dualmarkConfig from "./config.mjs";
150
+
151
+ const endpoint = makeListingEndpoint({
152
+ collectionName: ${JSON.stringify(collectionName)},
153
+ siteUrl: dualmarkConfig.siteUrl,
154
+ basePath: ${JSON.stringify("/" + route)},
155
+ title: ${JSON.stringify(c.listingMetadata?.title ?? collectionName)},
156
+ description: ${JSON.stringify(c.listingMetadata?.description ?? `All ${collectionName} entries.`)},
157
+ getCollection: (name, filter) => getCollection(name, filter),
158
+ responseOptions: { cacheControl: dualmarkConfig.cacheControl, noindex: dualmarkConfig.noindex },
159
+ });
160
+
161
+ export const GET = endpoint.GET;
162
+ `;
163
+ routes.push(
164
+ { pattern: detailPattern, fileName: `collection-${collectionName}-detail.mjs`, source: detailSource }
165
+ );
166
+ if (c.emitListing !== false) {
167
+ routes.push({
168
+ pattern: listingPattern,
169
+ fileName: `collection-${collectionName}-listing.mjs`,
170
+ source: listingSource
171
+ });
172
+ }
173
+ }
174
+ for (let i = 0; i < resolved.staticPages.length; i++) {
175
+ const sp = resolved.staticPages[i];
176
+ if (!sp) continue;
177
+ const safe = sp.pattern.replace(/[^a-z0-9]/gi, "_");
178
+ const fileName = `static-${i}-${safe}.mjs`;
179
+ const renderModulePath = join(generatedDir, `static-${i}-${safe}-render.mjs`);
180
+ writeFileSync(
181
+ renderModulePath,
182
+ `export default ${sp.render.toString()};
183
+ `,
184
+ "utf8"
185
+ );
186
+ const source = `import { makeStaticEndpoint } from "@dualmark/astro/endpoints/static";
187
+ import dualmarkConfig from "./config.mjs";
188
+ import render from "./${rel(generatedDir, renderModulePath)}";
189
+
190
+ const endpoint = makeStaticEndpoint({
191
+ render,
192
+ responseOptions: { cacheControl: dualmarkConfig.cacheControl, noindex: dualmarkConfig.noindex },
193
+ });
194
+
195
+ export const GET = endpoint.GET;
196
+ `;
197
+ const mdPattern = sp.pattern === "/" ? "/index.md" : sp.pattern.replace(/\/$/, "") + ".md";
198
+ routes.push({ pattern: mdPattern, fileName, source });
199
+ }
200
+ for (let i = 0; i < resolved.parameterizedRoutes.length; i++) {
201
+ const pr = resolved.parameterizedRoutes[i];
202
+ if (!pr) continue;
203
+ const safe = pr.pattern.replace(/[^a-z0-9]/gi, "_");
204
+ const renderModulePath = join(generatedDir, `param-${i}-${safe}-render.mjs`);
205
+ const pathsModulePath = join(generatedDir, `param-${i}-${safe}-paths.mjs`);
206
+ writeFileSync(renderModulePath, `export default ${pr.render.toString()};
207
+ `, "utf8");
208
+ writeFileSync(pathsModulePath, `export default ${pr.getStaticPaths.toString()};
209
+ `, "utf8");
210
+ const source = `import { makeParameterizedEndpoint } from "@dualmark/astro/endpoints/parameterized";
211
+ import dualmarkConfig from "./config.mjs";
212
+ import render from "./${rel(generatedDir, renderModulePath)}";
213
+ import getStaticPaths from "./${rel(generatedDir, pathsModulePath)}";
214
+
215
+ const endpoint = makeParameterizedEndpoint({
216
+ getStaticPaths: () => getStaticPaths(),
217
+ render,
218
+ responseOptions: { cacheControl: dualmarkConfig.cacheControl, noindex: dualmarkConfig.noindex },
219
+ });
220
+
221
+ export const getStaticPaths = endpoint.getStaticPaths;
222
+ export const GET = endpoint.GET;
223
+ `;
224
+ const pattern = pr.pattern.startsWith("/") ? pr.pattern + ".md" : `/${pr.pattern}.md`;
225
+ routes.push({
226
+ pattern,
227
+ fileName: `param-${i}-${safe}.mjs`,
228
+ source
229
+ });
230
+ }
231
+ if (resolved.llmsTxt?.enabled) {
232
+ const sections = resolved.llmsTxt.sections ?? [];
233
+ const source = `import { makeLlmsTxtEndpoint } from "@dualmark/astro/endpoints/llms-txt";
234
+
235
+ const endpoint = makeLlmsTxtEndpoint({
236
+ brandName: ${JSON.stringify(resolved.llmsTxt.brandName ?? "Site")},
237
+ description: ${JSON.stringify(resolved.llmsTxt.description ?? "")},
238
+ sections: ${JSON.stringify(sections)},
239
+ });
240
+
241
+ export const GET = endpoint.GET;
242
+ `;
243
+ routes.push({ pattern: "/llms.txt", fileName: `llms-txt.mjs`, source });
244
+ }
245
+ for (const r of routes) {
246
+ const filePath = join(generatedDir, r.fileName);
247
+ writeFileSync(filePath, r.source, "utf8");
248
+ opts.injectRoute({
249
+ pattern: r.pattern,
250
+ entrypoint: filePath,
251
+ prerender: true
252
+ });
253
+ }
254
+ if (resolved.middleware.injectLinkHeader && opts.addMiddleware) {
255
+ opts.addMiddleware({
256
+ order: "post",
257
+ entrypoint: "@dualmark/astro/middleware"
258
+ });
259
+ }
260
+ opts.logger.info(
261
+ `[@dualmark/astro] Injected ${routes.length} route(s) and ${resolved.middleware.injectLinkHeader ? "1" : "0"} middleware`
262
+ );
263
+ }
264
+ }
265
+ };
266
+ }
267
+ function dualmarkAstro(config) {
268
+ return createDualmarkIntegration(config);
269
+ }
270
+ function resolveBuiltInConverter(args) {
271
+ const basePath = `/${args.collectionName}`;
272
+ const cfg = { ...args.baseConfig, basePath };
273
+ switch (args.name) {
274
+ case "blog":
275
+ return blogConverter(cfg);
276
+ case "case-study":
277
+ return caseStudyConverter(cfg);
278
+ case "changelog":
279
+ return changelogConverter(cfg);
280
+ case "compare":
281
+ return compareConverter({ ...cfg, ourBrandColumn: "Us" });
282
+ case "docs":
283
+ return docsConverter(cfg);
284
+ case "feature":
285
+ return featureConverter(cfg);
286
+ case "glossary":
287
+ return glossaryConverter(cfg);
288
+ case "legal":
289
+ return legalConverter(cfg);
290
+ case "pricing":
291
+ return pricingConverter(cfg);
292
+ case "pseo":
293
+ return pseoConverter(cfg);
294
+ case "tool":
295
+ return toolConverter(cfg);
296
+ case "video":
297
+ return videoConverter(cfg);
298
+ default:
299
+ throw new Error(
300
+ `Dualmark: unknown built-in converter '${args.name}'. Valid names: blog, case-study, changelog, compare, docs, feature, glossary, legal, pricing, pseo, tool, video. Or pass a function.`
301
+ );
302
+ }
303
+ }
304
+
305
+ export { DualmarkConfigError, createDualmarkIntegration, dualmarkAstro as default, dualmarkAstro as dualmark, resolveBuiltInConverter, resolveConfig };
306
+ //# sourceMappingURL=index.js.map
307
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config-validation.ts","../src/integration.ts","../src/converter-registry.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { DualmarkAstroConfig, ResolvedDualmarkConfig } from \"./types.js\";\n\nexport class DualmarkConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DualmarkConfigError\";\n }\n}\n\nexport function resolveConfig(input: DualmarkAstroConfig): ResolvedDualmarkConfig {\n if (!input || typeof input !== \"object\") {\n throw new DualmarkConfigError(\"Dualmark config must be an object\");\n }\n if (typeof input.siteUrl !== \"string\" || !input.siteUrl) {\n throw new DualmarkConfigError(\"Dualmark config: siteUrl is required (e.g. 'https://example.com')\");\n }\n try {\n new URL(input.siteUrl);\n } catch {\n throw new DualmarkConfigError(`Dualmark config: siteUrl is not a valid URL: ${input.siteUrl}`);\n }\n if (input.siteUrl.endsWith(\"/\")) {\n throw new DualmarkConfigError(`Dualmark config: siteUrl must not end with '/': ${input.siteUrl}`);\n }\n\n const collections = input.collections ?? {};\n for (const [name, c] of Object.entries(collections)) {\n if (!c.converter) {\n throw new DualmarkConfigError(`Dualmark config: collection '${name}' is missing 'converter'`);\n }\n if (c.route && c.route.startsWith(\"/\")) {\n throw new DualmarkConfigError(\n `Dualmark config: collection '${name}' route should not start with '/' (got '${c.route}')`,\n );\n }\n }\n\n const staticPages = input.staticPages ?? [];\n for (const sp of staticPages) {\n if (!sp.pattern.startsWith(\"/\")) {\n throw new DualmarkConfigError(\n `Dualmark config: staticPages.pattern must start with '/' (got '${sp.pattern}')`,\n );\n }\n if (typeof sp.render !== \"function\") {\n throw new DualmarkConfigError(\n `Dualmark config: staticPages.render for '${sp.pattern}' must be a function`,\n );\n }\n }\n\n const parameterizedRoutes = input.parameterizedRoutes ?? [];\n for (const pr of parameterizedRoutes) {\n if (!pr.pattern.includes(\"[\")) {\n throw new DualmarkConfigError(\n `Dualmark config: parameterizedRoutes.pattern must contain at least one [param] (got '${pr.pattern}')`,\n );\n }\n }\n\n return {\n siteUrl: input.siteUrl,\n collections,\n staticPages,\n parameterizedRoutes,\n llmsTxt: input.llmsTxt,\n middleware: {\n injectLinkHeader: input.middleware?.injectLinkHeader !== false,\n },\n headers: {\n cacheControl: input.headers?.cacheControl ?? \"public, max-age=3600\",\n noindex: input.headers?.noindex !== false,\n },\n };\n}\n","import { mkdirSync, writeFileSync, existsSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { join, relative, resolve, sep } from \"node:path\";\nimport { resolveConfig, DualmarkConfigError } from \"./config-validation.js\";\nimport type { DualmarkAstroConfig, ResolvedDualmarkConfig } from \"./types.js\";\n\ninterface AstroIntegrationLogger {\n info: (msg: string) => void;\n warn: (msg: string) => void;\n error: (msg: string) => void;\n}\n\ninterface InjectedRoute {\n pattern: string;\n entrypoint: string | URL;\n prerender?: boolean;\n}\n\ninterface AstroIntegrationMiddleware {\n order: \"pre\" | \"post\";\n entrypoint: string | URL;\n}\n\ninterface ConfigSetupHookOptions {\n config: { root: URL; srcDir?: URL };\n command: string;\n isRestart?: boolean;\n injectRoute: (route: InjectedRoute) => void;\n addMiddleware?: (mw: AstroIntegrationMiddleware) => void;\n logger: AstroIntegrationLogger;\n updateConfig?: (cfg: unknown) => unknown;\n}\n\nexport interface AstroIntegrationLike {\n name: string;\n hooks: {\n \"astro:config:setup\": (opts: ConfigSetupHookOptions) => void | Promise<void>;\n };\n}\n\nconst GENERATED_DIR_NAME = \".dualmark-generated\";\n\ninterface RouteSpec {\n pattern: string;\n fileName: string;\n source: string;\n}\n\nfunction rel(from: string, to: string): string {\n const r = relative(from, to);\n return r.split(sep).join(\"/\");\n}\n\nexport function createDualmarkIntegration(input: DualmarkAstroConfig): AstroIntegrationLike {\n let resolved: ResolvedDualmarkConfig;\n try {\n resolved = resolveConfig(input);\n } catch (e) {\n if (e instanceof DualmarkConfigError) throw e;\n throw e;\n }\n\n return {\n name: \"@dualmark/astro\",\n hooks: {\n \"astro:config:setup\"(opts) {\n const root = fileURLToPath(opts.config.root);\n const generatedDir = join(root, \"node_modules\", GENERATED_DIR_NAME);\n if (!existsSync(generatedDir)) mkdirSync(generatedDir, { recursive: true });\n\n writeFileSync(\n join(generatedDir, \"config.mjs\"),\n `export default ${JSON.stringify(\n {\n siteUrl: resolved.siteUrl,\n cacheControl: resolved.headers.cacheControl,\n noindex: resolved.headers.noindex,\n },\n null,\n 2,\n )};\\n`,\n \"utf8\",\n );\n\n const routes: RouteSpec[] = [];\n\n for (const [collectionName, c] of Object.entries(resolved.collections)) {\n const route = c.route ?? collectionName;\n const slugSeg = c.slugStrategy === \"single\" ? \"[slug]\" : \"[...slug]\";\n const detailPattern = `/${route}/${slugSeg}.md`;\n const listingPattern = `/${route}.md`;\n if (typeof c.converter !== \"string\") {\n opts.logger.warn(\n `[@dualmark/astro] Collection '${collectionName}' uses an inline converter function — this isn't yet serializable into a generated route. Use a built-in converter name (e.g. 'blog') for now.`,\n );\n continue;\n }\n const converterImport = `import { resolveBuiltInConverter } from \"@dualmark/astro\";`;\n const detailSource = `${converterImport}\nimport { makeCollectionDetailEndpoint } from \"@dualmark/astro/endpoints/collection\";\nimport { getCollection } from \"astro:content\";\nimport dualmarkConfig from \"./config.mjs\";\n\nconst converter = resolveBuiltInConverter({\n name: ${JSON.stringify(c.converter)},\n collectionName: ${JSON.stringify(collectionName)},\n baseConfig: { siteUrl: dualmarkConfig.siteUrl },\n});\n\nconst endpoint = makeCollectionDetailEndpoint({\n collectionName: ${JSON.stringify(collectionName)},\n converter,\n getCollection: (name, filter) => getCollection(name, filter),\n responseOptions: { cacheControl: dualmarkConfig.cacheControl, noindex: dualmarkConfig.noindex },\n});\n\nexport const getStaticPaths = endpoint.getStaticPaths;\nexport const GET = endpoint.GET;\n`;\n\n const listingSource = `import { makeListingEndpoint } from \"@dualmark/astro/endpoints/listing\";\nimport { getCollection } from \"astro:content\";\nimport dualmarkConfig from \"./config.mjs\";\n\nconst endpoint = makeListingEndpoint({\n collectionName: ${JSON.stringify(collectionName)},\n siteUrl: dualmarkConfig.siteUrl,\n basePath: ${JSON.stringify(\"/\" + route)},\n title: ${JSON.stringify(c.listingMetadata?.title ?? collectionName)},\n description: ${JSON.stringify(c.listingMetadata?.description ?? `All ${collectionName} entries.`)},\n getCollection: (name, filter) => getCollection(name, filter),\n responseOptions: { cacheControl: dualmarkConfig.cacheControl, noindex: dualmarkConfig.noindex },\n});\n\nexport const GET = endpoint.GET;\n`;\n\n routes.push(\n { pattern: detailPattern, fileName: `collection-${collectionName}-detail.mjs`, source: detailSource },\n );\n if (c.emitListing !== false) {\n routes.push({\n pattern: listingPattern,\n fileName: `collection-${collectionName}-listing.mjs`,\n source: listingSource,\n });\n }\n }\n\n for (let i = 0; i < resolved.staticPages.length; i++) {\n const sp = resolved.staticPages[i];\n if (!sp) continue;\n const safe = sp.pattern.replace(/[^a-z0-9]/gi, \"_\");\n const fileName = `static-${i}-${safe}.mjs`;\n const renderModulePath = join(generatedDir, `static-${i}-${safe}-render.mjs`);\n writeFileSync(\n renderModulePath,\n `export default ${sp.render.toString()};\\n`,\n \"utf8\",\n );\n const source = `import { makeStaticEndpoint } from \"@dualmark/astro/endpoints/static\";\nimport dualmarkConfig from \"./config.mjs\";\nimport render from \"./${rel(generatedDir, renderModulePath)}\";\n\nconst endpoint = makeStaticEndpoint({\n render,\n responseOptions: { cacheControl: dualmarkConfig.cacheControl, noindex: dualmarkConfig.noindex },\n});\n\nexport const GET = endpoint.GET;\n`;\n const mdPattern = sp.pattern === \"/\" ? \"/index.md\" : sp.pattern.replace(/\\/$/, \"\") + \".md\";\n routes.push({ pattern: mdPattern, fileName, source });\n }\n\n for (let i = 0; i < resolved.parameterizedRoutes.length; i++) {\n const pr = resolved.parameterizedRoutes[i];\n if (!pr) continue;\n const safe = pr.pattern.replace(/[^a-z0-9]/gi, \"_\");\n const renderModulePath = join(generatedDir, `param-${i}-${safe}-render.mjs`);\n const pathsModulePath = join(generatedDir, `param-${i}-${safe}-paths.mjs`);\n writeFileSync(renderModulePath, `export default ${pr.render.toString()};\\n`, \"utf8\");\n writeFileSync(pathsModulePath, `export default ${pr.getStaticPaths.toString()};\\n`, \"utf8\");\n const source = `import { makeParameterizedEndpoint } from \"@dualmark/astro/endpoints/parameterized\";\nimport dualmarkConfig from \"./config.mjs\";\nimport render from \"./${rel(generatedDir, renderModulePath)}\";\nimport getStaticPaths from \"./${rel(generatedDir, pathsModulePath)}\";\n\nconst endpoint = makeParameterizedEndpoint({\n getStaticPaths: () => getStaticPaths(),\n render,\n responseOptions: { cacheControl: dualmarkConfig.cacheControl, noindex: dualmarkConfig.noindex },\n});\n\nexport const getStaticPaths = endpoint.getStaticPaths;\nexport const GET = endpoint.GET;\n`;\n const pattern = pr.pattern.startsWith(\"/\") ? pr.pattern + \".md\" : `/${pr.pattern}.md`;\n routes.push({\n pattern,\n fileName: `param-${i}-${safe}.mjs`,\n source,\n });\n }\n\n if (resolved.llmsTxt?.enabled) {\n const sections = resolved.llmsTxt.sections ?? [];\n const source = `import { makeLlmsTxtEndpoint } from \"@dualmark/astro/endpoints/llms-txt\";\n\nconst endpoint = makeLlmsTxtEndpoint({\n brandName: ${JSON.stringify(resolved.llmsTxt.brandName ?? \"Site\")},\n description: ${JSON.stringify(resolved.llmsTxt.description ?? \"\")},\n sections: ${JSON.stringify(sections)},\n});\n\nexport const GET = endpoint.GET;\n`;\n routes.push({ pattern: \"/llms.txt\", fileName: `llms-txt.mjs`, source });\n }\n\n for (const r of routes) {\n const filePath = join(generatedDir, r.fileName);\n writeFileSync(filePath, r.source, \"utf8\");\n opts.injectRoute({\n pattern: r.pattern,\n entrypoint: filePath,\n prerender: true,\n });\n }\n\n if (resolved.middleware.injectLinkHeader && opts.addMiddleware) {\n opts.addMiddleware({\n order: \"post\",\n entrypoint: \"@dualmark/astro/middleware\",\n });\n }\n\n opts.logger.info(\n `[@dualmark/astro] Injected ${routes.length} route(s) and ${\n resolved.middleware.injectLinkHeader ? \"1\" : \"0\"\n } middleware`,\n );\n\n void resolve;\n },\n },\n };\n}\n\nexport default function dualmarkAstro(config: DualmarkAstroConfig): AstroIntegrationLike {\n return createDualmarkIntegration(config);\n}\n","import {\n blogConverter,\n caseStudyConverter,\n changelogConverter,\n compareConverter,\n docsConverter,\n featureConverter,\n glossaryConverter,\n legalConverter,\n pricingConverter,\n pseoConverter,\n toolConverter,\n videoConverter,\n type BaseConverterConfig,\n type CollectionEntry,\n type Converter,\n} from \"@dualmark/converters\";\n\nexport type BuiltInConverterName =\n | \"blog\"\n | \"case-study\"\n | \"changelog\"\n | \"compare\"\n | \"docs\"\n | \"feature\"\n | \"glossary\"\n | \"legal\"\n | \"pricing\"\n | \"pseo\"\n | \"tool\"\n | \"video\";\n\nexport interface ResolveConverterArgs {\n name: string;\n collectionName: string;\n baseConfig: BaseConverterConfig;\n}\n\nexport function resolveBuiltInConverter(\n args: ResolveConverterArgs,\n): Converter<CollectionEntry<unknown>> {\n const basePath = `/${args.collectionName}`;\n const cfg = { ...args.baseConfig, basePath };\n switch (args.name as BuiltInConverterName) {\n case \"blog\":\n return blogConverter(cfg) as Converter<CollectionEntry<unknown>>;\n case \"case-study\":\n return caseStudyConverter(cfg) as Converter<CollectionEntry<unknown>>;\n case \"changelog\":\n return changelogConverter(cfg) as Converter<CollectionEntry<unknown>>;\n case \"compare\":\n return compareConverter({ ...cfg, ourBrandColumn: \"Us\" }) as Converter<CollectionEntry<unknown>>;\n case \"docs\":\n return docsConverter(cfg) as Converter<CollectionEntry<unknown>>;\n case \"feature\":\n return featureConverter(cfg) as Converter<CollectionEntry<unknown>>;\n case \"glossary\":\n return glossaryConverter(cfg) as Converter<CollectionEntry<unknown>>;\n case \"legal\":\n return legalConverter(cfg) as Converter<CollectionEntry<unknown>>;\n case \"pricing\":\n return pricingConverter(cfg) as Converter<CollectionEntry<unknown>>;\n case \"pseo\":\n return pseoConverter(cfg) as Converter<CollectionEntry<unknown>>;\n case \"tool\":\n return toolConverter(cfg) as Converter<CollectionEntry<unknown>>;\n case \"video\":\n return videoConverter(cfg) as Converter<CollectionEntry<unknown>>;\n default:\n throw new Error(\n `Dualmark: unknown built-in converter '${args.name}'. Valid names: blog, case-study, changelog, compare, docs, feature, glossary, legal, pricing, pseo, tool, video. Or pass a function.`,\n );\n }\n}\n"]}
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ var core = require('@dualmark/core');
4
+
5
+ // src/middleware.ts
6
+ async function dualmarkOnRequest(context, next) {
7
+ const response = await next();
8
+ const ct = response.headers.get("content-type") ?? "";
9
+ if (!ct.toLowerCase().includes("text/html")) return response;
10
+ if (context.url.pathname.endsWith(".md")) return response;
11
+ return core.injectMarkdownAlternateLink(
12
+ response,
13
+ context.url.pathname,
14
+ core.toMarkdownPath(context.url.pathname)
15
+ );
16
+ }
17
+ var onRequest = dualmarkOnRequest;
18
+
19
+ exports.dualmarkOnRequest = dualmarkOnRequest;
20
+ exports.onRequest = onRequest;
21
+ //# sourceMappingURL=middleware.cjs.map
22
+ //# sourceMappingURL=middleware.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware.ts"],"names":["injectMarkdownAlternateLink","toMarkdownPath"],"mappings":";;;;;AASA,eAAsB,iBAAA,CACpB,SACA,IAAA,EACmB;AACnB,EAAA,MAAM,QAAA,GAAW,MAAM,IAAA,EAAK;AAC5B,EAAA,MAAM,EAAA,GAAK,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA,IAAK,EAAA;AACnD,EAAA,IAAI,CAAC,EAAA,CAAG,WAAA,GAAc,QAAA,CAAS,WAAW,GAAG,OAAO,QAAA;AACpD,EAAA,IAAI,QAAQ,GAAA,CAAI,QAAA,CAAS,QAAA,CAAS,KAAK,GAAG,OAAO,QAAA;AACjD,EAAA,OAAOA,gCAAA;AAAA,IACL,QAAA;AAAA,IACA,QAAQ,GAAA,CAAI,QAAA;AAAA,IACZC,mBAAA,CAAe,OAAA,CAAQ,GAAA,CAAI,QAAQ;AAAA,GACrC;AACF;AAEO,IAAM,SAAA,GAAY","file":"middleware.cjs","sourcesContent":["import { injectMarkdownAlternateLink, toMarkdownPath } from \"@dualmark/core\";\n\ninterface MiddlewareContext {\n url: URL;\n request: Request;\n}\n\ntype Next = () => Promise<Response>;\n\nexport async function dualmarkOnRequest(\n context: MiddlewareContext,\n next: Next,\n): Promise<Response> {\n const response = await next();\n const ct = response.headers.get(\"content-type\") ?? \"\";\n if (!ct.toLowerCase().includes(\"text/html\")) return response;\n if (context.url.pathname.endsWith(\".md\")) return response;\n return injectMarkdownAlternateLink(\n response,\n context.url.pathname,\n toMarkdownPath(context.url.pathname),\n );\n}\n\nexport const onRequest = dualmarkOnRequest;\n"]}
@@ -0,0 +1,9 @@
1
+ interface MiddlewareContext {
2
+ url: URL;
3
+ request: Request;
4
+ }
5
+ type Next = () => Promise<Response>;
6
+ declare function dualmarkOnRequest(context: MiddlewareContext, next: Next): Promise<Response>;
7
+ declare const onRequest: typeof dualmarkOnRequest;
8
+
9
+ export { dualmarkOnRequest, onRequest };
@@ -0,0 +1,9 @@
1
+ interface MiddlewareContext {
2
+ url: URL;
3
+ request: Request;
4
+ }
5
+ type Next = () => Promise<Response>;
6
+ declare function dualmarkOnRequest(context: MiddlewareContext, next: Next): Promise<Response>;
7
+ declare const onRequest: typeof dualmarkOnRequest;
8
+
9
+ export { dualmarkOnRequest, onRequest };
@@ -0,0 +1,19 @@
1
+ import { injectMarkdownAlternateLink, toMarkdownPath } from '@dualmark/core';
2
+
3
+ // src/middleware.ts
4
+ async function dualmarkOnRequest(context, next) {
5
+ const response = await next();
6
+ const ct = response.headers.get("content-type") ?? "";
7
+ if (!ct.toLowerCase().includes("text/html")) return response;
8
+ if (context.url.pathname.endsWith(".md")) return response;
9
+ return injectMarkdownAlternateLink(
10
+ response,
11
+ context.url.pathname,
12
+ toMarkdownPath(context.url.pathname)
13
+ );
14
+ }
15
+ var onRequest = dualmarkOnRequest;
16
+
17
+ export { dualmarkOnRequest, onRequest };
18
+ //# sourceMappingURL=middleware.js.map
19
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware.ts"],"names":[],"mappings":";;;AASA,eAAsB,iBAAA,CACpB,SACA,IAAA,EACmB;AACnB,EAAA,MAAM,QAAA,GAAW,MAAM,IAAA,EAAK;AAC5B,EAAA,MAAM,EAAA,GAAK,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA,IAAK,EAAA;AACnD,EAAA,IAAI,CAAC,EAAA,CAAG,WAAA,GAAc,QAAA,CAAS,WAAW,GAAG,OAAO,QAAA;AACpD,EAAA,IAAI,QAAQ,GAAA,CAAI,QAAA,CAAS,QAAA,CAAS,KAAK,GAAG,OAAO,QAAA;AACjD,EAAA,OAAO,2BAAA;AAAA,IACL,QAAA;AAAA,IACA,QAAQ,GAAA,CAAI,QAAA;AAAA,IACZ,cAAA,CAAe,OAAA,CAAQ,GAAA,CAAI,QAAQ;AAAA,GACrC;AACF;AAEO,IAAM,SAAA,GAAY","file":"middleware.js","sourcesContent":["import { injectMarkdownAlternateLink, toMarkdownPath } from \"@dualmark/core\";\n\ninterface MiddlewareContext {\n url: URL;\n request: Request;\n}\n\ntype Next = () => Promise<Response>;\n\nexport async function dualmarkOnRequest(\n context: MiddlewareContext,\n next: Next,\n): Promise<Response> {\n const response = await next();\n const ct = response.headers.get(\"content-type\") ?? \"\";\n if (!ct.toLowerCase().includes(\"text/html\")) return response;\n if (context.url.pathname.endsWith(\".md\")) return response;\n return injectMarkdownAlternateLink(\n response,\n context.url.pathname,\n toMarkdownPath(context.url.pathname),\n );\n}\n\nexport const onRequest = dualmarkOnRequest;\n"]}
@@ -0,0 +1,64 @@
1
+ import { CollectionEntry, Converter } from '@dualmark/converters';
2
+ import { LlmsTxtSection } from '@dualmark/core';
3
+
4
+ type SlugStrategy = "catch-all" | "single";
5
+ interface CollectionConfig<TEntry = CollectionEntry<unknown>> {
6
+ converter: string | Converter<TEntry>;
7
+ route?: string;
8
+ slugStrategy?: SlugStrategy;
9
+ filter?: (entry: TEntry) => boolean;
10
+ sort?: (a: TEntry, b: TEntry) => number;
11
+ listingMetadata?: {
12
+ title: string;
13
+ description: string;
14
+ };
15
+ emitListing?: boolean;
16
+ }
17
+ interface StaticPageConfig {
18
+ pattern: string;
19
+ render: () => string | Promise<string>;
20
+ }
21
+ interface ParameterizedRouteConfig {
22
+ pattern: string;
23
+ getStaticPaths: () => Promise<Array<{
24
+ params: Record<string, string>;
25
+ }>> | Array<{
26
+ params: Record<string, string>;
27
+ }>;
28
+ render: (args: {
29
+ params: Record<string, string>;
30
+ }) => string | Promise<string>;
31
+ }
32
+ interface DualmarkAstroConfig {
33
+ siteUrl: string;
34
+ collections?: Record<string, CollectionConfig>;
35
+ staticPages?: StaticPageConfig[];
36
+ parameterizedRoutes?: ParameterizedRouteConfig[];
37
+ llmsTxt?: {
38
+ enabled?: boolean;
39
+ brandName?: string;
40
+ description?: string;
41
+ sections?: LlmsTxtSection[];
42
+ };
43
+ middleware?: {
44
+ injectLinkHeader?: boolean;
45
+ };
46
+ headers?: {
47
+ cacheControl?: string;
48
+ noindex?: boolean;
49
+ };
50
+ }
51
+ interface ResolvedDualmarkConfig extends DualmarkAstroConfig {
52
+ collections: Record<string, CollectionConfig>;
53
+ staticPages: StaticPageConfig[];
54
+ parameterizedRoutes: ParameterizedRouteConfig[];
55
+ middleware: {
56
+ injectLinkHeader: boolean;
57
+ };
58
+ headers: {
59
+ cacheControl: string;
60
+ noindex: boolean;
61
+ };
62
+ }
63
+
64
+ export type { CollectionConfig as C, DualmarkAstroConfig as D, ParameterizedRouteConfig as P, ResolvedDualmarkConfig as R, SlugStrategy as S, StaticPageConfig as a };
@@ -0,0 +1,64 @@
1
+ import { CollectionEntry, Converter } from '@dualmark/converters';
2
+ import { LlmsTxtSection } from '@dualmark/core';
3
+
4
+ type SlugStrategy = "catch-all" | "single";
5
+ interface CollectionConfig<TEntry = CollectionEntry<unknown>> {
6
+ converter: string | Converter<TEntry>;
7
+ route?: string;
8
+ slugStrategy?: SlugStrategy;
9
+ filter?: (entry: TEntry) => boolean;
10
+ sort?: (a: TEntry, b: TEntry) => number;
11
+ listingMetadata?: {
12
+ title: string;
13
+ description: string;
14
+ };
15
+ emitListing?: boolean;
16
+ }
17
+ interface StaticPageConfig {
18
+ pattern: string;
19
+ render: () => string | Promise<string>;
20
+ }
21
+ interface ParameterizedRouteConfig {
22
+ pattern: string;
23
+ getStaticPaths: () => Promise<Array<{
24
+ params: Record<string, string>;
25
+ }>> | Array<{
26
+ params: Record<string, string>;
27
+ }>;
28
+ render: (args: {
29
+ params: Record<string, string>;
30
+ }) => string | Promise<string>;
31
+ }
32
+ interface DualmarkAstroConfig {
33
+ siteUrl: string;
34
+ collections?: Record<string, CollectionConfig>;
35
+ staticPages?: StaticPageConfig[];
36
+ parameterizedRoutes?: ParameterizedRouteConfig[];
37
+ llmsTxt?: {
38
+ enabled?: boolean;
39
+ brandName?: string;
40
+ description?: string;
41
+ sections?: LlmsTxtSection[];
42
+ };
43
+ middleware?: {
44
+ injectLinkHeader?: boolean;
45
+ };
46
+ headers?: {
47
+ cacheControl?: string;
48
+ noindex?: boolean;
49
+ };
50
+ }
51
+ interface ResolvedDualmarkConfig extends DualmarkAstroConfig {
52
+ collections: Record<string, CollectionConfig>;
53
+ staticPages: StaticPageConfig[];
54
+ parameterizedRoutes: ParameterizedRouteConfig[];
55
+ middleware: {
56
+ injectLinkHeader: boolean;
57
+ };
58
+ headers: {
59
+ cacheControl: string;
60
+ noindex: boolean;
61
+ };
62
+ }
63
+
64
+ export type { CollectionConfig as C, DualmarkAstroConfig as D, ParameterizedRouteConfig as P, ResolvedDualmarkConfig as R, SlugStrategy as S, StaticPageConfig as a };
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ // src/virtual.ts
4
+ function getDualmarkConfig() {
5
+ return __DUALMARK_CONFIG__;
6
+ }
7
+
8
+ exports.getDualmarkConfig = getDualmarkConfig;
9
+ //# sourceMappingURL=virtual.cjs.map
10
+ //# sourceMappingURL=virtual.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/virtual.ts"],"names":[],"mappings":";;;AAIO,SAAS,iBAAA,GAA4C;AAC1D,EAAA,OAAO,mBAAA;AACT","file":"virtual.cjs","sourcesContent":["import type { ResolvedDualmarkConfig } from \"./types.js\";\n\ndeclare const __DUALMARK_CONFIG__: ResolvedDualmarkConfig;\n\nexport function getDualmarkConfig(): ResolvedDualmarkConfig {\n return __DUALMARK_CONFIG__;\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import { R as ResolvedDualmarkConfig } from './types-BPk2cf2p.cjs';
2
+ import '@dualmark/converters';
3
+ import '@dualmark/core';
4
+
5
+ declare function getDualmarkConfig(): ResolvedDualmarkConfig;
6
+
7
+ export { getDualmarkConfig };
@@ -0,0 +1,7 @@
1
+ import { R as ResolvedDualmarkConfig } from './types-BPk2cf2p.js';
2
+ import '@dualmark/converters';
3
+ import '@dualmark/core';
4
+
5
+ declare function getDualmarkConfig(): ResolvedDualmarkConfig;
6
+
7
+ export { getDualmarkConfig };
@@ -0,0 +1,8 @@
1
+ // src/virtual.ts
2
+ function getDualmarkConfig() {
3
+ return __DUALMARK_CONFIG__;
4
+ }
5
+
6
+ export { getDualmarkConfig };
7
+ //# sourceMappingURL=virtual.js.map
8
+ //# sourceMappingURL=virtual.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/virtual.ts"],"names":[],"mappings":";AAIO,SAAS,iBAAA,GAA4C;AAC1D,EAAA,OAAO,mBAAA;AACT","file":"virtual.js","sourcesContent":["import type { ResolvedDualmarkConfig } from \"./types.js\";\n\ndeclare const __DUALMARK_CONFIG__: ResolvedDualmarkConfig;\n\nexport function getDualmarkConfig(): ResolvedDualmarkConfig {\n return __DUALMARK_CONFIG__;\n}\n"]}