@knitli/astro-docs-template 0.1.0 → 0.1.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.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bun
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env bun
2
+ import { stdout, stdin } from 'node:process';
3
+ import { createInterface } from 'node:readline/promises';
4
+ import { PIECES, PIECE_NAMES, addPieces, initDocsTemplate } from './index.js';
5
+
6
+ function parseArgs(argv) {
7
+ const flags = {};
8
+ const positional = [];
9
+ for (let i = 0; i < argv.length; i++) {
10
+ const arg = argv[i];
11
+ if (arg === "--help" || arg === "-h") {
12
+ flags.help = "true";
13
+ } else if (arg === "--force" || arg === "-f") {
14
+ flags.force = "true";
15
+ } else if (arg.startsWith("--") && i + 1 < argv.length) {
16
+ flags[arg.slice(2)] = argv[++i];
17
+ } else {
18
+ positional.push(arg);
19
+ }
20
+ }
21
+ return { flags, positional };
22
+ }
23
+ const HELP = `knitli-docs — scaffold and manage Knitli docs sites
24
+
25
+ Usage:
26
+ knitli-docs init [dir] Full scaffold into dir (default: .)
27
+ knitli-docs add <piece...> [--dir d] Add specific pieces to a project
28
+ knitli-docs list Show available pieces
29
+
30
+ Pieces:
31
+ ${Object.entries(PIECES).map(([name, { description }]) => ` ${name.padEnd(18)} ${description}`).join("\n")}
32
+
33
+ Options:
34
+ --app-name <name> Product name (e.g. "Recoco")
35
+ --name <pkg> npm package name (e.g. "@knitli-site/recoco-docs")
36
+ --description <desc> Short product description
37
+ --worker-name <name> Cloudflare Worker name (default: <appname>-docs)
38
+ --dir <path> Target directory for 'add' (default: .)
39
+ --force, -f Overwrite existing files
40
+ --help, -h Show this help
41
+ `;
42
+ async function getOptions(flags) {
43
+ const rl = createInterface({ input: stdin, output: stdout });
44
+ const ask = async (question, defaultValue) => {
45
+ const suffix = defaultValue ? ` [${defaultValue}]` : "";
46
+ const answer = await rl.question(`${question}${suffix}: `);
47
+ return answer.trim() || defaultValue || "";
48
+ };
49
+ try {
50
+ const appName = flags["app-name"] || await ask("Product name (e.g. Recoco)");
51
+ if (!appName) {
52
+ console.error("Error: product name is required");
53
+ process.exit(1);
54
+ }
55
+ const name = flags.name || await ask(
56
+ "npm package name",
57
+ `@knitli-site/${appName.toLowerCase()}-docs`
58
+ );
59
+ const description = flags.description || await ask("Short description");
60
+ const workerName = flags["worker-name"] || await ask("Worker name", `${appName.toLowerCase()}-docs`);
61
+ return { appName, name, description, workerName };
62
+ } finally {
63
+ rl.close();
64
+ }
65
+ }
66
+ async function main() {
67
+ const { flags, positional } = parseArgs(process.argv.slice(2));
68
+ const command = positional[0];
69
+ if (flags.help || !command) {
70
+ console.log(HELP);
71
+ process.exit(0);
72
+ }
73
+ switch (command) {
74
+ case "list": {
75
+ console.log("\nAvailable pieces:\n");
76
+ for (const [name, { description, paths }] of Object.entries(PIECES)) {
77
+ console.log(` ${name.padEnd(18)} ${description}`);
78
+ for (const p of paths) {
79
+ console.log(` ${"".padEnd(18)} └ ${p}`);
80
+ }
81
+ }
82
+ console.log();
83
+ break;
84
+ }
85
+ case "init": {
86
+ const dir = flags.dir || positional[1] || ".";
87
+ const options = await getOptions(flags);
88
+ console.log(`
89
+ Scaffolding into ${dir} ...
90
+ `);
91
+ const created = initDocsTemplate(dir, options);
92
+ console.log(`
93
+ Done — ${created.length} files created`);
94
+ break;
95
+ }
96
+ case "add": {
97
+ const pieces = positional.slice(1).filter((p) => !p.startsWith("-"));
98
+ if (pieces.length === 0) {
99
+ console.error(
100
+ "Error: specify at least one piece. Run 'knitli-docs list' to see options."
101
+ );
102
+ process.exit(1);
103
+ }
104
+ const invalid = pieces.filter(
105
+ (p) => !PIECE_NAMES.includes(p)
106
+ );
107
+ if (invalid.length > 0) {
108
+ console.error(`Error: unknown piece(s): ${invalid.join(", ")}`);
109
+ console.error(`Available: ${PIECE_NAMES.join(", ")}`);
110
+ process.exit(1);
111
+ }
112
+ const dir = flags.dir || ".";
113
+ const options = await getOptions(flags);
114
+ console.log(`
115
+ Adding ${pieces.join(", ")} to ${dir} ...
116
+ `);
117
+ const created = addPieces(dir, {
118
+ ...options,
119
+ pieces,
120
+ force: flags.force === "true"
121
+ });
122
+ console.log(`
123
+ Done — ${created.length} files created`);
124
+ break;
125
+ }
126
+ default: {
127
+ console.error(`Unknown command: ${command}
128
+ `);
129
+ console.log(HELP);
130
+ process.exit(1);
131
+ }
132
+ }
133
+ }
134
+ main().catch((err) => {
135
+ console.error(err.message);
136
+ process.exit(1);
137
+ });
@@ -0,0 +1,59 @@
1
+ import type { OutgoingHttpHeaders } from "node:http2";
2
+ type defaultIntegration = "sitemap" | "astroD2" | "markdoc" | "mdx" | "favicons" | "cloudflare-pages-headers";
3
+ export declare const headlineLogoDark: string, headlineLogoLight: string, variables: string, docsStyle: string, faviconIco: string, faviconSvg: string;
4
+ declare const shikiCfg: {
5
+ themes: {
6
+ light: "catppuccin-latte";
7
+ dark: "catppuccin-mocha";
8
+ };
9
+ bundledLangs: string[];
10
+ langAlias: {
11
+ js: string;
12
+ md: string;
13
+ py: string;
14
+ rs: string;
15
+ sh: string;
16
+ yml: string;
17
+ };
18
+ };
19
+ declare const imgDomains: {
20
+ protocol: string;
21
+ hostname: string;
22
+ }[];
23
+ export interface DocsTemplateOptions {
24
+ appName: string;
25
+ description: string;
26
+ llmConfig: {
27
+ llmDescription: string;
28
+ promotePatterns: string[];
29
+ demotePatterns: string[];
30
+ };
31
+ rootDir: string;
32
+ shikiConfig?: typeof shikiCfg;
33
+ logoDark?: string;
34
+ logoLight?: string;
35
+ imageDomains?: typeof imgDomains;
36
+ is_codeweaver?: boolean;
37
+ sitemapFilter?: (page: string) => boolean;
38
+ linkValidationConfig?: Record<string, unknown>;
39
+ sidebarConfig?: {
40
+ label: string;
41
+ autogenerate: {
42
+ directory: string;
43
+ };
44
+ }[];
45
+ unwantedPlugins?: string[];
46
+ pluginConfigs?: Record<string, any>;
47
+ unwantedIntegrations?: defaultIntegration[];
48
+ integrationConfigs?: Record<string, any>;
49
+ headersConfig?: OutgoingHttpHeaders;
50
+ }
51
+ export type IntegrationOptions = Pick<DocsTemplateOptions, "appName" | "sitemapFilter" | "integrationConfigs"> & {
52
+ unwantedIntegrations?: defaultIntegration[];
53
+ };
54
+ export type PluginOptions = Pick<DocsTemplateOptions, "appName" | "llmConfig" | "pluginConfigs"> & {
55
+ unwantedPlugins?: string[];
56
+ };
57
+ export default function createConfig(options: DocsTemplateOptions): import("astro").AstroUserConfig<never, never, never>;
58
+ export {};
59
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA6BtD,KAAK,kBAAkB,GACnB,SAAS,GACT,SAAS,GACT,SAAS,GACT,KAAK,GACL,UAAU,GACV,0BAA0B,CAAC;AAQ/B,eAAO,MACL,gBAAgB,UAChB,iBAAiB,UACjB,SAAS,UACT,SAAS,UACT,UAAU,UACV,UAAU,QACE,CAAC;AAEf,QAAA,MAAM,QAAQ;;;;;;;;;;;;;;CAyBb,CAAC;AAEF,QAAA,MAAM,UAAU;;;GAMf,CAAC;AA8DF,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE;QACT,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,EAAE,CAAC;QAC1B,cAAc,EAAE,MAAM,EAAE,CAAC;KAC1B,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,QAAQ,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,UAAU,CAAC;IACjC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/C,aAAa,CAAC,EAAE;QACd,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,EAAE;YAAE,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;KACrC,EAAE,CAAC;IACJ,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAE3B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACpC,oBAAoB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAE5C,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzC,aAAa,CAAC,EAAE,mBAAmB,CAAC;CACrC;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,mBAAmB,EACnB,SAAS,GAAG,eAAe,GAAG,oBAAoB,CACnD,GAAG;IAAE,oBAAoB,CAAC,EAAE,kBAAkB,EAAE,CAAA;CAAE,CAAC;AA+DpD,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,mBAAmB,EACnB,SAAS,GAAG,WAAW,GAAG,eAAe,CAC1C,GAAG;IAAE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAoEnC,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,OAAO,EAAE,mBAAmB,wDAuOhE"}
@@ -1,65 +1,43 @@
1
- // SPDX-FileCopyrightText: 2026 Knitli Inc.
2
- //
3
- // SPDX-License-Identifier: Apache-2.0
1
+ import cloudflare from '@astrojs/cloudflare';
2
+ import markdoc from '@astrojs/markdoc';
3
+ import mdx from '@astrojs/mdx';
4
+ import sitemap from '@astrojs/sitemap';
5
+ import starlight from '@astrojs/starlight';
6
+ import { DocsAssets } from '@knitli/docs-components';
7
+ import llmEnhancements from '@nuasite/llm-enhancements';
8
+ import { fontProviders, defineConfig } from 'astro/config';
9
+ import cloudflarePagesHeaders from 'astro-cloudflare-pages-headers';
10
+ import astroD2 from 'astro-d2';
11
+ import favicons from 'astro-favicons';
12
+ import rehypeExternalLinks from 'rehype-external-links';
13
+ import starlightAnnouncement from 'starlight-announcement';
14
+ import starlightChangelogs from 'starlight-changelogs';
15
+ import starlightHeadingBadges from 'starlight-heading-badges';
16
+ import starlightLinksValidator from 'starlight-links-validator';
17
+ import starlightLlmsText from 'starlight-llms-txt';
18
+ import starlightPageActions from 'starlight-page-actions';
19
+ import { starlightIconsIntegration, starlightIconsPlugin } from 'starlight-plugin-icons';
20
+ import starlightScrollToTop from 'starlight-scroll-to-top';
21
+ import starlightSidebarTopics from 'starlight-sidebar-topics';
22
+ import starlightTags from 'starlight-tags';
23
+ import { searchForWorkspaceRoot } from 'vite';
24
+ import viteTsconfigPaths from 'vite-tsconfig-paths';
4
25
 
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> {
26
+ function nonNullable(value) {
45
27
  return value != null;
46
28
  }
47
-
48
- // ── Defaults (defined before interface so `typeof` references work) ──
49
-
50
- export const {
29
+ const {
51
30
  headlineLogoDark,
52
31
  headlineLogoLight,
53
32
  variables,
54
33
  docsStyle,
55
34
  faviconIco,
56
- faviconSvg,
35
+ faviconSvg
57
36
  } = DocsAssets;
58
-
59
37
  const shikiCfg = {
60
38
  themes: {
61
- light: "catppuccin-latte" as const,
62
- dark: "catppuccin-mocha" as const,
39
+ light: "catppuccin-latte",
40
+ dark: "catppuccin-mocha"
63
41
  },
64
42
  bundledLangs: [
65
43
  "ansi",
@@ -71,7 +49,7 @@ const shikiCfg = {
71
49
  "rust",
72
50
  "toml",
73
51
  "typescript",
74
- "yaml",
52
+ "yaml"
75
53
  ],
76
54
  langAlias: {
77
55
  js: "typescript",
@@ -79,64 +57,61 @@ const shikiCfg = {
79
57
  py: "python",
80
58
  rs: "rust",
81
59
  sh: "bash",
82
- yml: "yaml",
83
- },
60
+ yml: "yaml"
61
+ }
84
62
  };
85
-
86
63
  const imgDomains = [
87
64
  { protocol: "https", hostname: "ui-avatars.com" },
88
65
  { protocol: "https", hostname: "knitli.com" },
89
66
  { protocol: "https", hostname: "*.knitli.com" },
90
67
  { protocol: "https", hostname: "*.githubusercontent.com" },
91
- { protocol: "https", hostname: "*.cloudflare.com" },
68
+ { protocol: "https", hostname: "*.cloudflare.com" }
92
69
  ];
93
-
94
70
  const defaultFontConfig = [
95
71
  {
96
72
  provider: fontProviders.google(),
97
73
  name: "DM Mono",
98
74
  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[]],
75
+ weights: [400, 500, 700],
76
+ styles: ["normal", "italic"],
77
+ subsets: ["latin"],
78
+ formats: ["woff2"],
103
79
  fallbacks: [
104
80
  "Roboto Mono",
105
81
  "Menlo",
106
82
  "Consolas",
107
83
  "DejaVu Sans Mono",
108
- "monospace",
109
- ],
84
+ "monospace"
85
+ ]
110
86
  },
111
87
  {
112
88
  provider: fontProviders.google(),
113
89
  name: "JetBrains Mono",
114
90
  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[]],
91
+ weights: [400, 500, 700],
92
+ styles: ["normal", "italic"],
93
+ subsets: ["latin"],
94
+ formats: ["woff2"],
119
95
  fallbacks: [
120
96
  "DM Mono",
121
97
  "Roboto Mono",
122
98
  "Menlo",
123
99
  "Consolas",
124
100
  "DejaVu Sans Mono",
125
- "monospace",
126
- ],
127
- },
101
+ "monospace"
102
+ ]
103
+ }
128
104
  ];
