toiljs 0.0.116 → 0.0.117

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 (325) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +16 -7
  3. package/build/backend/.tsbuildinfo +1 -1
  4. package/build/cli/.tsbuildinfo +1 -1
  5. package/build/cli/index.js +2375 -258
  6. package/build/client/.tsbuildinfo +1 -1
  7. package/build/client/dev/devtools.js +1 -1
  8. package/build/client/dev/error-overlay.js +1 -1
  9. package/build/client/routing/mount.js +1 -1
  10. package/build/compiler/.tsbuildinfo +1 -1
  11. package/build/compiler/auth-emails.js +7 -3
  12. package/build/compiler/emails.js +1 -1
  13. package/build/compiler/index.d.ts +1 -0
  14. package/build/compiler/index.js +25 -4
  15. package/build/compiler/pages.js +2 -3
  16. package/build/compiler/prerender.d.ts +3 -8
  17. package/build/compiler/prerender.js +88 -127
  18. package/build/compiler/ssg.js +3 -4
  19. package/build/compiler/template-build.js +3 -4
  20. package/build/compiler/toil-docs.generated.js +6 -5
  21. package/build/compiler/vite.js +3 -6
  22. package/build/devserver/.tsbuildinfo +1 -1
  23. package/build/devserver/db/database.js +7 -3
  24. package/build/devserver/db/types.js +1 -1
  25. package/build/devserver/production.js +4 -3
  26. package/build/devserver/runtime/crypto.d.ts +1 -1
  27. package/build/io/.tsbuildinfo +1 -1
  28. package/build/logger/.tsbuildinfo +1 -1
  29. package/build/shared/.tsbuildinfo +1 -1
  30. package/examples/basic/client/routes/analytics.tsx +7 -5
  31. package/examples/basic/server/tsconfig.json +1 -3
  32. package/package.json +45 -44
  33. package/presets/no-uint8array-tostring.js +120 -153
  34. package/presets/oxlint.d.ts +5 -0
  35. package/presets/oxlint.js +224 -0
  36. package/server/runtime/tsconfig.json +1 -3
  37. package/tsconfig.json +2 -11
  38. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  39. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
  40. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  41. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  42. package/.github/PULL_REQUEST_TEMPLATE.md +0 -43
  43. package/.github/changelog-config.json +0 -45
  44. package/.github/dependabot.yml +0 -27
  45. package/.github/workflows/ci.yml +0 -222
  46. package/.vscode/settings.json +0 -10
  47. package/docs/README.md +0 -107
  48. package/docs/auth/README.md +0 -104
  49. package/docs/auth/configuration.md +0 -105
  50. package/docs/auth/emails.md +0 -92
  51. package/docs/auth/extending.md +0 -202
  52. package/docs/auth/how-it-works.md +0 -138
  53. package/docs/auth/usage.md +0 -188
  54. package/docs/backend/README.md +0 -143
  55. package/docs/backend/data.md +0 -351
  56. package/docs/backend/rest.md +0 -402
  57. package/docs/backend/rpc.md +0 -226
  58. package/docs/background/README.md +0 -114
  59. package/docs/background/daemons.md +0 -276
  60. package/docs/background/derive.md +0 -179
  61. package/docs/cli/README.md +0 -379
  62. package/docs/concepts/ai-guide.md +0 -132
  63. package/docs/concepts/config.md +0 -416
  64. package/docs/concepts/decorators.md +0 -127
  65. package/docs/concepts/security.md +0 -108
  66. package/docs/concepts/tiers.md +0 -166
  67. package/docs/concepts/types.md +0 -216
  68. package/docs/database/README.md +0 -143
  69. package/docs/database/capacity.md +0 -350
  70. package/docs/database/counters.md +0 -174
  71. package/docs/database/documents.md +0 -277
  72. package/docs/database/events.md +0 -307
  73. package/docs/database/membership.md +0 -246
  74. package/docs/database/setup.md +0 -155
  75. package/docs/database/unique.md +0 -216
  76. package/docs/database/views.md +0 -246
  77. package/docs/frontend/README.md +0 -104
  78. package/docs/frontend/components.md +0 -286
  79. package/docs/frontend/data-fetching.md +0 -260
  80. package/docs/frontend/images.md +0 -148
  81. package/docs/frontend/metadata.md +0 -344
  82. package/docs/frontend/navigation.md +0 -296
  83. package/docs/frontend/rendering.md +0 -236
  84. package/docs/frontend/routing.md +0 -346
  85. package/docs/frontend/scripts.md +0 -118
  86. package/docs/frontend/search.md +0 -191
  87. package/docs/frontend/styling.md +0 -147
  88. package/docs/frontend/toil-global.md +0 -216
  89. package/docs/getting-started/README.md +0 -81
  90. package/docs/getting-started/create-project.md +0 -131
  91. package/docs/getting-started/deploy.md +0 -101
  92. package/docs/getting-started/first-app.md +0 -215
  93. package/docs/getting-started/installation.md +0 -124
  94. package/docs/getting-started/migrating.md +0 -125
  95. package/docs/getting-started/project-structure.md +0 -163
  96. package/docs/introduction/README.md +0 -66
  97. package/docs/introduction/design-principles.md +0 -81
  98. package/docs/introduction/distributed.md +0 -91
  99. package/docs/introduction/how-it-works.md +0 -121
  100. package/docs/introduction/hyperscale.md +0 -70
  101. package/docs/introduction/modern-stack.md +0 -85
  102. package/docs/introduction/vs-other-frameworks.md +0 -64
  103. package/docs/introduction/why-toil.md +0 -99
  104. package/docs/llms.txt +0 -95
  105. package/docs/realtime/README.md +0 -102
  106. package/docs/realtime/channels.md +0 -211
  107. package/docs/realtime/streams.md +0 -369
  108. package/docs/services/README.md +0 -60
  109. package/docs/services/analytics.md +0 -279
  110. package/docs/services/caching.md +0 -175
  111. package/docs/services/cookies.md +0 -235
  112. package/docs/services/crypto.md +0 -209
  113. package/docs/services/email.md +0 -289
  114. package/docs/services/environment.md +0 -141
  115. package/docs/services/ratelimit.md +0 -174
  116. package/docs/services/time.md +0 -85
  117. package/eslint.config.js +0 -57
  118. package/examples/basic/client/routes/docs/[...slug].tsx +0 -12
  119. package/presets/eslint.js +0 -88
  120. package/src/backend/index.ts +0 -204
  121. package/src/cli/configure.ts +0 -363
  122. package/src/cli/create.ts +0 -924
  123. package/src/cli/db.ts +0 -158
  124. package/src/cli/diagnostics.ts +0 -665
  125. package/src/cli/doctor.ts +0 -982
  126. package/src/cli/features.ts +0 -170
  127. package/src/cli/index.ts +0 -326
  128. package/src/cli/notify.ts +0 -120
  129. package/src/cli/proc.ts +0 -56
  130. package/src/cli/ui.ts +0 -169
  131. package/src/cli/update.ts +0 -248
  132. package/src/cli/updates.ts +0 -92
  133. package/src/cli/validate.ts +0 -31
  134. package/src/cli/version-check.ts +0 -125
  135. package/src/client/auth.ts +0 -869
  136. package/src/client/channel/channel.ts +0 -146
  137. package/src/client/components/Form.tsx +0 -65
  138. package/src/client/components/Image.tsx +0 -160
  139. package/src/client/components/Script.tsx +0 -113
  140. package/src/client/components/Slot.tsx +0 -21
  141. package/src/client/dev/devtools.tsx +0 -1089
  142. package/src/client/dev/error-overlay.tsx +0 -225
  143. package/src/client/errors.ts +0 -11
  144. package/src/client/head/head.ts +0 -182
  145. package/src/client/head/metadata.ts +0 -110
  146. package/src/client/index.ts +0 -130
  147. package/src/client/navigation/Link.tsx +0 -108
  148. package/src/client/navigation/NavLink.tsx +0 -86
  149. package/src/client/navigation/navigation.ts +0 -235
  150. package/src/client/navigation/prefetch.ts +0 -169
  151. package/src/client/navigation/scroll.ts +0 -53
  152. package/src/client/routing/Router.tsx +0 -216
  153. package/src/client/routing/action.ts +0 -122
  154. package/src/client/routing/error-boundary.tsx +0 -43
  155. package/src/client/routing/hooks.ts +0 -140
  156. package/src/client/routing/lazy.ts +0 -77
  157. package/src/client/routing/loader.ts +0 -381
  158. package/src/client/routing/match.ts +0 -47
  159. package/src/client/routing/mount.tsx +0 -90
  160. package/src/client/routing/params-context.ts +0 -10
  161. package/src/client/routing/slot-context.ts +0 -7
  162. package/src/client/rpc.ts +0 -97
  163. package/src/client/search/search.ts +0 -189
  164. package/src/client/search/use-page-search.ts +0 -73
  165. package/src/client/ssr/markers.tsx +0 -172
  166. package/src/client/stream/client.ts +0 -237
  167. package/src/client/types.ts +0 -73
  168. package/src/compiler/auth-emails.ts +0 -312
  169. package/src/compiler/config.ts +0 -369
  170. package/src/compiler/docs.ts +0 -127
  171. package/src/compiler/email-preview.ts +0 -314
  172. package/src/compiler/emails.ts +0 -404
  173. package/src/compiler/fonts.ts +0 -89
  174. package/src/compiler/generate.ts +0 -471
  175. package/src/compiler/image-blur.ts +0 -42
  176. package/src/compiler/image-report.ts +0 -88
  177. package/src/compiler/index.ts +0 -1362
  178. package/src/compiler/pages.ts +0 -49
  179. package/src/compiler/plugin.ts +0 -391
  180. package/src/compiler/prerender.ts +0 -263
  181. package/src/compiler/routes.ts +0 -170
  182. package/src/compiler/seo.ts +0 -415
  183. package/src/compiler/sri.ts +0 -144
  184. package/src/compiler/ssg.ts +0 -177
  185. package/src/compiler/ssr-codegen.ts +0 -85
  186. package/src/compiler/template-build.ts +0 -676
  187. package/src/compiler/template.ts +0 -262
  188. package/src/compiler/toil-docs.generated.ts +0 -77
  189. package/src/compiler/vite.ts +0 -245
  190. package/src/devserver/analytics/index.ts +0 -317
  191. package/src/devserver/config/dotenv.ts +0 -102
  192. package/src/devserver/config/env.ts +0 -30
  193. package/src/devserver/config/ratelimit.ts +0 -123
  194. package/src/devserver/daemon/catalog.ts +0 -120
  195. package/src/devserver/daemon/cron.ts +0 -87
  196. package/src/devserver/daemon/host.ts +0 -134
  197. package/src/devserver/daemon/index.ts +0 -369
  198. package/src/devserver/daemon/runtime.ts +0 -48
  199. package/src/devserver/db/catalog.ts +0 -109
  200. package/src/devserver/db/database.ts +0 -1834
  201. package/src/devserver/db/derives.ts +0 -121
  202. package/src/devserver/db/index.ts +0 -20
  203. package/src/devserver/db/routeKinds.ts +0 -191
  204. package/src/devserver/db/types.ts +0 -168
  205. package/src/devserver/email/caps.ts +0 -0
  206. package/src/devserver/email/config.ts +0 -141
  207. package/src/devserver/email/console.ts +0 -348
  208. package/src/devserver/email/index.ts +0 -155
  209. package/src/devserver/email/providers.ts +0 -131
  210. package/src/devserver/email/status.ts +0 -23
  211. package/src/devserver/email/validate.ts +0 -37
  212. package/src/devserver/email/wire.ts +0 -55
  213. package/src/devserver/http/cache.ts +0 -0
  214. package/src/devserver/http/envelope.ts +0 -157
  215. package/src/devserver/http/proxy.ts +0 -172
  216. package/src/devserver/http/runtime.ts +0 -387
  217. package/src/devserver/index.ts +0 -43
  218. package/src/devserver/production-ipc.ts +0 -63
  219. package/src/devserver/production-worker.ts +0 -83
  220. package/src/devserver/production.ts +0 -718
  221. package/src/devserver/runtime/crypto.ts +0 -587
  222. package/src/devserver/runtime/host.ts +0 -427
  223. package/src/devserver/runtime/module.ts +0 -442
  224. package/src/devserver/server.ts +0 -251
  225. package/src/devserver/ssr.ts +0 -208
  226. package/src/devserver/stream/catalog.ts +0 -106
  227. package/src/devserver/stream/host.ts +0 -42
  228. package/src/devserver/stream/index.ts +0 -301
  229. package/src/devserver/stream/manager.ts +0 -167
  230. package/src/devserver/stream/router.ts +0 -101
  231. package/src/devserver/stream/wire.ts +0 -58
  232. package/src/devserver/stream/ws.ts +0 -78
  233. package/src/devserver/wasm/sections.ts +0 -59
  234. package/src/devserver/wasm/surface.ts +0 -115
  235. package/src/io/FastMap.ts +0 -151
  236. package/src/io/FastSet.ts +0 -113
  237. package/src/io/codec.ts +0 -362
  238. package/src/io/index.ts +0 -10
  239. package/src/io/lengths.ts +0 -14
  240. package/src/io/types.ts +0 -19
  241. package/src/logger/index.ts +0 -22
  242. package/src/shared/index.ts +0 -46
  243. package/test/analytics-dev.test.ts +0 -113
  244. package/test/assembly/aspect-shim.ts +0 -33
  245. package/test/assembly/cookie.spec.ts +0 -303
  246. package/test/assembly/example.spec.ts +0 -22
  247. package/test/assembly/ssr.spec.ts +0 -95
  248. package/test/assembly.test.ts +0 -59
  249. package/test/auth-emails.test.ts +0 -118
  250. package/test/built-ssr.test.ts +0 -148
  251. package/test/channel.test.ts +0 -21
  252. package/test/config-host-port.test.ts +0 -59
  253. package/test/configure.test.ts +0 -94
  254. package/test/daemon-build.test.ts +0 -278
  255. package/test/daemon-catalog.test.ts +0 -349
  256. package/test/daemon-emulation.test.ts +0 -256
  257. package/test/db.test.ts +0 -0
  258. package/test/devserver-database.test.ts +0 -1102
  259. package/test/devserver-email.test.ts +0 -306
  260. package/test/devserver-pqauth.test.ts +0 -93
  261. package/test/devserver-secrets.test.ts +0 -63
  262. package/test/devserver.test.ts +0 -259
  263. package/test/doctor.test.ts +0 -298
  264. package/test/dom/Image.test.tsx +0 -144
  265. package/test/dom/Link.test.tsx +0 -67
  266. package/test/dom/NavLink.test.tsx +0 -37
  267. package/test/dom/Script.test.tsx +0 -48
  268. package/test/dom/action.test.tsx +0 -146
  269. package/test/dom/error-overlay.test.tsx +0 -44
  270. package/test/dom/loader.test.tsx +0 -121
  271. package/test/dom/navigation.test.ts +0 -59
  272. package/test/dom/revalidate.test.tsx +0 -38
  273. package/test/dom/route-head.test.tsx +0 -56
  274. package/test/dom/router-loading.test.tsx +0 -44
  275. package/test/dom/scroll.test.ts +0 -56
  276. package/test/dom/slot.test.tsx +0 -131
  277. package/test/dom/use-metadata.test.tsx +0 -58
  278. package/test/dom/view-transitions.test.tsx +0 -53
  279. package/test/dynamic-routing.test.ts +0 -132
  280. package/test/email-console.test.ts +0 -86
  281. package/test/email-preview.test.ts +0 -73
  282. package/test/emails.test.ts +0 -58
  283. package/test/errors.test.ts +0 -21
  284. package/test/example-guestbook.test.ts +0 -123
  285. package/test/features.test.ts +0 -149
  286. package/test/fixtures/bignum-wire/spec.ts +0 -25
  287. package/test/fixtures/daemon-app.ts +0 -65
  288. package/test/fixtures/stream-echo.ts +0 -26
  289. package/test/fixtures/stream-gate.ts +0 -28
  290. package/test/fixtures/stream-trap.ts +0 -18
  291. package/test/fixtures/stream-typed.ts +0 -41
  292. package/test/fonts.test.ts +0 -28
  293. package/test/global-setup.ts +0 -17
  294. package/test/head.test.ts +0 -42
  295. package/test/image-blur.test.ts +0 -50
  296. package/test/io.test.ts +0 -117
  297. package/test/metadata.test.ts +0 -40
  298. package/test/navlink.test.ts +0 -28
  299. package/test/pages.test.ts +0 -105
  300. package/test/placeholder.test.ts +0 -9
  301. package/test/pqauth-e2e.test.ts +0 -216
  302. package/test/pqauth-email.test.ts +0 -801
  303. package/test/prerender.test.ts +0 -116
  304. package/test/prettier-plugin.test.ts +0 -46
  305. package/test/routes.test.ts +0 -76
  306. package/test/rpc-bignum-wire.test.ts +0 -164
  307. package/test/rpc-dispatch.test.ts +0 -132
  308. package/test/rpc-kinds.test.ts +0 -18
  309. package/test/rpc.test.ts +0 -66
  310. package/test/search.test.ts +0 -114
  311. package/test/seo.test.ts +0 -175
  312. package/test/slot-layouts.test.ts +0 -69
  313. package/test/sri.test.ts +0 -129
  314. package/test/ssg.test.ts +0 -36
  315. package/test/ssr-hydration.test.tsx +0 -122
  316. package/test/ssr-render.test.ts +0 -197
  317. package/test/ssr-template.test.tsx +0 -482
  318. package/test/stream-emulation.test.ts +0 -479
  319. package/test/ui.test.ts +0 -40
  320. package/test/update.test.ts +0 -58
  321. package/test/validate.test.ts +0 -42
  322. package/test/version-check.test.ts +0 -130
  323. package/tests/data-parity/generated-parity.ts +0 -99
  324. package/tests/data-parity/parity.ts +0 -80
  325. package/tests/data-parity/spec.ts +0 -46
