toiljs 0.0.11 → 0.0.14
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/README.md +3 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/configure.js +10 -4
- package/build/cli/create.js +58 -30
- package/build/cli/diagnostics.d.ts +55 -0
- package/build/cli/diagnostics.js +333 -0
- package/build/cli/doctor.d.ts +6 -0
- package/build/cli/doctor.js +249 -0
- package/build/cli/index.js +26 -0
- package/build/cli/proc.d.ts +5 -0
- package/build/cli/proc.js +20 -0
- package/build/cli/ui.d.ts +1 -0
- package/build/cli/ui.js +1 -0
- package/build/cli/update.d.ts +7 -0
- package/build/cli/update.js +117 -0
- package/build/cli/updates.d.ts +10 -0
- package/build/cli/updates.js +45 -0
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/dev/error-overlay.js +1 -1
- package/build/client/head/metadata.js +3 -1
- package/build/client/index.d.ts +5 -1
- package/build/client/index.js +2 -0
- package/build/client/navigation/navigation.js +1 -1
- package/build/client/routing/Router.js +2 -2
- package/build/client/search/search.d.ts +26 -0
- package/build/client/search/search.js +101 -0
- package/build/client/search/use-page-search.d.ts +8 -0
- package/build/client/search/use-page-search.js +21 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/generate.js +33 -24
- package/build/compiler/index.d.ts +2 -0
- package/build/compiler/index.js +1 -0
- package/build/compiler/pages.d.ts +8 -0
- package/build/compiler/pages.js +37 -0
- package/build/compiler/plugin.js +3 -1
- package/build/compiler/prerender.d.ts +1 -0
- package/build/compiler/prerender.js +11 -5
- package/build/compiler/seo.js +10 -3
- package/build/io/.tsbuildinfo +1 -1
- package/examples/basic/client/components/Header.tsx +43 -41
- package/examples/basic/client/components/HoneycombBackground.tsx +223 -230
- package/examples/basic/client/public/index.html +18 -16
- package/examples/basic/client/routes/(legal)/privacy.tsx +18 -19
- package/examples/basic/client/routes/(legal)/terms.tsx +15 -16
- package/examples/basic/client/routes/about.tsx +21 -22
- package/examples/basic/client/routes/blog/[id].tsx +26 -18
- package/examples/basic/client/routes/features/actions.tsx +67 -67
- package/examples/basic/client/routes/features/error/index.tsx +27 -27
- package/examples/basic/client/routes/features/head.tsx +38 -38
- package/examples/basic/client/routes/features/index.tsx +83 -75
- package/examples/basic/client/routes/features/realtime.tsx +34 -32
- package/examples/basic/client/routes/features/script.tsx +31 -31
- package/examples/basic/client/routes/features/seo.tsx +39 -39
- package/examples/basic/client/routes/features/template/index.tsx +20 -20
- package/examples/basic/client/routes/features/template/template.tsx +16 -18
- package/examples/basic/client/routes/gallery/@modal/(.)photo/[id].tsx +23 -23
- package/examples/basic/client/routes/gallery/index.tsx +42 -42
- package/examples/basic/client/routes/gallery/photo/[id].tsx +18 -18
- package/examples/basic/client/routes/get-started.tsx +157 -84
- package/examples/basic/client/routes/index.tsx +137 -96
- package/examples/basic/client/routes/loader-demo/index.tsx +59 -52
- package/examples/basic/client/routes/search.tsx +61 -0
- package/examples/basic/client/routes/test.tsx +7 -8
- package/examples/basic/client/styles/main.css +624 -552
- package/package.json +2 -2
- package/presets/eslint.js +10 -3
- package/src/cli/configure.ts +363 -353
- package/src/cli/create.ts +563 -530
- package/src/cli/diagnostics.ts +421 -0
- package/src/cli/doctor.ts +318 -0
- package/src/cli/features.ts +166 -160
- package/src/cli/index.ts +242 -211
- package/src/cli/proc.ts +30 -0
- package/src/cli/ui.ts +111 -103
- package/src/cli/update.ts +150 -0
- package/src/cli/updates.ts +69 -0
- package/src/client/components/Image.tsx +91 -89
- package/src/client/dev/error-overlay.tsx +193 -197
- package/src/client/head/metadata.ts +94 -92
- package/src/client/index.ts +79 -64
- package/src/client/navigation/Link.tsx +94 -100
- package/src/client/navigation/navigation.ts +215 -218
- package/src/client/routing/Router.tsx +210 -193
- package/src/client/routing/hooks.ts +110 -114
- package/src/client/routing/lazy.ts +77 -81
- package/src/client/search/search.ts +189 -0
- package/src/client/search/use-page-search.ts +73 -0
- package/src/compiler/config.ts +173 -171
- package/src/compiler/fonts.ts +89 -87
- package/src/compiler/generate.ts +45 -27
- package/src/compiler/image-report.ts +88 -85
- package/src/compiler/index.ts +2 -0
- package/src/compiler/pages.ts +70 -0
- package/src/compiler/plugin.ts +51 -47
- package/src/compiler/prerender.ts +152 -130
- package/src/compiler/routes.ts +132 -131
- package/src/compiler/seo.ts +381 -356
- package/src/compiler/vite.ts +155 -145
- package/src/io/FastSet.ts +99 -96
- package/test/configure.test.ts +94 -90
- package/test/doctor.test.ts +140 -0
- package/test/dom/Image.test.tsx +73 -46
- package/test/dom/Script.test.tsx +48 -45
- package/test/dom/action.test.tsx +146 -129
- package/test/dom/error-overlay.test.tsx +1 -1
- package/test/dom/loader.test.tsx +2 -2
- package/test/dom/revalidate.test.tsx +1 -1
- package/test/dom/route-head.test.tsx +1 -2
- package/test/dom/router-loading.test.tsx +1 -1
- package/test/dom/slot.test.tsx +131 -109
- package/test/dom/view-transitions.test.tsx +53 -51
- package/test/features.test.ts +149 -142
- package/test/fonts.test.ts +28 -26
- package/test/head.test.ts +45 -35
- package/test/metadata.test.ts +42 -41
- package/test/pages.test.ts +105 -0
- package/test/prerender.test.ts +54 -46
- package/test/search.test.ts +114 -0
- package/test/seo.test.ts +30 -8
- package/test/update.test.ts +44 -0
|
@@ -0,0 +1,421 @@
|
|
|
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
|
+
export function checkPackageManager(lockfiles: readonly string[]): Check {
|
|
87
|
+
if (lockfiles.length === 0) {
|
|
88
|
+
return {
|
|
89
|
+
id: 'pm',
|
|
90
|
+
label: 'Package manager',
|
|
91
|
+
status: 'warn',
|
|
92
|
+
detail: 'no lockfile found',
|
|
93
|
+
fix: 'Run an install (npm/pnpm/yarn/bun) to create a lockfile.',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { id: 'pm', label: 'Package manager', status: 'pass', detail: lockfiles.join(', ') };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function checkToiljsInstalled(version: string | null): Check {
|
|
100
|
+
return version
|
|
101
|
+
? { id: 'toiljs', label: 'toiljs', status: 'pass', detail: version }
|
|
102
|
+
: {
|
|
103
|
+
id: 'toiljs',
|
|
104
|
+
label: 'toiljs',
|
|
105
|
+
status: 'fail',
|
|
106
|
+
detail: 'not a dependency of this project',
|
|
107
|
+
fix: 'Add toiljs to dependencies, or run from the project root with --root.',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --- Project + routing ----------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
export function checkDir(id: string, label: string, exists: boolean, fix: string): Check {
|
|
114
|
+
return exists
|
|
115
|
+
? { id, label, status: 'pass' }
|
|
116
|
+
: { id, label, status: 'fail', detail: 'missing', fix };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Whether the app entry calls `mount(...)` with a `slots` argument. Without it, parallel and
|
|
121
|
+
* intercepting routes are silently dropped (a real bug we shipped a fix for). Heuristic, regex-based.
|
|
122
|
+
*/
|
|
123
|
+
export function checkMountSlots(entrySource: string | null): Check {
|
|
124
|
+
const label = 'App entry mount()';
|
|
125
|
+
if (entrySource === null) {
|
|
126
|
+
return {
|
|
127
|
+
id: 'mount',
|
|
128
|
+
label,
|
|
129
|
+
status: 'warn',
|
|
130
|
+
detail: 'entry file not found',
|
|
131
|
+
fix: 'Ensure client/toil.tsx calls Toil.mount(...).',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const call = /\bmount\s*\(([^)]*)\)/.exec(entrySource);
|
|
135
|
+
if (!call) {
|
|
136
|
+
return {
|
|
137
|
+
id: 'mount',
|
|
138
|
+
label,
|
|
139
|
+
status: 'warn',
|
|
140
|
+
detail: 'no mount() call found',
|
|
141
|
+
fix: 'Call Toil.mount(routes, layout, notFound, globalError, slots).',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const hasSlots = /\bslots\b/.test(call[1]);
|
|
145
|
+
return hasSlots
|
|
146
|
+
? { id: 'mount', label, status: 'pass', detail: 'passes slots' }
|
|
147
|
+
: {
|
|
148
|
+
id: 'mount',
|
|
149
|
+
label,
|
|
150
|
+
status: 'warn',
|
|
151
|
+
detail: 'mount() is missing the slots argument',
|
|
152
|
+
fix: 'Pass slots last: mount(routes, layout, notFound, globalError, slots). Without it, parallel and intercepting routes are ignored.',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function checkRootElement(indexHtml: string | null): Check {
|
|
157
|
+
const label = 'index.html mount target';
|
|
158
|
+
if (indexHtml === null) {
|
|
159
|
+
return {
|
|
160
|
+
id: 'root-el',
|
|
161
|
+
label,
|
|
162
|
+
status: 'fail',
|
|
163
|
+
detail: 'index.html not found',
|
|
164
|
+
fix: 'Add public/index.html with <div id="root"></div>.',
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const ok = /id\s*=\s*["']root["']/.test(indexHtml);
|
|
168
|
+
return ok
|
|
169
|
+
? { id: 'root-el', label, status: 'pass' }
|
|
170
|
+
: {
|
|
171
|
+
id: 'root-el',
|
|
172
|
+
label,
|
|
173
|
+
status: 'fail',
|
|
174
|
+
detail: 'no element with id="root"',
|
|
175
|
+
fix: 'Add <div id="root"></div> to public/index.html.',
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function checkRoutesPresent(routeCount: number): Check {
|
|
180
|
+
return routeCount > 0
|
|
181
|
+
? {
|
|
182
|
+
id: 'routes',
|
|
183
|
+
label: 'Routes',
|
|
184
|
+
status: 'pass',
|
|
185
|
+
detail: `${routeCount} route${routeCount === 1 ? '' : 's'}`,
|
|
186
|
+
}
|
|
187
|
+
: {
|
|
188
|
+
id: 'routes',
|
|
189
|
+
label: 'Routes',
|
|
190
|
+
status: 'fail',
|
|
191
|
+
detail: 'no routes found',
|
|
192
|
+
fix: 'Add a page, e.g. client/routes/index.tsx.',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function checkDuplicatePatterns(patterns: readonly string[]): Check {
|
|
197
|
+
const seen = new Set<string>();
|
|
198
|
+
const dupes = new Set<string>();
|
|
199
|
+
for (const p of patterns) {
|
|
200
|
+
if (seen.has(p)) dupes.add(p);
|
|
201
|
+
else seen.add(p);
|
|
202
|
+
}
|
|
203
|
+
return dupes.size === 0
|
|
204
|
+
? { id: 'route-dupes', label: 'Unique route patterns', status: 'pass' }
|
|
205
|
+
: {
|
|
206
|
+
id: 'route-dupes',
|
|
207
|
+
label: 'Unique route patterns',
|
|
208
|
+
status: 'warn',
|
|
209
|
+
detail: `duplicate: ${[...dupes].join(', ')}`,
|
|
210
|
+
fix: 'Two route files map to the same URL; rename or remove one.',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** A source file scanned for broken asset references. */
|
|
215
|
+
export interface SourceFile {
|
|
216
|
+
readonly path: string;
|
|
217
|
+
readonly source: string;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** A relative asset reference that will 404 on a nested route. */
|
|
221
|
+
export interface AssetIssue {
|
|
222
|
+
readonly file: string;
|
|
223
|
+
readonly line: number;
|
|
224
|
+
readonly value: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Whether a `src`/`href` string value is a root-relative asset path that breaks on nested routes. */
|
|
228
|
+
function isBrokenRelativeAsset(value: string): boolean {
|
|
229
|
+
if (value === '') return false;
|
|
230
|
+
if (value.startsWith('/')) return false; // root-absolute, resolves the same everywhere
|
|
231
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(value)) return false; // http:, https:, data:, mailto:, ...
|
|
232
|
+
if (value.startsWith('#') || value.startsWith('?')) return false;
|
|
233
|
+
// Only flag asset-looking values (a real file extension), to avoid false positives on app routes.
|
|
234
|
+
return /\.(svgz?|png|jpe?g|gif|webp|avif|ico|css|m?js|woff2?|ttf|otf|eot|mp4|webm|json)$/i.test(
|
|
235
|
+
value,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Finds string-literal `src=`/`href=` attributes pointing at broken relative asset paths. */
|
|
240
|
+
export function findRelativeAssets(files: readonly SourceFile[]): AssetIssue[] {
|
|
241
|
+
const issues: AssetIssue[] = [];
|
|
242
|
+
const attr = /\b(?:src|href)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
243
|
+
for (const file of files) {
|
|
244
|
+
const lines = file.source.split('\n');
|
|
245
|
+
for (let i = 0; i < lines.length; i++) {
|
|
246
|
+
attr.lastIndex = 0;
|
|
247
|
+
let m: RegExpExecArray | null;
|
|
248
|
+
while ((m = attr.exec(lines[i])) !== null) {
|
|
249
|
+
const value = m[1] ?? m[2] ?? '';
|
|
250
|
+
if (isBrokenRelativeAsset(value)) {
|
|
251
|
+
issues.push({ file: file.path, line: i + 1, value });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return issues;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function checkRelativeAssets(issues: readonly AssetIssue[]): Check {
|
|
260
|
+
if (issues.length === 0) return { id: 'rel-assets', label: 'Asset paths', status: 'pass' };
|
|
261
|
+
const shown = issues
|
|
262
|
+
.slice(0, 5)
|
|
263
|
+
.map((i) => `${i.file}:${String(i.line)} "${i.value}"`)
|
|
264
|
+
.join('; ');
|
|
265
|
+
const more = issues.length > 5 ? `, and ${String(issues.length - 5)} more` : '';
|
|
266
|
+
return {
|
|
267
|
+
id: 'rel-assets',
|
|
268
|
+
label: 'Asset paths',
|
|
269
|
+
status: 'warn',
|
|
270
|
+
detail: `${String(issues.length)} relative reference(s): ${shown}${more}`,
|
|
271
|
+
fix: 'Use a root-absolute path (e.g. "/images/logo.svg") or import the asset; relative paths 404 on nested routes.',
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// --- Config + assets ------------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
export function checkConfigLoads(loaded: boolean, error?: string): Check {
|
|
278
|
+
return loaded
|
|
279
|
+
? { id: 'config', label: 'toil.config loads', status: 'pass' }
|
|
280
|
+
: {
|
|
281
|
+
id: 'config',
|
|
282
|
+
label: 'toil.config loads',
|
|
283
|
+
status: 'fail',
|
|
284
|
+
detail: error ?? 'failed to load',
|
|
285
|
+
fix: 'Fix the error in your toil.config.* so dev/build can read it.',
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function checkBasePath(base: string): Check {
|
|
290
|
+
const ok = base === '/' || (base.startsWith('/') && base.endsWith('/'));
|
|
291
|
+
return ok
|
|
292
|
+
? { id: 'base', label: 'Base path', status: 'pass', detail: base }
|
|
293
|
+
: {
|
|
294
|
+
id: 'base',
|
|
295
|
+
label: 'Base path',
|
|
296
|
+
status: 'warn',
|
|
297
|
+
detail: `"${base}"`,
|
|
298
|
+
fix: 'A non-root base should start and end with "/" (e.g. "/app/").',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function checkSeoUrl(seoConfigured: boolean, hasUrl: boolean): Check {
|
|
303
|
+
if (!seoConfigured) {
|
|
304
|
+
return {
|
|
305
|
+
id: 'seo-url',
|
|
306
|
+
label: 'SEO site url',
|
|
307
|
+
status: 'pass',
|
|
308
|
+
detail: 'SEO not configured',
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
return hasUrl
|
|
312
|
+
? { id: 'seo-url', label: 'SEO site url', status: 'pass' }
|
|
313
|
+
: {
|
|
314
|
+
id: 'seo-url',
|
|
315
|
+
label: 'SEO site url',
|
|
316
|
+
status: 'warn',
|
|
317
|
+
detail: 'seo is set without a url',
|
|
318
|
+
fix: 'Set client.seo.url so sitemap.xml and canonical links are absolute.',
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Facts about the project's styling setup, derived by the orchestrator. */
|
|
323
|
+
export interface StylingFacts {
|
|
324
|
+
readonly preprocessorImported: Preprocessor | null;
|
|
325
|
+
readonly preprocessorInstalled: boolean;
|
|
326
|
+
readonly tailwindImported: boolean;
|
|
327
|
+
readonly tailwindInstalled: boolean;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function checkStyling(f: StylingFacts): Check {
|
|
331
|
+
const label = 'Styling';
|
|
332
|
+
if (f.preprocessorImported && f.preprocessorImported !== 'css' && !f.preprocessorInstalled) {
|
|
333
|
+
return {
|
|
334
|
+
id: 'styling',
|
|
335
|
+
label,
|
|
336
|
+
status: 'fail',
|
|
337
|
+
detail: `${f.preprocessorImported} stylesheet imported but ${f.preprocessorImported} is not installed`,
|
|
338
|
+
fix: `Install ${f.preprocessorImported}, or run toiljs configure.`,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
if (f.tailwindImported && !f.tailwindInstalled) {
|
|
342
|
+
return {
|
|
343
|
+
id: 'styling',
|
|
344
|
+
label,
|
|
345
|
+
status: 'fail',
|
|
346
|
+
detail: 'Tailwind entry imported but @tailwindcss/vite is not installed',
|
|
347
|
+
fix: 'Run toiljs configure --tailwind, or install @tailwindcss/vite.',
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return { id: 'styling', label, status: 'pass' };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// --- Server / WASM --------------------------------------------------------------------------------
|
|
354
|
+
|
|
355
|
+
export function checkToilconfig(present: boolean): Check {
|
|
356
|
+
return present
|
|
357
|
+
? { id: 'toilconfig', label: 'Server target (toilconfig.json)', status: 'pass' }
|
|
358
|
+
: {
|
|
359
|
+
id: 'toilconfig',
|
|
360
|
+
label: 'Server target (toilconfig.json)',
|
|
361
|
+
status: 'warn',
|
|
362
|
+
detail: 'no toilconfig.json (no WebAssembly server)',
|
|
363
|
+
fix: 'Add toilconfig.json + a server/ entry if you want a WASM backend.',
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function checkServerEntry(missing: readonly string[]): Check {
|
|
368
|
+
return missing.length === 0
|
|
369
|
+
? { id: 'server-entry', label: 'Server entries', status: 'pass' }
|
|
370
|
+
: {
|
|
371
|
+
id: 'server-entry',
|
|
372
|
+
label: 'Server entries',
|
|
373
|
+
status: 'fail',
|
|
374
|
+
detail: `missing: ${missing.join(', ')}`,
|
|
375
|
+
fix: 'Create the entry file(s) listed in toilconfig.json "entries", or update them.',
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function checkToilscriptInstalled(installed: boolean): Check {
|
|
380
|
+
return installed
|
|
381
|
+
? { id: 'toilscript', label: 'toilscript compiler', status: 'pass' }
|
|
382
|
+
: {
|
|
383
|
+
id: 'toilscript',
|
|
384
|
+
label: 'toilscript compiler',
|
|
385
|
+
status: 'warn',
|
|
386
|
+
detail: 'toilconfig.json present but toilscript is not installed',
|
|
387
|
+
fix: 'Install toilscript to compile the server to WebAssembly.',
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function checkWasmBuilt(exists: boolean): Check {
|
|
392
|
+
return exists
|
|
393
|
+
? { id: 'wasm', label: 'Server build', status: 'pass' }
|
|
394
|
+
: {
|
|
395
|
+
id: 'wasm',
|
|
396
|
+
label: 'Server build',
|
|
397
|
+
status: 'warn',
|
|
398
|
+
detail: 'no compiled .wasm found',
|
|
399
|
+
fix: 'Run your server build (toilscript) before toiljs start.',
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// --- Summary --------------------------------------------------------------------------------------
|
|
404
|
+
|
|
405
|
+
export function summarize(groups: readonly CheckGroup[]): DoctorSummary {
|
|
406
|
+
let pass = 0;
|
|
407
|
+
let warn = 0;
|
|
408
|
+
let fail = 0;
|
|
409
|
+
for (const group of groups) {
|
|
410
|
+
for (const check of group.checks) {
|
|
411
|
+
if (check.status === 'pass') pass++;
|
|
412
|
+
else if (check.status === 'warn') warn++;
|
|
413
|
+
else fail++;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return { pass, warn, fail };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export function hasFailures(summary: DoctorSummary): boolean {
|
|
420
|
+
return summary.fail > 0;
|
|
421
|
+
}
|