@webjsdev/cli 0.10.32 → 0.10.34
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 +63 -14
- package/package.json +1 -1
- package/templates/AGENTS.md +1 -0
- package/templates/CONVENTIONS.md +5 -1
- package/templates/gitignore +68 -0
package/lib/create.js
CHANGED
|
@@ -49,6 +49,27 @@ function runInstall(appDir, pm) {
|
|
|
49
49
|
return r.status === 0;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Author the INITIAL Drizzle migration for the shipped schema, so the app boots
|
|
54
|
+
* with its tables and the very first `run dev` works with no manual step. The
|
|
55
|
+
* scaffold's schema is TypeScript (`db/schema.server.ts`); `db migrate` (run in
|
|
56
|
+
* `webjs.dev.before` / `webjs.start.before`) only applies migration SQL FILES, so
|
|
57
|
+
* with no file the shipped example hits "no such table". `db generate` turns the
|
|
58
|
+
* schema into that first `db/migrations/*.sql`. It runs OFFLINE (a schema-to-SQL
|
|
59
|
+
* diff, no database connection), so it is safe for sqlite AND postgres here, well
|
|
60
|
+
* before any `DATABASE_URL` exists. Needs `drizzle-kit`, so it only runs after a
|
|
61
|
+
* successful install; on `--no-install` the printed next-steps still show
|
|
62
|
+
* `db:generate`. Returns whether a migration was authored.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} appDir
|
|
65
|
+
* @param {string} pm
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
function runDbGenerate(appDir, pm) {
|
|
69
|
+
const r = spawnSync(pm, ['run', 'db:generate'], { cwd: appDir, stdio: 'inherit' });
|
|
70
|
+
return r.status === 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
52
73
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
53
74
|
const TEMPLATES = resolve(__dirname, '..', 'templates');
|
|
54
75
|
|
|
@@ -521,9 +542,12 @@ export async function scaffoldApp(name, cwd, opts = {}) {
|
|
|
521
542
|
// Environment variables
|
|
522
543
|
'.env.example',
|
|
523
544
|
// Project-level gitignore (node_modules, .webjs, .env, OS junk).
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
|
|
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',
|
|
527
551
|
// Git hooks (blocks commits on main)
|
|
528
552
|
'.hooks/pre-commit',
|
|
529
553
|
// Claude Code config + hooks
|
|
@@ -596,14 +620,17 @@ export async function scaffoldApp(name, cwd, opts = {}) {
|
|
|
596
620
|
for (const f of templateFiles) {
|
|
597
621
|
const src = join(TEMPLATES, f);
|
|
598
622
|
if (existsSync(src)) {
|
|
599
|
-
|
|
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 });
|
|
600
627
|
let content = await readFile(src, 'utf8');
|
|
601
628
|
content = content.replace(/\{\{APP_NAME\}\}/g, name);
|
|
602
629
|
if (isBun) {
|
|
603
630
|
if (PROSE_REWRITE.has(f)) content = bunifyProse(content);
|
|
604
631
|
else if (FILE_REWRITE[f]) content = FILE_REWRITE[f](content);
|
|
605
632
|
}
|
|
606
|
-
await writeFile(join(appDir,
|
|
633
|
+
await writeFile(join(appDir, dest), content);
|
|
607
634
|
}
|
|
608
635
|
}
|
|
609
636
|
|
|
@@ -816,7 +843,9 @@ export default defineConfig({
|
|
|
816
843
|
const cur = await readFile(gitignore, 'utf8');
|
|
817
844
|
if (!cur.includes('db/dev.db')) await writeFile(gitignore, cur + gitignoreExtra);
|
|
818
845
|
} else {
|
|
819
|
-
|
|
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);
|
|
820
849
|
}
|
|
821
850
|
}
|
|
822
851
|
|
|
@@ -1072,6 +1101,15 @@ export default function RootLayout({ children }: { children: unknown }) {
|
|
|
1072
1101
|
const nonce = cspNonce();
|
|
1073
1102
|
return html\`
|
|
1074
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.
|
|
1075
1113
|
(function(){
|
|
1076
1114
|
try {
|
|
1077
1115
|
var mq = window.matchMedia('(prefers-color-scheme: light)');
|
|
@@ -1091,6 +1129,7 @@ export default function RootLayout({ children }: { children: unknown }) {
|
|
|
1091
1129
|
mq.addEventListener('change', apply);
|
|
1092
1130
|
} catch (_) {}
|
|
1093
1131
|
})();
|
|
1132
|
+
// ===== end optional theme apparatus =====
|
|
1094
1133
|
// The header is position:fixed (not sticky): a sticky header flickers on
|
|
1095
1134
|
// iOS WebKit during a client-router nav. fixed leaves normal flow, so
|
|
1096
1135
|
// --header-h reserves its height for the content below. Measured here so
|
|
@@ -1676,11 +1715,21 @@ For AI agents, read this before editing scaffolded files:
|
|
|
1676
1715
|
// (#541). Otherwise honour the invoking PM (npm / pnpm / yarn / bun).
|
|
1677
1716
|
const pm = isBun ? 'bun' : detectPackageManager();
|
|
1678
1717
|
let installed = false;
|
|
1718
|
+
let generatedMigration = false;
|
|
1679
1719
|
if (shouldInstall) {
|
|
1680
1720
|
console.log(`Running '${pm} install' in ${name}/ ...\n`);
|
|
1681
1721
|
installed = runInstall(appDir, pm);
|
|
1682
1722
|
if (!installed) {
|
|
1683
1723
|
console.log(`\n[warn] ${pm} install failed. Run '${pm} install' manually in ${name}/ to finish setup.\n`);
|
|
1724
|
+
} else {
|
|
1725
|
+
// Author the initial migration NOW (drizzle-kit is installed), so the
|
|
1726
|
+
// shipped schema's tables exist and the very first `run dev` works with no
|
|
1727
|
+
// manual step (webjs.*.before applies the migration on boot). See runDbGenerate.
|
|
1728
|
+
console.log(`Authoring the initial database migration ('${pm} run db:generate') ...\n`);
|
|
1729
|
+
generatedMigration = runDbGenerate(appDir, pm);
|
|
1730
|
+
if (!generatedMigration) {
|
|
1731
|
+
console.log(`\n[warn] '${pm} run db:generate' failed. Run it manually in ${name}/ before '${pm} run dev'.\n`);
|
|
1732
|
+
}
|
|
1684
1733
|
}
|
|
1685
1734
|
}
|
|
1686
1735
|
|
|
@@ -1691,14 +1740,14 @@ For AI agents, read this before editing scaffolded files:
|
|
|
1691
1740
|
// templates ship with @webjsdev/ui already initialised; the api
|
|
1692
1741
|
// template has no UI but may add one later.
|
|
1693
1742
|
const installSegment = installed ? '' : `${pm} install && `;
|
|
1694
|
-
//
|
|
1695
|
-
//
|
|
1696
|
-
//
|
|
1697
|
-
//
|
|
1698
|
-
//
|
|
1699
|
-
//
|
|
1700
|
-
//
|
|
1701
|
-
const dbSegment =
|
|
1743
|
+
// The shipped schema is applied on the first `run dev` (webjs.*.before runs
|
|
1744
|
+
// `db migrate`), but only if a migration FILE exists. When we installed, we
|
|
1745
|
+
// already authored it above (runDbGenerate), so the run command is just
|
|
1746
|
+
// `run dev`. Otherwise (--no-install, or generate failed) the user authors it
|
|
1747
|
+
// first: `db:generate` writes the migration from db/schema.server.ts, then
|
|
1748
|
+
// `run dev` applies it (Drizzle splits Prisma's `migrate dev` into
|
|
1749
|
+
// generate-then-migrate).
|
|
1750
|
+
const dbSegment = generatedMigration ? '' : `${pm} run db:generate && `;
|
|
1702
1751
|
const runCommand = `cd ${name} && ${installSegment}${dbSegment}${pm} run dev`;
|
|
1703
1752
|
// Postgres needs a reachable DATABASE_URL before any migrate (sqlite uses a
|
|
1704
1753
|
// local file with no .env). Point it at a running database; `dev` / `start`
|
package/package.json
CHANGED
package/templates/AGENTS.md
CHANGED
|
@@ -549,6 +549,7 @@ Scripts (all wrap `drizzle-kit`):
|
|
|
549
549
|
- `npm run db:studio`: `webjs db studio` (visual DB browser)
|
|
550
550
|
- `npm run db:seed`: `webjs db seed` (run `db/seed.server.ts`)
|
|
551
551
|
- `webjs.dev.before` and `webjs.start.before` both run `webjs db migrate` inside `webjs dev` / `webjs start` (idempotent; replaces the old `prestart` hook), so after you `db:generate` a migration it is applied on the next boot with no manual `db:migrate` step.
|
|
552
|
+
- The INITIAL migration for the shipped schema is authored by `webjs create` at setup time (right after install), so `db/migrations/` is populated and the first `run dev` works with no manual database step. You run `db:generate` yourself only when you CHANGE `db/schema.server.ts` (a new table or column), then the next `run dev` applies it.
|
|
552
553
|
|
|
553
554
|
Always import `db` from `db/connection.server.ts` (the globalThis-cached
|
|
554
555
|
singleton avoids opening a new connection on every dev-server reload), and
|
package/templates/CONVENTIONS.md
CHANGED
|
@@ -928,7 +928,11 @@ global light-DOM namespace.
|
|
|
928
928
|
|
|
929
929
|
Every page wraps its output in `<div class="page-<route>">`. Every
|
|
930
930
|
layout wraps in `<div class="layout-<name>">`. Components scope via
|
|
931
|
-
their tag.
|
|
931
|
+
their tag. In a PAGE or LAYOUT (which render server-only and never
|
|
932
|
+
hydrate) styles colocate as `const STYLES = css\`…\`` + `<style>${'$'}{STYLES.text}</style>`.
|
|
933
|
+
In a COMPONENT, do NOT interpolate into `<style>` (the client drops the
|
|
934
|
+
raw-text hole on hydrate, so the styles vanish); use `static styles =
|
|
935
|
+
css\`…\`` or Tailwind classes instead.
|
|
932
936
|
|
|
933
937
|
```ts
|
|
934
938
|
// app/dashboard/page.ts
|
|
@@ -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/**
|