129
-
130
105
  const codeweaverFontConfig = [
131
106
  { ...defaultFontConfig[1], cssVariable: "--font-sans" },
132
107
  {
133
108
  provider: fontProviders.google(),
134
109
  name: "IBM Plex Mono",
135
110
  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[]],
111
+ weights: [400, 500, 700],
112
+ styles: ["normal", "italic"],
113
+ subsets: ["latin"],
114
+ formats: ["woff2"],
140
115
  fallbacks: [
141
116
  "JetBrains Mono",
142
117
  "DM Mono",
@@ -144,53 +119,16 @@ const codeweaverFontConfig = [
144
119
  "Menlo",
145
120
  "Consolas",
146
121
  "DejaVu Sans Mono",
147
- "monospace",
148
- ],
149
- },
122
+ "monospace"
123
+ ]
124
+ }
150
125
  ];
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) => {
126
+ const getIntegrations = (options) => {
189
127
  const {
190
128
  appName,
191
129
  sitemapFilter,
192
130
  integrationConfigs,
193
- unwantedIntegrations = [],
131
+ unwantedIntegrations = []
194
132
  } = options;
195
133
  const defaultIntegrations = [
196
134
  astroD2({ skipGeneration: true }),
@@ -201,57 +139,45 @@ const getIntegrations = (options: IntegrationOptions) => {
201
139
  name: `${appName} Docs by Knitli`,
202
140
  short_name: `${appName} Docs`,
203
141
  input: {
204
- favicons: [faviconSvg],
205
- },
142
+ favicons: [faviconSvg]
143
+ }
206
144
  }),
207
145
  cloudflarePagesHeaders({}),
208
146
  sitemap({
209
147
  filter: sitemapFilter || ((page) => !/\^\/(?!cdn-cgi\/)/.test(page)),
210
148
  changefreq: "weekly",
211
149
  priority: 0.4,
212
- lastmod: new Date(),
150
+ lastmod: /* @__PURE__ */ new Date(),
213
151
  namespaces: {
214
152
  image: false,
215
- video: false,
216
- },
217
- }),
153
+ video: false
154
+ }
155
+ })
218
156
  ];
