@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
|
-
//
|
|
546
|
-
//
|
|
547
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
package/templates/AGENTS.md
CHANGED
|
@@ -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)
|
package/templates/CONVENTIONS.md
CHANGED
|
@@ -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.
|
|
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>${
|
|
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<'/features/routing/[id]'></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/**
|