@askrjs/cli 0.0.3 → 0.0.5

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 +102 -27
  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/generate.js CHANGED
@@ -1,5 +1,5 @@
1
- import { mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
2
- import { basename, dirname, join, resolve } from "node:path";
1
+ import { mkdir, mkdtemp, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
2
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { load } from "js-yaml";
5
5
  //#region src/generate/generator.ts
@@ -29,25 +29,38 @@ const pascal = (name) => {
29
29
  const result = name.replace(/(^|[^A-Za-z0-9]+)([A-Za-z0-9])/g, (_, _s, c) => c.toUpperCase()).replace(/[^A-Za-z0-9_$]/g, "");
30
30
  return /^\d/.test(result) ? `_${result}` : result;
31
31
  };
32
- function schemaType(schema, at, direction = "response") {
32
+ function schemaType(schema, at, direction = "response", references) {
33
33
  if (schema.$ref) {
34
34
  const name = decodeURIComponent(String(schema.$ref).split("/").at(-1));
35
- return pascal(name);
35
+ const typeName = pascal(name);
36
+ references?.add(typeName);
37
+ return typeName;
38
+ }
39
+ if (Array.isArray(schema.type)) {
40
+ const includesNull = schema.type.includes("null");
41
+ const rendered = [...new Set(schema.type.filter((value) => value !== "null"))].map((variant) => schemaType({
42
+ ...schema,
43
+ type: variant,
44
+ nullable: false
45
+ }, at, direction, references));
46
+ if (includesNull) rendered.push("null");
47
+ if (rendered.length === 0) fail("Schema type array must not be empty", at);
48
+ return [...new Set(rendered)].join(" | ");
36
49
  }
37
50
  if (schema.const !== void 0) return JSON.stringify(schema.const);
38
51
  if (schema.enum) return schema.enum.map((value) => JSON.stringify(value)).join(" | ") || "never";
39
- if (schema.oneOf || schema.anyOf) return (schema.oneOf ?? schema.anyOf).map((item, i) => schemaType(item, `${at}/${schema.oneOf ? "oneOf" : "anyOf"}/${i}`, direction)).join(" | ");
40
- if (schema.allOf) return schema.allOf.map((item, i) => schemaType(item, `${at}/allOf/${i}`, direction)).join(" & ");
52
+ if (schema.oneOf || schema.anyOf) return (schema.oneOf ?? schema.anyOf).map((item, i) => schemaType(item, `${at}/${schema.oneOf ? "oneOf" : "anyOf"}/${i}`, direction, references)).join(" | ");
53
+ if (schema.allOf) return schema.allOf.map((item, i) => schemaType(item, `${at}/allOf/${i}`, direction, references)).join(" & ");
41
54
  let type = "never";
42
55
  if (schema.type === "string") type = "string";
43
56
  else if (schema.type === "number" || schema.type === "integer") type = "number";
44
57
  else if (schema.type === "boolean") type = "boolean";
45
58
  else if (schema.type === "null") type = "null";
46
- else if (schema.type === "array" || schema.items) type = `Array<${schemaType(schema.items ?? {}, `${at}/items`, direction)}>`;
59
+ else if (schema.type === "array" || schema.items) type = `Array<${schemaType(schema.items ?? {}, `${at}/items`, direction, references)}>`;
47
60
  else if (schema.type === "object" || schema.properties || schema.additionalProperties !== void 0) {
48
61
  const required = new Set(schema.required ?? []);
49
- const properties = Object.entries(schema.properties ?? {}).filter(([, value]) => !(direction === "request" ? value.readOnly : value.writeOnly)).map(([key, value]) => ` ${JSON.stringify(key)}${required.has(key) ? "" : "?"}: ${schemaType(value, `${at}/properties/${key}`, direction)};`);
50
- if (schema.additionalProperties && typeof schema.additionalProperties === "object") properties.push(` [key: string]: ${schemaType(schema.additionalProperties, `${at}/additionalProperties`, direction)};`);
62
+ const properties = Object.entries(schema.properties ?? {}).filter(([, value]) => !(direction === "request" ? value.readOnly : value.writeOnly)).map(([key, value]) => ` ${JSON.stringify(key)}${required.has(key) ? "" : "?"}: ${schemaType(value, `${at}/properties/${key}`, direction, references)};`);
63
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object") properties.push(` [key: string]: ${schemaType(schema.additionalProperties, `${at}/additionalProperties`, direction, references)};`);
51
64
  type = `{\n${properties.join("\n")}\n}`;
52
65
  } else if (!schema.type) fail("Unsupported schema without a type", at);
53
66
  else fail(`Unsupported schema type ${schema.type}`, at);
@@ -56,7 +69,7 @@ function schemaType(schema, at, direction = "response") {
56
69
  function responseSchema(operation, at, success) {
57
70
  return Object.entries(operation.responses ?? {}).filter(([status]) => status === "default" ? !success : success === /^2\d\d$/.test(status)).sort(([a], [b]) => a.localeCompare(b, void 0, { numeric: true }));
58
71
  }
59
- function pickContent(content, at, direction) {
72
+ function pickContent(content, at, direction, references) {
60
73
  const entries = Object.entries(content ?? {}).sort(([a], [b]) => a.localeCompare(b));
61
74
  if (!entries.length) return {
62
75
  type: "undefined",
@@ -65,7 +78,7 @@ function pickContent(content, at, direction) {
65
78
  };
66
79
  const types = entries.map(([mediaType, value]) => ({
67
80
  mediaType,
68
- type: schemaType(value.schema ?? {}, `${at}/content/${mediaType.replace(/~/g, "~0").replace(/\//g, "~1")}/schema`, direction)
81
+ type: schemaType(value.schema ?? {}, `${at}/content/${mediaType.replace(/~/g, "~0").replace(/\//g, "~1")}/schema`, direction, references)
69
82
  }));
70
83
  const union = [...new Set(types.map((v) => v.type))].join(" | ");
71
84
  return {
@@ -218,6 +231,13 @@ function generateFiles(document) {
218
231
  names.set(safe, name);
219
232
  }
220
233
  const schemaLines = [...names].map(([safe, original]) => `export type ${safe} = ${schemaType(schemas[original], pointer("components", "schemas", original))};`);
234
+ const operationSchemaReferences = /* @__PURE__ */ new Set();
235
+ const apiSchemaReferences = /* @__PURE__ */ new Set();
236
+ const apiOperationReferences = /* @__PURE__ */ new Set();
237
+ const contentSchemaReferences = { add(name) {
238
+ operationSchemaReferences.add(name);
239
+ apiSchemaReferences.add(name);
240
+ } };
221
241
  const operationTypes = [];
222
242
  const descriptors = [];
223
243
  const seen = /* @__PURE__ */ new Set();
@@ -230,24 +250,38 @@ function generateFiles(document) {
230
250
  if (seen.has(id)) fail("Duplicate operationId", `${at}/operationId`, id);
231
251
  seen.add(id);
232
252
  if (operation.callbacks) fail("Callbacks are unsupported", `${at}/callbacks`, id);
233
- const parameters = [...document.paths[path].parameters ?? [], ...operation.parameters ?? []];
253
+ const parametersByIdentity = /* @__PURE__ */ new Map();
254
+ for (const parameter of [...document.paths[path].parameters ?? [], ...operation.parameters ?? []]) {
255
+ if (parameter.$ref) fail("Parameter references must be bundled", `${at}/parameters`, id);
256
+ if (typeof parameter.name !== "string" || typeof parameter.in !== "string") fail("Parameters require string name and in fields", `${at}/parameters`, id);
257
+ parametersByIdentity.set(`${parameter.in}\0${parameter.name}`, parameter);
258
+ }
259
+ const parameters = [...parametersByIdentity.values()];
234
260
  const byLocation = {
235
261
  path: [],
236
262
  query: [],
237
263
  header: []
238
264
  };
239
265
  for (const parameter of parameters) {
240
- if (parameter.$ref) fail("Parameter references must be bundled", `${at}/parameters`, id);
241
266
  if (parameter.in === "cookie") fail("Cookie parameters are unsupported", `${at}/parameters`, id);
242
267
  if (!byLocation[parameter.in]) fail(`Unsupported parameter location ${parameter.in}`, `${at}/parameters`, id);
243
268
  byLocation[parameter.in].push(parameter);
244
269
  }
270
+ const templateParameters = new Set([...path.matchAll(/\{([^{}]+)\}/g)].map((match) => match[1]));
271
+ const declaredPathParameters = new Set(byLocation.path.map((parameter) => parameter.name));
272
+ for (const name of templateParameters) {
273
+ const parameter = byLocation.path.find((candidate) => candidate.name === name);
274
+ if (!parameter) fail(`Path template parameter ${name} is not declared`, `${at}/parameters`, id);
275
+ if (!parameter || parameter.required !== true) fail(`Path parameter ${name} must be required`, `${at}/parameters`, id);
276
+ }
277
+ for (const name of declaredPathParameters) if (!templateParameters.has(name)) fail(`Path parameter ${name} is not present in the path template`, `${at}/parameters`, id);
245
278
  const prefix = pascal(id);
246
279
  const renderParameters = (location) => {
247
280
  const list = byLocation[location].sort((a, b) => a.name.localeCompare(b.name));
248
281
  if (!list.length) return void 0;
249
282
  const name = `${prefix}${pascal(location)}`;
250
- operationTypes.push(`export type ${name} = {\n${list.map((p) => ` ${JSON.stringify(p.name)}${p.required || location === "path" ? "" : "?"}: ${schemaType(p.schema ?? {}, `${at}/parameters`, "request")};`).join("\n")}\n};`);
283
+ apiOperationReferences.add(name);
284
+ operationTypes.push(`export type ${name} = {\n${list.map((p) => ` ${JSON.stringify(p.name)}${p.required || location === "path" ? "" : "?"}: ${schemaType(p.schema ?? {}, `${at}/parameters`, "request", operationSchemaReferences)};`).join("\n")}\n};`);
251
285
  const defaultStyle = location === "query" ? "form" : "simple";
252
286
  return {
253
287
  name,
@@ -259,7 +293,7 @@ function generateFiles(document) {
259
293
  };
260
294
  };
261
295
  const params = renderParameters("path"), query = renderParameters("query"), headers = renderParameters("header");
262
- const body = operation.requestBody ? pickContent(operation.requestBody.content, `${at}/requestBody`, "request") : void 0;
296
+ const body = operation.requestBody ? pickContent(operation.requestBody.content, `${at}/requestBody`, "request", contentSchemaReferences) : void 0;
263
297
  if (body) operationTypes.push(`export type ${prefix}Body = ${body.type};`);
264
298
  const successes = responseSchema(operation, at, true);
265
299
  if (!successes.length) fail("At least one 2xx response is required", `${at}/responses`, id);
@@ -271,7 +305,7 @@ function generateFiles(document) {
271
305
  if (body) chain.push(`body(${body.codec})`);
272
306
  for (const [status, response] of successes) {
273
307
  if (response.links) fail("Response links are unsupported", `${at}/responses/${status}/links`, id);
274
- const value = pickContent(response.content, `${at}/responses/${status}`, "response");
308
+ const value = pickContent(response.content, `${at}/responses/${status}`, "response", contentSchemaReferences);
275
309
  const typeName = `${prefix}Response${status}`;
276
310
  operationTypes.push(`export type ${typeName} = ${value.type};`);
277
311
  chain.push(`returns(${status === "200" ? "" : `${status}, `}${value.codec})`);
@@ -279,7 +313,7 @@ function generateFiles(document) {
279
313
  if (errors.length) {
280
314
  const rendered = errors.map(([status, response]) => {
281
315
  if (response.links) fail("Response links are unsupported", `${at}/responses/${status}/links`, id);
282
- const value = pickContent(response.content, `${at}/responses/${status}`, "response");
316
+ const value = pickContent(response.content, `${at}/responses/${status}`, "response", contentSchemaReferences);
283
317
  const typeName = `${prefix}Error${pascal(status)}`;
284
318
  operationTypes.push(`export type ${typeName} = ${value.type};`);
285
319
  return `${JSON.stringify(status)}: ${value.codec}`;
@@ -292,12 +326,12 @@ function generateFiles(document) {
292
326
  const imports = [...new Set(descriptors.join(" ").match(/\b(get|post|put|patch|del|head|options|json|text|urlEncoded|multipart|arrayBuffer|empty|content)\b/g) ?? [])].sort();
293
327
  const files = {
294
328
  "schemas.ts": `${schemaLines.join("\n\n")}\n`,
295
- "operations.ts": `${names.size ? `import type { ${[...names.keys()].join(", ")} } from "./schemas";\n\n` : ""}${operationTypes.join("\n\n")}\n`,
329
+ "operations.ts": `${operationSchemaReferences.size ? `import type { ${[...operationSchemaReferences].sort().join(", ")} } from "./schemas";\n\n` : ""}${operationTypes.join("\n\n")}\n`,
296
330
  "api.ts": `import { ${[
297
331
  "defineApi",
298
332
  "createClient",
299
333
  ...imports
300
- ].join(", ")} } from "@askrjs/fetch";\nimport type { ClientOptions } from "@askrjs/fetch";\nimport type { ${operationTypes.map((line) => line.match(/^export type (\w+)/)?.[1]).filter(Boolean).join(", ")} } from "./operations";\n\nexport const api = defineApi({\n${descriptors.join("\n")}\n}, ${JSON.stringify({
334
+ ].join(", ")} } from "@askrjs/fetch";\nimport type { ClientOptions } from "@askrjs/fetch";\n${apiSchemaReferences.size ? `import type { ${[...apiSchemaReferences].sort().join(", ")} } from "./schemas";\n` : ""}${apiOperationReferences.size ? `import type { ${[...apiOperationReferences].sort().join(", ")} } from "./operations";\n` : ""}\nexport const api = defineApi({\n${descriptors.join("\n")}\n}, ${JSON.stringify({
301
335
  servers: (document.servers ?? []).map((s) => s.url),
302
336
  securitySchemes: document.components?.securitySchemes ?? {}
303
337
  }, null, 2)});\n\nexport const createApiClient = (options?: ClientOptions) => createClient(api, options);\n`,
@@ -319,6 +353,8 @@ async function existingFiles(directory) {
319
353
  }
320
354
  async function writeGenerated(directory, files, check) {
321
355
  const output = resolve(directory);
356
+ const outputStat = await stat(output).catch(() => null);
357
+ if (outputStat && !outputStat.isDirectory()) throw new GenerationError(`Generated output must be a directory: ${output}`);
322
358
  const entries = await existingFiles(output);
323
359
  if (check) {
324
360
  if (!entries.length) throw new GenerationError(`Generated directory is missing: ${output}`);
@@ -357,6 +393,11 @@ async function writeGenerated(directory, files, check) {
357
393
  }
358
394
  }
359
395
  async function generate(input, output, check = false) {
396
+ if (!/^https?:\/\//.test(input)) {
397
+ const resolvedInput = resolve(input);
398
+ const inputWithinOutput = relative(resolve(output), resolvedInput);
399
+ if (!inputWithinOutput.startsWith("..") && !isAbsolute(inputWithinOutput)) throw new GenerationError("Generated output must not contain its OpenAPI input document");
400
+ }
360
401
  const files = generateFiles(await loadOpenApi(input));
361
402
  await mkdir(dirname(resolve(output)), { recursive: true });
362
403
  await writeGenerated(output, files, check);
@@ -364,33 +405,67 @@ async function generate(input, output, check = false) {
364
405
  //#endregion
365
406
  //#region src/bin/generate.ts
366
407
  async function runGenerateCli(args, io = console) {
408
+ const wantsJson = args.includes("--json");
367
409
  let output;
368
410
  let check = false;
411
+ let json = false;
412
+ let help = false;
369
413
  const inputs = [];
370
414
  for (let i = 0; i < args.length; i++) {
371
415
  const arg = args[i];
372
416
  if (arg === "--check") check = true;
373
- else if (arg === "-o" || arg === "--output") output = args[++i];
374
- else if (arg.startsWith("--output=")) output = arg.slice(9);
417
+ else if (arg === "--json") json = true;
418
+ else if (arg === "--help" || arg === "-h") help = true;
419
+ else if (arg === "-o" || arg === "--output") {
420
+ const value = args[i + 1];
421
+ if (!value || value.startsWith("-")) {
422
+ const message = `${arg} requires a path`;
423
+ io.error(wantsJson ? JSON.stringify({
424
+ status: "error",
425
+ error: message
426
+ }) : message);
427
+ return 1;
428
+ }
429
+ output = value;
430
+ i += 1;
431
+ } else if (arg.startsWith("--output=")) output = arg.slice(9);
375
432
  else if (arg.startsWith("-")) {
376
- io.error(`Unknown option: ${arg}`);
433
+ const message = `Unknown option: ${arg}`;
434
+ io.error(wantsJson ? JSON.stringify({
435
+ status: "error",
436
+ error: message
437
+ }) : message);
377
438
  return 1;
378
439
  } else inputs.push(arg);
379
440
  }
441
+ const usage = "Usage: askr generate <input> -o <output-directory> [--check] [--json]";
442
+ if (help) {
443
+ io.log(usage);
444
+ return 0;
445
+ }
380
446
  if (inputs.length !== 1 || !output) {
381
- io.error("Usage: askr generate <input> -o <output-directory> [--check]");
447
+ io.error(json ? JSON.stringify({
448
+ status: "error",
449
+ error: usage
450
+ }) : usage);
382
451
  return 1;
383
452
  }
384
453
  try {
385
454
  await generate(inputs[0], output, check);
386
- io.log(check ? "Generated client is current." : `Generated client in ${output}.`);
455
+ io.log(json ? JSON.stringify({
456
+ status: "ok",
457
+ action: check ? "checked" : "generated",
458
+ output
459
+ }) : check ? "Generated client is current." : `Generated client in ${output}.`);
387
460
  return 0;
388
461
  } catch (error) {
389
- io.error(error instanceof Error ? error.message : String(error));
462
+ const message = error instanceof Error ? error.message : String(error);
463
+ io.error(json ? JSON.stringify({
464
+ status: "error",
465
+ error: message
466
+ }) : message);
390
467
  return 1;
391
468
  }
392
469
  }
393
470
  //#endregion
394
471
  export { runGenerateCli };
395
-
396
- //# sourceMappingURL=generate.js.map
@@ -16,5 +16,3 @@ function isDirectExecution(moduleUrl, argvPath = process.argv[1]) {
16
16
  }
17
17
  //#endregion
18
18
  export { isDirectExecution as t };
19
-
20
- //# sourceMappingURL=is-direct-execution-Cdlr-ZUl.js.map
@@ -0,0 +1,25 @@
1
+ //#region src/bin/openapi.d.ts
2
+ type CliIo = Pick<Console, "error" | "log">;
3
+ interface ParsedArgs {
4
+ entry: string;
5
+ output: string;
6
+ check: boolean;
7
+ help: boolean;
8
+ json: boolean;
9
+ }
10
+ interface OpenApiDeps {
11
+ cwd(): string;
12
+ importModule(filePath: string): Promise<unknown>;
13
+ readFile(filePath: string): Promise<string>;
14
+ mkdir(directory: string): Promise<void>;
15
+ writeFile(filePath: string, contents: string): Promise<void>;
16
+ rename(from: string, to: string): Promise<void>;
17
+ rm(filePath: string): Promise<void>;
18
+ temporarySuffix(): string;
19
+ }
20
+ declare function parseOpenApiArgs(args: readonly string[]): ParsedArgs;
21
+ declare function serializeOpenApi(document: unknown): string;
22
+ declare function validateOpenApiDocument(document: unknown): asserts document is Record<string, unknown>;
23
+ declare function runOpenApiCli(args?: string[], io?: CliIo, overrides?: Partial<OpenApiDeps>): Promise<number>;
24
+ //#endregion
25
+ export { parseOpenApiArgs, runOpenApiCli, serializeOpenApi, validateOpenApiDocument };
package/dist/openapi.js CHANGED
@@ -15,6 +15,7 @@ Options:
15
15
  --entry <path> TypeScript API module (default: ./src/api.ts)
16
16
  --output <path> YAML artifact (default: ./openapi.yml)
17
17
  --check Fail when the artifact is missing or byte-stale; never write
18
+ --json Print machine-readable command results
18
19
  --help, -h Show help
19
20
  `;
20
21
  const defaultDeps = {
@@ -32,17 +33,19 @@ function parseOpenApiArgs(args) {
32
33
  entry: "./src/api.ts",
33
34
  output: "./openapi.yml",
34
35
  check: false,
35
- help: false
36
+ help: false,
37
+ json: false
36
38
  };
37
39
  for (let index = 0; index < args.length; index += 1) {
38
40
  const value = args[index];
39
41
  if (value === "--entry" || value === "--output") {
40
42
  const next = args[index + 1];
41
- if (!next) throw new Error(`${value} requires a path`);
43
+ if (!next || next.startsWith("-")) throw new Error(`${value} requires a path`);
42
44
  if (value === "--entry") parsed.entry = next;
43
45
  else parsed.output = next;
44
46
  index += 1;
45
47
  } else if (value === "--check") parsed.check = true;
48
+ else if (value === "--json") parsed.json = true;
46
49
  else if (value === "--help" || value === "-h") parsed.help = true;
47
50
  else throw new Error(`Unknown option: ${value}`);
48
51
  }
@@ -62,9 +65,18 @@ function serializeOpenApi(document) {
62
65
  noRefs: true
63
66
  }).replace(/\n+$/, "")}\n`;
64
67
  }
65
- async function generate(entry, deps) {
66
- const document = exporterFrom(await deps.importModule(entry)).toOpenApiDocument();
68
+ function validateOpenApiDocument(document) {
67
69
  if (!document || typeof document !== "object" || Array.isArray(document)) throw new Error("toOpenApiDocument() must return an OpenAPI document object");
70
+ const candidate = document;
71
+ if (typeof candidate.openapi !== "string" || !/^3\.(?:0|1)\.\d+(?:[-+].+)?$/.test(candidate.openapi)) throw new Error("OpenAPI document must declare a supported openapi 3.0.x or 3.1.x version");
72
+ if (!candidate.info || typeof candidate.info !== "object" || Array.isArray(candidate.info)) throw new Error("OpenAPI document must contain an info object");
73
+ const info = candidate.info;
74
+ if (typeof info.title !== "string" || !info.title.trim() || typeof info.version !== "string" || !info.version.trim()) throw new Error("OpenAPI info must contain non-empty title and version strings");
75
+ if (!candidate.paths || typeof candidate.paths !== "object" || Array.isArray(candidate.paths)) throw new Error("OpenAPI document must contain a paths object");
76
+ }
77
+ async function generate(entry, deps) {
78
+ const document = await exporterFrom(await deps.importModule(entry)).toOpenApiDocument();
79
+ validateOpenApiDocument(document);
68
80
  return serializeOpenApi(document);
69
81
  }
70
82
  async function atomicWrite(output, contents, deps) {
@@ -83,6 +95,7 @@ async function runOpenApiCli(args = process.argv.slice(2), io = console, overrid
83
95
  ...defaultDeps,
84
96
  ...overrides
85
97
  };
98
+ const wantsJson = args.includes("--json");
86
99
  try {
87
100
  const parsed = parseOpenApiArgs(args);
88
101
  if (parsed.help) {
@@ -91,6 +104,7 @@ async function runOpenApiCli(args = process.argv.slice(2), io = console, overrid
91
104
  }
92
105
  const entry = path.resolve(deps.cwd(), parsed.entry);
93
106
  const output = path.resolve(deps.cwd(), parsed.output);
107
+ if (entry === output) throw new Error("OpenAPI output must not overwrite its source entry");
94
108
  const expected = await generate(entry, deps);
95
109
  if (parsed.check) {
96
110
  let actual;
@@ -98,23 +112,43 @@ async function runOpenApiCli(args = process.argv.slice(2), io = console, overrid
98
112
  actual = await deps.readFile(output);
99
113
  } catch (error) {
100
114
  if (error.code === "ENOENT") {
101
- io.error(`OpenAPI artifact is missing: ${output}`);
115
+ io.error(parsed.json ? JSON.stringify({
116
+ status: "error",
117
+ reason: "missing",
118
+ output
119
+ }) : `OpenAPI artifact is missing: ${output}`);
102
120
  return 1;
103
121
  }
104
122
  throw error;
105
123
  }
106
124
  if (actual !== expected) {
107
- io.error(`OpenAPI artifact is stale: ${output}`);
125
+ io.error(parsed.json ? JSON.stringify({
126
+ status: "error",
127
+ reason: "stale",
128
+ output
129
+ }) : `OpenAPI artifact is stale: ${output}`);
108
130
  return 1;
109
131
  }
110
- io.log(`OpenAPI artifact is current: ${output}`);
132
+ io.log(parsed.json ? JSON.stringify({
133
+ status: "ok",
134
+ action: "checked",
135
+ output
136
+ }) : `OpenAPI artifact is current: ${output}`);
111
137
  return 0;
112
138
  }
113
139
  await atomicWrite(output, expected, deps);
114
- io.log(`Generated OpenAPI artifact: ${output}`);
140
+ io.log(parsed.json ? JSON.stringify({
141
+ status: "ok",
142
+ action: "generated",
143
+ output
144
+ }) : `Generated OpenAPI artifact: ${output}`);
115
145
  return 0;
116
146
  } catch (error) {
117
- io.error(error instanceof Error ? error.message : String(error));
147
+ const message = error instanceof Error ? error.message : String(error);
148
+ io.error(wantsJson ? JSON.stringify({
149
+ status: "error",
150
+ error: message
151
+ }) : message);
118
152
  return 1;
119
153
  }
120
154
  }
@@ -123,6 +157,4 @@ async function main() {
123
157
  }
124
158
  if (isDirectExecution(import.meta.url)) main();
125
159
  //#endregion
126
- export { parseOpenApiArgs, runOpenApiCli, serializeOpenApi };
127
-
128
- //# sourceMappingURL=openapi.js.map
160
+ export { parseOpenApiArgs, runOpenApiCli, serializeOpenApi, validateOpenApiDocument };
@@ -283,5 +283,3 @@ function planUpdates(options) {
283
283
  }
284
284
  //#endregion
285
285
  export { planUpdates };
286
-
287
- //# sourceMappingURL=planner-VAj7qlxr.js.map
@@ -123,5 +123,3 @@ function rewriteRange(shape, target, forcedBreaking) {
123
123
  }
124
124
  //#endregion
125
125
  export { isBreakingChange as n, rewriteRange as r, analyzeRange as t };
126
-
127
- //# sourceMappingURL=range-YUs9eimn.js.map
@@ -207,5 +207,3 @@ async function fetchPackuments(packageNames, configuration, options = {}) {
207
207
  }
208
208
  //#endregion
209
209
  export { fetchPackuments, loadNpmConfiguration };
210
-
211
- //# sourceMappingURL=registry-CyrRHRG5.js.map
@@ -28,7 +28,8 @@ Use this when the app renders outside the browser or produces static output. The
28
28
  2. Register routes at module load before boot or render.
29
29
  3. Keep render paths deterministic and environment-safe.
30
30
  4. Use route `entries` or the template's static config for parameterized SSG paths.
31
- 5. Build and preview after changes that cross the render boundary.
31
+ 5. Set the canonical `siteUrl` and explicitly exclude non-indexable routes in `sitemap.routes`.
32
+ 6. Build and preview after changes that cross the render boundary.
32
33
 
33
34
  ## Copy This Shape
34
35
 
@@ -61,6 +62,7 @@ npx @askrjs/cli ssg --config ./ssg.config.ts --output ./dist/static
61
62
  - SSR or SSG build output is deterministic for the same inputs.
62
63
  - Hydration has no structural mismatch warnings.
63
64
  - Generated static routes cover expected params.
65
+ - `sitemap.xml` contains every intended canonical route and excludes runtime-only or non-indexable routes.
64
66
  - Browser preview still navigates correctly after hydration.
65
67
 
66
68
  ## Done When