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.
- package/CHANGELOG.md +10 -0
- package/README.md +16 -7
- package/TYPESCRIPT_LAW.md +1946 -2073
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +2375 -258
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/dev/devtools.js +1 -1
- package/build/client/dev/error-overlay.js +1 -1
- package/build/client/routing/mount.js +1 -1
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/auth-emails.js +7 -3
- package/build/compiler/emails.js +1 -1
- package/build/compiler/index.d.ts +1 -0
- package/build/compiler/index.js +25 -4
- package/build/compiler/pages.js +2 -3
- package/build/compiler/prerender.d.ts +3 -8
- package/build/compiler/prerender.js +88 -127
- package/build/compiler/ssg.js +3 -4
- package/build/compiler/template-build.js +3 -4
- package/build/compiler/toil-docs.generated.js +6 -5
- package/build/compiler/vite.js +3 -6
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/db/database.js +7 -3
- package/build/devserver/db/types.js +1 -1
- package/build/devserver/production.js +4 -3
- package/build/devserver/runtime/crypto.d.ts +1 -1
- package/build/io/.tsbuildinfo +1 -1
- package/build/logger/.tsbuildinfo +1 -1
- package/build/shared/.tsbuildinfo +1 -1
- package/examples/basic/client/routes/analytics.tsx +7 -5
- package/examples/basic/server/tsconfig.json +1 -3
- package/package.json +45 -44
- package/presets/no-uint8array-tostring.js +120 -153
- package/presets/oxlint.d.ts +5 -0
- package/presets/oxlint.js +224 -0
- package/server/runtime/tsconfig.json +1 -3
- package/tsconfig.json +2 -11
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -43
- package/.github/changelog-config.json +0 -45
- package/.github/dependabot.yml +0 -27
- package/.github/workflows/ci.yml +0 -222
- package/.vscode/settings.json +0 -10
- package/docs/README.md +0 -107
- package/docs/auth/README.md +0 -104
- package/docs/auth/configuration.md +0 -105
- package/docs/auth/emails.md +0 -92
- package/docs/auth/extending.md +0 -202
- package/docs/auth/how-it-works.md +0 -138
- package/docs/auth/usage.md +0 -188
- package/docs/backend/README.md +0 -143
- package/docs/backend/data.md +0 -351
- package/docs/backend/rest.md +0 -402
- package/docs/backend/rpc.md +0 -226
- package/docs/background/README.md +0 -114
- package/docs/background/daemons.md +0 -276
- package/docs/background/derive.md +0 -179
- package/docs/cli/README.md +0 -379
- package/docs/concepts/ai-guide.md +0 -132
- package/docs/concepts/config.md +0 -416
- package/docs/concepts/decorators.md +0 -127
- package/docs/concepts/security.md +0 -108
- package/docs/concepts/tiers.md +0 -166
- package/docs/concepts/types.md +0 -216
- package/docs/database/README.md +0 -143
- package/docs/database/capacity.md +0 -350
- package/docs/database/counters.md +0 -174
- package/docs/database/documents.md +0 -277
- package/docs/database/events.md +0 -307
- package/docs/database/membership.md +0 -246
- package/docs/database/setup.md +0 -155
- package/docs/database/unique.md +0 -216
- package/docs/database/views.md +0 -246
- package/docs/frontend/README.md +0 -104
- package/docs/frontend/components.md +0 -286
- package/docs/frontend/data-fetching.md +0 -260
- package/docs/frontend/images.md +0 -148
- package/docs/frontend/metadata.md +0 -344
- package/docs/frontend/navigation.md +0 -296
- package/docs/frontend/rendering.md +0 -236
- package/docs/frontend/routing.md +0 -346
- package/docs/frontend/scripts.md +0 -118
- package/docs/frontend/search.md +0 -191
- package/docs/frontend/styling.md +0 -147
- package/docs/frontend/toil-global.md +0 -216
- package/docs/getting-started/README.md +0 -81
- package/docs/getting-started/create-project.md +0 -131
- package/docs/getting-started/deploy.md +0 -101
- package/docs/getting-started/first-app.md +0 -215
- package/docs/getting-started/installation.md +0 -124
- package/docs/getting-started/migrating.md +0 -125
- package/docs/getting-started/project-structure.md +0 -163
- package/docs/introduction/README.md +0 -66
- package/docs/introduction/design-principles.md +0 -81
- package/docs/introduction/distributed.md +0 -91
- package/docs/introduction/how-it-works.md +0 -121
- package/docs/introduction/hyperscale.md +0 -70
- package/docs/introduction/modern-stack.md +0 -85
- package/docs/introduction/vs-other-frameworks.md +0 -64
- package/docs/introduction/why-toil.md +0 -99
- package/docs/llms.txt +0 -95
- package/docs/realtime/README.md +0 -102
- package/docs/realtime/channels.md +0 -211
- package/docs/realtime/streams.md +0 -369
- package/docs/services/README.md +0 -60
- package/docs/services/analytics.md +0 -279
- package/docs/services/caching.md +0 -175
- package/docs/services/cookies.md +0 -235
- package/docs/services/crypto.md +0 -209
- package/docs/services/email.md +0 -289
- package/docs/services/environment.md +0 -141
- package/docs/services/ratelimit.md +0 -174
- package/docs/services/time.md +0 -85
- package/eslint.config.js +0 -57
- package/examples/basic/client/routes/docs/[...slug].tsx +0 -12
- package/presets/eslint.js +0 -88
- package/src/backend/index.ts +0 -204
- package/src/cli/configure.ts +0 -363
- package/src/cli/create.ts +0 -924
- package/src/cli/db.ts +0 -158
- package/src/cli/diagnostics.ts +0 -665
- package/src/cli/doctor.ts +0 -982
- package/src/cli/features.ts +0 -170
- package/src/cli/index.ts +0 -326
- package/src/cli/notify.ts +0 -120
- package/src/cli/proc.ts +0 -56
- package/src/cli/ui.ts +0 -169
- package/src/cli/update.ts +0 -248
- package/src/cli/updates.ts +0 -92
- package/src/cli/validate.ts +0 -31
- package/src/cli/version-check.ts +0 -125
- package/src/client/auth.ts +0 -869
- package/src/client/channel/channel.ts +0 -146
- package/src/client/components/Form.tsx +0 -65
- package/src/client/components/Image.tsx +0 -160
- package/src/client/components/Script.tsx +0 -113
- package/src/client/components/Slot.tsx +0 -21
- package/src/client/dev/devtools.tsx +0 -1089
- package/src/client/dev/error-overlay.tsx +0 -225
- package/src/client/errors.ts +0 -11
- package/src/client/head/head.ts +0 -182
- package/src/client/head/metadata.ts +0 -110
- package/src/client/index.ts +0 -130
- package/src/client/navigation/Link.tsx +0 -108
- package/src/client/navigation/NavLink.tsx +0 -86
- package/src/client/navigation/navigation.ts +0 -235
- package/src/client/navigation/prefetch.ts +0 -169
- package/src/client/navigation/scroll.ts +0 -53
- package/src/client/routing/Router.tsx +0 -216
- package/src/client/routing/action.ts +0 -122
- package/src/client/routing/error-boundary.tsx +0 -43
- package/src/client/routing/hooks.ts +0 -140
- package/src/client/routing/lazy.ts +0 -77
- package/src/client/routing/loader.ts +0 -381
- package/src/client/routing/match.ts +0 -47
- package/src/client/routing/mount.tsx +0 -90
- package/src/client/routing/params-context.ts +0 -10
- package/src/client/routing/slot-context.ts +0 -7
- package/src/client/rpc.ts +0 -97
- package/src/client/search/search.ts +0 -189
- package/src/client/search/use-page-search.ts +0 -73
- package/src/client/ssr/markers.tsx +0 -172
- package/src/client/stream/client.ts +0 -237
- package/src/client/types.ts +0 -73
- package/src/compiler/auth-emails.ts +0 -312
- package/src/compiler/config.ts +0 -369
- package/src/compiler/docs.ts +0 -127
- package/src/compiler/email-preview.ts +0 -314
- package/src/compiler/emails.ts +0 -404
- package/src/compiler/fonts.ts +0 -89
- package/src/compiler/generate.ts +0 -471
- package/src/compiler/image-blur.ts +0 -42
- package/src/compiler/image-report.ts +0 -88
- package/src/compiler/index.ts +0 -1362
- package/src/compiler/pages.ts +0 -49
- package/src/compiler/plugin.ts +0 -391
- package/src/compiler/prerender.ts +0 -263
- package/src/compiler/routes.ts +0 -170
- package/src/compiler/seo.ts +0 -415
- package/src/compiler/sri.ts +0 -144
- package/src/compiler/ssg.ts +0 -177
- package/src/compiler/ssr-codegen.ts +0 -85
- package/src/compiler/template-build.ts +0 -676
- package/src/compiler/template.ts +0 -262
- package/src/compiler/toil-docs.generated.ts +0 -77
- package/src/compiler/vite.ts +0 -245
- package/src/devserver/analytics/index.ts +0 -317
- package/src/devserver/config/dotenv.ts +0 -102
- package/src/devserver/config/env.ts +0 -30
- package/src/devserver/config/ratelimit.ts +0 -123
- package/src/devserver/daemon/catalog.ts +0 -120
- package/src/devserver/daemon/cron.ts +0 -87
- package/src/devserver/daemon/host.ts +0 -134
- package/src/devserver/daemon/index.ts +0 -369
- package/src/devserver/daemon/runtime.ts +0 -48
- package/src/devserver/db/catalog.ts +0 -109
- package/src/devserver/db/database.ts +0 -1834
- package/src/devserver/db/derives.ts +0 -121
- package/src/devserver/db/index.ts +0 -20
- package/src/devserver/db/routeKinds.ts +0 -191
- package/src/devserver/db/types.ts +0 -168
- package/src/devserver/email/caps.ts +0 -0
- package/src/devserver/email/config.ts +0 -141
- package/src/devserver/email/console.ts +0 -348
- package/src/devserver/email/index.ts +0 -155
- package/src/devserver/email/providers.ts +0 -131
- package/src/devserver/email/status.ts +0 -23
- package/src/devserver/email/validate.ts +0 -37
- package/src/devserver/email/wire.ts +0 -55
- package/src/devserver/http/cache.ts +0 -0
- package/src/devserver/http/envelope.ts +0 -157
- package/src/devserver/http/proxy.ts +0 -172
- package/src/devserver/http/runtime.ts +0 -387
- package/src/devserver/index.ts +0 -43
- package/src/devserver/production-ipc.ts +0 -63
- package/src/devserver/production-worker.ts +0 -83
- package/src/devserver/production.ts +0 -718
- package/src/devserver/runtime/crypto.ts +0 -587
- package/src/devserver/runtime/host.ts +0 -427
- package/src/devserver/runtime/module.ts +0 -442
- package/src/devserver/server.ts +0 -251
- package/src/devserver/ssr.ts +0 -208
- package/src/devserver/stream/catalog.ts +0 -106
- package/src/devserver/stream/host.ts +0 -42
- package/src/devserver/stream/index.ts +0 -301
- package/src/devserver/stream/manager.ts +0 -167
- package/src/devserver/stream/router.ts +0 -101
- package/src/devserver/stream/wire.ts +0 -58
- package/src/devserver/stream/ws.ts +0 -78
- package/src/devserver/wasm/sections.ts +0 -59
- package/src/devserver/wasm/surface.ts +0 -115
- package/src/io/FastMap.ts +0 -151
- package/src/io/FastSet.ts +0 -113
- package/src/io/codec.ts +0 -362
- package/src/io/index.ts +0 -10
- package/src/io/lengths.ts +0 -14
- package/src/io/types.ts +0 -19
- package/src/logger/index.ts +0 -22
- package/src/shared/index.ts +0 -46
- package/test/analytics-dev.test.ts +0 -113
- package/test/assembly/aspect-shim.ts +0 -33
- package/test/assembly/cookie.spec.ts +0 -303
- package/test/assembly/example.spec.ts +0 -22
- package/test/assembly/ssr.spec.ts +0 -95
- package/test/assembly.test.ts +0 -59
- package/test/auth-emails.test.ts +0 -118
- package/test/built-ssr.test.ts +0 -148
- package/test/channel.test.ts +0 -21
- package/test/config-host-port.test.ts +0 -59
- package/test/configure.test.ts +0 -94
- package/test/daemon-build.test.ts +0 -278
- package/test/daemon-catalog.test.ts +0 -349
- package/test/daemon-emulation.test.ts +0 -256
- package/test/db.test.ts +0 -0
- package/test/devserver-database.test.ts +0 -1102
- package/test/devserver-email.test.ts +0 -306
- package/test/devserver-pqauth.test.ts +0 -93
- package/test/devserver-secrets.test.ts +0 -63
- package/test/devserver.test.ts +0 -259
- package/test/doctor.test.ts +0 -298
- package/test/dom/Image.test.tsx +0 -144
- package/test/dom/Link.test.tsx +0 -67
- package/test/dom/NavLink.test.tsx +0 -37
- package/test/dom/Script.test.tsx +0 -48
- package/test/dom/action.test.tsx +0 -146
- package/test/dom/error-overlay.test.tsx +0 -44
- package/test/dom/loader.test.tsx +0 -121
- package/test/dom/navigation.test.ts +0 -59
- package/test/dom/revalidate.test.tsx +0 -38
- package/test/dom/route-head.test.tsx +0 -56
- package/test/dom/router-loading.test.tsx +0 -44
- package/test/dom/scroll.test.ts +0 -56
- package/test/dom/slot.test.tsx +0 -131
- package/test/dom/use-metadata.test.tsx +0 -58
- package/test/dom/view-transitions.test.tsx +0 -53
- package/test/dynamic-routing.test.ts +0 -132
- package/test/email-console.test.ts +0 -86
- package/test/email-preview.test.ts +0 -73
- package/test/emails.test.ts +0 -58
- package/test/errors.test.ts +0 -21
- package/test/example-guestbook.test.ts +0 -123
- package/test/features.test.ts +0 -149
- package/test/fixtures/bignum-wire/spec.ts +0 -25
- package/test/fixtures/daemon-app.ts +0 -65
- package/test/fixtures/stream-echo.ts +0 -26
- package/test/fixtures/stream-gate.ts +0 -28
- package/test/fixtures/stream-trap.ts +0 -18
- package/test/fixtures/stream-typed.ts +0 -41
- package/test/fonts.test.ts +0 -28
- package/test/global-setup.ts +0 -17
- package/test/head.test.ts +0 -42
- package/test/image-blur.test.ts +0 -50
- package/test/io.test.ts +0 -117
- package/test/metadata.test.ts +0 -40
- package/test/navlink.test.ts +0 -28
- package/test/pages.test.ts +0 -105
- package/test/placeholder.test.ts +0 -9
- package/test/pqauth-e2e.test.ts +0 -216
- package/test/pqauth-email.test.ts +0 -801
- package/test/prerender.test.ts +0 -116
- package/test/prettier-plugin.test.ts +0 -46
- package/test/routes.test.ts +0 -76
- package/test/rpc-bignum-wire.test.ts +0 -164
- package/test/rpc-dispatch.test.ts +0 -132
- package/test/rpc-kinds.test.ts +0 -18
- package/test/rpc.test.ts +0 -66
- package/test/search.test.ts +0 -114
- package/test/seo.test.ts +0 -175
- package/test/slot-layouts.test.ts +0 -69
- package/test/sri.test.ts +0 -129
- package/test/ssg.test.ts +0 -36
- package/test/ssr-hydration.test.tsx +0 -122
- package/test/ssr-render.test.ts +0 -197
- package/test/ssr-template.test.tsx +0 -482
- package/test/stream-emulation.test.ts +0 -479
- package/test/ui.test.ts +0 -40
- package/test/update.test.ts +0 -58
- package/test/validate.test.ts +0 -42
- package/test/version-check.test.ts +0 -130
- package/tests/data-parity/generated-parity.ts +0 -99
- package/tests/data-parity/parity.ts +0 -80
- package/tests/data-parity/spec.ts +0 -46
package/src/cli/diagnostics.ts
DELETED
|
@@ -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
|
-
}
|