@askrjs/cli 0.0.2 → 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 (310) hide show
  1. package/README.md +46 -7
  2. package/dist/add.d.ts +16 -0
  3. package/dist/add.js +558 -1
  4. package/dist/cli.d.ts +5 -0
  5. package/dist/cli.js +43 -16
  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 +323 -0
  10. package/dist/generate.d.ts +5 -0
  11. package/dist/generate.js +461 -0
  12. package/dist/is-direct-execution-Cdlr-ZUl.js +0 -2
  13. package/dist/openapi.d.ts +25 -0
  14. package/dist/openapi.js +49 -16
  15. package/dist/planner-VAj7qlxr.js +285 -0
  16. package/dist/range-YUs9eimn.js +125 -0
  17. package/dist/registry-CyrRHRG5.js +209 -0
  18. package/dist/skills/askr-dashboard-charts/SKILL.md +61 -44
  19. package/dist/skills/askr-dashboard-charts/agents/openai.yaml +2 -2
  20. package/dist/skills/askr-ssr-ssg/SKILL.md +3 -1
  21. package/dist/{skills-Bh0mEhIx.js → skills-B7CbWur9.js} +89 -26
  22. package/dist/skills.d.ts +18 -0
  23. package/dist/skills.js +1 -1
  24. package/dist/specification-DXnDOC-0.js +49 -0
  25. package/dist/ssg-config.d.ts +41 -0
  26. package/dist/ssg-config.js +1 -0
  27. package/dist/ssg.d.ts +61 -0
  28. package/dist/ssg.js +360 -24
  29. package/dist/templates/full-stack/AGENTS.md +0 -1
  30. package/dist/templates/full-stack/README.md +0 -1
  31. package/dist/templates/full-stack/index.html +0 -1
  32. package/dist/templates/full-stack/package.json +5 -3
  33. package/dist/templates/full-stack/server.ts +5 -9
  34. package/dist/templates/full-stack/src/action-authorizations.ts +2 -2
  35. package/dist/templates/full-stack/src/actions/create-message.ts +5 -5
  36. package/dist/templates/full-stack/src/i18n.ts +4 -4
  37. package/dist/templates/full-stack/src/main.tsx +4 -5
  38. package/dist/templates/full-stack/src/pages/home.tsx +5 -5
  39. package/dist/templates/full-stack/src/pages/layout.tsx +15 -5
  40. package/dist/templates/full-stack/src/pages/not-found.tsx +5 -2
  41. package/dist/templates/full-stack/src/routes.tsx +11 -12
  42. package/dist/templates/full-stack/src/schemas.ts +1 -2
  43. package/dist/templates/full-stack/src/server/action-registry.ts +5 -10
  44. package/dist/templates/full-stack/src/server/actions/create-message.ts +4 -4
  45. package/dist/templates/full-stack/src/server/app.ts +67 -57
  46. package/dist/templates/full-stack/src/server/dependencies.ts +10 -10
  47. package/dist/templates/full-stack/src/server/entry-server.ts +3 -3
  48. package/dist/templates/full-stack/src/telemetry.ts +1 -2
  49. package/dist/templates/full-stack/tests/actions/create-message.test.ts +7 -8
  50. package/dist/templates/full-stack/tsconfig.json +1 -2
  51. package/dist/templates/full-stack/vite.config.ts +5 -6
  52. package/dist/templates/spa/AGENTS.md +1 -1
  53. package/dist/templates/spa/package.json +3 -4
  54. package/dist/templates/spa/src/adapters/operations-client.ts +7 -2
  55. package/dist/templates/spa/src/pages/app/_routes.tsx +2 -0
  56. package/dist/templates/spa/src/pages/app/admin-home.tsx +21 -3
  57. package/dist/templates/spa/src/pages/public/_routes.tsx +2 -0
  58. package/dist/templates/spa/src/styles.css +1 -1
  59. package/dist/templates/spa/tsconfig.json +2 -1
  60. package/dist/templates/spa/vitest.config.ts +0 -4
  61. package/dist/templates/ssg/README.md +1 -1
  62. package/dist/templates/ssg/package.json +1 -1
  63. package/dist/templates/ssg/ssg.config.ts +7 -0
  64. package/dist/templates/ssg/tests/ssg-config.test.ts +5 -3
  65. package/dist/templates/ssg/tsconfig.json +2 -1
  66. package/dist/templates/ssg/vitest.config.ts +0 -4
  67. package/dist/templates/ssr/package.json +2 -2
  68. package/dist/templates/ssr/server.ts +4 -10
  69. package/dist/templates/ssr/src/entry-server.tsx +6 -4
  70. package/dist/templates/startkit/package.json +2 -2
  71. package/dist/templates/startkit/src/lib/mock-data.ts +17 -39
  72. package/dist/templates/startkit/tsconfig.json +2 -1
  73. package/dist/update.d.ts +41 -0
  74. package/dist/update.js +282 -2
  75. package/dist/writer-D8qe_7ud.js +226 -0
  76. package/package.json +23 -22
  77. package/dist/add-DqhqRzBQ.js +0 -555
  78. package/dist/add-DqhqRzBQ.js.map +0 -1
  79. package/dist/cli.js.map +0 -1
  80. package/dist/create.js.map +0 -1
  81. package/dist/is-direct-execution-Cdlr-ZUl.js.map +0 -1
  82. package/dist/openapi.js.map +0 -1
  83. package/dist/skills-Bh0mEhIx.js.map +0 -1
  84. package/dist/ssg.js.map +0 -1
  85. package/dist/templates/full-stack/src/vite-server.d.ts +0 -6
  86. package/dist/templates/spa/src/styles.d.ts +0 -1
  87. package/dist/templates/ssg/src/vite-env.d.ts +0 -5
  88. package/dist/templates/ssr/src/vite-server.d.ts +0 -7
  89. package/dist/templates/startkit/src/vite-env.d.ts +0 -5
  90. package/dist/update-DyW1na5V.js +0 -1140
  91. package/dist/update-DyW1na5V.js.map +0 -1
  92. package/skills/askr-accessibility/SKILL.md +0 -68
  93. package/skills/askr-accessibility/agents/openai.yaml +0 -4
  94. package/skills/askr-agent-execution/SKILL.md +0 -85
  95. package/skills/askr-agent-execution/agents/openai.yaml +0 -4
  96. package/skills/askr-agent-workflows/SKILL.md +0 -57
  97. package/skills/askr-agent-workflows/agents/openai.yaml +0 -4
  98. package/skills/askr-api-integration/SKILL.md +0 -79
  99. package/skills/askr-api-integration/agents/openai.yaml +0 -4
  100. package/skills/askr-app-builder/SKILL.md +0 -99
  101. package/skills/askr-app-builder/agents/openai.yaml +0 -4
  102. package/skills/askr-auth-access/SKILL.md +0 -74
  103. package/skills/askr-auth-access/agents/openai.yaml +0 -4
  104. package/skills/askr-cli-vite/SKILL.md +0 -76
  105. package/skills/askr-cli-vite/agents/openai.yaml +0 -4
  106. package/skills/askr-dashboard-charts/SKILL.md +0 -82
  107. package/skills/askr-dashboard-charts/agents/openai.yaml +0 -4
  108. package/skills/askr-design-system/SKILL.md +0 -86
  109. package/skills/askr-design-system/agents/openai.yaml +0 -4
  110. package/skills/askr-env-config/SKILL.md +0 -74
  111. package/skills/askr-env-config/agents/openai.yaml +0 -4
  112. package/skills/askr-error-loading-empty/SKILL.md +0 -89
  113. package/skills/askr-error-loading-empty/agents/openai.yaml +0 -4
  114. package/skills/askr-file-upload-artifacts/SKILL.md +0 -77
  115. package/skills/askr-file-upload-artifacts/agents/openai.yaml +0 -4
  116. package/skills/askr-forms-tables-crud/SKILL.md +0 -98
  117. package/skills/askr-forms-tables-crud/agents/openai.yaml +0 -4
  118. package/skills/askr-mental-model/SKILL.md +0 -109
  119. package/skills/askr-mental-model/agents/openai.yaml +0 -4
  120. package/skills/askr-migration-react/SKILL.md +0 -81
  121. package/skills/askr-migration-react/agents/openai.yaml +0 -4
  122. package/skills/askr-observability-debugging/SKILL.md +0 -83
  123. package/skills/askr-observability-debugging/agents/openai.yaml +0 -4
  124. package/skills/askr-project-structure/SKILL.md +0 -79
  125. package/skills/askr-project-structure/agents/openai.yaml +0 -4
  126. package/skills/askr-query-mutation/SKILL.md +0 -115
  127. package/skills/askr-query-mutation/agents/openai.yaml +0 -4
  128. package/skills/askr-realtime-streaming/SKILL.md +0 -76
  129. package/skills/askr-realtime-streaming/agents/openai.yaml +0 -4
  130. package/skills/askr-resources-data/SKILL.md +0 -100
  131. package/skills/askr-resources-data/agents/openai.yaml +0 -4
  132. package/skills/askr-routing-layouts/SKILL.md +0 -107
  133. package/skills/askr-routing-layouts/agents/openai.yaml +0 -4
  134. package/skills/askr-runtime-reactivity/SKILL.md +0 -92
  135. package/skills/askr-runtime-reactivity/agents/openai.yaml +0 -4
  136. package/skills/askr-ssr-ssg/SKILL.md +0 -76
  137. package/skills/askr-ssr-ssg/agents/openai.yaml +0 -4
  138. package/skills/askr-testing-determinism/SKILL.md +0 -75
  139. package/skills/askr-testing-determinism/agents/openai.yaml +0 -4
  140. package/skills/askr-theming/SKILL.md +0 -86
  141. package/skills/askr-theming/agents/openai.yaml +0 -4
  142. package/skills/askr-ui-composition/SKILL.md +0 -92
  143. package/skills/askr-ui-composition/agents/openai.yaml +0 -4
  144. package/templates/full-stack/AGENTS.md +0 -12
  145. package/templates/full-stack/README.md +0 -18
  146. package/templates/full-stack/gitignore.template +0 -4
  147. package/templates/full-stack/index.html +0 -14
  148. package/templates/full-stack/package.json +0 -37
  149. package/templates/full-stack/server.ts +0 -13
  150. package/templates/full-stack/src/action-authorizations.ts +0 -10
  151. package/templates/full-stack/src/actions/create-message.ts +0 -10
  152. package/templates/full-stack/src/i18n.ts +0 -8
  153. package/templates/full-stack/src/main.tsx +0 -12
  154. package/templates/full-stack/src/pages/home.tsx +0 -25
  155. package/templates/full-stack/src/pages/layout.tsx +0 -33
  156. package/templates/full-stack/src/pages/not-found.tsx +0 -4
  157. package/templates/full-stack/src/routes.tsx +0 -20
  158. package/templates/full-stack/src/schemas.ts +0 -11
  159. package/templates/full-stack/src/server/action-registry.ts +0 -12
  160. package/templates/full-stack/src/server/actions/create-message.ts +0 -14
  161. package/templates/full-stack/src/server/app.ts +0 -66
  162. package/templates/full-stack/src/server/dependencies.ts +0 -55
  163. package/templates/full-stack/src/server/entry-server.ts +0 -8
  164. package/templates/full-stack/src/telemetry.ts +0 -4
  165. package/templates/full-stack/src/vite-server.d.ts +0 -6
  166. package/templates/full-stack/tests/actions/create-message.test.ts +0 -13
  167. package/templates/full-stack/tsconfig.json +0 -16
  168. package/templates/full-stack/vite.config.ts +0 -11
  169. package/templates/spa/AGENTS.md +0 -60
  170. package/templates/spa/README.md +0 -96
  171. package/templates/spa/gitignore.template +0 -16
  172. package/templates/spa/index.html +0 -13
  173. package/templates/spa/package.json +0 -35
  174. package/templates/spa/src/adapters/operations-client.ts +0 -90
  175. package/templates/spa/src/components/shared/metric-card.tsx +0 -27
  176. package/templates/spa/src/components/shared/status-badge.tsx +0 -24
  177. package/templates/spa/src/features/operations/operations.query.ts +0 -9
  178. package/templates/spa/src/main.tsx +0 -10
  179. package/templates/spa/src/pages/_layout.tsx +0 -9
  180. package/templates/spa/src/pages/_routes.tsx +0 -27
  181. package/templates/spa/src/pages/app/_layout.tsx +0 -94
  182. package/templates/spa/src/pages/app/_routes.tsx +0 -10
  183. package/templates/spa/src/pages/app/admin-home.tsx +0 -152
  184. package/templates/spa/src/pages/app/agent-runs.tsx +0 -93
  185. package/templates/spa/src/pages/app/settings.tsx +0 -91
  186. package/templates/spa/src/pages/auth/_layout.tsx +0 -38
  187. package/templates/spa/src/pages/auth/_routes.tsx +0 -6
  188. package/templates/spa/src/pages/auth/login.tsx +0 -63
  189. package/templates/spa/src/pages/not-found.tsx +0 -22
  190. package/templates/spa/src/pages/public/_layout.tsx +0 -47
  191. package/templates/spa/src/pages/public/_routes.tsx +0 -6
  192. package/templates/spa/src/pages/public/home.tsx +0 -143
  193. package/templates/spa/src/shared/format.ts +0 -12
  194. package/templates/spa/src/shared/navigation.ts +0 -15
  195. package/templates/spa/src/styles/components.css +0 -49
  196. package/templates/spa/src/styles/layout.css +0 -192
  197. package/templates/spa/src/styles/reset.css +0 -44
  198. package/templates/spa/src/styles/theme.css +0 -57
  199. package/templates/spa/src/styles/tokens.css +0 -20
  200. package/templates/spa/src/styles.css +0 -7
  201. package/templates/spa/src/styles.d.ts +0 -1
  202. package/templates/spa/tests/app.test.tsx +0 -37
  203. package/templates/spa/tests/components/shared.test.tsx +0 -12
  204. package/templates/spa/tests/resources.test.ts +0 -22
  205. package/templates/spa/tsconfig.json +0 -20
  206. package/templates/spa/tsconfig.node.json +0 -10
  207. package/templates/spa/vite.config.ts +0 -24
  208. package/templates/spa/vitest.config.ts +0 -17
  209. package/templates/ssg/AGENTS.md +0 -55
  210. package/templates/ssg/README.md +0 -39
  211. package/templates/ssg/gitignore.template +0 -17
  212. package/templates/ssg/index.html +0 -13
  213. package/templates/ssg/package.json +0 -35
  214. package/templates/ssg/src/app.tsx +0 -14
  215. package/templates/ssg/src/components/badge.tsx +0 -3
  216. package/templates/ssg/src/components/counter.tsx +0 -30
  217. package/templates/ssg/src/components/site-shell.tsx +0 -111
  218. package/templates/ssg/src/jsx.d.ts +0 -23
  219. package/templates/ssg/src/main.tsx +0 -8
  220. package/templates/ssg/src/pages/about.tsx +0 -82
  221. package/templates/ssg/src/pages/content.tsx +0 -63
  222. package/templates/ssg/src/pages/example.tsx +0 -97
  223. package/templates/ssg/src/pages/home.tsx +0 -51
  224. package/templates/ssg/src/resources/user.ts +0 -15
  225. package/templates/ssg/src/routes.tsx +0 -15
  226. package/templates/ssg/src/styles.css +0 -89
  227. package/templates/ssg/src/vite-env.d.ts +0 -5
  228. package/templates/ssg/ssg.config.ts +0 -34
  229. package/templates/ssg/tests/app.test.tsx +0 -19
  230. package/templates/ssg/tests/components/counter.test.tsx +0 -18
  231. package/templates/ssg/tests/resources.test.ts +0 -11
  232. package/templates/ssg/tests/ssg-config.test.ts +0 -11
  233. package/templates/ssg/tsconfig.json +0 -20
  234. package/templates/ssg/tsconfig.node.json +0 -10
  235. package/templates/ssg/vite.config.ts +0 -24
  236. package/templates/ssg/vitest.config.ts +0 -17
  237. package/templates/ssr/AGENTS.md +0 -28
  238. package/templates/ssr/README.md +0 -38
  239. package/templates/ssr/gitignore.template +0 -16
  240. package/templates/ssr/index.html +0 -13
  241. package/templates/ssr/package.json +0 -34
  242. package/templates/ssr/server.ts +0 -16
  243. package/templates/ssr/src/app.tsx +0 -21
  244. package/templates/ssr/src/components/counter.tsx +0 -26
  245. package/templates/ssr/src/entry-server.tsx +0 -10
  246. package/templates/ssr/src/main.tsx +0 -11
  247. package/templates/ssr/src/pages/about.tsx +0 -75
  248. package/templates/ssr/src/pages/example.tsx +0 -113
  249. package/templates/ssr/src/pages/home.tsx +0 -56
  250. package/templates/ssr/src/resources/user.ts +0 -15
  251. package/templates/ssr/src/routes.tsx +0 -13
  252. package/templates/ssr/src/styles.css +0 -148
  253. package/templates/ssr/src/vite-server.d.ts +0 -7
  254. package/templates/ssr/tests/app.test.tsx +0 -24
  255. package/templates/ssr/tests/components/counter.test.tsx +0 -13
  256. package/templates/ssr/tests/resources.test.ts +0 -11
  257. package/templates/ssr/tsconfig.json +0 -22
  258. package/templates/ssr/tsconfig.node.json +0 -10
  259. package/templates/ssr/tsconfig.server.json +0 -15
  260. package/templates/ssr/vite.config.ts +0 -25
  261. package/templates/ssr/vitest.config.ts +0 -13
  262. package/templates/startkit/AGENTS.md +0 -77
  263. package/templates/startkit/README.md +0 -192
  264. package/templates/startkit/gitignore.template +0 -16
  265. package/templates/startkit/index.html +0 -13
  266. package/templates/startkit/package.json +0 -35
  267. package/templates/startkit/src/components/app-header.tsx +0 -93
  268. package/templates/startkit/src/components/app-sidebar.tsx +0 -95
  269. package/templates/startkit/src/components/data-table.tsx +0 -80
  270. package/templates/startkit/src/components/empty-state.tsx +0 -13
  271. package/templates/startkit/src/components/page-header.tsx +0 -23
  272. package/templates/startkit/src/components/stat-card.tsx +0 -24
  273. package/templates/startkit/src/features/accounts/account-filters.tsx +0 -61
  274. package/templates/startkit/src/features/accounts/account-table.tsx +0 -79
  275. package/templates/startkit/src/lib/format.ts +0 -31
  276. package/templates/startkit/src/lib/mock-data.ts +0 -441
  277. package/templates/startkit/src/lib/routes.ts +0 -116
  278. package/templates/startkit/src/main.tsx +0 -12
  279. package/templates/startkit/src/pages/_layout.tsx +0 -56
  280. package/templates/startkit/src/pages/auth/_layout.tsx +0 -7
  281. package/templates/startkit/src/pages/auth/login.tsx +0 -129
  282. package/templates/startkit/src/pages/home.tsx +0 -83
  283. package/templates/startkit/src/pages/not-found.tsx +0 -24
  284. package/templates/startkit/src/pages/workspace/_layout.tsx +0 -17
  285. package/templates/startkit/src/pages/workspace/accounts/index.tsx +0 -217
  286. package/templates/startkit/src/pages/workspace/dashboard.tsx +0 -118
  287. package/templates/startkit/src/pages/workspace/settings.tsx +0 -218
  288. package/templates/startkit/src/router.tsx +0 -7
  289. package/templates/startkit/src/routes/auth-config.ts +0 -38
  290. package/templates/startkit/src/routes/auth.ts +0 -8
  291. package/templates/startkit/src/routes/index.ts +0 -28
  292. package/templates/startkit/src/routes/public.ts +0 -8
  293. package/templates/startkit/src/routes/workspace/accounts.ts +0 -8
  294. package/templates/startkit/src/routes/workspace/index.ts +0 -14
  295. package/templates/startkit/src/styles/components.css +0 -396
  296. package/templates/startkit/src/styles/layout.css +0 -192
  297. package/templates/startkit/src/styles/reset.css +0 -38
  298. package/templates/startkit/src/styles/theme.css +0 -76
  299. package/templates/startkit/src/styles/tokens.css +0 -67
  300. package/templates/startkit/src/styles.css +0 -7
  301. package/templates/startkit/src/toast.ts +0 -55
  302. package/templates/startkit/src/utils/join-classes.ts +0 -4
  303. package/templates/startkit/src/vite-env.d.ts +0 -5
  304. package/templates/startkit/tests/app.test.tsx +0 -32
  305. package/templates/startkit/tests/preferences.test.ts +0 -43
  306. package/templates/startkit/tests/resources.test.ts +0 -82
  307. package/templates/startkit/tsconfig.json +0 -20
  308. package/templates/startkit/tsconfig.node.json +0 -10
  309. package/templates/startkit/vite.config.ts +0 -32
  310. package/templates/startkit/vitest.config.ts +0 -13
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
@@ -9,4 +9,3 @@ This project is the progressive full-stack stage of an Askr application.
9
9
  - Keep `index.html` as the only document source. Preserve exactly one `<!--askr-head-->` and one `<!--askr-app-->` marker.
