toiljs 0.0.115 → 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 (326) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +16 -7
  3. package/TYPESCRIPT_LAW.md +1946 -2073
  4. package/build/backend/.tsbuildinfo +1 -1
  5. package/build/cli/.tsbuildinfo +1 -1
  6. package/build/cli/index.js +2375 -258
  7. package/build/client/.tsbuildinfo +1 -1
  8. package/build/client/dev/devtools.js +1 -1
  9. package/build/client/dev/error-overlay.js +1 -1
  10. package/build/client/routing/mount.js +1 -1
  11. package/build/compiler/.tsbuildinfo +1 -1
  12. package/build/compiler/auth-emails.js +7 -3
  13. package/build/compiler/emails.js +1 -1
  14. package/build/compiler/index.d.ts +1 -0
  15. package/build/compiler/index.js +25 -4
  16. package/build/compiler/pages.js +2 -3
  17. package/build/compiler/prerender.d.ts +3 -8
  18. package/build/compiler/prerender.js +88 -127
  19. package/build/compiler/ssg.js +3 -4
  20. package/build/compiler/template-build.js +3 -4
  21. package/build/compiler/toil-docs.generated.js +6 -5
  22. package/build/compiler/vite.js +3 -6
  23. package/build/devserver/.tsbuildinfo +1 -1
  24. package/build/devserver/db/database.js +7 -3
  25. package/build/devserver/db/types.js +1 -1
  26. package/build/devserver/production.js +4 -3
  27. package/build/devserver/runtime/crypto.d.ts +1 -1
  28. package/build/io/.tsbuildinfo +1 -1
  29. package/build/logger/.tsbuildinfo +1 -1
  30. package/build/shared/.tsbuildinfo +1 -1
  31. package/examples/basic/client/routes/analytics.tsx +7 -5
  32. package/examples/basic/server/tsconfig.json +1 -3
  33. package/package.json +45 -44
  34. package/presets/no-uint8array-tostring.js +120 -153
  35. package/presets/oxlint.d.ts +5 -0
  36. package/presets/oxlint.js +224 -0
  37. package/server/runtime/tsconfig.json +1 -3
  38. package/tsconfig.json +2 -11
  39. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  40. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
  41. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  42. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  43. package/.github/PULL_REQUEST_TEMPLATE.md +0 -43
  44. package/.github/changelog-config.json +0 -45
  45. package/.github/dependabot.yml +0 -27
  46. package/.github/workflows/ci.yml +0 -222
  47. package/.vscode/settings.json +0 -10
  48. package/docs/README.md +0 -107
  49. package/docs/auth/README.md +0 -104
  50. package/docs/auth/configuration.md +0 -105
  51. package/docs/auth/emails.md +0 -92
  52. package/docs/auth/extending.md +0 -202
  53. package/docs/auth/how-it-works.md +0 -138
  54. package/docs/auth/usage.md +0 -188
  55. package/docs/backend/README.md +0 -143
  56. package/docs/backend/data.md +0 -351
  57. package/docs/backend/rest.md +0 -402
  58. package/docs/backend/rpc.md +0 -226
  59. package/docs/background/README.md +0 -114
  60. package/docs/background/daemons.md +0 -276
  61. package/docs/background/derive.md +0 -179
  62. package/docs/cli/README.md +0 -379
  63. package/docs/concepts/ai-guide.md +0 -132
  64. package/docs/concepts/config.md +0 -416
  65. package/docs/concepts/decorators.md +0 -127
  66. package/docs/concepts/security.md +0 -108
  67. package/docs/concepts/tiers.md +0 -166
  68. package/docs/concepts/types.md +0 -216
  69. package/docs/database/README.md +0 -143
  70. package/docs/database/capacity.md +0 -350
  71. package/docs/database/counters.md +0 -174
  72. package/docs/database/documents.md +0 -277
  73. package/docs/database/events.md +0 -307
  74. package/docs/database/membership.md +0 -246
  75. package/docs/database/setup.md +0 -155
  76. package/docs/database/unique.md +0 -216
  77. package/docs/database/views.md +0 -246
  78. package/docs/frontend/README.md +0 -104
  79. package/docs/frontend/components.md +0 -286
  80. package/docs/frontend/data-fetching.md +0 -260
  81. package/docs/frontend/images.md +0 -148
  82. package/docs/frontend/metadata.md +0 -344
  83. package/docs/frontend/navigation.md +0 -296
  84. package/docs/frontend/rendering.md +0 -236
  85. package/docs/frontend/routing.md +0 -346
  86. package/docs/frontend/scripts.md +0 -118
  87. package/docs/frontend/search.md +0 -191
  88. package/docs/frontend/styling.md +0 -147
  89. package/docs/frontend/toil-global.md +0 -216
  90. package/docs/getting-started/README.md +0 -81
  91. package/docs/getting-started/create-project.md +0 -131
  92. package/docs/getting-started/deploy.md +0 -101
  93. package/docs/getting-started/first-app.md +0 -215
  94. package/docs/getting-started/installation.md +0 -124
  95. package/docs/getting-started/migrating.md +0 -125
  96. package/docs/getting-started/project-structure.md +0 -163
  97. package/docs/introduction/README.md +0 -66
  98. package/docs/introduction/design-principles.md +0 -81
  99. package/docs/introduction/distributed.md +0 -91
  100. package/docs/introduction/how-it-works.md +0 -121
  101. package/docs/introduction/hyperscale.md +0 -70
  102. package/docs/introduction/modern-stack.md +0 -85
  103. package/docs/introduction/vs-other-frameworks.md +0 -64
  104. package/docs/introduction/why-toil.md +0 -99
  105. package/docs/llms.txt +0 -95
  106. package/docs/realtime/README.md +0 -102
  107. package/docs/realtime/channels.md +0 -211
  108. package/docs/realtime/streams.md +0 -369
  109. package/docs/services/README.md +0 -60
  110. package/docs/services/analytics.md +0 -279
  111. package/docs/services/caching.md +0 -175
  112. package/docs/services/cookies.md +0 -235
  113. package/docs/services/crypto.md +0 -209
  114. package/docs/services/email.md +0 -289
  115. package/docs/services/environment.md +0 -141
  116. package/docs/services/ratelimit.md +0 -174
  117. package/docs/services/time.md +0 -85
  118. package/eslint.config.js +0 -57
  119. package/examples/basic/client/routes/docs/[...slug].tsx +0 -12
  120. package/presets/eslint.js +0 -88
  121. package/src/backend/index.ts +0 -204
  122. package/src/cli/configure.ts +0 -363
  123. package/src/cli/create.ts +0 -924
  124. package/src/cli/db.ts +0 -158
  125. package/src/cli/diagnostics.ts +0 -665
  126. package/src/cli/doctor.ts +0 -982
  127. package/src/cli/features.ts +0 -170
  128. package/src/cli/index.ts +0 -326
  129. package/src/cli/notify.ts +0 -120
  130. package/src/cli/proc.ts +0 -56
  131. package/src/cli/ui.ts +0 -169
  132. package/src/cli/update.ts +0 -248
  133. package/src/cli/updates.ts +0 -92
  134. package/src/cli/validate.ts +0 -31
  135. package/src/cli/version-check.ts +0 -125
  136. package/src/client/auth.ts +0 -869
  137. package/src/client/channel/channel.ts +0 -146
  138. package/src/client/components/Form.tsx +0 -65
  139. package/src/client/components/Image.tsx +0 -160
  140. package/src/client/components/Script.tsx +0 -113
  141. package/src/client/components/Slot.tsx +0 -21
  142. package/src/client/dev/devtools.tsx +0 -1089
  143. package/src/client/dev/error-overlay.tsx +0 -225
  144. package/src/client/errors.ts +0 -11
  145. package/src/client/head/head.ts +0 -182
  146. package/src/client/head/metadata.ts +0 -110
  147. package/src/client/index.ts +0 -130
  148. package/src/client/navigation/Link.tsx +0 -108
  149. package/src/client/navigation/NavLink.tsx +0 -86
  150. package/src/client/navigation/navigation.ts +0 -235
  151. package/src/client/navigation/prefetch.ts +0 -169
  152. package/src/client/navigation/scroll.ts +0 -53
  153. package/src/client/routing/Router.tsx +0 -216
  154. package/src/client/routing/action.ts +0 -122
  155. package/src/client/routing/error-boundary.tsx +0 -43
  156. package/src/client/routing/hooks.ts +0 -140
  157. package/src/client/routing/lazy.ts +0 -77
  158. package/src/client/routing/loader.ts +0 -381
  159. package/src/client/routing/match.ts +0 -47
  160. package/src/client/routing/mount.tsx +0 -90
  161. package/src/client/routing/params-context.ts +0 -10
  162. package/src/client/routing/slot-context.ts +0 -7
  163. package/src/client/rpc.ts +0 -97
  164. package/src/client/search/search.ts +0 -189
  165. package/src/client/search/use-page-search.ts +0 -73
  166. package/src/client/ssr/markers.tsx +0 -172
  167. package/src/client/stream/client.ts +0 -237
  168. package/src/client/types.ts +0 -73
  169. package/src/compiler/auth-emails.ts +0 -312
  170. package/src/compiler/config.ts +0 -369
  171. package/src/compiler/docs.ts +0 -127
  172. package/src/compiler/email-preview.ts +0 -314
  173. package/src/compiler/emails.ts +0 -404
  174. package/src/compiler/fonts.ts +0 -89
  175. package/src/compiler/generate.ts +0 -471
  176. package/src/compiler/image-blur.ts +0 -42
  177. package/src/compiler/image-report.ts +0 -88
  178. package/src/compiler/index.ts +0 -1362
  179. package/src/compiler/pages.ts +0 -49
  180. package/src/compiler/plugin.ts +0 -391
  181. package/src/compiler/prerender.ts +0 -263
  182. package/src/compiler/routes.ts +0 -170
  183. package/src/compiler/seo.ts +0 -415
  184. package/src/compiler/sri.ts +0 -144
  185. package/src/compiler/ssg.ts +0 -177
  186. package/src/compiler/ssr-codegen.ts +0 -85
  187. package/src/compiler/template-build.ts +0 -676
  188. package/src/compiler/template.ts +0 -262
  189. package/src/compiler/toil-docs.generated.ts +0 -77
  190. package/src/compiler/vite.ts +0 -245
  191. package/src/devserver/analytics/index.ts +0 -317
  192. package/src/devserver/config/dotenv.ts +0 -102
  193. package/src/devserver/config/env.ts +0 -30
  194. package/src/devserver/config/ratelimit.ts +0 -123
  195. package/src/devserver/daemon/catalog.ts +0 -120
  196. package/src/devserver/daemon/cron.ts +0 -87
  197. package/src/devserver/daemon/host.ts +0 -134
  198. package/src/devserver/daemon/index.ts +0 -369
  199. package/src/devserver/daemon/runtime.ts +0 -48
  200. package/src/devserver/db/catalog.ts +0 -109
  201. package/src/devserver/db/database.ts +0 -1834
  202. package/src/devserver/db/derives.ts +0 -121
  203. package/src/devserver/db/index.ts +0 -20
  204. package/src/devserver/db/routeKinds.ts +0 -191
  205. package/src/devserver/db/types.ts +0 -168
  206. package/src/devserver/email/caps.ts +0 -0
  207. package/src/devserver/email/config.ts +0 -141
  208. package/src/devserver/email/console.ts +0 -348
  209. package/src/devserver/email/index.ts +0 -155
  210. package/src/devserver/email/providers.ts +0 -131
  211. package/src/devserver/email/status.ts +0 -23
  212. package/src/devserver/email/validate.ts +0 -37
  213. package/src/devserver/email/wire.ts +0 -55
  214. package/src/devserver/http/cache.ts +0 -0
  215. package/src/devserver/http/envelope.ts +0 -157
  216. package/src/devserver/http/proxy.ts +0 -172
  217. package/src/devserver/http/runtime.ts +0 -387
  218. package/src/devserver/index.ts +0 -43
  219. package/src/devserver/production-ipc.ts +0 -63
  220. package/src/devserver/production-worker.ts +0 -83
  221. package/src/devserver/production.ts +0 -718
  222. package/src/devserver/runtime/crypto.ts +0 -587
  223. package/src/devserver/runtime/host.ts +0 -427
  224. package/src/devserver/runtime/module.ts +0 -442
  225. package/src/devserver/server.ts +0 -251
  226. package/src/devserver/ssr.ts +0 -208
  227. package/src/devserver/stream/catalog.ts +0 -106
  228. package/src/devserver/stream/host.ts +0 -42
  229. package/src/devserver/stream/index.ts +0 -301
  230. package/src/devserver/stream/manager.ts +0 -167
  231. package/src/devserver/stream/router.ts +0 -101
  232. package/src/devserver/stream/wire.ts +0 -58
  233. package/src/devserver/stream/ws.ts +0 -78
  234. package/src/devserver/wasm/sections.ts +0 -59
  235. package/src/devserver/wasm/surface.ts +0 -115
  236. package/src/io/FastMap.ts +0 -151
  237. package/src/io/FastSet.ts +0 -113
  238. package/src/io/codec.ts +0 -362
  239. package/src/io/index.ts +0 -10
  240. package/src/io/lengths.ts +0 -14
  241. package/src/io/types.ts +0 -19
  242. package/src/logger/index.ts +0 -22
  243. package/src/shared/index.ts +0 -46
  244. package/test/analytics-dev.test.ts +0 -113
  245. package/test/assembly/aspect-shim.ts +0 -33
  246. package/test/assembly/cookie.spec.ts +0 -303
  247. package/test/assembly/example.spec.ts +0 -22
  248. package/test/assembly/ssr.spec.ts +0 -95
  249. package/test/assembly.test.ts +0 -59
  250. package/test/auth-emails.test.ts +0 -118
  251. package/test/built-ssr.test.ts +0 -148
  252. package/test/channel.test.ts +0 -21
  253. package/test/config-host-port.test.ts +0 -59
  254. package/test/configure.test.ts +0 -94
  255. package/test/daemon-build.test.ts +0 -278
  256. package/test/daemon-catalog.test.ts +0 -349
  257. package/test/daemon-emulation.test.ts +0 -256
  258. package/test/db.test.ts +0 -0
  259. package/test/devserver-database.test.ts +0 -1102
  260. package/test/devserver-email.test.ts +0 -306
  261. package/test/devserver-pqauth.test.ts +0 -93
  262. package/test/devserver-secrets.test.ts +0 -63
  263. package/test/devserver.test.ts +0 -259
  264. package/test/doctor.test.ts +0 -298
  265. package/test/dom/Image.test.tsx +0 -144
  266. package/test/dom/Link.test.tsx +0 -67
  267. package/test/dom/NavLink.test.tsx +0 -37
  268. package/test/dom/Script.test.tsx +0 -48
  269. package/test/dom/action.test.tsx +0 -146
  270. package/test/dom/error-overlay.test.tsx +0 -44
  271. package/test/dom/loader.test.tsx +0 -121
  272. package/test/dom/navigation.test.ts +0 -59
  273. package/test/dom/revalidate.test.tsx +0 -38
  274. package/test/dom/route-head.test.tsx +0 -56
  275. package/test/dom/router-loading.test.tsx +0 -44
  276. package/test/dom/scroll.test.ts +0 -56
  277. package/test/dom/slot.test.tsx +0 -131
  278. package/test/dom/use-metadata.test.tsx +0 -58
  279. package/test/dom/view-transitions.test.tsx +0 -53
  280. package/test/dynamic-routing.test.ts +0 -132
  281. package/test/email-console.test.ts +0 -86
  282. package/test/email-preview.test.ts +0 -73
  283. package/test/emails.test.ts +0 -58
  284. package/test/errors.test.ts +0 -21
  285. package/test/example-guestbook.test.ts +0 -123
  286. package/test/features.test.ts +0 -149
  287. package/test/fixtures/bignum-wire/spec.ts +0 -25
  288. package/test/fixtures/daemon-app.ts +0 -65
  289. package/test/fixtures/stream-echo.ts +0 -26
  290. package/test/fixtures/stream-gate.ts +0 -28
  291. package/test/fixtures/stream-trap.ts +0 -18
  292. package/test/fixtures/stream-typed.ts +0 -41
  293. package/test/fonts.test.ts +0 -28
  294. package/test/global-setup.ts +0 -17
  295. package/test/head.test.ts +0 -42
  296. package/test/image-blur.test.ts +0 -50
  297. package/test/io.test.ts +0 -117
  298. package/test/metadata.test.ts +0 -40
  299. package/test/navlink.test.ts +0 -28
  300. package/test/pages.test.ts +0 -105
  301. package/test/placeholder.test.ts +0 -9
  302. package/test/pqauth-e2e.test.ts +0 -216
  303. package/test/pqauth-email.test.ts +0 -801
  304. package/test/prerender.test.ts +0 -116
  305. package/test/prettier-plugin.test.ts +0 -46
  306. package/test/routes.test.ts +0 -76
  307. package/test/rpc-bignum-wire.test.ts +0 -164
  308. package/test/rpc-dispatch.test.ts +0 -132
  309. package/test/rpc-kinds.test.ts +0 -18
  310. package/test/rpc.test.ts +0 -66
  311. package/test/search.test.ts +0 -114
  312. package/test/seo.test.ts +0 -175
  313. package/test/slot-layouts.test.ts +0 -69
  314. package/test/sri.test.ts +0 -129
  315. package/test/ssg.test.ts +0 -36
  316. package/test/ssr-hydration.test.tsx +0 -122
  317. package/test/ssr-render.test.ts +0 -197
  318. package/test/ssr-template.test.tsx +0 -482
  319. package/test/stream-emulation.test.ts +0 -479
  320. package/test/ui.test.ts +0 -40
  321. package/test/update.test.ts +0 -58
  322. package/test/validate.test.ts +0 -42
  323. package/test/version-check.test.ts +0 -130
  324. package/tests/data-parity/generated-parity.ts +0 -99
  325. package/tests/data-parity/parity.ts +0 -80
  326. 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
- }