@knitli/astro-docs-template 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +81 -0
- package/scaffolding/README.md +42 -0
- package/scaffolding/astro.config.ts +16 -0
- package/scaffolding/mise.toml +65 -0
- package/scaffolding/package.json +23 -0
- package/scaffolding/public/.assetsignore +2 -0
- package/scaffolding/src/content/docs/guides/getting-started.md +18 -0
- package/scaffolding/src/content/docs/index.mdx +28 -0
- package/scaffolding/src/content/docs/reference/index.md +6 -0
- package/scaffolding/src/content.config.ts +7 -0
- package/scaffolding/src/env.d.ts +5 -0
- package/scaffolding/src/styles/custom.css +4 -0
- package/scaffolding/tags.yml +26 -0
- package/scaffolding/tsconfig.json +5 -0
- package/scaffolding/wrangler.jsonc +32 -0
- package/src/config.ts +551 -0
- package/src/index.ts +128 -0
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@knitli/astro-docs-template",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Opinionated Astro + Starlight docs site template with Knitli branding",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"knitli",
|
|
7
|
+
"astro",
|
|
8
|
+
"starlight",
|
|
9
|
+
"documentation",
|
|
10
|
+
"template"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://docs.knitli.com",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/knitli/knitli-site.git",
|
|
16
|
+
"directory": "packages/shared/astro-docs-template"
|
|
17
|
+
},
|
|
18
|
+
"license": "(MIT OR Apache-2.0) AND LicenseRef-KnitliProprietary",
|
|
19
|
+
"author": "Knitli Inc.",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./src/index.ts",
|
|
23
|
+
"./config": "./src/config.ts"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"src",
|
|
27
|
+
"scaffolding",
|
|
28
|
+
"README.md",
|
|
29
|
+
"CHANGELOG.md"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "bunx tsc --noCheck",
|
|
33
|
+
"prepublishOnly": "bun run build",
|
|
34
|
+
"publish": "npm publish --access public",
|
|
35
|
+
"typecheck": "bunx tsc --noEmit"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@astrojs/cloudflare": "catalog:astro-core",
|
|
39
|
+
"@astrojs/markdoc": "catalog:astro-plugins",
|
|
40
|
+
"@astrojs/mdx": "catalog:astro-core",
|
|
41
|
+
"@astrojs/sitemap": "catalog:astro-core",
|
|
42
|
+
"@astrojs/starlight": "catalog:astro-core",
|
|
43
|
+
"@knitli/docs-components": "workspace:*",
|
|
44
|
+
"@nuasite/llm-enhancements": "catalog:astro-plugins",
|
|
45
|
+
"astro": "catalog:astro-core",
|
|
46
|
+
"astro-cloudflare-pages-headers": "catalog:astro-plugins",
|
|
47
|
+
"astro-d2": "catalog:astro-plugins",
|
|
48
|
+
"astro-favicons": "catalog:astro-core",
|
|
49
|
+
"rehype-external-links": "^3.0.0",
|
|
50
|
+
"starlight-announcement": "catalog:starlight-plugins",
|
|
51
|
+
"starlight-changelogs": "catalog:starlight-plugins",
|
|
52
|
+
"starlight-heading-badges": "catalog:starlight-plugins",
|
|
53
|
+
"starlight-links-validator": "catalog:starlight-plugins",
|
|
54
|
+
"starlight-llms-txt": "catalog:starlight-plugins",
|
|
55
|
+
"starlight-page-actions": "catalog:starlight-plugins",
|
|
56
|
+
"starlight-plugin-icons": "catalog:starlight-plugins",
|
|
57
|
+
"starlight-scroll-to-top": "catalog:starlight-plugins",
|
|
58
|
+
"starlight-sidebar-topics": "catalog:starlight-plugins",
|
|
59
|
+
"starlight-tags": "catalog:starlight-plugins",
|
|
60
|
+
"vite-tsconfig-paths": "catalog:dev-common"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@astrojs/check": "catalog:dev-common",
|
|
64
|
+
"@astrojs/compiler-rs": "catalog:astro-core",
|
|
65
|
+
"@biomejs/biome": "catalog:dev-common",
|
|
66
|
+
"@knitli/tsconfig": "*",
|
|
67
|
+
"@types/node": "catalog:types",
|
|
68
|
+
"bun": "catalog:dev-common",
|
|
69
|
+
"lightningcss": "catalog:optimization",
|
|
70
|
+
"sharp": "catalog:astro-core",
|
|
71
|
+
"svgo": "catalog:optimization",
|
|
72
|
+
"typescript": "catalog:dev-common"
|
|
73
|
+
},
|
|
74
|
+
"devEngines": {
|
|
75
|
+
"packageManager": {
|
|
76
|
+
"name": "bun",
|
|
77
|
+
"version": "catalog:dev-common",
|
|
78
|
+
"onFail": "download"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# {{appName}} Docs
|
|
2
|
+
|
|
3
|
+
Documentation site for {{appName}}, built with [Astro](https://astro.build) + [Starlight](https://starlight.astro.build) using the Knitli docs template.
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install
|
|
9
|
+
bun run dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Deployment
|
|
13
|
+
|
|
14
|
+
Deployed to Cloudflare Pages:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun run deploy
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Project Structure
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
.
|
|
24
|
+
├── src/
|
|
25
|
+
│ ├── content/docs/ # Documentation pages (MDX/MD)
|
|
26
|
+
│ ├── styles/ # Site-specific CSS overrides
|
|
27
|
+
│ └── content.config.ts
|
|
28
|
+
├── astro.config.ts # Uses @knitli/astro-docs-template createConfig
|
|
29
|
+
├── wrangler.jsonc # Cloudflare Workers configuration
|
|
30
|
+
└── package.json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Configuration
|
|
34
|
+
|
|
35
|
+
This site uses `createConfig()` from `@knitli/astro-docs-template` which provides:
|
|
36
|
+
|
|
37
|
+
- Starlight with Knitli branding (header, footer, theme)
|
|
38
|
+
- Pre-configured plugins (tags, changelogs, LLM text, link validation, etc.)
|
|
39
|
+
- Cloudflare Pages adapter
|
|
40
|
+
- Optimized Vite build settings
|
|
41
|
+
|
|
42
|
+
Override defaults by passing options to `createConfig()` in `astro.config.ts`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Knitli Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: MIT OR Apache-2.0
|
|
4
|
+
|
|
5
|
+
import createConfig from "@knitli/astro-docs-template/config";
|
|
6
|
+
|
|
7
|
+
export default createConfig({
|
|
8
|
+
appName: "{{appName}}",
|
|
9
|
+
description: "{{description}}",
|
|
10
|
+
rootDir: import.meta.dirname,
|
|
11
|
+
llmConfig: {
|
|
12
|
+
llmDescription: "{{description}}",
|
|
13
|
+
promotePatterns: ["guides/**", "reference/**"],
|
|
14
|
+
demotePatterns: ["_*"],
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Knitli Inc.
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
#:tombi schema.strict = false
|
|
6
|
+
|
|
7
|
+
[tasks.installdeps]
|
|
8
|
+
description = "Install dependencies for the project"
|
|
9
|
+
run = "bun install"
|
|
10
|
+
tools.bun = "latest"
|
|
11
|
+
|
|
12
|
+
[tasks.gentypes]
|
|
13
|
+
description = "Generate Cloudflare types for the project"
|
|
14
|
+
run = "bunx wrangler types"
|
|
15
|
+
tools.bun = "latest"
|
|
16
|
+
tools.wrangler = "latest"
|
|
17
|
+
depends = ["installdeps"]
|
|
18
|
+
|
|
19
|
+
[tasks.dev]
|
|
20
|
+
description = "Start the development server"
|
|
21
|
+
run = "bun run dev"
|
|
22
|
+
tools.bun = "latest"
|
|
23
|
+
depends = ["gentypes"]
|
|
24
|
+
|
|
25
|
+
[tasks.build]
|
|
26
|
+
description = "Build the project for production"
|
|
27
|
+
run = "bun run build"
|
|
28
|
+
tools.bun = "latest"
|
|
29
|
+
depends = ["gentypes"]
|
|
30
|
+
|
|
31
|
+
[tasks.deploy]
|
|
32
|
+
description = "Deploy the project to Cloudflare"
|
|
33
|
+
run = '''
|
|
34
|
+
bun run deploy
|
|
35
|
+
'''
|
|
36
|
+
tools.bun = "latest"
|
|
37
|
+
depends = ["build"]
|
|
38
|
+
|
|
39
|
+
[tasks.clean]
|
|
40
|
+
description = "Clean the project by removing the dist directory"
|
|
41
|
+
run = [
|
|
42
|
+
"rm -rf dist",
|
|
43
|
+
"rm -rf .wrangler",
|
|
44
|
+
"rm -rf node_modules",
|
|
45
|
+
"rm -rf .astro",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[tasks.clean-install]
|
|
49
|
+
description = "Clean the project and install dependencies"
|
|
50
|
+
depends = ["clean", "installdeps"]
|
|
51
|
+
|
|
52
|
+
[tasks.fmt]
|
|
53
|
+
description = "Format the codebase using Biome"
|
|
54
|
+
run = "biome format . --write --changed"
|
|
55
|
+
tools.biome = "latest"
|
|
56
|
+
|
|
57
|
+
[tasks.lint]
|
|
58
|
+
description = "Lint the codebase using Biome"
|
|
59
|
+
run = "biome check . --changed"
|
|
60
|
+
tools.biome = "latest"
|
|
61
|
+
|
|
62
|
+
[tasks.fix]
|
|
63
|
+
description = "Fix linting issues using Biome"
|
|
64
|
+
run = "biome check . --write --changed"
|
|
65
|
+
tools.biome = "latest"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "bunx astro build",
|
|
8
|
+
"deploy": "bunx astro build && bunx wrangler deploy",
|
|
9
|
+
"dev": "bunx astro dev",
|
|
10
|
+
"generate-types": "bunx wrangler types",
|
|
11
|
+
"preview": "bunx astro build && bunx astro preview"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@knitli/astro-docs-template": "workspace:*",
|
|
15
|
+
"@knitli/docs-components": "workspace:*"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@astrojs/check": "catalog:dev-common",
|
|
19
|
+
"@types/node": "catalog:types",
|
|
20
|
+
"typescript": "catalog:dev-common",
|
|
21
|
+
"wrangler": "catalog:dev-common"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting Started
|
|
3
|
+
description: Set up and start using {{appName}}.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
bun add {{name}}
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
Get up and running in minutes.
|
|
15
|
+
|
|
16
|
+
## Next Steps
|
|
17
|
+
|
|
18
|
+
- Check the [API Reference](/reference/) for detailed documentation
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "{{appName}} Docs"
|
|
3
|
+
description: "{{description}}"
|
|
4
|
+
template: splash
|
|
5
|
+
hero:
|
|
6
|
+
tagline: "{{description}}"
|
|
7
|
+
actions:
|
|
8
|
+
- text: Get Started
|
|
9
|
+
link: /{{appNameLower}}/guides/getting-started/
|
|
10
|
+
icon: right-arrow
|
|
11
|
+
- text: API Reference
|
|
12
|
+
link: /{{appNameLower}}/reference/
|
|
13
|
+
icon: open-book
|
|
14
|
+
variant: minimal
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
import { Card, CardGrid } from "@astrojs/starlight/components";
|
|
18
|
+
|
|
19
|
+
## Next steps
|
|
20
|
+
|
|
21
|
+
<CardGrid stagger>
|
|
22
|
+
<Card title="Getting Started" icon="rocket">
|
|
23
|
+
Follow the [getting started guide](./guides/getting-started/) to set up your environment.
|
|
24
|
+
</Card>
|
|
25
|
+
<Card title="Configuration" icon="setting">
|
|
26
|
+
See the [configuration reference](./reference/) for all available options.
|
|
27
|
+
</Card>
|
|
28
|
+
</CardGrid>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { defineCollection } from "astro:content";
|
|
2
|
+
import { docsLoader } from "@astrojs/starlight/loaders";
|
|
3
|
+
import { docsSchema } from "@astrojs/starlight/schema";
|
|
4
|
+
|
|
5
|
+
export const collections = {
|
|
6
|
+
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
|
|
7
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# sample tags.yml for starlight-tags plugin configuration
|
|
2
|
+
tags:
|
|
3
|
+
getting-started:
|
|
4
|
+
label: "Getting Started"
|
|
5
|
+
description: "Essential first steps"
|
|
6
|
+
color: "#22c55e"
|
|
7
|
+
icon: "🚀"
|
|
8
|
+
priority: 100
|
|
9
|
+
|
|
10
|
+
components:
|
|
11
|
+
label: "Components"
|
|
12
|
+
description: "UI building blocks"
|
|
13
|
+
color: "#3b82f6"
|
|
14
|
+
icon: "🧩"
|
|
15
|
+
|
|
16
|
+
advanced:
|
|
17
|
+
label: "Advanced"
|
|
18
|
+
description: "For experienced users"
|
|
19
|
+
color: "#f59e0b"
|
|
20
|
+
icon: "⚡"
|
|
21
|
+
difficulty: advanced
|
|
22
|
+
prerequisites:
|
|
23
|
+
- getting-started
|
|
24
|
+
|
|
25
|
+
defaults:
|
|
26
|
+
color: "#6b7280"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "node_modules/wrangler/config-schema.json",
|
|
3
|
+
"account_id": "5def525918a785eeda6decf66cf6b99b",
|
|
4
|
+
"compatibility_date": "2026-03-17",
|
|
5
|
+
"compatibility_flags": ["global_fetch_strictly_public", "nodejs_compat"],
|
|
6
|
+
"name": "{{workerName}}",
|
|
7
|
+
"main": "@astrojs/cloudflare/entrypoints/server",
|
|
8
|
+
"assets": {
|
|
9
|
+
"directory": "./dist",
|
|
10
|
+
"binding": "ASSETS",
|
|
11
|
+
"html_handling": "auto-trailing-slash",
|
|
12
|
+
"not_found_handling": "404-errors",
|
|
13
|
+
"run_worker_first": false
|
|
14
|
+
},
|
|
15
|
+
"observability": {
|
|
16
|
+
"enabled": true
|
|
17
|
+
},
|
|
18
|
+
"placement": {
|
|
19
|
+
"mode": "smart"
|
|
20
|
+
},
|
|
21
|
+
"preview_urls": true,
|
|
22
|
+
"workers_dev": true,
|
|
23
|
+
"routes": [
|
|
24
|
+
{
|
|
25
|
+
"pattern": "https://docs.knitli.com/{{appNameLower}}/*",
|
|
26
|
+
"zone_name": "knitli.com"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"vars": {
|
|
30
|
+
"PUBLIC_DOCS_PRODUCT": "{{appName}}"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Knitli Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
// @ts-check
|
|
6
|
+
|
|
7
|
+
import type { OutgoingHttpHeaders } from "node:http2";
|
|
8
|
+
import cloudflare from "@astrojs/cloudflare";
|
|
9
|
+
import markdoc from "@astrojs/markdoc";
|
|
10
|
+
import mdx from "@astrojs/mdx";
|
|
11
|
+
import sitemap from "@astrojs/sitemap";
|
|
12
|
+
import starlight from "@astrojs/starlight";
|
|
13
|
+
import { DocsAssets } from "@knitli/docs-components";
|
|
14
|
+
import llmEnhancements from "@nuasite/llm-enhancements";
|
|
15
|
+
import { defineConfig, fontProviders } from "astro/config";
|
|
16
|
+
import cloudflarePagesHeaders from "astro-cloudflare-pages-headers";
|
|
17
|
+
import astroD2 from "astro-d2";
|
|
18
|
+
import favicons from "astro-favicons";
|
|
19
|
+
import rehypeExternalLinks from "rehype-external-links";
|
|
20
|
+
import starlightAnnouncement from "starlight-announcement";
|
|
21
|
+
import starlightChangelogs from "starlight-changelogs";
|
|
22
|
+
import starlightHeadingBadges from "starlight-heading-badges";
|
|
23
|
+
import starlightLinksValidator from "starlight-links-validator";
|
|
24
|
+
import starlightLlmsText from "starlight-llms-txt";
|
|
25
|
+
import starlightPageActions from "starlight-page-actions";
|
|
26
|
+
import {
|
|
27
|
+
starlightIconsIntegration,
|
|
28
|
+
starlightIconsPlugin,
|
|
29
|
+
} from "starlight-plugin-icons";
|
|
30
|
+
import starlightScrollToTop from "starlight-scroll-to-top";
|
|
31
|
+
import starlightSidebarTopics from "starlight-sidebar-topics";
|
|
32
|
+
import starlightTags from "starlight-tags";
|
|
33
|
+
import { searchForWorkspaceRoot } from "vite";
|
|
34
|
+
import viteTsconfigPaths from "vite-tsconfig-paths";
|
|
35
|
+
|
|
36
|
+
type defaultIntegration =
|
|
37
|
+
| "sitemap"
|
|
38
|
+
| "astroD2"
|
|
39
|
+
| "markdoc"
|
|
40
|
+
| "mdx"
|
|
41
|
+
| "favicons"
|
|
42
|
+
| "cloudflare-pages-headers";
|
|
43
|
+
|
|
44
|
+
function nonNullable<T>(value: T): value is NonNullable<T> {
|
|
45
|
+
return value != null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Defaults (defined before interface so `typeof` references work) ──
|
|
49
|
+
|
|
50
|
+
export const {
|
|
51
|
+
headlineLogoDark,
|
|
52
|
+
headlineLogoLight,
|
|
53
|
+
variables,
|
|
54
|
+
docsStyle,
|
|
55
|
+
faviconIco,
|
|
56
|
+
faviconSvg,
|
|
57
|
+
} = DocsAssets;
|
|
58
|
+
|
|
59
|
+
const shikiCfg = {
|
|
60
|
+
themes: {
|
|
61
|
+
light: "catppuccin-latte" as const,
|
|
62
|
+
dark: "catppuccin-mocha" as const,
|
|
63
|
+
},
|
|
64
|
+
bundledLangs: [
|
|
65
|
+
"ansi",
|
|
66
|
+
"astro",
|
|
67
|
+
"bash",
|
|
68
|
+
"json",
|
|
69
|
+
"markdown",
|
|
70
|
+
"python",
|
|
71
|
+
"rust",
|
|
72
|
+
"toml",
|
|
73
|
+
"typescript",
|
|
74
|
+
"yaml",
|
|
75
|
+
],
|
|
76
|
+
langAlias: {
|
|
77
|
+
js: "typescript",
|
|
78
|
+
md: "markdown",
|
|
79
|
+
py: "python",
|
|
80
|
+
rs: "rust",
|
|
81
|
+
sh: "bash",
|
|
82
|
+
yml: "yaml",
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const imgDomains = [
|
|
87
|
+
{ protocol: "https", hostname: "ui-avatars.com" },
|
|
88
|
+
{ protocol: "https", hostname: "knitli.com" },
|
|
89
|
+
{ protocol: "https", hostname: "*.knitli.com" },
|
|
90
|
+
{ protocol: "https", hostname: "*.githubusercontent.com" },
|
|
91
|
+
{ protocol: "https", hostname: "*.cloudflare.com" },
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const defaultFontConfig = [
|
|
95
|
+
{
|
|
96
|
+
provider: fontProviders.google(),
|
|
97
|
+
name: "DM Mono",
|
|
98
|
+
cssVariable: "--font-sans",
|
|
99
|
+
weights: [400, 500, 700] as [number, ...number[]],
|
|
100
|
+
styles: ["normal", "italic"] as [string, ...string[]],
|
|
101
|
+
subsets: ["latin"] as [string, ...string[]],
|
|
102
|
+
formats: ["woff2"] as [string, ...string[]],
|
|
103
|
+
fallbacks: [
|
|
104
|
+
"Roboto Mono",
|
|
105
|
+
"Menlo",
|
|
106
|
+
"Consolas",
|
|
107
|
+
"DejaVu Sans Mono",
|
|
108
|
+
"monospace",
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
provider: fontProviders.google(),
|
|
113
|
+
name: "JetBrains Mono",
|
|
114
|
+
cssVariable: "--font-mono",
|
|
115
|
+
weights: [400, 500, 700] as [number, ...number[]],
|
|
116
|
+
styles: ["normal", "italic"] as [string, ...string[]],
|
|
117
|
+
subsets: ["latin"] as [string, ...string[]],
|
|
118
|
+
formats: ["woff2"] as [string, ...string[]],
|
|
119
|
+
fallbacks: [
|
|
120
|
+
"DM Mono",
|
|
121
|
+
"Roboto Mono",
|
|
122
|
+
"Menlo",
|
|
123
|
+
"Consolas",
|
|
124
|
+
"DejaVu Sans Mono",
|
|
125
|
+
"monospace",
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
const codeweaverFontConfig = [
|
|
131
|
+
{ ...defaultFontConfig[1], cssVariable: "--font-sans" },
|
|
132
|
+
{
|
|
133
|
+
provider: fontProviders.google(),
|
|
134
|
+
name: "IBM Plex Mono",
|
|
135
|
+
cssVariable: "--font-mono",
|
|
136
|
+
weights: [400, 500, 700] as [number, ...number[]],
|
|
137
|
+
styles: ["normal", "italic"] as [string, ...string[]],
|
|
138
|
+
subsets: ["latin"] as [string, ...string[]],
|
|
139
|
+
formats: ["woff2"] as [string, ...string[]],
|
|
140
|
+
fallbacks: [
|
|
141
|
+
"JetBrains Mono",
|
|
142
|
+
"DM Mono",
|
|
143
|
+
"Roboto Mono",
|
|
144
|
+
"Menlo",
|
|
145
|
+
"Consolas",
|
|
146
|
+
"DejaVu Sans Mono",
|
|
147
|
+
"monospace",
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
// ── Public interface ──
|
|
153
|
+
|
|
154
|
+
export interface DocsTemplateOptions {
|
|
155
|
+
appName: string;
|
|
156
|
+
description: string;
|
|
157
|
+
llmConfig: {
|
|
158
|
+
llmDescription: string;
|
|
159
|
+
promotePatterns: string[];
|
|
160
|
+
demotePatterns: string[];
|
|
161
|
+
};
|
|
162
|
+
rootDir: string;
|
|
163
|
+
shikiConfig?: typeof shikiCfg;
|
|
164
|
+
logoDark?: string;
|
|
165
|
+
logoLight?: string;
|
|
166
|
+
imageDomains?: typeof imgDomains;
|
|
167
|
+
is_codeweaver?: boolean;
|
|
168
|
+
sitemapFilter?: (page: string) => boolean;
|
|
169
|
+
linkValidationConfig?: Record<string, unknown>;
|
|
170
|
+
sidebarConfig?: {
|
|
171
|
+
label: string;
|
|
172
|
+
autogenerate: { directory: string };
|
|
173
|
+
}[];
|
|
174
|
+
unwantedPlugins?: string[];
|
|
175
|
+
// biome-ignore lint/suspicious/noExplicitAny: plugin configs are opaque pass-throughs
|
|
176
|
+
pluginConfigs?: Record<string, any>;
|
|
177
|
+
unwantedIntegrations?: defaultIntegration[];
|
|
178
|
+
// biome-ignore lint/suspicious/noExplicitAny: integration configs are opaque pass-throughs
|
|
179
|
+
integrationConfigs?: Record<string, any>;
|
|
180
|
+
headersConfig?: OutgoingHttpHeaders;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export type IntegrationOptions = Pick<
|
|
184
|
+
DocsTemplateOptions,
|
|
185
|
+
"appName" | "sitemapFilter" | "integrationConfigs"
|
|
186
|
+
> & { unwantedIntegrations?: defaultIntegration[] };
|
|
187
|
+
|
|
188
|
+
const getIntegrations = (options: IntegrationOptions) => {
|
|
189
|
+
const {
|
|
190
|
+
appName,
|
|
191
|
+
sitemapFilter,
|
|
192
|
+
integrationConfigs,
|
|
193
|
+
unwantedIntegrations = [],
|
|
194
|
+
} = options;
|
|
195
|
+
const defaultIntegrations = [
|
|
196
|
+
astroD2({ skipGeneration: true }),
|
|
197
|
+
markdoc(),
|
|
198
|
+
llmEnhancements({ llmsTxt: false }),
|
|
199
|
+
mdx(),
|
|
200
|
+
favicons({
|
|
201
|
+
name: `${appName} Docs by Knitli`,
|
|
202
|
+
short_name: `${appName} Docs`,
|
|
203
|
+
input: {
|
|
204
|
+
favicons: [faviconSvg],
|
|
205
|
+
},
|
|
206
|
+
}),
|
|
207
|
+
cloudflarePagesHeaders({}),
|
|
208
|
+
sitemap({
|
|
209
|
+
filter: sitemapFilter || ((page) => !/\^\/(?!cdn-cgi\/)/.test(page)),
|
|
210
|
+
changefreq: "weekly",
|
|
211
|
+
priority: 0.4,
|
|
212
|
+
lastmod: new Date(),
|
|
213
|
+
namespaces: {
|
|
214
|
+
image: false,
|
|
215
|
+
video: false,
|
|
216
|
+
},
|
|
217
|
+
}),
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
const filtered = defaultIntegrations.filter((integration) => {
|
|
221
|
+
const name = integration?.name;
|
|
222
|
+
return !name || !unwantedIntegrations.includes(name as defaultIntegration);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if (!integrationConfigs) return filtered;
|
|
226
|
+
|
|
227
|
+
const overrides = Object.entries(integrationConfigs)
|
|
228
|
+
.map(([integrationName, config]) => {
|
|
229
|
+
switch (integrationName) {
|
|
230
|
+
case "astroD2":
|
|
231
|
+
return astroD2(config);
|
|
232
|
+
case "markdoc":
|
|
233
|
+
return markdoc(config);
|
|
234
|
+
case "mdx":
|
|
235
|
+
return mdx(config);
|
|
236
|
+
case "favicons":
|
|
237
|
+
return favicons(config);
|
|
238
|
+
case "sitemap":
|
|
239
|
+
return sitemap(config);
|
|
240
|
+
default:
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
.filter(nonNullable);
|
|
245
|
+
|
|
246
|
+
return [...filtered, ...overrides];
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export type PluginOptions = Pick<
|
|
250
|
+
DocsTemplateOptions,
|
|
251
|
+
"appName" | "llmConfig" | "pluginConfigs"
|
|
252
|
+
> & { unwantedPlugins?: string[] };
|
|
253
|
+
|
|
254
|
+
const get_plugins = (options: PluginOptions) => {
|
|
255
|
+
const { appName, llmConfig, pluginConfigs, unwantedPlugins = [] } = options;
|
|
256
|
+
const defaultPlugins = [
|
|
257
|
+
starlightAnnouncement(),
|
|
258
|
+
starlightChangelogs(),
|
|
259
|
+
starlightHeadingBadges(),
|
|
260
|
+
starlightIconsIntegration(),
|
|
261
|
+
starlightIconsPlugin(),
|
|
262
|
+
starlightLinksValidator(),
|
|
263
|
+
starlightPageActions({
|
|
264
|
+
baseUrl: `https://docs.knitli.com/${appName.toLowerCase()}`,
|
|
265
|
+
actions: { claude: true, chatgpt: true, markdown: true },
|
|
266
|
+
share: true,
|
|
267
|
+
}),
|
|
268
|
+
starlightTags({ onInlineTagsNotFound: "warn" }),
|
|
269
|
+
pluginConfigs?.starlightSidebarTopics
|
|
270
|
+
? starlightSidebarTopics(pluginConfigs.starlightSidebarTopics)
|
|
271
|
+
: null,
|
|
272
|
+
starlightScrollToTop({ showOnHomepage: false }),
|
|
273
|
+
// We need to configure starlight-tags with a tags.yml.
|
|
274
|
+
//starlightTags(),
|
|
275
|
+
starlightLlmsText({
|
|
276
|
+
projectName: appName,
|
|
277
|
+
description: llmConfig.llmDescription,
|
|
278
|
+
promote: llmConfig.promotePatterns,
|
|
279
|
+
demote: llmConfig.demotePatterns,
|
|
280
|
+
minify: {
|
|
281
|
+
whitespace: true,
|
|
282
|
+
note: true,
|
|
283
|
+
details: true,
|
|
284
|
+
},
|
|
285
|
+
}),
|
|
286
|
+
];
|
|
287
|
+
|
|
288
|
+
const filtered = defaultPlugins
|
|
289
|
+
.filter(nonNullable)
|
|
290
|
+
.filter((plugin) => !unwantedPlugins.includes(plugin.name));
|
|
291
|
+
|
|
292
|
+
if (!pluginConfigs) return filtered;
|
|
293
|
+
|
|
294
|
+
const overrides = Object.entries(pluginConfigs)
|
|
295
|
+
.map(([pluginName, config]) => {
|
|
296
|
+
switch (pluginName) {
|
|
297
|
+
case "starlightAnnouncement":
|
|
298
|
+
return starlightAnnouncement(config);
|
|
299
|
+
case "starlightIconsIntegration":
|
|
300
|
+
return starlightIconsIntegration(config);
|
|
301
|
+
case "starlightIconsPlugin":
|
|
302
|
+
return starlightIconsPlugin(config);
|
|
303
|
+
case "starlightLinksValidator":
|
|
304
|
+
return starlightLinksValidator(config);
|
|
305
|
+
case "starlightPageActions":
|
|
306
|
+
return starlightPageActions(config);
|
|
307
|
+
case "starlightTags":
|
|
308
|
+
return starlightTags(config);
|
|
309
|
+
case "starlightScrollToTop":
|
|
310
|
+
return starlightScrollToTop(config);
|
|
311
|
+
default:
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
.filter(nonNullable);
|
|
316
|
+
|
|
317
|
+
return [...filtered, ...overrides];
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
export default function createConfig(options: DocsTemplateOptions) {
|
|
321
|
+
const {
|
|
322
|
+
appName,
|
|
323
|
+
description,
|
|
324
|
+
llmConfig,
|
|
325
|
+
rootDir,
|
|
326
|
+
shikiConfig = shikiCfg,
|
|
327
|
+
logoDark = headlineLogoDark,
|
|
328
|
+
logoLight = headlineLogoLight,
|
|
329
|
+
imageDomains = imgDomains,
|
|
330
|
+
is_codeweaver = false,
|
|
331
|
+
sitemapFilter,
|
|
332
|
+
sidebarConfig,
|
|
333
|
+
pluginConfigs,
|
|
334
|
+
integrationConfigs,
|
|
335
|
+
unwantedIntegrations,
|
|
336
|
+
unwantedPlugins,
|
|
337
|
+
headersConfig,
|
|
338
|
+
} = options;
|
|
339
|
+
|
|
340
|
+
// https://astro.build/config
|
|
341
|
+
return defineConfig({
|
|
342
|
+
site: "https://docs.knitli.com",
|
|
343
|
+
base: `/${appName.toLowerCase()}/`,
|
|
344
|
+
adapter: cloudflare({
|
|
345
|
+
prerenderEnvironment: "workerd",
|
|
346
|
+
experimental: {
|
|
347
|
+
headersAndRedirectsDevModeSupport: true,
|
|
348
|
+
},
|
|
349
|
+
configPath: `${rootDir}/wrangler.jsonc`,
|
|
350
|
+
imageService: "compile",
|
|
351
|
+
}),
|
|
352
|
+
// Image optimization
|
|
353
|
+
image: {
|
|
354
|
+
service: {
|
|
355
|
+
entrypoint: "astro/assets/services/sharp",
|
|
356
|
+
},
|
|
357
|
+
responsiveStyles: true,
|
|
358
|
+
layout: "constrained",
|
|
359
|
+
remotePatterns: imageDomains,
|
|
360
|
+
},
|
|
361
|
+
compressHTML: true,
|
|
362
|
+
// biome-ignore lint/suspicious/noExplicitAny: Astro's FontFamily generic inference doesn't match spread patterns
|
|
363
|
+
fonts: (is_codeweaver ? codeweaverFontConfig : defaultFontConfig) as any,
|
|
364
|
+
// Build optimizations
|
|
365
|
+
build: {
|
|
366
|
+
inlineStylesheets: "auto",
|
|
367
|
+
assets: "_astro",
|
|
368
|
+
},
|
|
369
|
+
markdown: {
|
|
370
|
+
shikiConfig: Object.fromEntries(
|
|
371
|
+
Object.entries(shikiConfig).filter(([key]) => key !== "bundledLangs"),
|
|
372
|
+
),
|
|
373
|
+
rehypePlugins: [
|
|
374
|
+
rehypeExternalLinks({
|
|
375
|
+
content: { type: "text", value: " 🔗" },
|
|
376
|
+
rel: ["nofollow"],
|
|
377
|
+
}),
|
|
378
|
+
],
|
|
379
|
+
},
|
|
380
|
+
server: {
|
|
381
|
+
headers: headersConfig,
|
|
382
|
+
},
|
|
383
|
+
trailingSlash: "always",
|
|
384
|
+
// Vite configuration for better bundling
|
|
385
|
+
vite: {
|
|
386
|
+
server: {
|
|
387
|
+
fs: {
|
|
388
|
+
allow: [searchForWorkspaceRoot(rootDir)],
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
assetsInclude: [
|
|
392
|
+
"src/*.webp",
|
|
393
|
+
"src/*.png",
|
|
394
|
+
"src/*.jpg",
|
|
395
|
+
"src/*.jpeg",
|
|
396
|
+
"src/*.svg",
|
|
397
|
+
"src/*.avif",
|
|
398
|
+
],
|
|
399
|
+
plugins: [viteTsconfigPaths({ loose: true })],
|
|
400
|
+
define: {
|
|
401
|
+
"import.meta.env.PUBLIC_DOCS_PRODUCT": JSON.stringify(appName),
|
|
402
|
+
},
|
|
403
|
+
build: {
|
|
404
|
+
cssMinify: "lightningcss",
|
|
405
|
+
minify: "esbuild",
|
|
406
|
+
cssCodeSplit: true,
|
|
407
|
+
rollupOptions: {
|
|
408
|
+
external: ["shiki", "shiki-esbuild"],
|
|
409
|
+
output: {
|
|
410
|
+
format: "es",
|
|
411
|
+
dir: `${rootDir}/dist/_astro/`,
|
|
412
|
+
compact: true,
|
|
413
|
+
interop: "esModule",
|
|
414
|
+
experimentalMinChunkSize: 10000,
|
|
415
|
+
banner: `
|
|
416
|
+
/* SPDX-FileCopyrightText: 2026 Knitli Inc.
|
|
417
|
+
* SPDX-License-Identifier: MIT OR Apache-2.0
|
|
418
|
+
*/
|
|
419
|
+
`.trim(),
|
|
420
|
+
minifyInternalExports: true,
|
|
421
|
+
sourcemap: false,
|
|
422
|
+
generatedCode: {
|
|
423
|
+
arrowFunctions: true,
|
|
424
|
+
constBindings: true,
|
|
425
|
+
objectShorthand: true,
|
|
426
|
+
symbols: true,
|
|
427
|
+
},
|
|
428
|
+
entryFileNames: "assets/[name]-[hash].js",
|
|
429
|
+
chunkFileNames: "assets/[name]-[hash].js",
|
|
430
|
+
assetFileNames: "assets/[name]-[hash][extname]",
|
|
431
|
+
},
|
|
432
|
+
treeshake: "smallest",
|
|
433
|
+
},
|
|
434
|
+
ssr: false,
|
|
435
|
+
},
|
|
436
|
+
css: {
|
|
437
|
+
lightningcss: {},
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
prefetch: {
|
|
441
|
+
defaultStrategy: "viewport",
|
|
442
|
+
},
|
|
443
|
+
prerenderConflictBehavior: "warn",
|
|
444
|
+
experimental: {
|
|
445
|
+
chromeDevtoolsWorkspace: true,
|
|
446
|
+
clientPrerender: true,
|
|
447
|
+
contentIntellisense: true,
|
|
448
|
+
queuedRendering: {
|
|
449
|
+
contentCache: true,
|
|
450
|
+
enabled: true,
|
|
451
|
+
},
|
|
452
|
+
rustCompiler: true,
|
|
453
|
+
svgo: {
|
|
454
|
+
plugins: [
|
|
455
|
+
{
|
|
456
|
+
name: "preset-default",
|
|
457
|
+
params: {
|
|
458
|
+
overrides: {
|
|
459
|
+
removeMetadata: false,
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
// Static site generation for Cloudflare
|
|
468
|
+
output: "static",
|
|
469
|
+
integrations: [
|
|
470
|
+
...getIntegrations({
|
|
471
|
+
appName,
|
|
472
|
+
sitemapFilter,
|
|
473
|
+
integrationConfigs,
|
|
474
|
+
unwantedIntegrations,
|
|
475
|
+
}),
|
|
476
|
+
starlight({
|
|
477
|
+
title: `${appName} Docs`,
|
|
478
|
+
pagefind: true,
|
|
479
|
+
description: description,
|
|
480
|
+
logo: {
|
|
481
|
+
dark: logoDark,
|
|
482
|
+
light: logoLight,
|
|
483
|
+
alt: is_codeweaver ? "CodeWeaver Logo" : "Knitli Logo",
|
|
484
|
+
replacesTitle: true,
|
|
485
|
+
},
|
|
486
|
+
editLink: {
|
|
487
|
+
baseUrl: `https://github.com/knitli/${appName.toLowerCase()}/edit/main/docs-site/src`,
|
|
488
|
+
},
|
|
489
|
+
expressiveCode: {
|
|
490
|
+
useStarlightDarkModeSwitch: true,
|
|
491
|
+
themes: [shikiConfig.themes.dark, shikiConfig.themes.light],
|
|
492
|
+
removeUnusedThemes: true,
|
|
493
|
+
shiki: {
|
|
494
|
+
...Object.fromEntries(
|
|
495
|
+
Object.entries(shikiConfig).filter(([key]) => key !== "themes"),
|
|
496
|
+
),
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
plugins: get_plugins({
|
|
500
|
+
appName,
|
|
501
|
+
llmConfig,
|
|
502
|
+
pluginConfigs,
|
|
503
|
+
unwantedPlugins,
|
|
504
|
+
// biome-ignore lint/suspicious/noExplicitAny: Starlight plugin types are complex Zod inferences
|
|
505
|
+
}) as any[],
|
|
506
|
+
social: [
|
|
507
|
+
{
|
|
508
|
+
icon: "github",
|
|
509
|
+
label: "GitHub",
|
|
510
|
+
href: `https://github.com/knitli/${appName.toLowerCase()}`,
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
components: {
|
|
514
|
+
Footer: "@knitli/docs-components/Footer.astro",
|
|
515
|
+
PageFrame: "@knitli/docs-components/PageFrame.astro",
|
|
516
|
+
},
|
|
517
|
+
customCss: [variables, docsStyle, `${rootDir}/src/styles/custom.css`],
|
|
518
|
+
head: [
|
|
519
|
+
{
|
|
520
|
+
tag: "meta",
|
|
521
|
+
attrs: {
|
|
522
|
+
property: "og:image",
|
|
523
|
+
content: `https://docs.knitli.com/${appName.toLowerCase()}/og-image.png`,
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
tag: "meta",
|
|
528
|
+
attrs: {
|
|
529
|
+
property: "twitter:card",
|
|
530
|
+
content: "summary_large_image",
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
],
|
|
534
|
+
sidebar: sidebarConfig || [
|
|
535
|
+
{
|
|
536
|
+
label: "Guides",
|
|
537
|
+
autogenerate: { directory: "guides" },
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
label: "Examples",
|
|
541
|
+
autogenerate: { directory: "examples" },
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
label: "Reference",
|
|
545
|
+
autogenerate: { directory: "reference" },
|
|
546
|
+
},
|
|
547
|
+
],
|
|
548
|
+
}),
|
|
549
|
+
],
|
|
550
|
+
});
|
|
551
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Knitli Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
cpSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
export { type DocsTemplateOptions, default as createConfig } from "./config.js";
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const SCAFFOLDING_DIR = resolve(__dirname, "../scaffolding");
|
|
21
|
+
|
|
22
|
+
export interface InitOptions {
|
|
23
|
+
/** Display name for the product (e.g. "Recoco", "CodeWeaver") */
|
|
24
|
+
appName: string;
|
|
25
|
+
/** npm package name (e.g. "@knitli-site/recoco-docs") */
|
|
26
|
+
name: string;
|
|
27
|
+
/** Short product description */
|
|
28
|
+
description: string;
|
|
29
|
+
/** Cloudflare Worker name (e.g. "recoco-docs") */
|
|
30
|
+
workerName?: string;
|
|
31
|
+
/** Whether this is a CodeWeaver-branded site */
|
|
32
|
+
is_codeweaver?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** File extensions that should have placeholder substitution applied */
|
|
36
|
+
const TEXT_EXTENSIONS = new Set([
|
|
37
|
+
".ts",
|
|
38
|
+
".js",
|
|
39
|
+
".mjs",
|
|
40
|
+
".json",
|
|
41
|
+
".jsonc",
|
|
42
|
+
".md",
|
|
43
|
+
".mdx",
|
|
44
|
+
".yml",
|
|
45
|
+
".yaml",
|
|
46
|
+
".css",
|
|
47
|
+
".html",
|
|
48
|
+
".astro",
|
|
49
|
+
".d.ts",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
function isTextFile(filePath: string): boolean {
|
|
53
|
+
return TEXT_EXTENSIONS.has(filePath.slice(filePath.lastIndexOf(".")));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildReplacements(options: InitOptions): Record<string, string> {
|
|
57
|
+
return {
|
|
58
|
+
"{{appName}}": options.appName,
|
|
59
|
+
"{{appNameLower}}": options.appName.toLowerCase(),
|
|
60
|
+
"{{name}}": options.name,
|
|
61
|
+
"{{description}}": options.description,
|
|
62
|
+
"{{workerName}}":
|
|
63
|
+
options.workerName ?? `${options.appName.toLowerCase()}-docs`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function applyReplacements(
|
|
68
|
+
content: string,
|
|
69
|
+
replacements: Record<string, string>,
|
|
70
|
+
): string {
|
|
71
|
+
let result = content;
|
|
72
|
+
for (const [placeholder, value] of Object.entries(replacements)) {
|
|
73
|
+
result = result.replaceAll(placeholder, value);
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Initialize a new Knitli docs site from the template scaffolding.
|
|
80
|
+
*
|
|
81
|
+
* Copies all files from the scaffolding directory to the target path,
|
|
82
|
+
* applying placeholder substitution to text files.
|
|
83
|
+
*
|
|
84
|
+
* @param targetPath - Directory to scaffold into (will be created if it doesn't exist)
|
|
85
|
+
* @param options - Configuration for placeholder substitution
|
|
86
|
+
* @returns List of files created
|
|
87
|
+
*/
|
|
88
|
+
export function initDocsTemplate(
|
|
89
|
+
targetPath: string,
|
|
90
|
+
options: InitOptions,
|
|
91
|
+
): string[] {
|
|
92
|
+
const target = resolve(targetPath);
|
|
93
|
+
const replacements = buildReplacements(options);
|
|
94
|
+
const created: string[] = [];
|
|
95
|
+
|
|
96
|
+
if (!existsSync(SCAFFOLDING_DIR)) {
|
|
97
|
+
throw new Error(`Scaffolding directory not found at ${SCAFFOLDING_DIR}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function copyDir(srcDir: string, destDir: string) {
|
|
101
|
+
if (!existsSync(destDir)) {
|
|
102
|
+
mkdirSync(destDir, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const entry of readdirSync(srcDir)) {
|
|
106
|
+
const srcPath = join(srcDir, entry);
|
|
107
|
+
const destPath = join(destDir, entry);
|
|
108
|
+
const stat = statSync(srcPath);
|
|
109
|
+
|
|
110
|
+
if (stat.isDirectory()) {
|
|
111
|
+
copyDir(srcPath, destPath);
|
|
112
|
+
} else if (isTextFile(srcPath)) {
|
|
113
|
+
const content = readFileSync(srcPath, "utf-8");
|
|
114
|
+
const processed = applyReplacements(content, replacements);
|
|
115
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
116
|
+
writeFileSync(destPath, processed, "utf-8");
|
|
117
|
+
created.push(destPath);
|
|
118
|
+
} else {
|
|
119
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
120
|
+
cpSync(srcPath, destPath);
|
|
121
|
+
created.push(destPath);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
copyDir(SCAFFOLDING_DIR, target);
|
|
127
|
+
return created;
|
|
128
|
+
}
|