@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 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
- // The SQLite dev.db rule is appended programmatically below so it
525
- // only appears for the sqlite dialect.
526
- '.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',
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
- 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 });
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, f), content);
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
- 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);
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
- // Some examples query the db on their first request, so a migration must be
1695
- // authored first: `db:generate` writes it and the `webjs.dev.before` migrate
1696
- // applies it on `run dev` (Drizzle splits Prisma's `migrate dev` into
1697
- // generate-then-migrate). The saas example queries users (auth); the
1698
- // full-stack scaffold ships the gallery's /examples/todo route (queries todos).
1699
- // The api template has no such first-request query, so it boots with just
1700
- // `run dev`; once you add a db route, `db:generate` then `run dev` is the loop.
1701
- const dbSegment = isApi ? '' : `${pm} run db:generate && `;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/cli",
3
- "version": "0.10.32",
3
+ "version": "0.10.34",
4
4
  "type": "module",
5
5
  "description": "webjs CLI - dev, start, create, db",
6
6
  "bin": {
@@ -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
@@ -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. Styles colocate as `const STYLES = css\`…\`` + `<style>${'$'}{STYLES.text}</style>`.
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/**