@webjsdev/cli 0.10.49 → 0.10.51

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 (59) hide show
  1. package/README.md +5 -3
  2. package/bin/webjs.js +133 -30
  3. package/lib/api-gallery.js +6 -7
  4. package/lib/app-name.js +208 -0
  5. package/lib/create.js +67 -18
  6. package/lib/doctor.js +479 -7
  7. package/package.json +2 -2
  8. package/templates/.agents/rules/workflow.md +9 -1
  9. package/templates/.agents/skills/webjs/SKILL.md +26 -11
  10. package/templates/.agents/skills/webjs/references/auth-and-sessions.md +2 -2
  11. package/templates/.agents/skills/webjs/references/built-ins.md +38 -6
  12. package/templates/.agents/skills/webjs/references/client-router-and-streaming.md +17 -2
  13. package/templates/.agents/skills/webjs/references/components.md +16 -2
  14. package/templates/.agents/skills/webjs/references/data-and-actions.md +42 -8
  15. package/templates/.agents/skills/webjs/references/muscle-memory-gotchas.md +98 -1
  16. package/templates/.agents/skills/webjs/references/optimistic-ui.md +35 -14
  17. package/templates/.agents/skills/webjs/references/routing-and-pages.md +35 -16
  18. package/templates/.agents/skills/webjs/references/runtime.md +5 -1
  19. package/templates/.agents/skills/webjs/references/service-worker.md +3 -1
  20. package/templates/.agents/skills/webjs/references/styling.md +30 -1
  21. package/templates/.agents/skills/webjs/references/testing.md +61 -3
  22. package/templates/.agents/skills/webjs/references/typescript.md +71 -2
  23. package/templates/.agents/skills/webjs/references/ui-kit.md +5 -2
  24. package/templates/.github/pull_request_template.md +1 -0
  25. package/templates/.github/workflows/ci.yml +13 -0
  26. package/templates/AGENTS.md +31 -5
  27. package/templates/CONVENTIONS.md +4 -1
  28. package/templates/gallery/app/examples/layout.ts +2 -1
  29. package/templates/gallery/app/examples/todo/page.ts +5 -17
  30. package/templates/gallery/app/features/auth/dashboard/layout.ts +2 -1
  31. package/templates/gallery/app/features/auth/signup/page.ts +4 -23
  32. package/templates/gallery/app/features/caching/page.ts +33 -9
  33. package/templates/gallery/app/features/file-storage/page.ts +8 -19
  34. package/templates/gallery/app/features/forms/page.ts +12 -38
  35. package/templates/gallery/app/features/layout.ts +6 -2
  36. package/templates/gallery/app/features/route-handler/data/route.ts +2 -1
  37. package/templates/gallery/app/features/server-actions/page.ts +43 -0
  38. package/templates/gallery/app/features/view-transitions/page.ts +1 -1
  39. package/templates/gallery/app/global-error.ts +7 -4
  40. package/templates/gallery/modules/auth/actions/signup.server.ts +27 -11
  41. package/templates/gallery/modules/auth/queries/current-user.server.ts +7 -5
  42. package/templates/gallery/modules/file-storage/actions/store-upload.server.ts +21 -10
  43. package/templates/gallery/modules/forms/actions/send-message.server.ts +34 -0
  44. package/templates/gallery/modules/gallery/nav.ts +2 -2
  45. package/templates/gallery/modules/server-actions/actions/bump-clock.server.ts +20 -0
  46. package/templates/gallery/modules/server-actions/actions/greet.test.ts +6 -5
  47. package/templates/gallery/modules/server-actions/components/clock-reader.ts +98 -0
  48. package/templates/gallery/modules/server-actions/queries/read-clock.server.ts +38 -0
  49. package/templates/gallery/modules/server-actions/utils/clock.server.ts +27 -0
  50. package/templates/gallery/modules/todo/actions/submit-todo.server.ts +31 -0
  51. package/templates/gallery/modules/todo/components/todo-app.ts +8 -5
  52. package/templates/gallery/modules/todo/queries/list-todos.server.ts +4 -2
  53. package/templates/gallery/modules/todo/types.ts +15 -10
  54. package/templates/gallery/test/auth/auth.test.ts +31 -16
  55. package/templates/partials/agents-playbook-api.md +5 -0
  56. package/templates/partials/agents-playbook-fullstack.md +5 -0
  57. package/templates/public/sw.js +11 -2
  58. package/templates/scripts/clear-gallery.mjs +11 -6
  59. package/templates/test/hello/e2e/hello.test.ts +18 -1