219
-
220
157
  const filtered = defaultIntegrations.filter((integration) => {
221
158
  const name = integration?.name;
222
- return !name || !unwantedIntegrations.includes(name as defaultIntegration);
159
+ return !name || !unwantedIntegrations.includes(name);
223
160
  });
224
-
225
161
  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
-
162
+ const overrides = Object.entries(integrationConfigs).map(([integrationName, config]) => {
163
+ switch (integrationName) {
164
+ case "astroD2":
165
+ return astroD2(config);
166
+ case "markdoc":
167
+ return markdoc(config);
168
+ case "mdx":
169
+ return mdx(config);
170
+ case "favicons":
171
+ return favicons(config);
172
+ case "sitemap":
173
+ return sitemap(config);
174
+ default:
175
+ return null;
176
+ }
177
+ }).filter(nonNullable);
246
178
  return [...filtered, ...overrides];
247
179
  };
248
-
249
- export type PluginOptions = Pick<
250
- DocsTemplateOptions,
251
- "appName" | "llmConfig" | "pluginConfigs"
252
- > & { unwantedPlugins?: string[] };
253
-
254
- const get_plugins = (options: PluginOptions) => {
180
+ const get_plugins = (options) => {
255
181
  const { appName, llmConfig, pluginConfigs, unwantedPlugins = [] } = options;
256
182
  const defaultPlugins = [
257
183
  starlightAnnouncement(),
@@ -263,12 +189,10 @@ const get_plugins = (options: PluginOptions) => {
263
189
  starlightPageActions({
264
190
  baseUrl: `https://docs.knitli.com/${appName.toLowerCase()}`,
265
191
  actions: { claude: true, chatgpt: true, markdown: true },
266
- share: true,
192
+ share: true
267
193
  }),
268
194
  starlightTags({ onInlineTagsNotFound: "warn" }),
269
- pluginConfigs?.starlightSidebarTopics
270
- ? starlightSidebarTopics(pluginConfigs.starlightSidebarTopics)
271
- : null,
195
+ pluginConfigs?.starlightSidebarTopics ? starlightSidebarTopics(pluginConfigs.starlightSidebarTopics) : null,
272
196
  starlightScrollToTop({ showOnHomepage: false }),
273
197
  // We need to configure starlight-tags with a tags.yml.
274
198
  //starlightTags(),
@@ -280,44 +204,35 @@ const get_plugins = (options: PluginOptions) => {
280
204
  minify: {
281
205
  whitespace: true,
282
206
  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;
207
+ details: true
313
208
  }
314
209
  })
315
- .filter(nonNullable);
316
-
210
+ ];
211
+ const filtered = defaultPlugins.filter(nonNullable).filter((plugin) => !unwantedPlugins.includes(plugin.name));
212
+ if (!pluginConfigs) return filtered;
213
+ const overrides = Object.entries(pluginConfigs).map(([pluginName, config]) => {
214
+ switch (pluginName) {
215
+ case "starlightAnnouncement":
216
+ return starlightAnnouncement(config);
217
+ case "starlightIconsIntegration":
218
+ return starlightIconsIntegration(config);
219
+ case "starlightIconsPlugin":
220
+ return starlightIconsPlugin(config);
221
+ case "starlightLinksValidator":
222
+ return starlightLinksValidator(config);
223
+ case "starlightPageActions":
224
+ return starlightPageActions(config);
225
+ case "starlightTags":
226
+ return starlightTags(config);
227
+ case "starlightScrollToTop":
228
+ return starlightScrollToTop(config);
229
+ default:
230
+ return null;
231
+ }
232
+ }).filter(nonNullable);
317
233
  return [...filtered, ...overrides];
318
234
  };
319
-
320
- export default function createConfig(options: DocsTemplateOptions) {
235
+ function createConfig(options) {
321
236
  const {
322
237
  appName,
323
238
  description,
@@ -334,59 +249,57 @@ export default function createConfig(options: DocsTemplateOptions) {
334
249
  integrationConfigs,
335
250
  unwantedIntegrations,
336
251
  unwantedPlugins,
337
- headersConfig,
252
+ headersConfig
338
253
  } = options;
339
-
340
- // https://astro.build/config
341
254
  return defineConfig({
342
255
  site: "https://docs.knitli.com",
343
256
  base: `/${appName.toLowerCase()}/`,
344
257
  adapter: cloudflare({
345
258
  prerenderEnvironment: "workerd",
346
259
  experimental: {
347
- headersAndRedirectsDevModeSupport: true,
260
+ headersAndRedirectsDevModeSupport: true
348
261
  },
349
262
  configPath: `${rootDir}/wrangler.jsonc`,
350
- imageService: "compile",
263
+ imageService: "compile"
351
264
  }),
352
265
  // Image optimization
353
266
  image: {
354
267
  service: {
355
- entrypoint: "astro/assets/services/sharp",
268
+ entrypoint: "astro/assets/services/sharp"
356
269
  },
357
270
  responsiveStyles: true,
358
271
  layout: "constrained",
359
- remotePatterns: imageDomains,
272
+ remotePatterns: imageDomains
360
273
  },
361
274
  compressHTML: true,
362
275
  // biome-ignore lint/suspicious/noExplicitAny: Astro's FontFamily generic inference doesn't match spread patterns
363
- fonts: (is_codeweaver ? codeweaverFontConfig : defaultFontConfig) as any,
276
+ fonts: is_codeweaver ? codeweaverFontConfig : defaultFontConfig,
364
277
  // Build optimizations
365
278
  build: {
366
279
  inlineStylesheets: "auto",
367
- assets: "_astro",
280
+ assets: "_astro"
368
281
  },
369
282
  markdown: {
370
283
  shikiConfig: Object.fromEntries(
371
- Object.entries(shikiConfig).filter(([key]) => key !== "bundledLangs"),
284
+ Object.entries(shikiConfig).filter(([key]) => key !== "bundledLangs")
372
285
  ),
373
286
  rehypePlugins: [
374
287
  rehypeExternalLinks({
375
- content: { type: "text", value: " 🔗" },
376
- rel: ["nofollow"],
377
- }),
378
- ],
288
+ content: { type: "text", value: " " },
289
+ rel: ["nofollow"]
290
+ })
291
+ ]
379
292
  },
380
293
  server: {
381
- headers: headersConfig,
294
+ headers: headersConfig
382
295
  },
383
296
  trailingSlash: "always",
384
297
  // Vite configuration for better bundling
385
298
  vite: {
386
299
  server: {
387
300
  fs: {
388
- allow: [searchForWorkspaceRoot(rootDir)],
389
- },
301
+ allow: [searchForWorkspaceRoot(rootDir)]
302
+ }
390
303
  },
391
304
  assetsInclude: [
392
305
  "src/*.webp",
@@ -394,11 +307,11 @@ export default function createConfig(options: DocsTemplateOptions) {
394
307
  "src/*.jpg",
395
308
  "src/*.jpeg",
396
309
  "src/*.svg",
397
- "src/*.avif",
310
+ "src/*.avif"
398
311
  ],
399
312
  plugins: [viteTsconfigPaths({ loose: true })],
400
313
  define: {
401
- "import.meta.env.PUBLIC_DOCS_PRODUCT": JSON.stringify(appName),
314
+ "import.meta.env.PUBLIC_DOCS_PRODUCT": JSON.stringify(appName)
402
315
  },
403
316
  build: {
404
317
  cssMinify: "lightningcss",
@@ -411,7 +324,7 @@ export default function createConfig(options: DocsTemplateOptions) {
411
324
  dir: `${rootDir}/dist/_astro/`,
412
325
  compact: true,
413
326
  interop: "esModule",
414
- experimentalMinChunkSize: 10000,
327
+ experimentalMinChunkSize: 1e4,
415
328
  banner: `
416
329
  /* SPDX-FileCopyrightText: 2026 Knitli Inc.
417
330
  * SPDX-License-Identifier: MIT OR Apache-2.0
@@ -423,22 +336,22 @@ export default function createConfig(options: DocsTemplateOptions) {
423
336
  arrowFunctions: true,
424
337
  constBindings: true,
425
338
  objectShorthand: true,
426
- symbols: true,
339
+ symbols: true
427
340
  },
428
341
  entryFileNames: "assets/[name]-[hash].js",
429
342
  chunkFileNames: "assets/[name]-[hash].js",
430
- assetFileNames: "assets/[name]-[hash][extname]",
343
+ assetFileNames: "assets/[name]-[hash][extname]"
431
344
  },
432
- treeshake: "smallest",
345
+ treeshake: "smallest"
433
346
  },
434
- ssr: false,
347
+ ssr: false
435
348
  },
436
349
  css: {
437
- lightningcss: {},
438
- },
350
+ lightningcss: {}
351
+ }
439
352
  },
440
353
  prefetch: {
441
- defaultStrategy: "viewport",
354
+ defaultStrategy: "viewport"
442
355
  },
443
356
  prerenderConflictBehavior: "warn",
444
357
  experimental: {
@@ -447,7 +360,7 @@ export default function createConfig(options: DocsTemplateOptions) {
447
360
  contentIntellisense: true,
448
361
  queuedRendering: {
449
362
  contentCache: true,
450
- enabled: true,
363
+ enabled: true
451
364
  },
452
365
  rustCompiler: true,
453
366
  svgo: {
@@ -456,14 +369,13 @@ export default function createConfig(options: DocsTemplateOptions) {
456
369
  name: "preset-default",
457
370
  params: {
458
371
  overrides: {
459
- removeMetadata: false,
460
- },
461
- },
462
- },
463
- ],
464
- },
372
+ removeMetadata: false
373
+ }
374
+ }
375
+ }
376
+ ]
377
+ }
465
378
  },
466
-
467
379
  // Static site generation for Cloudflare
468
380
  output: "static",
469
381
  integrations: [
@@ -471,20 +383,20 @@ export default function createConfig(options: DocsTemplateOptions) {
471
383
  appName,
472
384
  sitemapFilter,
473
385
  integrationConfigs,
474
- unwantedIntegrations,
386
+ unwantedIntegrations
475
387
  }),
476
388
  starlight({
477
389
  title: `${appName} Docs`,
478
390
  pagefind: true,
479
- description: description,
391
+ description,
480
392
  logo: {
481
393
  dark: logoDark,
482
394
  light: logoLight,
483
395
  alt: is_codeweaver ? "CodeWeaver Logo" : "Knitli Logo",
484
- replacesTitle: true,
396
+ replacesTitle: true
485
397
  },
486
398
  editLink: {
487
- baseUrl: `https://github.com/knitli/${appName.toLowerCase()}/edit/main/docs-site/src`,
399
+ baseUrl: `https://github.com/knitli/${appName.toLowerCase()}/edit/main/docs-site/src`
488
400
  },
489
401
  expressiveCode: {
490
402
  useStarlightDarkModeSwitch: true,
@@ -492,27 +404,27 @@ export default function createConfig(options: DocsTemplateOptions) {
492
404
  removeUnusedThemes: true,
493
405
  shiki: {
494
406
  ...Object.fromEntries(
495
- Object.entries(shikiConfig).filter(([key]) => key !== "themes"),
496
- ),
497
- },
407
+ Object.entries(shikiConfig).filter(([key]) => key !== "themes")
408
+ )
409
+ }
498
410
  },
499
411
  plugins: get_plugins({
500
412
  appName,
501
413
  llmConfig,
502
414
  pluginConfigs,
503
- unwantedPlugins,
415
+ unwantedPlugins
504
416
  // biome-ignore lint/suspicious/noExplicitAny: Starlight plugin types are complex Zod inferences
505
- }) as any[],
417
+ }),
506
418
  social: [
507
419
  {
508
420
  icon: "github",
509
421
  label: "GitHub",
510
- href: `https://github.com/knitli/${appName.toLowerCase()}`,
511
- },
422
+ href: `https://github.com/knitli/${appName.toLowerCase()}`
423
+ }
512
424
  ],
513
425
  components: {
514
426
  Footer: "@knitli/docs-components/Footer.astro",
515
- PageFrame: "@knitli/docs-components/PageFrame.astro",
427
+ PageFrame: "@knitli/docs-components/PageFrame.astro"
516
428
  },
517
429
  customCss: [variables, docsStyle, `${rootDir}/src/styles/custom.css`],
518
430
  head: [
@@ -520,32 +432,34 @@ export default function createConfig(options: DocsTemplateOptions) {
520
432
  tag: "meta",
521
433
  attrs: {
522
434
  property: "og:image",
523
- content: `https://docs.knitli.com/${appName.toLowerCase()}/og-image.png`,
524
- },
435
+ content: `https://docs.knitli.com/${appName.toLowerCase()}/og-image.png`
436
+ }
525
437
  },
526
438
  {
527
439
  tag: "meta",
528
440
  attrs: {
529
441
  property: "twitter:card",
530
- content: "summary_large_image",
531
- },
532
- },
442
+ content: "summary_large_image"
443
+ }
444
+ }
533
445
  ],