10
10
  - Use native forms first; enhanced submission must preserve the same validation and authorization behavior.
11
11
  - Never log cookies, authorization values, tokens, form fields, request bodies, or personal data.
12
-
@@ -15,4 +15,3 @@ npm run preview
15
15
  Use `askr add action <name> --route <path>` to generate a browser-safe descriptor, server handler, composition-root registration, route authorization, and focused test.
16
16
 
17
17
  `index.html` owns the static document. `@askrjs/vite/server` injects only Askr-owned head nodes and composes the streamed app response at `<!--askr-app-->`.
18
-
@@ -11,4 +11,3 @@
11
11
  <script type="module" src="/src/main.tsx"></script>
12
12
  </body>
13
13
  </html>
14
-
@@ -14,8 +14,8 @@
14
14
  "check": "npm run lint && npm run typecheck && npm test && npm run build"
15
15
  },
16
16
  "dependencies": {
17
- "@askrjs/auth": ">=0.0.1 <0.1.0",
18
17
  "@askrjs/askr": ">=0.0.53 <0.1.0",
18
+ "@askrjs/auth": ">=0.0.1 <0.1.0",
19
19
  "@askrjs/i18n": ">=0.0.1 <0.1.0",
20
20
  "@askrjs/node": ">=0.0.1 <0.1.0",
21
21
  "@askrjs/otel": ">=0.0.1 <0.1.0",
@@ -27,11 +27,13 @@
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",
34
34
  "vitest": "^4.1.10"
35
35
  },
