@askrjs/cli 0.0.3 → 0.0.4

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 (287) hide show
  1. package/README.md +37 -0
  2. package/dist/add.d.ts +16 -0
  3. package/dist/add.js +33 -29
  4. package/dist/cli.d.ts +5 -0
  5. package/dist/cli.js +0 -2
  6. package/dist/create.d.ts +5 -0
  7. package/dist/create.js +104 -26
  8. package/dist/directory-swap-DWoHtx7C.js +35 -0
  9. package/dist/discovery-Difb7Y4G.js +0 -2
  10. package/dist/generate.d.ts +5 -0
  11. package/dist/generate.js +77 -12
  12. package/dist/is-direct-execution-Cdlr-ZUl.js +0 -2
  13. package/dist/openapi.d.ts +25 -0
  14. package/dist/openapi.js +44 -12
  15. package/dist/planner-VAj7qlxr.js +0 -2
  16. package/dist/range-YUs9eimn.js +0 -2
  17. package/dist/registry-CyrRHRG5.js +0 -2
  18. package/dist/skills/askr-ssr-ssg/SKILL.md +3 -1
  19. package/dist/{skills-Bh0mEhIx.js → skills-B7CbWur9.js} +89 -26
  20. package/dist/skills.d.ts +18 -0
  21. package/dist/skills.js +1 -1
  22. package/dist/specification-DXnDOC-0.js +0 -2
  23. package/dist/ssg-config.d.ts +41 -0
  24. package/dist/ssg-config.js +1 -0
  25. package/dist/ssg.d.ts +61 -0
  26. package/dist/ssg.js +360 -24
  27. package/dist/templates/full-stack/package.json +1 -1
  28. package/dist/templates/full-stack/src/server/action-registry.ts +0 -1
  29. package/dist/templates/full-stack/tsconfig.json +1 -1
  30. package/dist/templates/spa/package.json +2 -3
  31. package/dist/templates/spa/src/pages/app/_routes.tsx +2 -0
  32. package/dist/templates/spa/src/pages/public/_routes.tsx +2 -0
  33. package/dist/templates/spa/tsconfig.json +2 -1
  34. package/dist/templates/spa/vitest.config.ts +0 -4
  35. package/dist/templates/ssg/README.md +1 -1
  36. package/dist/templates/ssg/package.json +1 -1
  37. package/dist/templates/ssg/ssg.config.ts +7 -0
  38. package/dist/templates/ssg/tests/ssg-config.test.ts +2 -0
  39. package/dist/templates/ssg/tsconfig.json +2 -1
  40. package/dist/templates/ssg/vitest.config.ts +0 -4
  41. package/dist/templates/ssr/package.json +2 -2
  42. package/dist/templates/startkit/package.json +2 -2
  43. package/dist/templates/startkit/src/lib/mock-data.ts +17 -39
  44. package/dist/templates/startkit/tsconfig.json +2 -1
  45. package/dist/update.d.ts +41 -0
  46. package/dist/update.js +0 -2
  47. package/dist/writer-D8qe_7ud.js +0 -2
  48. package/package.json +13 -8
  49. package/dist/add.js.map +0 -1
  50. package/dist/cli.js.map +0 -1
  51. package/dist/create.js.map +0 -1
  52. package/dist/discovery-Difb7Y4G.js.map +0 -1
  53. package/dist/generate.js.map +0 -1
  54. package/dist/is-direct-execution-Cdlr-ZUl.js.map +0 -1
  55. package/dist/openapi.js.map +0 -1
  56. package/dist/planner-VAj7qlxr.js.map +0 -1
  57. package/dist/range-YUs9eimn.js.map +0 -1
  58. package/dist/registry-CyrRHRG5.js.map +0 -1
  59. package/dist/skills-Bh0mEhIx.js.map +0 -1
  60. package/dist/specification-DXnDOC-0.js.map +0 -1
  61. package/dist/ssg.js.map +0 -1
  62. package/dist/templates/full-stack/src/vite-server.d.ts +0 -5
  63. package/dist/templates/spa/src/styles.d.ts +0 -1
  64. package/dist/templates/ssg/src/vite-env.d.ts +0 -5
  65. package/dist/templates/ssr/src/vite-server.d.ts +0 -7
  66. package/dist/templates/startkit/src/vite-env.d.ts +0 -5
  67. package/dist/update.js.map +0 -1
  68. package/dist/writer-D8qe_7ud.js.map +0 -1
  69. package/skills/askr-accessibility/SKILL.md +0 -68
  70. package/skills/askr-accessibility/agents/openai.yaml +0 -4
  71. package/skills/askr-agent-execution/SKILL.md +0 -85
  72. package/skills/askr-agent-execution/agents/openai.yaml +0 -4
  73. package/skills/askr-agent-workflows/SKILL.md +0 -57
  74. package/skills/askr-agent-workflows/agents/openai.yaml +0 -4
  75. package/skills/askr-api-integration/SKILL.md +0 -79
  76. package/skills/askr-api-integration/agents/openai.yaml +0 -4
  77. package/skills/askr-app-builder/SKILL.md +0 -99
  78. package/skills/askr-app-builder/agents/openai.yaml +0 -4
  79. package/skills/askr-auth-access/SKILL.md +0 -74
  80. package/skills/askr-auth-access/agents/openai.yaml +0 -4
  81. package/skills/askr-cli-vite/SKILL.md +0 -76
  82. package/skills/askr-cli-vite/agents/openai.yaml +0 -4
  83. package/skills/askr-dashboard-charts/SKILL.md +0 -99
  84. package/skills/askr-dashboard-charts/agents/openai.yaml +0 -4
  85. package/skills/askr-design-system/SKILL.md +0 -86
  86. package/skills/askr-design-system/agents/openai.yaml +0 -4
  87. package/skills/askr-env-config/SKILL.md +0 -74
  88. package/skills/askr-env-config/agents/openai.yaml +0 -4
  89. package/skills/askr-error-loading-empty/SKILL.md +0 -89
  90. package/skills/askr-error-loading-empty/agents/openai.yaml +0 -4
  91. package/skills/askr-file-upload-artifacts/SKILL.md +0 -77
  92. package/skills/askr-file-upload-artifacts/agents/openai.yaml +0 -4
  93. package/skills/askr-forms-tables-crud/SKILL.md +0 -98
  94. package/skills/askr-forms-tables-crud/agents/openai.yaml +0 -4
  95. package/skills/askr-mental-model/SKILL.md +0 -109
  96. package/skills/askr-mental-model/agents/openai.yaml +0 -4
  97. package/skills/askr-migration-react/SKILL.md +0 -81
  98. package/skills/askr-migration-react/agents/openai.yaml +0 -4
  99. package/skills/askr-observability-debugging/SKILL.md +0 -83
  100. package/skills/askr-observability-debugging/agents/openai.yaml +0 -4
  101. package/skills/askr-project-structure/SKILL.md +0 -79
  102. package/skills/askr-project-structure/agents/openai.yaml +0 -4
  103. package/skills/askr-query-mutation/SKILL.md +0 -115
  104. package/skills/askr-query-mutation/agents/openai.yaml +0 -4
  105. package/skills/askr-realtime-streaming/SKILL.md +0 -76
  106. package/skills/askr-realtime-streaming/agents/openai.yaml +0 -4
  107. package/skills/askr-resources-data/SKILL.md +0 -100
  108. package/skills/askr-resources-data/agents/openai.yaml +0 -4
  109. package/skills/askr-routing-layouts/SKILL.md +0 -107
  110. package/skills/askr-routing-layouts/agents/openai.yaml +0 -4
  111. package/skills/askr-runtime-reactivity/SKILL.md +0 -92
  112. package/skills/askr-runtime-reactivity/agents/openai.yaml +0 -4
  113. package/skills/askr-ssr-ssg/SKILL.md +0 -76
  114. package/skills/askr-ssr-ssg/agents/openai.yaml +0 -4
  115. package/skills/askr-testing-determinism/SKILL.md +0 -75
  116. package/skills/askr-testing-determinism/agents/openai.yaml +0 -4
  117. package/skills/askr-theming/SKILL.md +0 -86
  118. package/skills/askr-theming/agents/openai.yaml +0 -4
  119. package/skills/askr-ui-composition/SKILL.md +0 -92
  120. package/skills/askr-ui-composition/agents/openai.yaml +0 -4
  121. package/templates/full-stack/AGENTS.md +0 -11
  122. package/templates/full-stack/README.md +0 -17
  123. package/templates/full-stack/gitignore.template +0 -4
  124. package/templates/full-stack/index.html +0 -13
  125. package/templates/full-stack/package.json +0 -39
  126. package/templates/full-stack/server.ts +0 -9
  127. package/templates/full-stack/src/action-authorizations.ts +0 -10
  128. package/templates/full-stack/src/actions/create-message.ts +0 -10
  129. package/templates/full-stack/src/i18n.ts +0 -8
  130. package/templates/full-stack/src/main.tsx +0 -11
  131. package/templates/full-stack/src/pages/home.tsx +0 -25
  132. package/templates/full-stack/src/pages/layout.tsx +0 -43
  133. package/templates/full-stack/src/pages/not-found.tsx +0 -7
  134. package/templates/full-stack/src/routes.tsx +0 -19
  135. package/templates/full-stack/src/schemas.ts +0 -10
  136. package/templates/full-stack/src/server/action-registry.ts +0 -8
  137. package/templates/full-stack/src/server/actions/create-message.ts +0 -14
  138. package/templates/full-stack/src/server/app.ts +0 -76
  139. package/templates/full-stack/src/server/dependencies.ts +0 -55
  140. package/templates/full-stack/src/server/entry-server.ts +0 -8
  141. package/templates/full-stack/src/telemetry.ts +0 -3
  142. package/templates/full-stack/src/vite-server.d.ts +0 -5
  143. package/templates/full-stack/tests/actions/create-message.test.ts +0 -12
  144. package/templates/full-stack/tsconfig.json +0 -15
  145. package/templates/full-stack/vite.config.ts +0 -10
  146. package/templates/spa/AGENTS.md +0 -60
  147. package/templates/spa/README.md +0 -96
  148. package/templates/spa/gitignore.template +0 -16
  149. package/templates/spa/index.html +0 -13
  150. package/templates/spa/package.json +0 -35
  151. package/templates/spa/src/adapters/operations-client.ts +0 -95
  152. package/templates/spa/src/components/shared/metric-card.tsx +0 -27
  153. package/templates/spa/src/components/shared/status-badge.tsx +0 -24
  154. package/templates/spa/src/features/operations/operations.query.ts +0 -9
  155. package/templates/spa/src/main.tsx +0 -10
  156. package/templates/spa/src/pages/_layout.tsx +0 -9
  157. package/templates/spa/src/pages/_routes.tsx +0 -27
  158. package/templates/spa/src/pages/app/_layout.tsx +0 -94
  159. package/templates/spa/src/pages/app/_routes.tsx +0 -10
  160. package/templates/spa/src/pages/app/admin-home.tsx +0 -170
  161. package/templates/spa/src/pages/app/agent-runs.tsx +0 -93
  162. package/templates/spa/src/pages/app/settings.tsx +0 -91
  163. package/templates/spa/src/pages/auth/_layout.tsx +0 -38
  164. package/templates/spa/src/pages/auth/_routes.tsx +0 -6
  165. package/templates/spa/src/pages/auth/login.tsx +0 -63
  166. package/templates/spa/src/pages/not-found.tsx +0 -22
  167. package/templates/spa/src/pages/public/_layout.tsx +0 -47
  168. package/templates/spa/src/pages/public/_routes.tsx +0 -6
  169. package/templates/spa/src/pages/public/home.tsx +0 -143
  170. package/templates/spa/src/shared/format.ts +0 -12
  171. package/templates/spa/src/shared/navigation.ts +0 -15
  172. package/templates/spa/src/styles/components.css +0 -49
  173. package/templates/spa/src/styles/layout.css +0 -192
  174. package/templates/spa/src/styles/reset.css +0 -44
  175. package/templates/spa/src/styles/theme.css +0 -57
  176. package/templates/spa/src/styles/tokens.css +0 -20
  177. package/templates/spa/src/styles.css +0 -7
  178. package/templates/spa/src/styles.d.ts +0 -1
  179. package/templates/spa/tests/app.test.tsx +0 -37
  180. package/templates/spa/tests/components/shared.test.tsx +0 -12
  181. package/templates/spa/tests/resources.test.ts +0 -22
  182. package/templates/spa/tsconfig.json +0 -20
  183. package/templates/spa/tsconfig.node.json +0 -10
  184. package/templates/spa/vite.config.ts +0 -24
  185. package/templates/spa/vitest.config.ts +0 -17
  186. package/templates/ssg/AGENTS.md +0 -55
  187. package/templates/ssg/README.md +0 -39
  188. package/templates/ssg/gitignore.template +0 -17
  189. package/templates/ssg/index.html +0 -13
  190. package/templates/ssg/package.json +0 -35
  191. package/templates/ssg/src/app.tsx +0 -14
  192. package/templates/ssg/src/components/badge.tsx +0 -3
  193. package/templates/ssg/src/components/counter.tsx +0 -30
  194. package/templates/ssg/src/components/site-shell.tsx +0 -111
  195. package/templates/ssg/src/jsx.d.ts +0 -23
  196. package/templates/ssg/src/main.tsx +0 -8
  197. package/templates/ssg/src/pages/about.tsx +0 -82
  198. package/templates/ssg/src/pages/content.tsx +0 -63
  199. package/templates/ssg/src/pages/example.tsx +0 -97
  200. package/templates/ssg/src/pages/home.tsx +0 -51
  201. package/templates/ssg/src/resources/user.ts +0 -15
  202. package/templates/ssg/src/routes.tsx +0 -15
  203. package/templates/ssg/src/styles.css +0 -89
  204. package/templates/ssg/src/vite-env.d.ts +0 -5
  205. package/templates/ssg/ssg.config.ts +0 -34
  206. package/templates/ssg/tests/app.test.tsx +0 -19
  207. package/templates/ssg/tests/components/counter.test.tsx +0 -18
  208. package/templates/ssg/tests/resources.test.ts +0 -11
  209. package/templates/ssg/tests/ssg-config.test.ts +0 -11
  210. package/templates/ssg/tsconfig.json +0 -20
  211. package/templates/ssg/tsconfig.node.json +0 -10
  212. package/templates/ssg/vite.config.ts +0 -24
  213. package/templates/ssg/vitest.config.ts +0 -17
  214. package/templates/ssr/AGENTS.md +0 -28
  215. package/templates/ssr/README.md +0 -38
  216. package/templates/ssr/gitignore.template +0 -16
  217. package/templates/ssr/index.html +0 -13
  218. package/templates/ssr/package.json +0 -34
  219. package/templates/ssr/server.ts +0 -10
  220. package/templates/ssr/src/app.tsx +0 -21
  221. package/templates/ssr/src/components/counter.tsx +0 -26
  222. package/templates/ssr/src/entry-server.tsx +0 -12
  223. package/templates/ssr/src/main.tsx +0 -11
  224. package/templates/ssr/src/pages/about.tsx +0 -75
  225. package/templates/ssr/src/pages/example.tsx +0 -113
  226. package/templates/ssr/src/pages/home.tsx +0 -56
  227. package/templates/ssr/src/resources/user.ts +0 -15
  228. package/templates/ssr/src/routes.tsx +0 -13
  229. package/templates/ssr/src/styles.css +0 -148
  230. package/templates/ssr/src/vite-server.d.ts +0 -7
  231. package/templates/ssr/tests/app.test.tsx +0 -24
  232. package/templates/ssr/tests/components/counter.test.tsx +0 -13
  233. package/templates/ssr/tests/resources.test.ts +0 -11
  234. package/templates/ssr/tsconfig.json +0 -22
  235. package/templates/ssr/tsconfig.node.json +0 -10
  236. package/templates/ssr/tsconfig.server.json +0 -15
  237. package/templates/ssr/vite.config.ts +0 -25
  238. package/templates/ssr/vitest.config.ts +0 -13
  239. package/templates/startkit/AGENTS.md +0 -77
  240. package/templates/startkit/README.md +0 -192
  241. package/templates/startkit/gitignore.template +0 -16
  242. package/templates/startkit/index.html +0 -13
  243. package/templates/startkit/package.json +0 -35
  244. package/templates/startkit/src/components/app-header.tsx +0 -93
  245. package/templates/startkit/src/components/app-sidebar.tsx +0 -95
  246. package/templates/startkit/src/components/data-table.tsx +0 -80
  247. package/templates/startkit/src/components/empty-state.tsx +0 -13
  248. package/templates/startkit/src/components/page-header.tsx +0 -23
  249. package/templates/startkit/src/components/stat-card.tsx +0 -24
  250. package/templates/startkit/src/features/accounts/account-filters.tsx +0 -61
  251. package/templates/startkit/src/features/accounts/account-table.tsx +0 -79
  252. package/templates/startkit/src/lib/format.ts +0 -31
  253. package/templates/startkit/src/lib/mock-data.ts +0 -441
  254. package/templates/startkit/src/lib/routes.ts +0 -116
  255. package/templates/startkit/src/main.tsx +0 -12
  256. package/templates/startkit/src/pages/_layout.tsx +0 -56
  257. package/templates/startkit/src/pages/auth/_layout.tsx +0 -7
  258. package/templates/startkit/src/pages/auth/login.tsx +0 -129
  259. package/templates/startkit/src/pages/home.tsx +0 -83
  260. package/templates/startkit/src/pages/not-found.tsx +0 -24
  261. package/templates/startkit/src/pages/workspace/_layout.tsx +0 -17
  262. package/templates/startkit/src/pages/workspace/accounts/index.tsx +0 -217
  263. package/templates/startkit/src/pages/workspace/dashboard.tsx +0 -118
  264. package/templates/startkit/src/pages/workspace/settings.tsx +0 -218
  265. package/templates/startkit/src/router.tsx +0 -7
  266. package/templates/startkit/src/routes/auth-config.ts +0 -38
  267. package/templates/startkit/src/routes/auth.ts +0 -8
  268. package/templates/startkit/src/routes/index.ts +0 -28
  269. package/templates/startkit/src/routes/public.ts +0 -8
  270. package/templates/startkit/src/routes/workspace/accounts.ts +0 -8
  271. package/templates/startkit/src/routes/workspace/index.ts +0 -14
  272. package/templates/startkit/src/styles/components.css +0 -396
  273. package/templates/startkit/src/styles/layout.css +0 -192
  274. package/templates/startkit/src/styles/reset.css +0 -38
  275. package/templates/startkit/src/styles/theme.css +0 -76
  276. package/templates/startkit/src/styles/tokens.css +0 -67
  277. package/templates/startkit/src/styles.css +0 -7
  278. package/templates/startkit/src/toast.ts +0 -55
  279. package/templates/startkit/src/utils/join-classes.ts +0 -4
  280. package/templates/startkit/src/vite-env.d.ts +0 -5
  281. package/templates/startkit/tests/app.test.tsx +0 -32
  282. package/templates/startkit/tests/preferences.test.ts +0 -43
  283. package/templates/startkit/tests/resources.test.ts +0 -82
  284. package/templates/startkit/tsconfig.json +0 -20
  285. package/templates/startkit/tsconfig.node.json +0 -10
  286. package/templates/startkit/vite.config.ts +0 -32
  287. package/templates/startkit/vitest.config.ts +0 -13