534
446
  sidebar: sidebarConfig || [
535
447
  {
536
448
  label: "Guides",
537
- autogenerate: { directory: "guides" },
449
+ autogenerate: { directory: "guides" }
538
450
  },
539
451
  {
540
452
  label: "Examples",
541
- autogenerate: { directory: "examples" },
453
+ autogenerate: { directory: "examples" }
542
454
  },
543
455
  {
544
456
  label: "Reference",
545
- autogenerate: { directory: "reference" },
546
- },
547
- ],
548
- }),
549
- ],
457
+ autogenerate: { directory: "reference" }
458
+ }
459
+ ]
460
+ })
461
+ ]
550
462
  });
551
463
  }
464
+
465
+ export { createConfig as default, docsStyle, faviconIco, faviconSvg, headlineLogoDark, headlineLogoLight, variables };
@@ -0,0 +1,69 @@
1
+ export { type DocsTemplateOptions, default as createConfig } from "./config.js";
2
+ export interface InitOptions {
3
+ /** Display name for the product (e.g. "Recoco", "CodeWeaver") */
4
+ appName: string;
5
+ /** npm package name (e.g. "@knitli-site/recoco-docs") */
6
+ name: string;
7
+ /** Short product description */
8
+ description: string;
9
+ /** Cloudflare Worker name (e.g. "recoco-docs") */
10
+ workerName?: string;
11
+ /** Whether this is a CodeWeaver-branded site */
12
+ is_codeweaver?: boolean;
13
+ }
14
+ export interface AddPiecesOptions extends InitOptions {
15
+ pieces: PieceName[];
16
+ /** Overwrite existing files (default: false — skips with a warning) */
17
+ force?: boolean;
18
+ }
19
+ export declare const PIECES: {
20
+ readonly config: {
21
+ readonly description: "Astro config using createConfig()";
22
+ readonly paths: readonly ["astro.config.ts"];
23
+ };
24
+ readonly wrangler: {
25
+ readonly description: "Cloudflare Worker deployment config";
26
+ readonly paths: readonly ["wrangler.jsonc"];
27
+ };
28
+ readonly tsconfig: {
29
+ readonly description: "TypeScript config extending shared base";
30
+ readonly paths: readonly ["tsconfig.json"];
31
+ };
32
+ readonly content: {
33
+ readonly description: "Content collections, env types, and tags";
34
+ readonly paths: readonly ["src/content.config.ts", "src/env.d.ts", "tags.yml"];
35
+ };
36
+ readonly mise: {
37
+ readonly description: "mise dev tasks (build, deploy, clean, etc.)";
38
+ readonly paths: readonly ["mise.toml"];
39
+ };
40
+ readonly styles: {
41
+ readonly description: "Custom CSS hook for site-specific styles";
42
+ readonly paths: readonly ["src/styles/custom.css"];
43
+ };
44
+ readonly deps: {
45
+ readonly description: "package.json with template dependencies";
46
+ readonly paths: readonly ["package.json"];
47
+ };
48
+ readonly "starter-content": {
49
+ readonly description: "Example documentation pages";
50
+ readonly paths: readonly ["src/content/docs"];
51
+ };
52
+ };
53
+ export type PieceName = keyof typeof PIECES;
54
+ export declare const PIECE_NAMES: PieceName[];
55
+ /**
56
+ * Initialize a new Knitli docs site from the template scaffolding.
57
+ *
58
+ * Copies all files from the scaffolding directory to the target path,
59
+ * applying placeholder substitution to text files.
60
+ */
61
+ export declare function initDocsTemplate(targetPath: string, options: InitOptions): string[];
62
+ /**
63
+ * Add specific pieces from the template scaffolding to an existing project.
64
+ *
65
+ * Unlike initDocsTemplate, this only copies the files belonging to the
66
+ * requested pieces and skips existing files unless force is set.
67
+ */
68
+ export declare function addPieces(targetPath: string, options: AddPiecesOptions): string[];
69
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,mBAAmB,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,aAAa,CAAC;AAOhF,MAAM,WAAW,WAAW;IAC1B,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,uEAAuE;IACvE,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAID,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCT,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,MAAM,CAAC;AAC5C,eAAO,MAAM,WAAW,EAA0B,SAAS,EAAE,CAAC;AAyF9D;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,WAAW,GACnB,MAAM,EAAE,CAWV;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,gBAAgB,GACxB,MAAM,EAAE,CAyCV"}
package/dist/index.js ADDED
@@ -0,0 +1,150 @@
1
+ import { existsSync, statSync, mkdirSync, readdirSync, readFileSync, writeFileSync, cpSync } from 'node:fs';
2
+ import { dirname, resolve, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ export { default as createConfig } from './config.js';
5
+
6
+ const __dirname$1 = dirname(fileURLToPath(import.meta.url));
7
+ const SCAFFOLDING_DIR = resolve(__dirname$1, "../scaffolding");
8
+ const PIECES = {
9
+ config: {
10
+ description: "Astro config using createConfig()",
11
+ paths: ["astro.config.ts"]
12
+ },
13
+ wrangler: {
14
+ description: "Cloudflare Worker deployment config",
15
+ paths: ["wrangler.jsonc"]
16
+ },
17
+ tsconfig: {
18
+ description: "TypeScript config extending shared base",
19
+ paths: ["tsconfig.json"]
20
+ },
21
+ content: {
22
+ description: "Content collections, env types, and tags",
23
+ paths: ["src/content.config.ts", "src/env.d.ts", "tags.yml"]
24
+ },
25
+ mise: {
26
+ description: "mise dev tasks (build, deploy, clean, etc.)",
27
+ paths: ["mise.toml"]
28
+ },
29
+ styles: {
30
+ description: "Custom CSS hook for site-specific styles",
31
+ paths: ["src/styles/custom.css"]
32
+ },
33
+ deps: {
34
+ description: "package.json with template dependencies",
35
+ paths: ["package.json"]
36
+ },
37
+ "starter-content": {
38
+ description: "Example documentation pages",
39
+ paths: ["src/content/docs"]
40
+ }
41
+ };
42
+ const PIECE_NAMES = Object.keys(PIECES);
43
+ const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
44
+ ".ts",
45
+ ".js",
46
+ ".mjs",
47
+ ".json",
48
+ ".jsonc",
49
+ ".md",
50
+ ".mdx",
51
+ ".yml",
52
+ ".yaml",
53
+ ".css",
54
+ ".html",
55
+ ".astro",
56
+ ".toml"
57
+ ]);
58
+ function isTextFile(filePath) {
59
+ return TEXT_EXTENSIONS.has(filePath.slice(filePath.lastIndexOf(".")));
60
+ }
61
+ function buildReplacements(options) {
62
+ return {
63
+ "{{appName}}": options.appName,
64
+ "{{appNameLower}}": options.appName.toLowerCase(),
65
+ "{{name}}": options.name,
66
+ "{{description}}": options.description,
67
+ "{{workerName}}": options.workerName ?? `${options.appName.toLowerCase()}-docs`
68
+ };
69
+ }
70
+ function applyReplacements(content, replacements) {
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
+ function copyFile(srcPath, destPath, replacements, created, force) {
78
+ if (!force && existsSync(destPath)) {
79
+ console.log(` skip ${destPath} (exists, use --force to overwrite)`);
80
+ return;
81
+ }
82
+ mkdirSync(dirname(destPath), { recursive: true });
83
+ if (isTextFile(srcPath)) {
84
+ const content = readFileSync(srcPath, "utf-8");
85
+ writeFileSync(destPath, applyReplacements(content, replacements), "utf-8");
86
+ } else {
87
+ cpSync(srcPath, destPath);
88
+ }
89
+ created.push(destPath);
90
+ }
91
+ function copyDirRecursive(srcDir, destDir, replacements, created, force) {
92
+ mkdirSync(destDir, { recursive: true });
93
+ for (const entry of readdirSync(srcDir)) {
94
+ const srcPath = join(srcDir, entry);
95
+ const destPath = join(destDir, entry);
96
+ if (statSync(srcPath).isDirectory()) {
97
+ copyDirRecursive(srcPath, destPath, replacements, created, force);
98
+ } else {
99
+ copyFile(srcPath, destPath, replacements, created, force);
100
+ }
101
+ }
102
+ }
103
+ function initDocsTemplate(targetPath, options) {
104
+ const target = resolve(targetPath);
105
+ const replacements = buildReplacements(options);
106
+ const created = [];
107
+ if (!existsSync(SCAFFOLDING_DIR)) {
108
+ throw new Error(`Scaffolding directory not found at ${SCAFFOLDING_DIR}`);
109
+ }
110
+ copyDirRecursive(SCAFFOLDING_DIR, target, replacements, created, true);
111
+ return created;
112
+ }
113
+ function addPieces(targetPath, options) {
114
+ const target = resolve(targetPath);
115
+ const replacements = buildReplacements(options);
116
+ const created = [];
117
+ if (!existsSync(SCAFFOLDING_DIR)) {
118
+ throw new Error(`Scaffolding directory not found at ${SCAFFOLDING_DIR}`);
119
+ }
120
+ for (const piece of options.pieces) {
121
+ const pieceDef = PIECES[piece];
122
+ if (!pieceDef) {
123
+ throw new Error(
124
+ `Unknown piece: ${piece}. Available: ${PIECE_NAMES.join(", ")}`
125
+ );
126
+ }
127
+ for (const relPath of pieceDef.paths) {
128
+ const srcPath = join(SCAFFOLDING_DIR, relPath);
129
+ const destPath = join(target, relPath);
130
+ if (!existsSync(srcPath)) {
131
+ console.warn(` warn: ${relPath} not found in scaffolding, skipping`);
132
+ continue;
133
+ }
134
+ if (statSync(srcPath).isDirectory()) {
135
+ copyDirRecursive(
136
+ srcPath,
137
+ destPath,
138
+ replacements,
139
+ created,
140
+ options.force
141
+ );
142
+ } else {
143
+ copyFile(srcPath, destPath, replacements, created, options.force);
144
+ }
145
+ }
146
+ }
147
+ return created;
148
+ }
149
+
150
+ export { PIECES, PIECE_NAMES, addPieces, initDocsTemplate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knitli/astro-docs-template",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Opinionated Astro + Starlight docs site template with Knitli branding",
5
5
  "keywords": [
6
6
  "knitli",
@@ -19,17 +19,26 @@
19
19
  "author": "Knitli Inc.",
20
20
  "type": "module",
21
21
  "exports": {
22
- ".": "./src/index.ts",
23
- "./config": "./src/config.ts"
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ },
26
+ "./config": {
27
+ "types": "./dist/config.d.ts",
28
+ "import": "./dist/config.js"
29
+ }
30
+ },
31
+ "bin": {
32
+ "knitli-docs": "dist/cli.js"
24
33
  },
25
34
  "files": [
26
- "src",
35
+ "dist",
27
36
  "scaffolding",
28
37
  "README.md",
29
38
  "CHANGELOG.md"
30
39
  ],
31
40
  "scripts": {
32
- "build": "bunx tsc --noCheck",
41
+ "build": "vite build && tsc --emitDeclarationOnly --noCheck --noEmit false",
33
42
  "prepublishOnly": "bun run build",
34
43
  "publish": "npm publish --access public",
35
44
  "typecheck": "bunx tsc --noEmit"
@@ -69,7 +78,8 @@
69
78
  "lightningcss": "catalog:optimization",
70
79
  "sharp": "catalog:astro-core",
71
80
  "svgo": "catalog:optimization",
72
- "typescript": "catalog:dev-common"
81
+ "typescript": "catalog:dev-common",
82
+ "vite": "catalog:dev-common"
73
83
  },
74
84
  "devEngines": {
75
85
  "packageManager": {
@@ -4,11 +4,15 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
+ "astro": "bunx astro",
8
+ "prepare": "test -f ./node_modules/bun/install.js && node ./node_modules/bun/install.js || true",
9
+
10
+ "prebuild": "bunx wrangler types; bunx astro sync",
7
11
  "build": "bunx astro build",
8
- "deploy": "bunx astro build && bunx wrangler deploy",
12
+ "deploy": "bun run build && bunx wrangler deploy",
9
13
  "dev": "bunx astro dev",
10
- "generate-types": "bunx wrangler types",
11
- "preview": "bunx astro build && bunx astro preview"
14
+ "preview": "bunx astro preview",
15
+ "wrangler": "bunx wrangler"
12
16
  },
13
17
  "dependencies": {
14
18
  "@knitli/astro-docs-template": "workspace:*",
package/src/index.ts DELETED
@@ -1,128 +0,0 @@
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
- }