@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.
- package/README.md +5 -3
- package/bin/webjs.js +133 -30
- package/lib/api-gallery.js +6 -7
- package/lib/app-name.js +208 -0
- package/lib/create.js +67 -18
- package/lib/doctor.js +479 -7
- package/package.json +2 -2
- package/templates/.agents/rules/workflow.md +9 -1
- package/templates/.agents/skills/webjs/SKILL.md +26 -11
- package/templates/.agents/skills/webjs/references/auth-and-sessions.md +2 -2
- package/templates/.agents/skills/webjs/references/built-ins.md +38 -6
- package/templates/.agents/skills/webjs/references/client-router-and-streaming.md +17 -2
- package/templates/.agents/skills/webjs/references/components.md +16 -2
- package/templates/.agents/skills/webjs/references/data-and-actions.md +42 -8
- package/templates/.agents/skills/webjs/references/muscle-memory-gotchas.md +98 -1
- package/templates/.agents/skills/webjs/references/optimistic-ui.md +35 -14
- package/templates/.agents/skills/webjs/references/routing-and-pages.md +35 -16
- package/templates/.agents/skills/webjs/references/runtime.md +5 -1
- package/templates/.agents/skills/webjs/references/service-worker.md +3 -1
- package/templates/.agents/skills/webjs/references/styling.md +30 -1
- package/templates/.agents/skills/webjs/references/testing.md +61 -3
- package/templates/.agents/skills/webjs/references/typescript.md +71 -2
- package/templates/.agents/skills/webjs/references/ui-kit.md +5 -2
- package/templates/.github/pull_request_template.md +1 -0
- package/templates/.github/workflows/ci.yml +13 -0
- package/templates/AGENTS.md +31 -5
- package/templates/CONVENTIONS.md +4 -1
- package/templates/gallery/app/examples/layout.ts +2 -1
- package/templates/gallery/app/examples/todo/page.ts +5 -17
- package/templates/gallery/app/features/auth/dashboard/layout.ts +2 -1
- package/templates/gallery/app/features/auth/signup/page.ts +4 -23
- package/templates/gallery/app/features/caching/page.ts +33 -9
- package/templates/gallery/app/features/file-storage/page.ts +8 -19
- package/templates/gallery/app/features/forms/page.ts +12 -38
- package/templates/gallery/app/features/layout.ts +6 -2
- package/templates/gallery/app/features/route-handler/data/route.ts +2 -1
- package/templates/gallery/app/features/server-actions/page.ts +43 -0
- package/templates/gallery/app/features/view-transitions/page.ts +1 -1
- package/templates/gallery/app/global-error.ts +7 -4
- package/templates/gallery/modules/auth/actions/signup.server.ts +27 -11
- package/templates/gallery/modules/auth/queries/current-user.server.ts +7 -5
- package/templates/gallery/modules/file-storage/actions/store-upload.server.ts +21 -10
- package/templates/gallery/modules/forms/actions/send-message.server.ts +34 -0
- package/templates/gallery/modules/gallery/nav.ts +2 -2
- package/templates/gallery/modules/server-actions/actions/bump-clock.server.ts +20 -0
- package/templates/gallery/modules/server-actions/actions/greet.test.ts +6 -5
- package/templates/gallery/modules/server-actions/components/clock-reader.ts +98 -0
- package/templates/gallery/modules/server-actions/queries/read-clock.server.ts +38 -0
- package/templates/gallery/modules/server-actions/utils/clock.server.ts +27 -0
- package/templates/gallery/modules/todo/actions/submit-todo.server.ts +31 -0
- package/templates/gallery/modules/todo/components/todo-app.ts +8 -5
- package/templates/gallery/modules/todo/queries/list-todos.server.ts +4 -2
- package/templates/gallery/modules/todo/types.ts +15 -10
- package/templates/gallery/test/auth/auth.test.ts +31 -16
- package/templates/partials/agents-playbook-api.md +5 -0
- package/templates/partials/agents-playbook-fullstack.md +5 -0
- package/templates/public/sw.js +11 -2
- package/templates/scripts/clear-gallery.mjs +11 -6
- 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:
|
|
170
|
-
//
|
|
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
|
|
260
|
-
// stays the raw slug
|
|
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
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
//
|
|
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
|
|
951
|
-
// max-age in seconds
|
|
952
|
-
// data is identical for EVERY visitor); 'tags' label the cached entry so a
|
|
953
|
-
// mutation can evict it.
|
|
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
|
|
970
|
-
// serving a stale browser-cached value.
|
|
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
|
-
|
|
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
|
|
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
|