@@ -1,665 +0,0 @@
1
- /**
2
- * Pure diagnostics for `toiljs doctor`. Every check is a pure function that takes already-gathered
3
- * facts (versions, parsed package data, file contents, scanned routes) and returns a {@link Check}.
4
- * Kept IO-free so it can be unit-tested in isolation; the file reads, config load, and rendering
5
- * live in `doctor.ts`. Mirrors the pure/IO split of `validate.ts` and `features.ts`.
6
- */
7
- import { type Preprocessor } from './features.js';
8
-
9
- export type CheckStatus = 'pass' | 'warn' | 'fail';
10
-
11
- /** One diagnostic result: a labelled outcome with an optional detail and a fix hint. */
12
- export interface Check {
13
- readonly id: string;
14
- readonly label: string;
15
- readonly status: CheckStatus;
16
- readonly detail?: string;
17
- readonly fix?: string;
18
- }
19
-
20
- /** A titled group of related checks (Environment, Project, ...). */
21
- export interface CheckGroup {
22
- readonly title: string;
23
- readonly checks: Check[];
24
- }
25
-
26
- /** Tallied counts across all groups. */
27
- export interface DoctorSummary {
28
- readonly pass: number;
29
- readonly warn: number;
30
- readonly fail: number;
31
- }
32
-
33
- /** Parses a version string's leading `x.y.z` into a numeric tuple (missing parts default to 0). */
34
- function parseVersion(v: string): [number, number, number] {
35
- const m = /(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(v);
36
- if (!m) return [0, 0, 0];
37
- return [Number(m[1]), Number(m[2] ?? 0), Number(m[3] ?? 0)];
38
- }
39
-
40
- /**
41
- * Whether `version` meets a simple minimum `range` (`>=x.y.z` or a bare `x.y.z`). toiljs's peer
42
- * ranges are all `>=`, so a full semver resolver is unnecessary; a declared range like `^19.2.6` is
43
- * compared by its floor.
44
- */
45
- export function satisfiesMin(version: string, range: string): boolean {
46
- const [a, b, c] = parseVersion(version);
47
- const [x, y, z] = parseVersion(range);
48
- if (a !== x) return a > x;
49
- if (b !== y) return b > y;
50
- return c >= z;
51
- }
52
-
53
- // --- Environment ----------------------------------------------------------------------------------
54
-
55
- export function checkNode(current: string, requiredRange: string): Check {
56
- const ok = satisfiesMin(current, requiredRange);
57
- return {
58
- id: 'node',
59
- label: 'Node.js',
60
- status: ok ? 'pass' : 'fail',
61
- detail: `${current} (requires ${requiredRange})`,
62
- fix: ok ? undefined : `Upgrade Node to ${requiredRange}.`,
63
- };
64
- }
65
-
66
- export function checkPeer(name: string, installed: string | null, range: string): Check {
67
- if (installed === null) {
68
- return {
69
- id: `peer:${name}`,
70
- label: name,
71
- status: 'fail',
72
- detail: `not installed (requires ${range})`,
73
- fix: `Install ${name}@"${range}".`,
74
- };
75
- }
76
- const ok = satisfiesMin(installed, range);
77
- return {
78
- id: `peer:${name}`,
79
- label: name,
80
- status: ok ? 'pass' : 'warn',
81
- detail: `${installed} (requires ${range})`,
82
- fix: ok ? undefined : `Update ${name} to ${range}.`,
83
- };
84
- }
85
-
86
- /** The TypeScript range toiljs supports, and the range `doctor --fix` pins an unsupported one to. */
87
- export const TYPESCRIPT_SUPPORTED = '>=6.0.0 <7.0.0';
88
- export const TYPESCRIPT_FIX_RANGE = '^6.0.3';
89
-
90
- /** First TypeScript major that dropped the JavaScript compiler API (the native port). */
91
- const TYPESCRIPT_NATIVE_MAJOR = 7;
92
-
93
- /**
94
- * The major a dependency range floats to: the first number in the range, once the comparators are
95
- * stripped (`^7.0.2` -> 7, `>=6.0.0 <7.0.0` -> 6). Returns null for a range that names no version
96
- * (`*`, `latest`), which is treated as "unpinned" rather than "unsupported".
97
- */
98
- export function rangeMajor(range: string): number | null {
99
- const m = /(\d+)/.exec(range.replace(/^[\s^~>=<v]*/, ''));
100
- return m ? Number(m[1]) : null;
101
- }
102
-
103
- /**
104
- * TypeScript 7 is the native (Go) port: it publishes `tsc` but no longer exports the JavaScript
105
- * compiler API, whose main entry is now just `{ version, versionMajorMinor }`. toiljs reads each
106
- * route's static `metadata` through that API to bake SEO tags into the built HTML, and the eslint
107
- * and typedoc presets load it too. So a TS 7 install does not fail loudly, it silently stops baking
108
- * metadata (and hard-crashes typescript-eslint), which is worth a `fail` rather than a warning.
109
- *
110
- * `installed` is the version resolved from node_modules (authoritative, it is what actually runs);
111
- * `declared` is the project's package.json range, which is flagged when it *admits* 7 even if the
112
- * currently-installed copy is older, since the next install would break.
113
- */
114
- export function checkTypeScript(installed: string | null, declared: string | null): Check {
115
- const id = 'peer:typescript';
116
- const label = 'typescript';
117
- const unsupported = (version: string, detail: string): Check => ({
118
- id,
119
- label,
120
- status: 'fail',
121
- detail,
122
- fix:
123
- `TypeScript ${version} removed the JavaScript compiler API (it moved to ` +
124
- `'typescript/unstable/*'), which toiljs's route-metadata extractor, the eslint preset, ` +
125
- `and typedoc all load. Pin typescript to "${TYPESCRIPT_FIX_RANGE}" and reinstall ` +
126
- `(\`toiljs doctor --fix\` does this).`,
127
- });
128
-
129
- if (installed === null) {
130
- return {
131
- id,
132
- label,
133
- status: 'fail',
134
- detail: `not installed (requires ${TYPESCRIPT_SUPPORTED})`,
135
- fix: `Install typescript@"${TYPESCRIPT_FIX_RANGE}".`,
136
- };
137
- }
138
-
139
- const installedMajor = rangeMajor(installed);
140
- if (installedMajor !== null && installedMajor >= TYPESCRIPT_NATIVE_MAJOR) {
141
- return unsupported(String(installedMajor), `${installed} installed, unsupported`);
142
- }
143
-
144
- // Installed copy is fine, but the declared range would pull an unsupported major next install.
145
- const declaredMajor = declared === null ? null : rangeMajor(declared);
146
- if (declaredMajor !== null && declaredMajor >= TYPESCRIPT_NATIVE_MAJOR) {
147
- return unsupported(String(declaredMajor), `package.json declares ${declared ?? ''}`);
148
- }
149
-
150
- const ok = satisfiesMin(installed, TYPESCRIPT_SUPPORTED);
151
- return {
152
- id,
153
- label,
154
- status: ok ? 'pass' : 'warn',
155
- detail: `${installed} (requires ${TYPESCRIPT_SUPPORTED})`,
156
- fix: ok ? undefined : `Update typescript to ${TYPESCRIPT_SUPPORTED}.`,
157
- };
158
- }
159
-
160
- export function checkPackageManager(lockfiles: readonly string[]): Check {
161
- if (lockfiles.length === 0) {
162
- return {
163
- id: 'pm',
164
- label: 'Package manager',
165
- status: 'warn',
166
- detail: 'no lockfile found',
167
- fix: 'Run an install (npm/pnpm/yarn/bun) to create a lockfile.',
168
- };
169
- }
170
- return { id: 'pm', label: 'Package manager', status: 'pass', detail: lockfiles.join(', ') };
171
- }
172
-
173
- /**
174
- * Flags `npx toiljs ...` inside package.json scripts. Under `npm run`, `node_modules/.bin` is
175
- * already on PATH, so the `npx` is redundant; worse, the extra `npx` process puts the console in
176
- * raw / VT-input mode and does not restore it when the run is Ctrl+C'd, leaving the terminal
177
- * echoing arrow keys as `^[[A` and mis-reading typed input (Windows cmd especially). The scaffold
178
- * uses a bare `toiljs <cmd>`; this catches projects whose scripts wrap it in `npx`.
179
- */
180
- export function checkDevScripts(scripts: Record<string, string>): Check {
181
- const NPX_TOILJS = /(?:^|[\s&|;(])npx\s+toiljs\b/;
182
- const offenders = Object.keys(scripts).filter((name) => NPX_TOILJS.test(scripts[name] ?? ''));
183
- if (offenders.length === 0) {
184
- return { id: 'scripts-npx', label: 'Scripts', status: 'pass' };
185
- }
186
- return {
187
- id: 'scripts-npx',
188
- label: 'Scripts',
189
- status: 'warn',
190
- detail: `${offenders.join(', ')} run via "npx toiljs"`,
191
- fix: 'Drop npx: use "toiljs <cmd>" (npm run already puts node_modules/.bin on PATH). The npx layer can leave the terminal in raw mode after Ctrl+C, which garbles the shell.',
192
- };
193
- }
194
-
195
- export function checkToiljsInstalled(version: string | null): Check {
196
- return version
197
- ? { id: 'toiljs', label: 'toiljs', status: 'pass', detail: version }
198
- : {
199
- id: 'toiljs',
200
- label: 'toiljs',
201
- status: 'fail',
202
- detail: 'not a dependency of this project',
203
- fix: 'Add toiljs to dependencies, or run from the project root with --root.',
204
- };
205
- }
206
-
207
- // --- Project + routing ----------------------------------------------------------------------------
208
-
209
- export function checkDir(id: string, label: string, exists: boolean, fix: string): Check {
210
- return exists
211
- ? { id, label, status: 'pass' }
212
- : { id, label, status: 'fail', detail: 'missing', fix };
213
- }
214
-
215
- /**
216
- * Whether the app entry calls `mount(...)` with a `slots` argument. Without it, parallel and
217
- * intercepting routes are silently dropped (a real bug we shipped a fix for). Heuristic, regex-based.
218
- */
219
- export function checkMountSlots(entrySource: string | null): Check {
220
- const label = 'App entry mount()';
221
- if (entrySource === null) {
222
- return {
223
- id: 'mount',
224
- label,
225
- status: 'warn',
226
- detail: 'entry file not found',
227
- fix: 'Ensure client/toil.tsx calls Toil.mount(...).',
228
- };
229
- }
230
- const call = /\bmount\s*\(([^)]*)\)/.exec(entrySource);
231
- if (!call) {
232
- return {
233
- id: 'mount',
234
- label,
235
- status: 'warn',
236
- detail: 'no mount() call found',
237
- fix: 'Call Toil.mount(routes, layout, notFound, globalError, slots).',
238
- };
239
- }
240
- const hasSlots = /\bslots\b/.test(call[1]);
241
- return hasSlots
242
- ? { id: 'mount', label, status: 'pass', detail: 'passes slots' }
243
- : {
244
- id: 'mount',
245
- label,
246
- status: 'warn',
247
- detail: 'mount() is missing the slots argument',
248
- fix: 'Pass slots last: mount(routes, layout, notFound, globalError, slots). Without it, parallel and intercepting routes are ignored.',
249
- };
250
- }
251
-
252
- export function checkRootElement(indexHtml: string | null): Check {
253
- const label = 'index.html mount target';
254
- if (indexHtml === null) {
255
- return {
256
- id: 'root-el',
257
- label,
258
- status: 'fail',
259
- detail: 'index.html not found',
260
- fix: 'Add public/index.html with <div id="root"></div>.',
261
- };
262
- }
263
- const ok = /id\s*=\s*["']root["']/.test(indexHtml);
264
- return ok
265
- ? { id: 'root-el', label, status: 'pass' }
266
- : {
267
- id: 'root-el',
268
- label,
269
- status: 'fail',
270
- detail: 'no element with id="root"',
271
- fix: 'Add <div id="root"></div> to public/index.html.',
272
- };
273
- }
274
-
275
- export function checkRoutesPresent(routeCount: number): Check {
276
- return routeCount > 0
277
- ? {
278
- id: 'routes',
279
- label: 'Routes',
280
- status: 'pass',
281
- detail: `${routeCount} route${routeCount === 1 ? '' : 's'}`,
282
- }
283
- : {
284
- id: 'routes',
285
- label: 'Routes',
286
- status: 'fail',
287
- detail: 'no routes found',
288
- fix: 'Add a page, e.g. client/routes/index.tsx.',
289
- };
290
- }
291
-
292
- export function checkDuplicatePatterns(patterns: readonly string[]): Check {
293
- const seen = new Set<string>();
294
- const dupes = new Set<string>();
295
- for (const p of patterns) {
296
- if (seen.has(p)) dupes.add(p);
297
- else seen.add(p);
298
- }
299
- return dupes.size === 0
300
- ? { id: 'route-dupes', label: 'Unique route patterns', status: 'pass' }
301
- : {
302
- id: 'route-dupes',
303
- label: 'Unique route patterns',
304
- status: 'warn',
305
- detail: `duplicate: ${[...dupes].join(', ')}`,
306
- fix: 'Two route files map to the same URL; rename or remove one.',
307
- };
308
- }
309
-
310
- /** A source file scanned for broken asset references. */
311
- export interface SourceFile {
312
- readonly path: string;
313
- readonly source: string;
314
- }
315
-
316
- /** A relative asset reference that will 404 on a nested route. */
317
- export interface AssetIssue {
318
- readonly file: string;
319
- readonly line: number;
320
- readonly value: string;
321
- }
322
-
323
- /** Whether a `src`/`href` string value is a root-relative asset path that breaks on nested routes. */
324
- function isBrokenRelativeAsset(value: string): boolean {
325
- if (value === '') return false;
326
- if (value.startsWith('/')) return false; // root-absolute, resolves the same everywhere
327
- if (/^[a-z][a-z0-9+.-]*:/i.test(value)) return false; // http:, https:, data:, mailto:, ...
328
- if (value.startsWith('#') || value.startsWith('?')) return false;
329
- // Only flag asset-looking values (a real file extension), to avoid false positives on app routes.
330
- return /\.(svgz?|png|jpe?g|gif|webp|avif|ico|css|m?js|woff2?|ttf|otf|eot|mp4|webm|json)$/i.test(
331
- value,
332
- );
333
- }
334
-
335
- /** Finds string-literal `src=`/`href=` attributes pointing at broken relative asset paths. */
336
- export function findRelativeAssets(files: readonly SourceFile[]): AssetIssue[] {
337
- const issues: AssetIssue[] = [];
338
- const attr = /\b(?:src|href)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
339
- for (const file of files) {
340
- const lines = file.source.split('\n');
341
- for (let i = 0; i < lines.length; i++) {
342
- attr.lastIndex = 0;
343
- let m: RegExpExecArray | null;
344
- while ((m = attr.exec(lines[i])) !== null) {
345
- const value = m[1] ?? m[2] ?? '';
346
- if (isBrokenRelativeAsset(value)) {
347
- issues.push({ file: file.path, line: i + 1, value });
348
- }
349
- }
350
- }
351
- }
352
- return issues;
353
- }
354
-
355
- export function checkRelativeAssets(issues: readonly AssetIssue[]): Check {
356
- if (issues.length === 0) return { id: 'rel-assets', label: 'Asset paths', status: 'pass' };
357
- const shown = issues
358
- .slice(0, 5)
359
- .map((i) => `${i.file}:${String(i.line)} "${i.value}"`)
360
- .join('; ');
361
- const more = issues.length > 5 ? `, and ${String(issues.length - 5)} more` : '';
362
- return {
363
- id: 'rel-assets',
364
- label: 'Asset paths',
365
- status: 'warn',
366
- detail: `${String(issues.length)} relative reference(s): ${shown}${more}`,
367
- fix: 'Use a root-absolute path (e.g. "/images/logo.svg") or import the asset; relative paths 404 on nested routes.',
368
- };
369
- }
370
-
371
- // --- Config + assets ------------------------------------------------------------------------------
372
-
373
- export function checkConfigLoads(loaded: boolean, error?: string): Check {
374
- return loaded
375
- ? { id: 'config', label: 'toil.config loads', status: 'pass' }
376
- : {
377
- id: 'config',
378
- label: 'toil.config loads',
379
- status: 'fail',
380
- detail: error ?? 'failed to load',
381
- fix: 'Fix the error in your toil.config.* so dev/build can read it.',
382
- };
383
- }
384
-
385
- export function checkBasePath(base: string): Check {
386
- const ok = base === '/' || (base.startsWith('/') && base.endsWith('/'));
387
- return ok
388
- ? { id: 'base', label: 'Base path', status: 'pass', detail: base }
389
- : {
390
- id: 'base',
391
- label: 'Base path',
392
- status: 'warn',
393
- detail: `"${base}"`,
394
- fix: 'A non-root base should start and end with "/" (e.g. "/app/").',
395
- };
396
- }
397
-
398
- export function checkSeoUrl(seoConfigured: boolean, hasUrl: boolean): Check {
399
- if (!seoConfigured) {
400
- return {
401
- id: 'seo-url',
402
- label: 'SEO site url',
403
- status: 'pass',
404
- detail: 'SEO not configured',
405
- };
406
- }
407
- return hasUrl
408
- ? { id: 'seo-url', label: 'SEO site url', status: 'pass' }
409
- : {
410
- id: 'seo-url',
411
- label: 'SEO site url',
412
- status: 'warn',
413
- detail: 'seo is set without a url',
414
- fix: 'Set client.seo.url so sitemap.xml and canonical links are absolute.',
415
- };
416
- }
417
-
418
- /** Facts about the project's styling setup, derived by the orchestrator. */
419
- export interface StylingFacts {
420
- readonly preprocessorImported: Preprocessor | null;
421
- readonly preprocessorInstalled: boolean;
422
- readonly tailwindImported: boolean;
423
- readonly tailwindInstalled: boolean;
424
- }
425
-
426
- export function checkStyling(f: StylingFacts): Check {
427
- const label = 'Styling';
428
- if (f.preprocessorImported && f.preprocessorImported !== 'css' && !f.preprocessorInstalled) {
429
- return {
430
- id: 'styling',
431
- label,
432
- status: 'fail',
433
- detail: `${f.preprocessorImported} stylesheet imported but ${f.preprocessorImported} is not installed`,
434
- fix: `Install ${f.preprocessorImported}, or run toiljs configure.`,
435
- };
436
- }
437
- if (f.tailwindImported && !f.tailwindInstalled) {
438
- return {
439
- id: 'styling',
440
- label,
441
- status: 'fail',
442
- detail: 'Tailwind entry imported but @tailwindcss/vite is not installed',
443
- fix: 'Run toiljs configure --tailwind, or install @tailwindcss/vite.',
444
- };
445
- }
446
- return { id: 'styling', label, status: 'pass' };
447
- }
448
-
449
- // --- Server / WASM --------------------------------------------------------------------------------
450
-
451
- export function checkToilconfig(present: boolean): Check {
452
- return present
453
- ? { id: 'toilconfig', label: 'Server target (toilconfig.json)', status: 'pass' }
454
- : {
455
- id: 'toilconfig',
456
- label: 'Server target (toilconfig.json)',
457
- status: 'warn',
458
- detail: 'no toilconfig.json (no WebAssembly server)',
459
- fix: 'Add toilconfig.json + a server/ entry if you want a WASM backend.',
460
- };
461
- }
462
-
463
- export function checkServerEntry(missing: readonly string[]): Check {
464
- return missing.length === 0
465
- ? { id: 'server-entry', label: 'Server entries', status: 'pass' }
466
- : {
467
- id: 'server-entry',
468
- label: 'Server entries',
469
- status: 'fail',
470
- detail: `missing: ${missing.join(', ')}`,
471
- fix: 'Create the entry file(s) listed in toilconfig.json "entries", or update them.',
472
- };
473
- }
474
-
475
- export function checkToilscriptInstalled(installed: boolean): Check {
476
- return installed
477
- ? { id: 'toilscript', label: 'toilscript compiler', status: 'pass' }
478
- : {
479
- id: 'toilscript',
480
- label: 'toilscript compiler',
481
- status: 'warn',
482
- detail: 'toilconfig.json present but toilscript is not installed',
483
- fix: 'Install toilscript to compile the server to WebAssembly.',
484
- };
485
- }
486
-
487
- export function checkWasmBuilt(exists: boolean): Check {
488
- return exists
489
- ? { id: 'wasm', label: 'Server build', status: 'pass' }
490
- : {
491
- id: 'wasm',
492
- label: 'Server build',
493
- status: 'warn',
494
- detail: 'no compiled .wasm found',
495
- fix: 'Run your server build (toilscript) before toiljs start.',
496
- };
497
- }
498
-
499
- // --- Typed RPC (@data / @remote / @service) -------------------------------------------------------
500
-
501
- /** Minimum toilscript: @rest/@route + RPC codegen + hardened decoders + @data editor decls (TS2395 fix) + RateLimit-enum @ratelimit typing. */
502
- export const RPC_TOILSCRIPT_MIN = '0.1.27';
503
-
504
- /** Whether each piece of the typed-RPC wiring is in place (computed in `doctor.ts`). */
505
- export interface RpcFacts {
506
- /** `build:server` runs toilscript with `--rpcModule`. */
507
- readonly buildServerWired: boolean;
508
- /** tsconfig includes `shared` and has the `shared/*` path alias. */
509
- readonly tsconfigWired: boolean;
510
- /** `.gitignore` ignores the generated `shared/server.ts`. */
511
- readonly gitignoreWired: boolean;
512
- /** The declared toilscript range is at least {@link RPC_TOILSCRIPT_MIN}. */
513
- readonly toilscriptOk: boolean;
514
- }
515
-
516
- /**
517
- * One check for the typed-RPC setup (`@data`/`@remote` -> generated `Server`). Warns (does not fail)
518
- * when an existing project predates the feature, and points at the one-command upgrade.
519
- */
520
- export function checkRpcWiring(f: RpcFacts): Check {
521
- const missing: string[] = [];
522
- if (!f.toilscriptOk) missing.push(`toilscript >=${RPC_TOILSCRIPT_MIN}`);
523
- if (!f.buildServerWired) missing.push('build:server --rpcModule');
524
- if (!f.tsconfigWired) missing.push('tsconfig shared/ + alias');
525
- if (!f.gitignoreWired) missing.push('.gitignore shared/server.ts');
526
- if (missing.length === 0) {
527
- return { id: 'rpc-wiring', label: 'typed RPC wiring', status: 'pass' };
528
- }
529
- return {
530
- id: 'rpc-wiring',
531
- label: 'typed RPC wiring',
532
- status: 'warn',
533
- detail: `missing: ${missing.join(', ')}`,
534
- fix: 'Run `toiljs doctor --fix` to wire @data/@remote RPC (build:server, tsconfig, .gitignore, toilscript).',
535
- };
536
- }
537
-
538
- /**
539
- * Whether the project's prettier setup pulls in the toilscript plugin (`toiljs/prettier-plugin`,
540
- * or the `toiljs/prettier` shareable that bundles it). Without it, prettier throws on the server's
541
- * native function decorators (`@main`, `@remote function ...`).
542
- */
543
- export function checkPrettierPlugin(present: boolean): Check {
544
- return present
545
- ? { id: 'prettier-plugin', label: 'prettier toilscript plugin', status: 'pass' }
546
- : {
547
- id: 'prettier-plugin',
548
- label: 'prettier toilscript plugin',
549
- status: 'warn',
550
- detail: 'prettier will fail on @main / @remote-on-function in server code',
551
- fix: 'Run `toiljs doctor --fix` to add toiljs/prettier-plugin to your prettier config.',
552
- };
553
- }
554
-
555
- export interface RestFacts {
556
- /** The server declares at least one `@rest` controller. */
557
- readonly hasControllers: boolean;
558
- /** Some server file dispatches them: a `Rest.dispatch(` call, or a `RestHandler`. */
559
- readonly dispatched: boolean;
560
- }
561
-
562
- /**
563
- * Guards the easy-to-miss wiring step for the HTTP layer: a `@rest` controller self-registers,
564
- * but its routes are only served if a handler calls `Rest.dispatch(req)` (or the project uses
565
- * `RestHandler`). Without that, the routes silently 404 - a confusing footgun, so we warn.
566
- */
567
- export function checkRestDispatch(f: RestFacts): Check {
568
- if (!f.hasControllers || f.dispatched) {
569
- return { id: 'rest-dispatch', label: 'REST dispatch wiring', status: 'pass' };
570
- }
571
- return {
572
- id: 'rest-dispatch',
573
- label: 'REST dispatch wiring',
574
- status: 'warn',
575
- detail: '@rest controllers found, but nothing calls Rest.dispatch(req) - their routes will not be served',
576
- fix: 'In your handler add `const hit = Rest.dispatch(req); if (hit != null) return hit;`, or set `Server.handler = () => new RestHandler()`.',
577
- };
578
- }
579
-
580
- /**
581
- * Whether the server's tsconfig wires the toilscript language-service plugin. The compiler turns
582
- * each `@collection` field into a STATIC handle (`GuestbookDb.totals`) and injects the `@data`
583
- * codec / `@user` members, none of which stock TypeScript can see, so without the plugin the editor
584
- * false-flags them as TS2339. The plugin (editor-only; never runs under `tsc`) clears them.
585
- */
586
- export function checkServerTsPlugin(present: boolean): Check {
587
- return present
588
- ? { id: 'server-ts-plugin', label: 'toilscript editor plugin', status: 'pass' }
589
- : {
590
- id: 'server-ts-plugin',
591
- label: 'toilscript editor plugin',
592
- status: 'warn',
593
- detail: 'server tsconfig is missing the toilscript LS plugin, so the editor wrongly flags @database static collections (e.g. GuestbookDb.totals) and @data members as TS2339',
594
- fix: 'Run `toiljs doctor --fix` to add { "plugins": [{ "name": "toilscript/std/ts-plugin.cjs" }] } to your server tsconfig, then pick the workspace TypeScript version and restart the TS server.',
595
- };
596
- }
597
-
598
- /**
599
- * Whether the server has a `migrations/` folder. ToilDB `@migrate` functions MUST live in a
600
- * `*.migration.ts` file under `migrations/` (the toilscript compiler enforces folder + extension as
601
- * a compile error), and the build auto-discovers them, so a server project should keep the folder
602
- * ready. Older projects predate the convention, so this WARNS (does not fail) and points at the
603
- * one-command fix (`toiljs update` creates it).
604
- */
605
- export function checkMigrationsDir(exists: boolean): Check {
606
- return exists
607
- ? { id: 'migrations-dir', label: 'server/migrations/ directory', status: 'pass' }
608
- : {
609
- id: 'migrations-dir',
610
- label: 'server/migrations/ directory',
611
- status: 'warn',
612
- detail: 'no server/migrations/ folder; ToilDB @migrate functions must live in a *.migration.ts file under it',
613
- fix: 'Create server/migrations/ (one <Type>.migration.ts per evolving @data value type), or run `toiljs update` to add it.',
614
- };
615
- }
616
-
617
- // --- Security -------------------------------------------------------------------------------------
618
-
619
- /** Whether the project uses the auth primitive, and whether its session secret is configured. */
620
- export interface AuthFacts {
621
- /** A server source references the auth primitive (`AuthService` / `@user` / `@auth`). */
622
- readonly usesAuth: boolean;
623
- /** `AUTH_SESSION_SECRET` is assigned a non-empty value in the local secrets source. */
624
- readonly sessionSecretSet: boolean;
625
- }
626
-
627
- /**
628
- * Flags the silent insecure default behind the auth primitive. When a project uses sessions but
629
- * never sets `AUTH_SESSION_SECRET`, the server falls back to a PUBLISHED dev key (see
630
- * `server/globals/auth.ts`), so anyone can forge a session cookie and skip login. doctor can only
631
- * see the local secrets source, so it WARNS (the real secret may live on the deploy target) rather
632
- * than failing CI on a false positive.
633
- */
634
- export function checkAuthSecrets(f: AuthFacts): Check {
635
- if (!f.usesAuth || f.sessionSecretSet) {
636
- return { id: 'auth-secrets', label: 'Session secret', status: 'pass' };
637
- }
638
- return {
639
- id: 'auth-secrets',
640
- label: 'Session secret',
641
- status: 'warn',
642
- detail: 'auth is used but AUTH_SESSION_SECRET is unset: sessions fall back to a PUBLISHED key, so anyone can forge a session cookie and skip login',
643
- fix: 'Set AUTH_SESSION_SECRET to a long random value in .env.secrets (local) and on your deploy target (also AUTH_OPRF_SEED / AUTH_KEM_SK if you use password login). Generate one: node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))".',
644
- };
645
- }
646
-
647
- // --- Summary --------------------------------------------------------------------------------------
648
-
649
- export function summarize(groups: readonly CheckGroup[]): DoctorSummary {
650
- let pass = 0;
651
- let warn = 0;
652
- let fail = 0;
653
- for (const group of groups) {
654
- for (const check of group.checks) {
655
- if (check.status === 'pass') pass++;
656
- else if (check.status === 'warn') warn++;
657
- else fail++;
658
- }
659
- }
660
- return { pass, warn, fail };
661
- }
662
-
663
- export function hasFailures(summary: DoctorSummary): boolean {
664
- return summary.fail > 0;
665
- }