@webjsdev/cli 0.10.22 → 0.10.23

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/bin/webjs.js CHANGED
@@ -47,7 +47,7 @@ const USAGE = `webjs commands:
47
47
  webjs test [--server|--browser] Run server + browser tests
48
48
  webjs check [--json] Run correctness checks on the app (--json emits structured violations)
49
49
  webjs mcp Start the read-only MCP server (routes / actions / components / check)
50
- webjs doctor Verify project health (Node, tsconfig, env, vendor pins, importmap coherence, @webjsdev versions, git hook)
50
+ webjs doctor Verify project health (Node, tsconfig, env, vendor pins, importmap coherence, @webjsdev versions, git hook, page/layout elision)
51
51
  webjs types Generate .webjs/routes.d.ts (typed Route union + per-route params)
52
52
  webjs typecheck [tsc args...] Type-check the app with the project's tsc --noEmit (non-zero on errors)
53
53
  webjs create <name> [--template full-stack|api|saas] [--db sqlite|postgres] [--runtime node|bun] [--no-install] Scaffold a new webjs app
package/lib/doctor.js CHANGED
@@ -36,7 +36,7 @@
36
36
 
37
37
  import { existsSync, statSync } from 'node:fs';
38
38
  import { readFile } from 'node:fs/promises';
39
- import { join } from 'node:path';
39
+ import { join, relative } from 'node:path';
40
40
  import { checkNodeInline } from './node-preflight.js';
41
41
 
42
42
  /**
@@ -795,6 +795,53 @@ function checkGitHook(appDir) {
795
795
  * instead of a real live resolve / node_modules read.
796
796
  * @returns {Promise<DoctorResult[]>}
797
797
  */
798
+ /**
799
+ * Advisory (#646): name why a page/layout SHIPS its module to the browser
800
+ * instead of being elided. A page/layout that is a pure carrier (import-only
801
+ * #605 / inert #179) stays out of the browser; one that ships whole is pinned
802
+ * by a specific client-effecting NON-component in its closure (a util touching
803
+ * a client global, a module-scope side effect, a bare side-effect import) or by
804
+ * its own client work. This turns that invisible #605/#179 regression into a
805
+ * named line. WARN only: a page legitimately MAY ship, and the analyser is
806
+ * biased toward shipping by design (server AGENTS invariant 7), so this is a
807
+ * "you may not have intended this" hint, never a hard fail.
808
+ * @param {string} appDir
809
+ * @returns {Promise<DoctorResult>}
810
+ */
811
+ async function checkElisionCarriers(appDir) {
812
+ const name = 'Page/layout elision (carrier hygiene)';
813
+ let report;
814
+ try {
815
+ const { analyzeAppElision } = await import('@webjsdev/server');
816
+ report = await analyzeAppElision(appDir);
817
+ } catch {
818
+ // Analysis unavailable (no app, malformed, server import failed): no advice.
819
+ return { name, status: 'pass', message: 'not analysed (no routable app or analysis unavailable)' };
820
+ }
821
+ if (!report.analysed) {
822
+ return { name, status: 'pass', message: 'not analysed (no routable app, or elision is disabled)' };
823
+ }
824
+ if (report.shipped.length === 0) {
825
+ return { name, status: 'pass', message: 'every page/layout is elided (a pure import-only or inert carrier)' };
826
+ }
827
+ const rel = (f) => relative(appDir, f) || f;
828
+ // Name the FIRST client-effecting blocker (there may be more than one; the
829
+ // module stays shipped until every such blocker is moved out).
830
+ const lines = report.shipped.map(({ file, blocker, reason }) =>
831
+ blocker
832
+ ? `${rel(file)} ships whole. Its first client-effecting blocker is ${rel(blocker)}, which ${reason} and is not a component`
833
+ : `${rel(file)} ships whole because it ${reason}`,
834
+ );
835
+ return {
836
+ name,
837
+ status: 'warn',
838
+ message:
839
+ `${report.shipped.length} page/layout module(s) ship to the browser instead of being elided:\n` +
840
+ lines.map((l) => ` ${l}`).join('\n'),
841
+ fix: 'Move the client work out of the page/layout closure (into a component, or a .server module reached through an action) so the carrier can be elided, or accept that it ships. See agent-docs/components.md.',
842
+ };
843
+ }
844
+
798
845
  export async function runDoctorChecks(appDir, opts = {}) {
799
846
  const cliDir = opts.cliDir || new URL('.', import.meta.url).pathname;
800
847
  const results = await Promise.all([
@@ -806,6 +853,7 @@ export async function runDoctorChecks(appDir, opts = {}) {
806
853
  checkWebjsVersions(appDir),
807
854
  checkImportmapCoherence(appDir, opts),
808
855
  Promise.resolve(checkGitHook(appDir)),
856
+ checkElisionCarriers(appDir),
809
857
  ]);
810
858
  return results;
811
859
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/cli",
3
- "version": "0.10.22",
3
+ "version": "0.10.23",
4
4
  "type": "module",
5
5
  "description": "webjs CLI - dev, start, create, db",
6
6
  "bin": {
@@ -506,7 +506,7 @@ SSR, page actions, server-action RPC, auth + CSRF), drive
506
506
 
507
507
  ```ts
508
508
  import { createRequestHandler } from '@webjsdev/server';
509
- import { testRequest, getCsrf, invokeActionForTest, loginAndGetCookies, withSessionCookie }
509
+ import { testRequest, invokeActionForTest, loginAndGetCookies, withSessionCookie }
510
510
  from '@webjsdev/server/testing';
511
511
 
512
512
  const app = await createRequestHandler({ appDir: process.cwd(), dev: true });
@@ -524,8 +524,9 @@ const out = await invokeActionForTest(app, 'modules/posts/actions/create.server.
524
524
 
525
525
  Prefer `invokeActionForTest` over a direct import of the action when you want
526
526
  to verify the production contract: it exercises the wire serializer (a `Date` /
527
- `Map` arg survives), CSRF, and prod error sanitization, which a direct call
528
- bypasses. The saas template's `test/auth/auth.test.ts` is a worked example.
527
+ `Map` arg survives), the Origin / Sec-Fetch-Site CSRF check (it models a
528
+ same-origin POST), and prod error sanitization, which a direct call bypasses.
529
+ The saas template's `test/auth/auth.test.ts` is a worked example.
529
530
 
530
531
  This is also why the auth test lives at `test/auth/auth.test.ts` (the
531
532
  feature-folder convention), NOT `test/unit/auth.test.ts`. Test KIND is a