@webjsdev/cli 0.10.33 → 0.10.35

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 CHANGED
@@ -542,9 +542,12 @@ export async function scaffoldApp(name, cwd, opts = {}) {
542
542
  // Environment variables
543
543
  '.env.example',
544
544
  // Project-level gitignore (node_modules, .webjs, .env, OS junk).
545
- // The SQLite dev.db rule is appended programmatically below so it
546
- // only appears for the sqlite dialect.
547
- '.gitignore',
545
+ // Shipped as `gitignore` (no dot) and renamed to `.gitignore` on copy:
546
+ // npm STRIPS a `.gitignore` from a published tarball, so a dotfile name
547
+ // would arrive missing and the app would ship without a `.env` ignore
548
+ // (dogfood #845). The SQLite dev.db rule is appended programmatically
549
+ // below so it only appears for the sqlite dialect.
550
+ 'gitignore',
548
551
  // Git hooks (blocks commits on main)
549
552
  '.hooks/pre-commit',
550
553
  // Claude Code config + hooks
@@ -617,14 +620,17 @@ export async function scaffoldApp(name, cwd, opts = {}) {
617
620
  for (const f of templateFiles) {
618
621
  const src = join(TEMPLATES, f);
619
622
  if (existsSync(src)) {
620
- await mkdir(dirname(join(appDir, f)), { recursive: true });
623
+ // `gitignore` ships without a dot (npm strips a published `.gitignore`)
624
+ // and is written to `.gitignore` in the generated app.
625
+ const dest = f === 'gitignore' ? '.gitignore' : f;
626
+ await mkdir(dirname(join(appDir, dest)), { recursive: true });
621
627
  let content = await readFile(src, 'utf8');
622
628
  content = content.replace(/\{\{APP_NAME\}\}/g, name);
623
629
  if (isBun) {
624
630
  if (PROSE_REWRITE.has(f)) content = bunifyProse(content);
625
631
  else if (FILE_REWRITE[f]) content = FILE_REWRITE[f](content);
626
632
  }
627
- await writeFile(join(appDir, f), content);
633
+ await writeFile(join(appDir, dest), content);
628
634
  }
629
635
  }
630
636
 
@@ -837,7 +843,9 @@ export default defineConfig({
837
843
  const cur = await readFile(gitignore, 'utf8');
838
844
  if (!cur.includes('db/dev.db')) await writeFile(gitignore, cur + gitignoreExtra);
839
845
  } else {
840
- await writeFile(gitignore, 'node_modules\n.webjs\n' + gitignoreExtra);
846
+ // Defense in depth: if the template gitignore is ever absent, still
847
+ // never leave a real `.env` trackable (dogfood #845).
848
+ await writeFile(gitignore, 'node_modules\n.webjs\n.env\n.env.*\n!.env.example\n' + gitignoreExtra);
841
849
  }
842
850
  }
843
851
 
@@ -1093,6 +1101,15 @@ export default function RootLayout({ children }: { children: unknown }) {
1093
1101
  const nonce = cspNonce();
1094
1102
  return html\`
1095
1103
  <script nonce="\${nonce}">
1104
+ // ===== OPTIONAL: light/dark theme apparatus (remove as one unit) =====
1105
+ // This IIFE reads the saved or OS theme and toggles the data-theme
1106
+ // attribute plus the dark class the ui kit reads, so the token VALUES in
1107
+ // the root, dark, and data-theme style blocks below switch. It is what
1108
+ // makes the app theme-aware. Building a SINGLE-theme app of your own?
1109
+ // Delete this IIFE, delete the dark and light style blocks below, and set
1110
+ // your palette once on the root selector. That removes the wiring so it
1111
+ // cannot fight your own colours (it will not override a plain root
1112
+ // palette). The header-measure IIFE that follows is unrelated, keep it.
1096
1113
  (function(){
1097
1114
  try {
1098
1115
  var mq = window.matchMedia('(prefers-color-scheme: light)');
@@ -1112,6 +1129,7 @@ export default function RootLayout({ children }: { children: unknown }) {
1112
1129
  mq.addEventListener('change', apply);
1113
1130
  } catch (_) {}
1114
1131
  })();
1132
+ // ===== end optional theme apparatus =====
1115
1133
  // The header is position:fixed (not sticky): a sticky header flickers on
1116
1134
  // iOS WebKit during a client-router nav. fixed leaves normal flow, so
1117
1135
  // --header-h reserves its height for the content below. Measured here so
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/cli",
3
- "version": "0.10.33",
3
+ "version": "0.10.35",
4
4
  "type": "module",
5
5
  "description": "webjs CLI - dev, start, create, db",
6
6
  "bin": {
@@ -351,7 +351,11 @@ app/ ROUTING ONLY: thin route adapters (import from modules/
351
351
  layout.ts root layout, wraps every page
352
352
  error.ts error boundary (render failures → user-friendly)
353
353
  loading.ts Suspense fallback for sibling page
354
- not-found.ts custom 404 page
354
+ not-found.ts custom 404 page (nearest wins on notFound())
355
+ forbidden.ts 403 page (nearest wins on forbidden())
356
+ unauthorized.ts 401 page (nearest wins on unauthorized())
357
+ global-error.ts root-only app-wide error boundary (owns its <html>)
358
+ global-not-found.ts root-only 404 for an unmatched-anywhere URL
355
359
  middleware.ts global request middleware
356
360
  [slug]/page.ts dynamic route segment
357
361
  [...rest]/page.ts catch-all
@@ -392,6 +396,8 @@ test/<feature>/ feature-scoped tests, one folder per concern
392
396
  e2e/<name>.test.ts end-to-end test (full app boot, opt in via WEBJS_E2E=1)
393
397
  smoke/<name>.test.ts fast post-deploy sanity check
394
398
  middleware.ts root middleware (optional, outermost)
399
+ instrumentation.ts optional boot hook: register() runs once; wire APM via setOnError
400
+ instrumentation-client.ts optional client boot hook, runs first before app modules
395
401
  ```
396
402
 
397
403
  ### The gallery (reference content, prune it)
@@ -495,11 +495,17 @@ Routes live under `app/` and follow NextJs App Router conventions:
495
495
  - `app/**/layout.ts`: Layout wrapper
496
496
  - `app/**/error.ts`: Error boundary
497
497
  - `app/**/middleware.ts`: Per-segment middleware
498
+ - `instrumentation.ts` (app root): optional boot hook. `register()` runs once at startup; call `setOnError()` (from `@webjsdev/server`) inside it to wire an APM error sink (composes with `createRequestHandler({ onError })`).
499
+ - `instrumentation-client.ts` (app root): optional client boot hook, imported first in the browser boot so it runs before app modules.
498
500
 
499
501
  **Special route files:**
500
502
  - `app/**/error.ts`: Error boundary. Default export receives `{ error }`, returns `TemplateResult`. Nearest boundary catches errors from pages below it.
501
503
  - `app/**/loading.ts`: Loading state. Auto-wraps the sibling page in a `Suspense` boundary. Shown while async page functions resolve.
502
504
  - `app/**/not-found.ts`: 404 page. Nearest wins when `notFound()` is thrown.
505
+ - `app/**/forbidden.ts`: 403 page. Nearest wins when `forbidden()` is thrown (an authenticated user who lacks permission).
506
+ - `app/**/unauthorized.ts`: 401 page. Nearest wins when `unauthorized()` is thrown (a request that is not authenticated).
507
+ - `app/global-error.ts` (root only): app-wide error boundary, tried after nested `error.ts` boundaries. Renders its OWN `<!doctype><html><body>` document.
508
+ - `app/global-not-found.ts` (root only): 404 for a URL that matches nothing anywhere.
503
509
  - `app/sitemap.ts`: Dynamic sitemap at `/sitemap.xml`. Export a function returning an array of `{ url, lastModified }`.
504
510
  - `app/robots.ts`: Dynamic robots.txt at `/robots.txt`.
505
511
  - `app/manifest.ts`: Web app manifest at `/manifest.json`.
@@ -928,7 +934,11 @@ global light-DOM namespace.
928
934
 
929
935
  Every page wraps its output in `<div class="page-<route>">`. Every
930
936
  layout wraps in `<div class="layout-<name>">`. Components scope via
931
- their tag. Styles colocate as `const STYLES = css\`…\`` + `<style>${'$'}{STYLES.text}</style>`.
937
+ their tag. In a PAGE or LAYOUT (which render server-only and never
938
+ hydrate) styles colocate as `const STYLES = css\`…\`` + `<style>${'$'}{STYLES.text}</style>`.
939
+ In a COMPONENT, do NOT interpolate into `<style>` (the client drops the
940
+ raw-text hole on hydrate, so the styles vanish); use `static styles =
941
+ css\`…\`` or Tailwind classes instead.
932
942
 
933
943
  ```ts
934
944
  // app/dashboard/page.ts
@@ -2,6 +2,10 @@
2
2
  // (possibly async) function receiving { params, searchParams, url }; it runs
3
3
  // ONLY on the server. Throw notFound() / redirect() to short-circuit.
4
4
  //
5
+ // params / searchParams are BOTH synchronously readable AND awaitable: read
6
+ // `params.id` directly, or `const { id } = await params` (the Next 15/16
7
+ // pattern, supported so that muscle memory transfers). Either form is correct.
8
+ //
5
9
  // Type-safe routes: instead of hand-typing `{ params: { id: string } }`, type
6
10
  // the props with PageProps<'<route>'>. `webjs types` (run automatically by
7
11
  // `webjs dev`) generates .webjs/routes.d.ts with a Route union and per-route
@@ -11,14 +15,18 @@
11
15
  import { html } from '@webjsdev/core';
12
16
  import type { PageProps } from '@webjsdev/core';
13
17
 
14
- export default function RoutingParam({ params }: PageProps<'/features/routing/[id]'>) {
18
+ export default async function RoutingParam({ params }: PageProps<'/features/routing/[id]'>) {
19
+ // The Next-style await also works; `params.id` sync would be identical.
20
+ const { id } = await params;
15
21
  return html`
16
22
  <h1 class="text-h2 font-bold mb-4">Route param</h1>
17
- <p>The <code>[id]</code> segment is: <strong>${params.id}</strong></p>
23
+ <p>The <code>[id]</code> segment is: <strong>${id}</strong></p>
18
24
  <p class="text-muted-foreground text-sm mt-3">
19
25
  Typed with <code class="font-mono">PageProps&lt;'/features/routing/[id]'&gt;</code>,
20
26
  so <code class="font-mono">params.id</code> is a checked
21
27
  <code class="font-mono">string</code> from the generated route union.
28
+ <code class="font-mono">params</code> is awaitable too:
29
+ <code class="font-mono">const { id } = await params</code> works, same value.
22
30
  </p>
23
31
  <p class="mt-3"><a class="text-primary" href="/features/routing">Back</a></p>
24
32
  `;
@@ -0,0 +1,68 @@
1
+ # deps
2
+ node_modules/
3
+
4
+ # webjs / framework caches.
5
+ # `.webjs/routes.d.ts` (the generated route-types overlay, regenerated per
6
+ # machine by `webjs types` / `webjs dev`) is correctly ignored by `**/.webjs/*`.
7
+ # `.webjs/vendor/` is the EXCEPTION: it holds the committed importmap
8
+ # manifest (.webjs/vendor/importmap.json) and optionally the vendored
9
+ # bundle files (after `webjs vendor pin --download`). Both ship to
10
+ # production via source control so the server doesn't need
11
+ # api.jspm.io reachable at boot. Pattern is `**/.webjs/*` ignored,
12
+ # `.webjs/vendor/` un-ignored, mirroring Rails' config/importmap.rb
13
+ # + vendor/javascript/ being committed.
14
+ #
15
+ # DO NOT "simplify" the three lines below to `.webjs/`. Git's
16
+ # gitignore semantics excludes the parent first; once the parent is
17
+ # excluded, no `!**/.webjs/vendor/` negation can ever re-include children
18
+ # (the failure is silent: `webjs vendor pin` runs, writes files, and
19
+ # git ignores them with no warning). The `gitignore-vendor-not-ignored`
20
+ # lint rule (run via `webjs check`) verifies this with
21
+ # `git check-ignore` and will fail CI if the pattern regresses.
22
+ #
23
+ # The `**/` prefix matches `.webjs/` at ANY depth, not just this app's
24
+ # root. A slash-bearing `.webjs/*` anchors to this file's directory, so
25
+ # an app nested below its repo root (a monorepo package) would leak its
26
+ # generated `.webjs/routes.d.ts` into `git status`. `**/.webjs/*` covers
27
+ # the nested case while the negations still re-include vendor at each
28
+ # depth (a re-included parent dir permits a child negation).
29
+ **/.webjs/*
30
+ !**/.webjs/vendor/
31
+ !**/.webjs/vendor/**
32
+
33
+ # generated Tailwind CSS, built from public/input.css via npm run dev / start
34
+ public/tailwind.css
35
+
36
+ # env (.env.example stays tracked; the real .env never is)
37
+ .env
38
+ .env.*
39
+ !.env.example
40
+
41
+ # logs
42
+ *.log
43
+ npm-debug.log*
44
+
45
+ # OS
46
+ .DS_Store
47
+ Thumbs.db
48
+
49
+ # editors
50
+ # `.vscode/*` ignored, but `.vscode/settings.json` (the webjs-config JSON
51
+ # Schema association, #259) is committed so the editor validates package.json's
52
+ # webjs block out of the box. Same gitignore shape as `.webjs/*` above: a bare
53
+ # `.vscode/` would exclude the directory and no negation could re-include a
54
+ # child, so the settings file would silently never ship.
55
+ .vscode/*
56
+ !.vscode/settings.json
57
+ .idea/
58
+
59
+ # test artifacts
60
+ coverage/
61
+
62
+ # AI assistants: local session state, scheduled-task locks, etc.
63
+ # Repo-shared config (settings.json + hooks scripts) stays tracked so
64
+ # every contributor and agent gets the same PreToolUse rules.
65
+ .claude/*
66
+ !.claude/settings.json
67
+ !.claude/hooks/
68
+ !.claude/hooks/**