package/dist/ssg.d.ts ADDED
@@ -0,0 +1,61 @@
1
+ import { existsSync } from "node:fs";
2
+ //#region src/bin/ssg.d.ts
3
+ type CliIo = Pick<Console, "error" | "log">;
4
+ interface ParsedSsgArgs {
5
+ configPath: string;
6
+ outputDir: string;
7
+ workers: number | "auto";
8
+ incremental: boolean;
9
+ changedKeys: string[];
10
+ changedRoutes: string[];
11
+ forceFull: boolean;
12
+ help: boolean;
13
+ errors: string[];
14
+ }
15
+ interface RouteResult {
16
+ path: string;
17
+ filePath: string;
18
+ status: string;
19
+ error?: string;
20
+ }
21
+ interface GenerateResult {
22
+ mode: string;
23
+ successful: number;
24
+ totalRoutes: number;
25
+ failed: number;
26
+ rebuilt: number;
27
+ skipped: number;
28
+ removed: number;
29
+ cacheHits: number;
30
+ routes: RouteResult[];
31
+ }
32
+ interface StaticGen {
33
+ generate(options: ReturnType<typeof toGenerateOptions>): Promise<GenerateResult>;
34
+ }
35
+ interface SsgDeps {
36
+ cwd?: () => string;
37
+ now?: () => number;
38
+ existsSync?: typeof existsSync;
39
+ importConfig?: (filePath: string) => Promise<unknown>;
40
+ createStaticGen?: (options: {
41
+ routes?: unknown[];
42
+ registry?: unknown;
43
+ outputDir: string;
44
+ seed?: unknown;
45
+ dataOverrides?: unknown;
46
+ concurrency?: number;
47
+ parallelism: number | "auto";
48
+ document?: unknown;
49
+ assets?: unknown[];
50
+ }) => StaticGen;
51
+ }
52
+ declare function parseCliArgs(args: string[]): ParsedSsgArgs;
53
+ declare function toGenerateOptions(args: ParsedSsgArgs): {
54
+ mode: string;
55
+ changedKeys: string[];
56
+ changedRoutes: string[];
57
+ forceFull: boolean;
58
+ };
59
+ declare function runSsgCli(args?: string[], deps?: SsgDeps, io?: CliIo): Promise<number>;
60
+ //#endregion
61
+ export { parseCliArgs, runSsgCli };
package/dist/ssg.js CHANGED
@@ -1,9 +1,272 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as isDirectExecution } from "./is-direct-execution-Cdlr-ZUl.js";
3
- import { dirname, resolve } from "node:path";
4
- import { existsSync } from "node:fs";
3
+ import { n as publishStagedDirectory, t as createSiblingStage } from "./directory-swap-DWoHtx7C.js";
4
+ import * as fs$1 from "node:fs/promises";
5
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
6
+ import * as path$1 from "node:path";
7
+ import path, { dirname, resolve } from "node:path";
8
+ import { constants, existsSync } from "node:fs";
5
9
  import { pathToFileURL } from "node:url";