package/lib/create.js CHANGED
@@ -18,6 +18,7 @@ import { existsSync } from 'node:fs';
18
18
  import { createRequire } from 'node:module';
19
19
  import { spawnSync } from 'node:child_process';
20
20
  import { bunifyProse, bunifyDockerfile, bunifyCompose, bunifyCi } from './runtime-rewrite.js';
21
+ import { assertValidAppName } from './app-name.js';
21
22
 
22
23
  /**
23
24
  * Detect which package manager invoked us. Reads `npm_config_user_agent`,
@@ -166,8 +167,11 @@ async function writeUiBootstrap(appDir) {
166
167
  );
167
168
  await writeFile(join(appDir, 'lib', 'utils', 'dom.ts'), domContent);
168
169
 
169
- // 2) components.json: the same shape `webjsui init` writes for webjs
170
- // projects (see packages/ui/src/utils/detect-project.js). The utils alias
170
+ // 2) components.json: byte for byte what `webjsui init` writes (see the
171
+ // DEFAULT_ALIASES / DEFAULT_TAILWIND_CSS constants in
172
+ // packages/ui/src/commands/init.js, which #1129 made the single source of
173
+ // these values). Keep the two in step: an app that scaffolds and one that
174
+ // runs `webjs ui init` must end up with the same config. The utils alias
171
175
  // is lib/utils/cn so get-config.js's `+ '.ts'` resolves to lib/utils/cn.ts.
172
176
  // The theme CSS lives at styles/globals.css, NOT app/globals.css: app/ is
173
177
  // routing-only, so a non-routing stylesheet does not belong there.
@@ -255,9 +259,15 @@ function assertUiRegistryAvailable() {
255
259
  * @param {string} cwd Current working directory
256
260
  */
