@webjsdev/cli 0.10.71 → 0.10.73
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/lib/create.js +2 -6
- package/lib/doctor/codes.js +0 -1
- package/lib/doctor/runner.js +0 -2
- package/package.json +2 -2
- package/templates/.agents/skills/webjs/references/built-ins.md +0 -2
- package/templates/.agents/skills/webjs/references/styling.md +1 -1
- package/templates/AGENTS.md +10 -1
- package/lib/doctor/probes/dark-theme.js +0 -91
package/lib/create.js
CHANGED
|
@@ -585,13 +585,9 @@ export async function scaffoldApp(name, cwd, opts = {}) {
|
|
|
585
585
|
// layout already writes asset(), so a fresh app is green on day one.
|
|
586
586
|
// Two checks are fatal with no entry here at all, NODE_VERSION and
|
|
587
587
|
// TSCONFIG_ERASABLE, because either would 500 the app at runtime;
|
|
588
|
-
//
|
|
589
|
-
// tokens, the generated layout writes them, and a layout that drops them
|
|
590
|
-
// leaves the stylesheet's .dark block dead and the OS setting ignored
|
|
591
|
-
// (#1628); elsewhere it is a design convention and stays a warning.
|
|
592
|
-
// Everything else keeps its default warn. Add a code with "off" to
|
|
588
|
+
// everything else keeps its default warn. Add a code with "off" to
|
|
593
589
|
// silence it, or "error" to make it fatal too.
|
|
594
|
-
doctor: { gate: { UNMARKED_ASSET_LINKS: 'error'
|
|
590
|
+
doctor: { gate: { UNMARKED_ASSET_LINKS: 'error' } },
|
|
595
591
|
// The dependency audit's allowlist (#1492), the ONE place an accepted
|
|
596
592
|
// advisory is listed, each with the reason it is safe. `webjs audit`
|
|
597
593
|
// (the CI step below) fails on every other advisory at `level` or above,
|
package/lib/doctor/codes.js
CHANGED
package/lib/doctor/runner.js
CHANGED
|
@@ -13,7 +13,6 @@ import { checkUnmarkedAssetLinks } from './probes/unmarked-asset-links.js';
|
|
|
13
13
|
import { checkFrameworkResolves, checkFrameworkLinks } from './probes/framework-resolves.js';
|
|
14
14
|
import { checkWorkspaceOverrides } from './probes/workspace-overrides.js';
|
|
15
15
|
import { checkAppIcon } from './probes/app-icon.js';
|
|
16
|
-
import { checkDarkTheme } from './probes/dark-theme.js';
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* @typedef {import('./codes.js').DoctorResult} DoctorResult
|
|
@@ -69,7 +68,6 @@ export async function runDoctorChecks(appDir, opts = {}) {
|
|
|
69
68
|
checkUnmarkedAssetLinks(appDir),
|
|
70
69
|
Promise.resolve(checkWorkspaceOverrides(appDir)),
|
|
71
70
|
Promise.resolve(checkAppIcon(appDir)),
|
|
72
|
-
checkDarkTheme(appDir),
|
|
73
71
|
]);
|
|
74
72
|
// Attach the stable machine code to every result (#975). Centralized here so
|
|
75
73
|
// each check function stays free of the code-contract concern.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.73",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The CLI for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Runs the dev and production servers, scaffolds apps, validates conventions, and drives the database. Node 24+ or Bun.",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"README.md"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@webjsdev/mcp": "^0.1.
|
|
20
|
+
"@webjsdev/mcp": "^0.1.17",
|
|
21
21
|
"@webjsdev/server": "^0.8.89",
|
|
22
22
|
"@webjsdev/ui": "^0.3.15"
|
|
23
23
|
},
|
|
@@ -298,8 +298,6 @@ Two guarantees worth knowing. A result that could not check (a network or toolch
|
|
|
298
298
|
|
|
299
299
|
`APP_ICON` warns while the favicon is still the scaffold's placeholder `app/icon.svg` (marked `data-webjs-placeholder`). Replace it with the app's own icon (`references/routing-and-pages.md`, "App icon and manifest").
|
|
300
300
|
|
|
301
|
-
`DARK_THEME_UNREACHABLE` warns when the app defines colour tokens with a light value only and nothing applies a dark half: no `light-dark()`, no `@media (prefers-color-scheme: dark)` rule, no theme script in the root layout. The scaffold gates it `error` in its `package.json` because its AGENTS.md mandates `light-dark()` tokens; the fix is the token block in `references/styling.md`.
|
|
302
|
-
|
|
303
301
|
### Dependency audit allowlist
|
|
304
302
|
|
|
305
303
|
`webjs audit` runs `npm audit` or `bun audit` (by the nearest lockfile, so a workspace member uses the root's) and fails on any advisory at or above `webjs.audit.level` (default `high`) that `webjs.audit.ignore` does not list. The scaffold's `Security: dependency audit` CI step runs it.
|
|
@@ -193,7 +193,7 @@ Whichever form you use, a token nothing references is dropped in both, so an unu
|
|
|
193
193
|
</style>
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
-
`light-dark()` is a native CSS function (CSS Color 5, Baseline 2024), not a library, so nothing to import. A single-theme app drops the `[data-theme]` rules and gives each token one colour.
|
|
196
|
+
`light-dark()` is a native CSS function (CSS Color 5, Baseline 2024), not a library, so nothing to import. A single-theme app drops the `[data-theme]` rules and gives each token one colour.
|
|
197
197
|
|
|
198
198
|
**A manual theme toggle** writes `data-theme` on `<html>` (`light` / `dark`, or removes it for "follow the OS"). If you use `@webjsdev/ui` components, ALSO keep the `.dark` class in sync (the ui kit keys its own tokens off `.dark`), and apply the saved choice in a tiny inline `<script>` in the layout head so there is no first-paint flash. Verify dark mode in a real browser. Light mode passing proves nothing about dark.
|
|
199
199
|
|
package/templates/AGENTS.md
CHANGED
|
@@ -36,7 +36,16 @@ This is what separates a working app from a broken one.
|
|
|
36
36
|
|
|
37
37
|
Derive the type at every boundary from its source. Never reach for `any`, and
|
|
38
38
|
never `unknown` where a real type exists. The rule is step 8 of the skill's "Default
|
|
39
|
-
Workflow"; the
|
|
39
|
+
Workflow"; the forms to reach for:
|
|
40
|
+
|
|
41
|
+
- A database row: `typeof table.$inferSelect` (`$inferInsert` for a write),
|
|
42
|
+
carried into a shipping component with `import type`.
|
|
43
|
+
- An action's input: a named `interface`. Its result: `ActionResult<T>`.
|
|
44
|
+
- Routing files: `PageProps<'/blog/[slug]'>`, `LayoutProps`,
|
|
45
|
+
`RouteHandlerContext`. `npx webjsdev types` writes the typed `Route` union.
|
|
46
|
+
- A reactive property: `prop<Student>(Object)`, `prop<Tag[]>(Array)`.
|
|
47
|
+
|
|
48
|
+
The full ladder, with an end-to-end example, is
|
|
40
49
|
`.agents/skills/webjs/references/typescript.md`.
|
|
41
50
|
|
|
42
51
|
Keep server-only code (database drivers, secrets, `node:*` builtins) in
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { join, relative } from 'node:path';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @typedef {import('../codes.js').DoctorResult} DoctorResult
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const ROOT_LAYOUT = /^app\/layout\.(?:js|mjs|ts|mts)$/;
|
|
10
|
-
/** Stylesheet sources: the Tailwind input and any hand-written sheet; never the compiled output. */
|
|
11
|
-
const STYLE_SOURCE = /^(?:public|styles)\/(?:.+\/)?[^/]+\.css$/;
|
|
12
|
-
const COMPILED_OR_VENDORED = /(?:^|\/)(?:tailwind\.css|node_modules\/|\.webjs\/|dist\/)/;
|
|
13
|
-
/** A colour token definition: the two every palette starts from. */
|
|
14
|
-
const TOKEN_DEF = /--(?:background|foreground)\s*:/;
|
|
15
|
-
/** Any of these makes the dark half reachable. */
|
|
16
|
-
const DUAL_TOKENS = /light-dark\s*\(/;
|
|
17
|
-
const SCHEME_QUERY = /prefers-color-scheme/;
|
|
18
|
-
/** A head script that applies a saved or detected theme. */
|
|
19
|
-
const THEME_SCRIPT = /<script\b[^>]*>[\s\S]*?(?:prefers-color-scheme|localStorage|data-theme|classList\.(?:add|toggle)\(\s*['"`]dark['"`]|dataset\.theme)[\s\S]*?<\/script>/;
|
|
20
|
-
|
|
21
|
-
/** CSS and JS block comments and line comments, so a commented-out token or hint does not count. */
|
|
22
|
-
function stripComments(text) {
|
|
23
|
-
return text.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The app's colour-token sources: the root layout (where the generated app
|
|
28
|
-
* writes its palette) and the stylesheet sources under `public/` and `styles/`.
|
|
29
|
-
* @param {string} appDir
|
|
30
|
-
* @returns {Promise<Array<{ rel: string, content: string }>>}
|
|
31
|
-
*/
|
|
32
|
-
async function tokenSources(appDir) {
|
|
33
|
-
/** @type {Array<{ rel: string, content: string }>} */
|
|
34
|
-
const out = [];
|
|
35
|
-
const read = async (rel) => {
|
|
36
|
-
try { out.push({ rel, content: await readFile(join(appDir, rel), 'utf8') }); } catch { /* unreadable: not a source */ }
|
|
37
|
-
};
|
|
38
|
-
for (const ext of ['ts', 'js', 'mts', 'mjs']) {
|
|
39
|
-
const rel = `app/layout.${ext}`;
|
|
40
|
-
if (existsSync(join(appDir, rel))) { await read(rel); break; }
|
|
41
|
-
}
|
|
42
|
-
for (const dir of ['public', 'styles']) {
|
|
43
|
-
const abs = join(appDir, dir);
|
|
44
|
-
if (!existsSync(abs)) continue;
|
|
45
|
-
for (const e of readdirSync(abs, { recursive: true, withFileTypes: true })) {
|
|
46
|
-
if (!e.isFile() || !e.name.endsWith('.css')) continue;
|
|
47
|
-
const rel = relative(appDir, join(e.parentPath || abs, e.name)).split('\\').join('/');
|
|
48
|
-
if (STYLE_SOURCE.test(rel) && !COMPILED_OR_VENDORED.test(rel)) await read(rel);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return out;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* `DARK_THEME_UNREACHABLE` (#1628): the app defines colour tokens but nothing
|
|
56
|
-
* applies a dark half. The scaffold's stylesheet keeps the ui kit's `.dark`
|
|
57
|
-
* token block, and `AGENTS.md` asks for `light-dark()` tokens under
|
|
58
|
-
* `color-scheme: light dark`; a layout that uses neither, has no
|
|
59
|
-
* `prefers-color-scheme` rule and runs no theme script ships dead CSS and
|
|
60
|
-
* ignores the OS setting, which no light-mode screenshot reveals. A design
|
|
61
|
-
* convention rather than a runtime break, so it lives in doctor (WARN by
|
|
62
|
-
* default) and the scaffold gates it `error` because its AGENTS.md mandates
|
|
63
|
-
* the tokens. Silent when the app defines no colour tokens at all.
|
|
64
|
-
* @param {string} appDir
|
|
65
|
-
* @returns {Promise<DoctorResult>}
|
|
66
|
-
*/
|
|
67
|
-
export async function checkDarkTheme(appDir) {
|
|
68
|
-
const name = 'dark-theme';
|
|
69
|
-
if (!existsSync(join(appDir, 'app'))) {
|
|
70
|
-
return { name, status: 'pass', message: 'no app/ directory to analyse' };
|
|
71
|
-
}
|
|
72
|
-
const sources = await tokenSources(appDir);
|
|
73
|
-
const defining = sources.filter((s) => TOKEN_DEF.test(stripComments(s.content)));
|
|
74
|
-
if (!defining.length) {
|
|
75
|
-
return { name, status: 'pass', message: 'no colour tokens defined (nothing to reach)' };
|
|
76
|
-
}
|
|
77
|
-
const all = sources.map((s) => stripComments(s.content)).join('\n');
|
|
78
|
-
const layout = sources.find((s) => ROOT_LAYOUT.test(s.rel));
|
|
79
|
-
if (DUAL_TOKENS.test(all) || SCHEME_QUERY.test(all) || (layout && THEME_SCRIPT.test(layout.content))) {
|
|
80
|
-
return { name, status: 'pass', message: 'the dark half of the colour tokens is reachable' };
|
|
81
|
-
}
|
|
82
|
-
const where = defining.map((s) => s.rel).join(' and ');
|
|
83
|
-
return {
|
|
84
|
-
name,
|
|
85
|
-
status: 'warn',
|
|
86
|
-
message:
|
|
87
|
-
`colour tokens are defined in ${where} with a light value only: nothing applies a dark half (no light-dark(), no prefers-color-scheme rule, no theme script in the root layout), so the OS dark setting is ignored and any .dark block is dead CSS`,
|
|
88
|
-
fix:
|
|
89
|
-
'Write each colour token ONCE as light-dark(LIGHT, DARK) under `color-scheme: light dark` in the root layout (the block in .agents/skills/webjs/references/styling.md), or give the tokens an `@media (prefers-color-scheme: dark)` half, or add a head <script> that applies the saved theme (data-theme / the .dark class). Then check the app in a real browser with the OS in dark mode.',
|
|
90
|
-
};
|
|
91
|
-
}
|