36
- "engines": { "node": "^20.19.0 || >=22.12.0" }
36
+ "engines": {
37
+ "node": "^20.19.0 || >=22.12.0"
38
+ }
37
39
  }
@@ -1,13 +1,9 @@
1
- import { listen } from '@askrjs/node';
2
- import app from 'virtual:askr-server';
1
+ import { serve } from "@askrjs/node";
2
+ import app from "virtual:askr-server";
3
3
 
4
4
  const port = Number(process.env.PORT ?? 3000);
5
- const server = await listen(app, {
6
- host: process.env.HOST ?? '127.0.0.1',
5
+ await serve(app, {
6
+ host: process.env.HOST ?? "127.0.0.1",
7
7
  port,
8
+ assets: { root: new URL("./dist", import.meta.url).pathname },
8
9
  });
9
-
10
- const shutdown = () => server.close();
11
- process.once('SIGINT', shutdown);
12
- process.once('SIGTERM', shutdown);
13
-
@@ -1,5 +1,5 @@
1
- import type { ActionDescriptor } from '@askrjs/askr/actions';
2
- import { createMessageAction } from './actions/create-message';
1
+ import type { ActionDescriptor } from "@askrjs/askr/actions";
2
+ import { createMessageAction } from "./actions/create-message";
3
3
 
4
4
  const actionsByRoute: Readonly<Record<string, readonly ActionDescriptor[]>> = Object.freeze({
5
5
  "/": [createMessageAction],
@@ -1,10 +1,10 @@
1
- import { defineAction } from '@askrjs/askr/actions';
2
- import { MessageInput } from '../schemas';
1
+ import { defineAction } from "@askrjs/askr/actions";
2
+ import { MessageInput } from "../schemas";
3
3
 
4
- export const createMessageActionRoute = '/';
4
+ export const createMessageActionRoute = "/";
5
5
 
6
6
  export const createMessageAction = defineAction({
7
- id: 'create-message',
7
+ id: "create-message",
8
8
  input: MessageInput,
9
- invalidates: ['messages'],
9
+ invalidates: ["messages"],
10
10
  });
@@ -1,8 +1,8 @@
1
- import { createI18n } from '@askrjs/i18n';
1
+ import { createI18n } from "@askrjs/i18n";
2
2
 
3
- export const messages = createI18n('en', {
3
+ export const messages = createI18n("en", {
4
4
  en: {
5
- title: () => '{{appName}}',
6
- intro: () => 'A progressive full-stack Askr application.',
5
+ title: () => "{{appName}}",
6
+ intro: () => "A progressive full-stack Askr application.",
7
7
  },
8
8
  });
@@ -1,12 +1,11 @@
1
- import { createSPA, hydrateSPA } from '@askrjs/askr/boot';
2
- import { pageRegistry } from './routes';
1
+ import { createSPA, hydrateSPA } from "@askrjs/askr/boot";
2
+ import { pageRegistry } from "./routes";
3
3
 
4
- const root = document.getElementById('app');
5
- if (!root) throw new Error('Missing #app root element.');
4
+ const root = document.getElementById("app");
5
+ if (!root) throw new Error("Missing #app root element.");
6
6
 
7
7
  if (root.hasChildNodes()) {
8
8
  await hydrateSPA({ root, registry: pageRegistry });
9
9
  } else {
10
10
  await createSPA({ root, registry: pageRegistry });
11
11
  }
12
-
@@ -1,12 +1,12 @@
1
- import { ActionForm } from '@askrjs/askr/actions';
2
- import { createMessageAction } from '../actions/create-message';
3
- import { messages } from '../i18n';
1
+ import { ActionForm } from "@askrjs/askr/actions";
2
+ import { createMessageAction } from "../actions/create-message";
3
+ import { messages } from "../i18n";
4
4
 
5
5
  export function HomePage() {
6
6
  return (
7
7
  <main>
8
- <h1>{messages.text('title')}</h1>
9
- <p>{messages.text('intro')}</p>
8
+ <h1>{messages.text("title")}</h1>
9
+ <p>{messages.text("intro")}</p>
10
10
  <form method="post" action="/api/session">
11
11
  <button type="submit">Start demo session</button>
12
12
  </form>
@@ -1,7 +1,15 @@
1
- import { Link } from '@askrjs/askr/router';
2
- import { Block, Container, Header, Navbar, NavBrand, NavGroup, NavLink } from '@askrjs/themes/components';
3
- import { ThemeScope, ThemeToggle } from '@askrjs/themes/theme';
4
- import { messages } from '../i18n';
1
+ import { Link } from "@askrjs/askr/router";
2
+ import {
3
+ Block,
4
+ Container,
5
+ Header,
6
+ Navbar,
7
+ NavBrand,
8
+ NavGroup,
9
+ NavLink,
10
+ } from "@askrjs/themes/components";
11
+ import { ThemeScope, ThemeToggle } from "@askrjs/themes/theme";
12
+ import { messages } from "../i18n";
5
13
 
6
14
  function ScopedLayout({ children }: { children?: unknown }) {
7
15
  return (
@@ -10,7 +18,9 @@ function ScopedLayout({ children }: { children?: unknown }) {
10
18
  <Header sticky>
11
19
  <Container paddingY="sm">
12
20
  <Navbar aria-label="Primary navigation" width="full">
13
- <NavBrand><Link href="/">{messages.text('title')}</Link></NavBrand>
21
+ <NavBrand>
22
+ <Link href="/">{messages.text("title")}</Link>
23
+ </NavBrand>
14
24
  <NavGroup align="end">
15
25
  <NavLink href="/">Home</NavLink>
16
26
  <ThemeToggle aria-label="Toggle theme" />
@@ -1,4 +1,7 @@
1
1
  export function NotFoundPage() {
2
- return <main><h1>Not found</h1></main>;
2
+ return (
3
+ <main>
4
+ <h1>Not found</h1>
5
+ </main>
6
+ );
3
7
  }
4
-