257
261
  export async function scaffoldApp(name, cwd, opts = {}) {
262
+ // Defence in depth, same as the template check below. `webjs create` already
263
+ // validates the name, but a programmatic caller can pass anything, and the
264
+ // name is interpolated into generated source as a template-literal value
265
+ // (#1066), so an unvalidated quote / backtick / `${` would emit a file that
266
+ // fails to parse. Throwing here happens before any directory is created.
267
+ assertValidAppName(name);
258
268
  const template = opts.template || 'full-stack';
259
- // A human-friendly display title for the example home page. The npm `name`
260
- // stays the raw slug (lowercase, hyphenated), but showing a hyphenated slug as
269
+ // A human-friendly display title for the example home page. The package
270
+ // `name` stays the raw slug as typed, but showing a hyphenated slug as
261
271
  // a hero title looks unpolished, so title-case it for display ("my-app" ->
262
272
  // "My App"). Replace this with your real brand anyway.
263
273
  const displayName = name.replace(/[-_]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
@@ -388,10 +398,12 @@ export async function scaffoldApp(name, cwd, opts = {}) {
388
398
  'test:browser': 'webjs test --browser',
389
399
  check: 'webjs check',
390
400
  typecheck: 'webjs typecheck',
391
- // Onboarding/setup-verify: a contributor runs `npm run doctor` after
392
- // cloning to assert the toolchain (Node floor, tsconfig flag, env drift,
393
- // vendor pins, @webjsdev versions, git hook). Local tool, NOT a CI gate
394
- // (its env-drift + network pin-freshness checks would make CI flaky).
401
+ // Project health: a contributor runs `npm run doctor` after cloning to
402
+ // assert the toolchain (Node floor, tsconfig flag, env drift, vendor
403
+ // pins, @webjsdev versions, git hook), and CI runs the same script. Which
404
+ // findings are FATAL comes from the `webjs.doctor.gate` block below, so
405
+ // the environment-shaped checks (env drift, pin freshness over the
406
+ // network, the git hook) stay warns and cannot make CI flaky.
395
407
  doctor: 'webjs doctor',
396
408
  'db:generate': 'webjs db generate',
397
409
  'db:migrate': 'webjs db migrate',
@@ -481,6 +493,17 @@ export async function scaffoldApp(name, cwd, opts = {}) {
481
493
  }),
482
494
  },
483
495
  start: { before: isApi ? ['webjs db migrate'] : ['webjs db migrate', cssBuildCmd] },
496
+ // Which doctor findings are FATAL is the app's own call (#1257), declared
497
+ // here rather than in the CI workflow so `npm run doctor` locally and the
498
+ // workflow step agree about what fails. UNMARKED_ASSET_LINKS starts at
499
+ // error because an un-versioned /public url is a real deploy-staleness
500
+ // bug (it shipped a visible regression on webjs.dev) and the generated
501
+ // layout already writes asset(), so a fresh app is green on day one.
502
+ // Two checks are fatal with no entry here at all, NODE_VERSION and
503
+ // TSCONFIG_ERASABLE, because either would 500 the app at runtime;
504
+ // everything else keeps its default warn. Add a code with "off" to
505
+ // silence it, or "error" to make it fatal too.
506
+ doctor: { gate: { UNMARKED_ASSET_LINKS: 'error' } },
484
507
  },
485
508
  }, null, 2) + '\n');
486
509
 