6
10
  import { register } from "tsx/esm/api";
11
+ //#region src/ssg/sitemap.ts
12
+ const datePattern = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(Z|[+-](\d{2}):(\d{2})))?$/;
13
+ const languagePattern = /^(?:x-default|[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*)$/;
14
+ const changeFrequencies = /* @__PURE__ */ new Set([
15
+ "always",
16
+ "hourly",
17
+ "daily",
18
+ "weekly",
19
+ "monthly",
20
+ "yearly",
21
+ "never"
22
+ ]);
23
+ const MAX_URLS_PER_SITEMAP = 5e4;
24
+ const MAX_SITEMAP_BYTES = 50 * 1024 * 1024;
25
+ const MANIFEST_PATH = ".askr/sitemap-manifest.json";
26
+ function escapeXml(value) {
27
+ return value.replace(/[&<>"']/g, (character) => {
28
+ return {
29
+ "&": "&amp;",
30
+ "<": "&lt;",
31
+ ">": "&gt;",
32
+ "\"": "&quot;",
33
+ "'": "&apos;"
34
+ }[character];
35
+ });
36
+ }
37
+ function resolveSiteUrl(siteUrl) {
38
+ let parsed;
39
+ try {
40
+ parsed = new URL(siteUrl);
41
+ } catch {
42
+ throw new Error(`SSG siteUrl must be an absolute HTTP(S) URL: ${siteUrl}`);
43
+ }
44
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`SSG siteUrl must use HTTP or HTTPS: ${siteUrl}`);
45
+ if (parsed.search || parsed.hash) throw new Error("SSG siteUrl must not contain a query string or fragment");
46
+ if (parsed.username || parsed.password) throw new Error("SSG siteUrl must not contain credentials");
47
+ if (!parsed.pathname.endsWith("/")) parsed.pathname += "/";
48
+ return parsed;
49
+ }
50
+ function resolveLocation(value, siteUrl) {
51
+ let resolved;
52
+ try {
53
+ resolved = new URL(value.replace(/^\//, ""), siteUrl);
54
+ } catch {
55
+ throw new Error(`Invalid sitemap URL: ${value}`);
56
+ }
57
+ if (resolved.protocol !== "http:" && resolved.protocol !== "https:") throw new Error(`Sitemap URLs must use HTTP or HTTPS: ${value}`);
58
+ if (resolved.hash) throw new Error(`Sitemap URLs must not contain fragments: ${value}`);
59
+ return resolved.href;
60
+ }
61
+ function formatLastModified(value) {
62
+ if (value instanceof Date) {
63
+ if (Number.isNaN(value.getTime())) throw new Error("Invalid sitemap lastModified Date");
64
+ return value.toISOString();
65
+ }
66
+ const match = datePattern.exec(value);
67
+ if (!match) throw new Error(`Invalid sitemap lastModified value: ${value}`);
68
+ const [, yearText, monthText, dayText, hourText, minuteText, secondText, zone, offsetHour, offsetMinute] = match;
69
+ const year = Number(yearText);
70
+ const month = Number(monthText);
71
+ const day = Number(dayText);
72
+ const daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate();
73
+ const invalidTime = hourText !== void 0 && (Number(hourText) > 23 || Number(minuteText) > 59 || Number(secondText) > 59 || zone !== "Z" && (Number(offsetHour) > 14 || Number(offsetMinute) > 59 || Number(offsetHour) === 14 && Number(offsetMinute) !== 0));
74
+ if (month < 1 || month > 12 || day < 1 || day > daysInMonth || invalidTime || Number.isNaN(Date.parse(value))) throw new Error(`Invalid sitemap lastModified value: ${value}`);
75
+ return value;
76
+ }
77
+ function validateChangeFrequency(value) {
78
+ if (!changeFrequencies.has(value)) throw new Error(`Invalid sitemap changeFrequency value: ${value}`);
79
+ return value;
80
+ }
81
+ function validatePriority(value) {
82
+ if (!Number.isFinite(value) || value < 0 || value > 1) throw new Error(`Sitemap priority must be between 0 and 1: ${value}`);
83
+ return value;
84
+ }
85
+ function outputPath(outputDir, configuredPath) {
86
+ const relativePath = configuredPath ?? "sitemap.xml";
87
+ if (path.isAbsolute(relativePath)) throw new Error(`Sitemap output must be relative to the SSG output directory: ${relativePath}`);
88
+ const resolved = path.resolve(outputDir, relativePath);
89
+ const relative = path.relative(path.resolve(outputDir), resolved);
90
+ if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Sitemap output must stay inside the SSG output directory: ${relativePath}`);
91
+ return resolved;
92
+ }
93
+ function relativeOutputPath(outputDir, absolutePath) {
94
+ return path.relative(path.resolve(outputDir), absolutePath).replace(/\\/g, "/");
95
+ }
96
+ function validateLimit(value, maximum, label) {
97
+ const resolved = value ?? maximum;
98
+ if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > maximum) throw new Error(`${label} must be an integer from 1 through ${maximum}`);
99
+ return resolved;
100
+ }
101
+ async function atomicWrite(destination, contents) {
102
+ await mkdir(path.dirname(destination), { recursive: true });
103
+ const temporary = `${destination}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
104
+ try {
105
+ await writeFile(temporary, contents, "utf8");
106
+ await rename(temporary, destination);
107
+ } catch (error) {
108
+ await rm(temporary, { force: true });
109
+ throw error;
110
+ }
111
+ }
112
+ function renderUrlSet(routes) {
113
+ return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset${routes.some((route) => route.alternates.length > 0) ? " xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\"" : " xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\""}>\n${routes.map(renderRoute).join("\n")}\n</urlset>\n`;
114
+ }
115
+ function partitionRoutes(routes, urlsPerFile, bytesPerFile) {
116
+ if (routes.length === 0) return [[]];
117
+ const chunks = [];
118
+ let current = [];
119
+ let currentEntryBytes = 0;
120
+ let currentHasAlternates = false;
121
+ const envelopeBytes = {
122
+ plain: Buffer.byteLength(renderUrlSet([])),
123
+ alternates: Buffer.byteLength("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\n\n</urlset>\n")
124
+ };
125
+ const envelope = (hasAlternates) => hasAlternates ? envelopeBytes.alternates : envelopeBytes.plain;
126
+ for (const route of routes) {
127
+ const routeBytes = Buffer.byteLength(renderRoute(route));
128
+ const nextHasAlternates = currentHasAlternates || route.alternates.length > 0;
129
+ const separatorBytes = current.length > 0 ? 1 : 0;
130
+ const nextBytes = envelope(nextHasAlternates) + currentEntryBytes + separatorBytes + routeBytes;
131
+ if (current.length > 0 && (current.length + 1 > urlsPerFile || nextBytes > bytesPerFile)) {
132
+ chunks.push(current);
133
+ current = [route];
134
+ currentEntryBytes = routeBytes;
135
+ currentHasAlternates = route.alternates.length > 0;
136
+ } else {
137
+ current.push(route);
138
+ currentEntryBytes += separatorBytes + routeBytes;
139
+ currentHasAlternates = nextHasAlternates;
140
+ }
141
+ if (envelope(currentHasAlternates) + currentEntryBytes > bytesPerFile) throw new Error(`A sitemap URL entry exceeds the configured ${bytesPerFile}-byte limit`);
142
+ }
143
+ chunks.push(current);
144
+ return chunks;
145
+ }
146
+ function sitemapIndex(locations) {
147
+ return `<?xml version="1.0" encoding="UTF-8"?>\n<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${locations.map((location) => ` <sitemap>\n <loc>${escapeXml(location)}</loc>\n </sitemap>`).join("\n")}\n</sitemapindex>\n`;
148
+ }
149
+ async function mapConcurrent(values, concurrency, mapper) {
150
+ const results = Array.from({ length: values.length });
151
+ let cursor = 0;
152
+ const workers = Array.from({ length: Math.min(concurrency, values.length) }, async () => {
153
+ while (cursor < values.length) {
154
+ const index = cursor;
155
+ cursor += 1;
156
+ results[index] = await mapper(values[index], index);
157
+ }
158
+ });
159
+ await Promise.all(workers);
160
+ return results;
161
+ }
162
+ async function readManifest(outputDir) {
163
+ try {
164
+ const parsed = JSON.parse(await readFile(outputPath(outputDir, MANIFEST_PATH), "utf8"));
165
+ return {
166
+ files: Array.isArray(parsed.files) ? parsed.files.filter((value) => typeof value === "string") : [],
167
+ ...typeof parsed.robots === "string" ? { robots: parsed.robots } : {}
168
+ };
169
+ } catch {
170
+ return { files: [] };
171
+ }
172
+ }
173
+ function withoutSitemapDirective(contents) {
174
+ return contents.split(/\r?\n/).filter((line) => !/^\s*Sitemap\s*:/i.test(line)).join("\n").replace(/\n+$/, "");
175
+ }
176
+ async function updateRobots(outputDir, previousPath, configured, sitemapUrl) {
177
+ const nextPath = configured === false ? void 0 : configured && typeof configured === "object" ? configured.output ?? "robots.txt" : "robots.txt";
178
+ const paths = new Set([previousPath, nextPath].filter((value) => Boolean(value)));
179
+ for (const relativePath of paths) {
180
+ const destination = outputPath(outputDir, relativePath);
181
+ let contents = withoutSitemapDirective(await readFile(destination, "utf8").catch(() => ""));
182
+ if (relativePath === nextPath) {
183
+ if (!contents) contents = "User-agent: *\nAllow: /";
184
+ contents = `${contents}\nSitemap: ${sitemapUrl}`;
185
+ }
186
+ if (contents) await atomicWrite(destination, `${contents}\n`);
187
+ else await rm(destination, { force: true });
188
+ }
189
+ return nextPath;
190
+ }
191
+ async function removeGeneratedSitemap(outputDir) {
192
+ const previous = await readManifest(outputDir);
193
+ await mapConcurrent(previous.files, 32, async (file) => rm(outputPath(outputDir, file), { force: true }));
194
+ await updateRobots(outputDir, previous.robots, false, "");
195
+ await rm(outputPath(outputDir, MANIFEST_PATH), { force: true });
196
+ }
197
+ function renderRoute(route) {
198
+ const lines = [" <url>", ` <loc>${escapeXml(route.location)}</loc>`];
199
+ if (route.lastModified) lines.push(` <lastmod>${escapeXml(route.lastModified)}</lastmod>`);
200
+ if (route.changeFrequency) lines.push(` <changefreq>${route.changeFrequency}</changefreq>`);
201
+ if (route.priority !== void 0) lines.push(` <priority>${route.priority}</priority>`);
202
+ for (const alternate of route.alternates) lines.push(` <xhtml:link rel="alternate" hreflang="${escapeXml(alternate.language)}" href="${escapeXml(alternate.location)}" />`);
203
+ lines.push(" </url>");
204
+ return lines.join("\n");
205
+ }
206
+ async function generateSitemap(outputDir, siteUrl, routes, config = {}) {
207
+ const baseUrl = resolveSiteUrl(siteUrl);
208
+ const resolverConcurrency = validateLimit(config.resolverConcurrency ?? 16, 128, "Sitemap resolver concurrency");
209
+ const generated = (await mapConcurrent(routes.filter((route) => (route.status === "success" || route.status === "skipped") && !route.path.includes("*")), resolverConcurrency, async (route) => {
210
+ const exact = config.routes?.[route.path];
211
+ if (exact === false) return void 0;
212
+ const resolved = await config.resolve?.(route);
213
+ if (resolved === false) return void 0;
214
+ const merged = {
215
+ ...config.defaults,
216
+ ...exact,
217
+ ...resolved
218
+ };
219
+ const alternates = [];
220
+ for (const [language, location] of Object.entries(merged.alternates ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
221
+ if (!languagePattern.test(language)) throw new Error(`Invalid sitemap hreflang value: ${language}`);
222
+ alternates.push({
223
+ language,
224
+ location: resolveLocation(location, baseUrl)
225
+ });
226
+ }
227
+ return {
228
+ location: resolveLocation(merged.url ?? route.path, baseUrl),
229
+ ...merged.lastModified !== void 0 ? { lastModified: formatLastModified(merged.lastModified) } : {},
230
+ ...merged.changeFrequency ? { changeFrequency: validateChangeFrequency(merged.changeFrequency) } : {},
231
+ ...merged.priority !== void 0 ? { priority: validatePriority(merged.priority) } : {},
232
+ alternates
233
+ };
234
+ })).filter((route) => Boolean(route));
235
+ generated.sort((a, b) => a.location.localeCompare(b.location));
236
+ const locations = /* @__PURE__ */ new Set();
237
+ for (const route of generated) {
238
+ if (locations.has(route.location)) throw new Error(`Sitemap contains a duplicate canonical URL: ${route.location}`);
239
+ locations.add(route.location);
240
+ }
241
+ const destination = outputPath(outputDir, config.output);
242
+ const chunks = partitionRoutes(generated, validateLimit(config.limits?.urlsPerFile, MAX_URLS_PER_SITEMAP, "Sitemap URL limit"), validateLimit(config.limits?.bytesPerFile, MAX_SITEMAP_BYTES, "Sitemap byte limit"));
243
+ if (chunks.length > MAX_URLS_PER_SITEMAP) throw new Error(`Sitemap index exceeds ${MAX_URLS_PER_SITEMAP} sitemap entries`);
244
+ const generatedFiles = [];
245
+ if (chunks.length === 1) {
246
+ await atomicWrite(destination, renderUrlSet(chunks[0]));
247
+ generatedFiles.push(relativeOutputPath(outputDir, destination));
248
+ } else {
249
+ const extension = path.extname(destination);
250
+ const base = destination.slice(0, extension ? -extension.length : void 0);
251
+ const chunkDestinations = chunks.map((_, index) => `${base}-${index + 1}.xml`);
252
+ await mapConcurrent(chunks, 16, async (chunk, index) => atomicWrite(chunkDestinations[index], renderUrlSet(chunk)));
253
+ const indexXml = sitemapIndex(chunkDestinations.map((chunkPath) => resolveLocation(relativeOutputPath(outputDir, chunkPath), baseUrl)));
254
+ if (Buffer.byteLength(indexXml) > MAX_SITEMAP_BYTES) throw new Error(`Sitemap index exceeds ${MAX_SITEMAP_BYTES} bytes`);
255
+ await atomicWrite(destination, indexXml);
256
+ generatedFiles.push(relativeOutputPath(outputDir, destination), ...chunkDestinations.map((chunkPath) => relativeOutputPath(outputDir, chunkPath)));
257
+ }
258
+ const previous = await readManifest(outputDir);
259
+ const current = new Set(generatedFiles);
260
+ await mapConcurrent(previous.files.filter((file) => !current.has(file)), 32, async (file) => rm(outputPath(outputDir, file), { force: true }));
261
+ const sitemapUrl = resolveLocation(relativeOutputPath(outputDir, destination), baseUrl);
262
+ const robots = await updateRobots(outputDir, previous.robots, config.robots, sitemapUrl);
263
+ await atomicWrite(outputPath(outputDir, MANIFEST_PATH), `${JSON.stringify({
264
+ files: generatedFiles,
265
+ ...robots ? { robots } : {}
266
+ }, null, 2)}\n`);
267
+ return destination;
268
+ }
269
+ //#endregion
7
270
  //#region src/bin/ssg.ts
8
271
  const helpText = `
9
272
  askr ssg - Static Site Generation for Askr
@@ -30,6 +293,14 @@ const defaultDeps = {
30
293
  now: () => Date.now(),
31
294
  existsSync
32
295
  };
296
+ async function pathExists(filePath) {
297
+ try {
298
+ await fs$1.access(filePath);
299
+ return true;
300
+ } catch {
301
+ return false;
302
+ }
303
+ }
33
304
  async function importProjectConfig(filePath) {
34
305
  const tsconfig = resolve(dirname(filePath), "tsconfig.json");
35
306
  const unregister = register(existsSync(tsconfig) ? { tsconfig } : void 0);
@@ -57,26 +328,56 @@ function parseCliArgs(args) {
57
328
  changedKeys: [],
58
329
  changedRoutes: [],
59
330
  forceFull: false,
60
- help: false
331
+ help: false,
332
+ errors: []
333
+ };
334
+ const valueFor = (index, option) => {
335
+ const value = args[index + 1];
336
+ if (!value || value.startsWith("-")) {
337
+ parsed.errors.push(`Missing value for ${option}`);
338
+ return;
339
+ }
340
+ return value;
61
341
  };
62
- for (let i = 0; i < args.length; i += 1) if (args[i] === "--config" && i + 1 < args.length) {
63
- parsed.configPath = args[i + 1];
64
- i += 1;
65
- } else if (args[i] === "--output" && i + 1 < args.length) {
66
- parsed.outputDir = args[i + 1];
67
- i += 1;
68
- } else if (args[i] === "--workers" && i + 1 < args.length) {
69
- parsed.workers = args[i + 1] === "auto" ? "auto" : Number(args[i + 1]);
70
- i += 1;
71
- } else if (args[i] === "--changed-key" && i + 1 < args.length) {
72
- parsed.changedKeys.push(args[i + 1]);
73
- i += 1;
74
- } else if (args[i] === "--changed-route" && i + 1 < args.length) {
75
- parsed.changedRoutes.push(args[i + 1]);
76
- i += 1;
342
+ for (let i = 0; i < args.length; i += 1) if (args[i] === "--config") {
343
+ const value = valueFor(i, "--config");
344
+ if (value) {
345
+ parsed.configPath = value;
346
+ i += 1;
347
+ }
348
+ } else if (args[i] === "--output") {
349
+ const value = valueFor(i, "--output");
350
+ if (value) {
351
+ parsed.outputDir = value;
352
+ i += 1;
353
+ }
354
+ } else if (args[i] === "--workers") {
355
+ const value = valueFor(i, "--workers");
356
+ if (value) {
357
+ if (value === "auto") parsed.workers = "auto";
358
+ else {
359
+ const workers = Number(value);
360
+ if (!Number.isSafeInteger(workers) || workers < 1) parsed.errors.push("--workers must be 'auto' or a positive integer");
361
+ else parsed.workers = workers;
362
+ }
363
+ i += 1;
364
+ }
365
+ } else if (args[i] === "--changed-key") {
366
+ const value = valueFor(i, "--changed-key");
367
+ if (value) {
368
+ parsed.changedKeys.push(value);
369
+ i += 1;
370
+ }
371
+ } else if (args[i] === "--changed-route") {
372
+ const value = valueFor(i, "--changed-route");
373
+ if (value) {
374
+ parsed.changedRoutes.push(value);
375
+ i += 1;
376
+ }
77
377
  } else if (args[i] === "--incremental") parsed.incremental = true;
78
378
  else if (args[i] === "--force-full") parsed.forceFull = true;
79
379
  else if (args[i] === "--help" || args[i] === "-h") parsed.help = true;
380
+ else parsed.errors.push(`Unknown option: ${args[i]}`);
80
381
  return parsed;
81
382
  }
82
383
  function toGenerateOptions(args) {
@@ -87,7 +388,7 @@ function toGenerateOptions(args) {
87
388
  forceFull: args.forceFull
88
389
  };
89
390
  }
90
- function printSummary(io, outputDir, durationSeconds, result) {
391
+ function printSummary(io, outputDir, durationSeconds, result, sitemapPath) {
91
392
  io.log("");
92
393
  io.log(`Generation complete in ${durationSeconds}s`);
93
394
  io.log(` Mode: ${result.mode}`);
@@ -99,6 +400,7 @@ function printSummary(io, outputDir, durationSeconds, result) {
99
400
  io.log(` CacheHit: ${result.cacheHits} routes`);
100
401
  io.log(` Output: ${outputDir}`);
101
402
  io.log(` Metadata: ${outputDir}/metadata.json`);
403
+ if (sitemapPath) io.log(` Sitemap: ${sitemapPath}`);
102
404
  io.log("");
103
405
  }
104
406
  async function runSsgCli(args = process.argv.slice(2), deps = {}, io = console) {
@@ -107,6 +409,10 @@ async function runSsgCli(args = process.argv.slice(2), deps = {}, io = console)
107
409
  io.log(helpText);
108
410
  return 0;
109
411
  }
412
+ if (parsed.errors.length > 0) {
413
+ for (const error of parsed.errors) io.error(`Error: ${error}`);
414
+ return 1;
415
+ }
110
416
  if (!parsed.configPath) {
111
417
  io.error("Error: --config argument is required");
112
418
  return 1;
@@ -125,11 +431,22 @@ async function runSsgCli(args = process.argv.slice(2), deps = {}, io = console)
125
431
  };
126
432
  const resolvedConfigPath = resolve(resolvedDeps.cwd(), parsed.configPath);
127
433
  const resolvedOutputDir = resolve(resolvedDeps.cwd(), parsed.outputDir);
434
+ const outputStat = await fs$1.stat(resolvedOutputDir).catch(() => null);
435
+ if (outputStat && !outputStat.isDirectory()) {
436
+ io.error(`Error: SSG output exists and is not a directory: ${resolvedOutputDir}`);
437
+ return 1;
438
+ }
439
+ const configWithinOutput = path$1.relative(resolvedOutputDir, resolvedConfigPath);
440
+ if (resolvedOutputDir === path$1.resolve(resolvedDeps.cwd()) || !configWithinOutput.startsWith("..") && !path$1.isAbsolute(configWithinOutput)) {
441
+ io.error("Error: --output must not contain the project or SSG config file");
442
+ return 1;
443
+ }
128
444
  if (!resolvedDeps.existsSync(resolvedConfigPath)) {
129
445
  io.error(`Error: Config file not found: ${resolvedConfigPath}`);
130
446
  return 1;
131
447
  }
132
448
  let unregisterProjectLoader;
449
+ let cliStagingDir;
133
450
  try {
134
451
  io.log(`Loading config: ${resolvedConfigPath}`);
135
452
  let imported;
@@ -147,10 +464,21 @@ async function runSsgCli(args = process.argv.slice(2), deps = {}, io = console)
147
464
  return 1;
148
465
  }
149
466
  const config = candidate;
467
+ if (config.sitemap !== false && !config.siteUrl) {
468
+ io.error("Error: Config must provide siteUrl to generate sitemap.xml, or set sitemap: false");
469
+ return 1;
470
+ }
150
471
  io.log(hasRoutes ? `Generating ${config.routes?.length ?? 0} routes...` : "Generating registered routes...");
151
- const ssg = (typeof resolvedDeps.createStaticGen === "function" ? resolvedDeps.createStaticGen : await loadCreateStaticGen())({
472
+ const createStaticGen = typeof resolvedDeps.createStaticGen === "function" ? resolvedDeps.createStaticGen : await loadCreateStaticGen();
473
+ cliStagingDir = await createSiblingStage(resolvedOutputDir, "askr-ssg");
474
+ if (parsed.incremental && !parsed.forceFull && await pathExists(resolvedOutputDir)) await fs$1.cp(resolvedOutputDir, cliStagingDir, {
475
+ recursive: true,
476
+ mode: constants.COPYFILE_FICLONE
477
+ });
478
+ const generationOutputDir = cliStagingDir;
479
+ const ssg = createStaticGen({
152
480
  ...hasRoutes ? { routes: config.routes } : { registry: config.registry },
153
- outputDir: resolvedOutputDir,
481
+ outputDir: generationOutputDir,
154
482
  seed: config.seed,
155
483
  dataOverrides: config.dataOverrides,
156
484
  concurrency: config.concurrency,
@@ -160,7 +488,13 @@ async function runSsgCli(args = process.argv.slice(2), deps = {}, io = console)
160
488
  });
161
489
  const startTime = resolvedDeps.now();
162
490
  const result = await ssg.generate(toGenerateOptions(parsed));
163
- printSummary(io, resolvedOutputDir, ((resolvedDeps.now() - startTime) / 1e3).toFixed(2), result);
491
+ const sitemapPath = result.failed === 0 && config.sitemap !== false && config.siteUrl ? await generateSitemap(generationOutputDir, config.siteUrl, result.routes, config.sitemap) : void 0;
492
+ if (result.failed === 0 && config.sitemap === false) await removeGeneratedSitemap(generationOutputDir);
493
+ if (result.failed === 0 && cliStagingDir) {
494
+ await publishStagedDirectory(cliStagingDir, resolvedOutputDir);
495
+ cliStagingDir = void 0;
496
+ }
497
+ printSummary(io, resolvedOutputDir, ((resolvedDeps.now() - startTime) / 1e3).toFixed(2), result, sitemapPath ? path$1.join(resolvedOutputDir, path$1.relative(generationOutputDir, sitemapPath)) : void 0);
164
498
  if (result.failed > 0) {
165
499
  io.log("Errors encountered:");
166
500
  for (const route of result.routes) if (route.status === "error") io.log(` ${route.path}: ${route.error}`);
@@ -174,6 +508,10 @@ async function runSsgCli(args = process.argv.slice(2), deps = {}, io = console)
174
508
  if (error instanceof Error && error.stack) io.error(error.stack);
175
509
  return 1;
176
510
  } finally {
511
+ if (cliStagingDir) await fs$1.rm(cliStagingDir, {
512
+ recursive: true,
513
+ force: true
514
+ });
177
515
  await unregisterProjectLoader?.();
178
516
  }
179
517
  }
@@ -184,5 +522,3 @@ async function main() {
184
522
  if (isDirectExecution(import.meta.url)) main();
185
523
  //#endregion
186
524
  export { parseCliArgs, runSsgCli };
187
-
188
- //# sourceMappingURL=ssg.js.map
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "devDependencies": {
29
29
  "@askrjs/vite": ">=0.0.6 <0.1.0",
30
- "@types/node": "^26.0.0",
30
+ "@types/node": "^20.19.0",
31
31
  "jsdom": "^29.1.1",
32
32
  "typescript": "^6.0.3",
33
33
  "vite-plus": "^0.2.4",
@@ -1,7 +1,6 @@
1
1
  import { handleAction } from "@askrjs/server/askr";
2
2
  import { createMessageAction } from "../actions/create-message";
3
3
  import { createMessage } from "./actions/create-message";
4
- import type { AppDependencies } from "./dependencies";
5
4
 
6
5
  export function createActionHandlers() {
7
6
  return [handleAction(createMessageAction, createMessage)] as const;
@@ -9,7 +9,7 @@
9
9
  "noEmit": true,
10
10
  "jsx": "preserve",
11
11
  "jsxImportSource": "@askrjs/askr",
12
- "types": ["vite/client", "node"]
12
+ "types": ["vite/client", "node", "@askrjs/themes"]
13
13
  },
14
14
  "include": ["src", "tests", "server.ts", "vite.config.ts"]
15
15
  }
@@ -7,7 +7,7 @@
7
7
  "dev": "vp dev",
8
8
  "build": "vp build",
9
9
  "preview": "vp preview",
10
- "test": "vp test run",
10
+ "test": "vp test run -c ./vitest.config.ts",
11
11
  "typecheck": "tsc --noEmit",
12
12
  "lint": "vp lint .",
13
13
  "lint:fix": "npm run lint -- --fix",
@@ -17,14 +17,13 @@
17
17
  "dependencies": {
18
18
  "@askrjs/askr": ">=0.0.53 <0.1.0",
19
19
  "@askrjs/charts": ">=0.1.0 <0.2.0",
20
- "@askrjs/logos": ">=0.0.3 <0.1.0",
21
20
  "@askrjs/lucide": ">=0.0.3 <0.1.0",
22
21
  "@askrjs/themes": ">=0.0.12 <0.1.0",
23
22
  "@askrjs/ui": ">=0.0.13 <0.1.0"
24
23
  },
25
24
  "devDependencies": {
26
25
  "@askrjs/vite": ">=0.0.6 <0.1.0",
27
- "@types/node": "^26.0.0",
26
+ "@types/node": "^20.19.0",
28
27
  "jsdom": "^29.1.1",
29
28
  "typescript": "^6.0.3",
30
29
  "vite-plus": "^0.2.4"
@@ -2,8 +2,10 @@ import { route } from '@askrjs/askr/router';
2
2
  import AgentRunsPage from './agent-runs';
3
3
  import AdminHomePage from './admin-home';
4
4
  import SettingsPage from './settings';
5
+ // askr:imports
5
6
 
6
7
  export function registerAppRoutes(): void {
8
+ // askr:routes
7
9
  route('/app', AdminHomePage);
8
10
  route('/app/agents', AgentRunsPage);
9
11
  route('/app/settings', SettingsPage);
@@ -1,6 +1,8 @@
1
1
  import { route } from '@askrjs/askr/router';
2
2
  import HomePage from './home';
3
+ // askr:imports
3
4
 
4
5
  export function registerPublicRoutes(): void {
6
+ // askr:routes
5
7
  route('/', HomePage);
6
8
  }
@@ -13,7 +13,8 @@
13
13
  "isolatedModules": true,
14
14
  "noEmit": true,
15
15
  "jsx": "preserve",
16
- "jsxImportSource": "@askrjs/askr"
16
+ "jsxImportSource": "@askrjs/askr",
17
+ "types": ["@askrjs/themes"]
17
18
  },
18
19
  "include": ["src", "tests"],
19
20
  "references": [{ "path": "./tsconfig.node.json" }]
@@ -3,10 +3,6 @@ import { askr } from '@askrjs/vite';
3
3
 
4
4
  export default defineConfig({
5
5
  plugins: [askr()],
6
- esbuild: {
7
- jsx: 'automatic',
8
- jsxImportSource: '@askrjs/askr',
9
- },
10
6
  test: {
11
7
  environment: 'jsdom',
12
8
  globals: true,
@@ -34,6 +34,6 @@ npm run typecheck # Type check
34
34
 
35
35
  ## Static generation
36
36
 
37
- Run `npm run build` to compile browser assets and pre-render every route into `dist/`. The `askr ssg` command owns both full and incremental generation, and publishes routes, metadata, the incremental manifest, and browser assets together.
37
+ Run `npm run build` to compile browser assets and pre-render every route into `dist/`. The `askr ssg` command owns both full and incremental generation, and publishes routes, metadata, the incremental manifest, browser assets, `sitemap.xml`, and the managed `robots.txt` sitemap directive together.
38
38
 
39
39
  `ssg.config.ts` passes the same immutable route registry used in dev mode, so the shell and leaf pages stay in sync.
@@ -24,7 +24,7 @@
24
24
  "devDependencies": {
25
25
  "@askrjs/cli": ">=0.0.2 <0.1.0",
26
26
  "@askrjs/vite": ">=0.0.6 <0.1.0",
27
- "@types/node": "^26.0.0",
27
+ "@types/node": "^20.19.0",
28
28
  "jsdom": "^29.1.1",
29
29
  "typescript": "^6.0.3",
30
30
  "vite-plus": "^0.2.4"
@@ -4,6 +4,7 @@ import type { DocumentRenderArgs } from '@askrjs/askr/ssg';
4
4
  import { pageRegistry } from './src/routes';
5
5
 
6
6
  export const outputDir = './dist';
7
+ export const siteUrl = 'https://example.com';
7
8
 
8
9
  let clientTemplate: string | undefined;
9
10
 
@@ -24,6 +25,12 @@ function renderDocument({ appHtml }: DocumentRenderArgs) {
24
25
  export const staticConfig = {
25
26
  registry: pageRegistry,
26
27
  outputDir,
28
+ siteUrl,
29
+ sitemap: {
30
+ routes: {
31
+ '/preview': false,
32
+ },
33
+ },
27
34
  document: renderDocument,
28
35
  assets: [
29
36
  {