@@ -947,10 +970,13 @@ export default cors({
947
970
 
948
971
  // A GET server action (#488): a read declares its HTTP semantics via reserved
949
972
  // sibling exports the framework reads statically. 'method' makes the call ride
950
- // the URL (cacheable, ETag/304-aware, SSR-seeded on first paint); 'cache' is the
951
- // max-age in seconds (private by default, do NOT add { public: true } unless the
952
- // data is identical for EVERY visitor); 'tags' label the cached entry so a
953
- // mutation can evict it. One function per file.
973
+ // the URL and be ETag/304-aware; 'cache' is what makes the response cacheable at
974
+ // all (the max-age in seconds, private by default, do NOT add { public: true }
975
+ // unless the data is identical for EVERY visitor); 'tags' label the cached entry so a
976
+ // mutation can evict it. All three shape the RPC endpoint a browser import hits,
977
+ // so they do nothing for the in-process call in app/api/users/route.ts; they are
978
+ // here as the idiom to carry into a client that imports the action. One function
979
+ // per file.
954
980
  export const method = 'GET';
955
981
  export const cache = 30;
956
982
  export const tags = () => ['users'];
@@ -966,8 +992,12 @@ export async function listUsers() {
966
992
 
967
993
  // A mutation server action (#488). With no 'method' export it defaults to POST
968
994
  // (CSRF-protected, rich request body). 'invalidates' lists the cache tags to
969
- // evict on success, so the next listUsers() read refetches fresh instead of
970
- // serving a stale browser-cached value. One function per file.
995
+ // evict once the action completes without throwing, so a client that read
996
+ // listUsers() refetches fresh instead of serving a stale browser-cached value.
997
+ // (A returned { success: false } envelope still evicts, since the action ran.)
998
+ // It applies on the RPC endpoint a browser import hits. The route.ts in this
999
+ // template calls the function directly, which is in-process, so the config
1000
+ // exports do not fire there. One function per file.
971
1001
  export const invalidates = () => ['users'];
972
1002
  export async function createUser(input: { name: string; email: string }) {
973
1003
  // TODO: validate input, persist to database
@@ -977,6 +1007,13 @@ export async function createUser(input: { name: string; email: string }) {
977
1007
  await writeFile(join(appDir, 'app', 'api', 'users', 'route.ts'), `/**
978
1008
  * /api/users: thin route wrapper over typed server actions.
979
1009
  * Business logic lives in modules/users/, not here.
1010
+ *
1011
+ * These calls are in-process, so the actions' config exports do NOT apply here.
1012
+ * method / cache / tags / invalidates shape the RPC endpoint a browser import
1013
+ * hits, and nothing else applies them, so this endpoint sets its own headers if
1014
+ * it wants caching. The namespace form of the route() adapter from
1015
+ * \@webjsdev/server (import * as q; export const GET = route(q)) picks up the
1016
+ * declared validate and middleware, which are the two an endpoint can reuse.
980
1017
  */
981
1018
  import { listUsers } from '#modules/users/queries/list-users.server.ts';
982
1019
  import { createUser } from '#modules/users/actions/create-user.server.ts';
@@ -1134,7 +1171,8 @@ ${uiThemeRaw}
1134
1171
  // or shed the whole gallery at once with `gallery:clear`.
1135
1172
  await copyGallery(appDir);
1136
1173
 
1137
- await writeFile(join(appDir, 'app', 'layout.ts'), `import { html, cspNonce } from '@webjsdev/core';
1174
+ await writeFile(join(appDir, 'app', 'layout.ts'), `import { html, cspNonce, asset } from '@webjsdev/core';
1175
+ import type { LayoutProps } from '@webjsdev/core';
1138
1176
  import '#components/theme-toggle.ts';
1139
1177
 
1140
1178
  /**
@@ -1153,7 +1191,11 @@ import '#components/theme-toggle.ts';
1153
1191
  // lives at public/favicon.svg and serves at /public/favicon.svg.
1154
1192
  export const metadata = { icons: '/public/favicon.svg' };
1155
1193
 
1156
- export default function RootLayout({ children }: { children: unknown }) {
1194
+ // LayoutProps types every layout argument (children, params, searchParams,
1195
+ // url) from the framework, so children is a TemplateResult rather than an
1196
+ // untyped value. Derive types like this everywhere instead of widening to
1197
+ // unknown; see .agents/skills/webjs/references/typescript.md.
1198
+ export default function RootLayout({ children }: LayoutProps) {
1157
1199
  // Read the in-flight request's CSP nonce so the theme-detection inline script
1158
1200
  // passes strict CSP. Returns '' when no CSP nonce is set.
1159
1201
  const nonce = cspNonce();
@@ -1212,9 +1254,16 @@ export default function RootLayout({ children }: { children: unknown }) {
1212
1254
  public/tailwind.css by css:build (run automatically by the dev and start
1213
1255
  tasks; in dev it is also recompiled on request when a source changes, so
1214
1256
  it never goes stale). A real stylesheet, so the app is fully styled with
1215
- JavaScript DISABLED (no in-browser compile). -->
1257
+ JavaScript DISABLED (no in-browser compile).
1258
+
1259
+ asset() adds a content hash in production (/public/tailwind.css?v=...)
1260
+ and the framework then serves it immutable for a year, so a deploy that
1261
+ changes the CSS changes the url and no browser or CDN can serve the old
1262
+ bytes. Mark the thing that FETCHES: do not wrap a <link rel="preload">
1263
+ whose asset is really fetched by an @font-face url() in the CSS, or the
1264
+ preload can never match the request and the file downloads twice. -->
1216
1265
 
1217
- <link rel="stylesheet" href="/public/tailwind.css">
1266
+ <link rel="stylesheet" href=\${asset('/public/tailwind.css')}>
1218
1267
  <style>
1219
1268
  /* Design tokens: ONE definition per colour via light-dark(LIGHT, DARK), so
1220
1269
  a palette change lands in a single place (DRY). The token NAMES are