Skip to content

V8 - #26

Open
qodesmith wants to merge 476 commits into
mainfrom
v8
Open

V8#26
qodesmith wants to merge 476 commits into
mainfrom
v8

Conversation

@qodesmith

@qodesmith qodesmith commented May 4, 2026

Copy link
Copy Markdown
Owner

Summary

Complete ground-up rewrite of create-new-app from v7 → v8. The old CLI is deleted entirely and replaced with a Bun-native TypeScript CLI that generates one of two opinionated, production-ready templates.

Scope: 376 files changed, 257 added / 116 deleted / 3 modified (~20k lines in, ~17.6k lines out).

What changed

  • CLI rewrite. The Node + commander + EJS + webpack-config-generator pipeline is gone. The new CLI lives in src/cli/:
    • src/cli/index.ts — entrypoint (#!/usr/bin/env bun), prints intro/outro via @clack/prompts.
    • src/cli/options-parser.tsnode:util parseArgs wrapper.
    • src/cli/resolveProjectOptions.ts — turns raw args into validated options; prompts only for values that are missing or invalid (unless --yes).
    • src/cli/generateProject.ts — plans the file copy, replaces {{PROJECT_NAME}} / {{BETTER_AUTH_SECRET}}, applies writes, runs bun install / bunx biomeInit / git init.
  • CLI surface is intentionally small. [project-name], --type (fullstack | client-only), --yes, --help, --version. The v7 flag soup (--router, --express, --mongo, --api, --apiPort, --mongoPort, --mongoPortProd, --mongoUser, --mongoAuthSource, --browserslist, --sandbox, --offline, --force, --noGit, --title, etc.) is gone — replaced by two well-tested project shapes.
  • Bin entries. package.json exposes both create-new-app and cna, both pointing at src/cli/index.ts.
  • Two opinionated templates under src/projects/:
    • fullstack/ — React 19 + TanStack Router (file-based, type-safe) + TanStack Query + TanStack Form + Jotai on the client; Bun serve() + Hono + Drizzle ORM + bun:sqlite + LiteFS (Fly.io) + Better Auth (with @better-auth/passkey) + Resend / React Email + Sharp on the server. Ships with multi-stage Dockerfile, Dockerfile.local, fly.toml, litefs.yml, a deploy.ts script, and a Drizzle Studio build for prod.
    • client-only-react/ — React 19 SPA with the same TanStack stack and Tailwind v4 + shadcn/ui, served by Bun's bundler.
  • Templates as Bun workspace members. Root package.json declares "workspaces": ["src/projects/*"], so the templates can be developed standalone (cd src/projects/fullstack && bun install) while still being shipped as files.
  • -keep rename trick. Files/dirs that npm strips on publish (.gitignore-keep, .vscode-keep, etc.) or that Bun workspaces would over-eagerly resolve (biome.jsonc-keep, .claude-keep) live with -keep suffixes in the repo and are renamed on copy by generateProject.ts.
  • Test suite rewritten with bun:test. tests/ now contains 4 files covering CLI arg parsing, option resolution / prompting, the template planning step, and an end-to-end generate-project integration test. All v7 Jest tests under tests/unit/*.js are deleted.
  • Tooling. Biome (@qodestack/biome-config) replaces Prettier; bun build --compile produces a standalone binary for distribution alongside the npm package; TypeScript 6.
  • Old surface removed in full. main.js, all of modules/, all of file-creators/, all of files/ (the v7 webpack/EJS template tree including Express, MongoDB, React Router, and sandbox files), checkDependencies.js, removeTestFolders.js, versionCheck.js, .npmignore, .prettierignore, .watchmanconfig. All Express/Mongo/sandbox/webpack code paths are gone.
  • README.md rewritten to reflect the new architecture, the two-template model, and the new CLI surface. Instructions are Bun-only (bun install -g, bunx); the npm version badge is preserved.

Migration note

This is a hard break from v7. Anyone wanting the legacy Express/MongoDB/sandbox CLI should pin to v7. v8 is a different tool published under the same name.

Test plan

  • bun install at the repo root succeeds
  • bun test — all 4 test files pass (cli.test.ts, resolve-project-options.test.ts, plan-template.test.ts, generate-project.integration.test.ts)
  • bun run check — Biome passes clean
  • bun run buildbun build --compile produces a working dist/create-new-app binary
  • bun run dev (i.e. running the CLI from source) with no args → guided prompts ask for name + type, then generate
  • bun run dev my-app -t fullstack -y → no prompts, generates fullstack template
  • bun run dev my-app -t client-only -y → no prompts, generates client-only template
  • Invalid name (MyApp, 1app, -app) → validation error or re-prompt
  • Invalid type with --yes → exits with error
  • --help and --version print and exit 0
  • Generated fullstack project: bun dev boots, browser opens, auth flows work, Drizzle Studio reachable via bun run db:view
  • Generated client-only project: bun dev boots and renders
  • git init runs in the generated project (and CLI logs a warning rather than failing if git is unavailable)
  • {{PROJECT_NAME}} placeholders are replaced everywhere (package.json, fly.toml, litefs.yml, Dockerfile, env files)
  • A fresh BETTER_AUTH_SECRET is generated per run for fullstack projects
  • -keep files/dirs are renamed on copy (e.g. .gitignore-keep.gitignore, .claude-keep/.claude/)

qodesmith added 30 commits May 30, 2026 17:32
Adds null as a third sort state so clicking a sorted-desc column
removes the sort rather than snapping back to asc. Also fixes a
side-effect-in-updater bug where setSortDirection was called inside
setSortBy's pure updater, which React StrictMode could invoke out
of order.
Extract the inline search debounce in UsersTable into a reusable
useDebouncedValue hook. The optional onDebounced callback fires only when
the debounced value actually changes (via an Object.is guard against the
previous value), so it does not fire on mount or on no-op re-settles.
Switch the delete-account confirmation input to type=text (so Bitwarden's
keyword/type heuristics don't detect it) and reproduce the native bullet
masking with CSS -webkit-text-security. Add typeOverride and placeholder
props to PasswordInput so all other password fields stay native, and drop
the PasswordManagerHint from the delete dialog.
Surface the admin and system audit logs as paginated, sortable,
filterable tables in the admin route.

Server:
- Add GET /admin-audit-logs and /system-audit-logs endpoints with
  arktype-validated query params (page, pageSize, sortBy, sortDirection,
  action). Numerics are coerced from query strings via morphs; sorting
  and filtering on `action` reach into the JSON metadata column via
  SQLite's `->>` operator. Admin logs inner-join their acting user.
- Add GET /avatar/:userId to serve an arbitrary user's avatar, mirroring
  the authenticated handler (weak ETag, 304 revalidation, no-cache).

Client:
- Add AdminAuditLogsTable and SystemAuditLogsTable with their column
  defs, wired into route.lazy via new AdminSection blocks.
- Extract SortableHeader into its own component, shared with the users
  table.
- getUserInitials now takes Pick<User, 'name' | 'lastName'>.

Shared:
- Add arrayOfAll helper for exhaustive, type-checked literal arrays.
- Add adminAuditLogActions / systemAuditLogActions as the single source
  of truth for the audit log action filters.

Tests:
- Add adminRoutes integration tests plus a bunfig preload that sets the
  env vars server modules read at module-load time.
Sync the fullstack template with the upstream example: extract better-auth
options into authOptions.ts and add admin user-management audit logging,
tests, types, and constants. Preserves the {{PROJECT_NAME}} placeholder.
….toml in the same way that `npm pack` excludes `.npmrc`
…on types

Make each enum-like type/array pair have one canonical definition that
everything else derives from, in whichever direction fits the data.

- ErrorContext: the `errorContexts` array in shared/constants.ts is now
  the source of truth (`[...] as const`); the union derives from it via
  `(typeof errorContexts)[number]`. Drops the `arrayOfAll` wrapper and the
  duplicated hand-maintained union, so the two can no longer drift.

- Audit-log actions: keep the metadata discriminated unions canonical, but
  move AdminAuditLogAction/SystemAuditLogAction into server/types.d.ts and
  derive them via `Pick<...Metadata, 'action'>['action']` instead of
  indexing through AppSchemaSelect. Label SharedAuditLogsMetadata as the
  source of truth. `arrayOfAll` stays here — it's the correct exhaustiveness
  check against a schema-derived type, not a duplication to collapse.

- Admin tables: import the action types from @/shared/types; drop the local
  `ActionFilter` alias in -SystemAuditLogsTable in favor of the shared
  SystemAuditLogAction type.

- Update the add-error-context skill to document the array as source of truth.
- Move DefaultErrorComponent out of router.tsx into its own file so the
  router module no longer mixes a component with non-component code,
  restoring a clean Fast Refresh boundary (only-export-components).
- Group AdminAuditLogsTable's 5 related table-query useState calls into a
  single useReducer with typed actions, centralizing the reset-to-page-1
  logic and making each transition one atomic update (prefer-useReducer).
…file

Port React Doctor maintainability fixes into the fullstack template:
- deslop/unused-export: bunPluginTailwind was exported as a named function
  but only the default export is consumed (by build.ts). Drop the named
  export keyword; the default export is unchanged.
- deslop/unused-file: genDbHash.ts was unreachable from any entry point
  and imported nowhere. Delete it.
- Destructure useQuery results in 7 admin tables/dialogs so TanStack
  Query's tracked-property optimization stays effective
  (query-destructure-result)
- Honor prefers-reduced-motion: useReducedMotion() halts the border-beam
  loop and a global CSS media query collapses CSS animations/transitions
  (require-reduced-motion, WCAG 2.3.3)
- Key the field error list by message instead of array index
  (no-array-index-as-key)
Resolves react-doctor no-tiny-text warnings across the five
transactional email templates.
Mirror of fullstack-example b475b3d.

- extract generateStrongPassword into client/lib/utils.ts; admin CreateUser/SetPassword dialogs now import it (removes the duplicated block)
- rename passwordCharset -> pwGenCharset to avoid a secret-scanner false positive (no-secrets-in-client-code)
- AccountAvatar: selectedFile useState -> useRef since it's only read in handlers (rerender-state-only-in-handlers)
- combine flatMap().filter() into a single pass in handleFormSubmitInvalid (js-combine-iterations)
…pers

Mirror of fullstack-example 8ba1be9.

Extract three copy-pasted patterns into shared homes:

- usePasswordGenerator hook: the generate + copy-to-clipboard state/handlers
  duplicated between the create-user and set-password admin dialogs.
- nameFieldValidator: the first/last-name field validator (single regex check;
  the prior regex+arktype double-check was redundant).
- passwordsMatchValidator: the "confirm password" matcher shared by
  set-password, signup, and reset-password; unified to validate live and on
  submit with an empty-field guard.
Port of the same changes from fullstack-example into the fullstack project
template.

- only-export-components: extract badge/button cva variants into sibling
  *.variants.ts files; move DetailText into its own file
- use-lazy-motion: hoist <LazyMotion features={domAnimation} strict> to
  app.tsx, convert border-beam/magic-card to <m.div>
- no-derived-state/no-event-handler: replace ChangeRoleDialog reset effect
  with a render-time previous-value state sync
- biome: restrict the full `motion` import in client code (use `m`)
- conventions: document provider placement, motion `m`, comment style
…files

Reverse the file extractions made while resolving React Doctor warnings
in the fullstack project template, moving each back into its original module:
- DefaultErrorComponent -> router.tsx
- DetailText -> -adminAuditLogsColumns.tsx
- badgeVariants -> badge.tsx
- usePasswordGenerator hook -> -CreateUserDialog.tsx and -SetPasswordDialog.tsx

No behavior change.
Make shared/validators.ts the single home for the person's-name check:
- add framework-agnostic validateName(value, label?) — now the only
  nameRegex.test caller
- nameFieldValidator becomes a thin TanStack adapter over validateName
- migrate the missed ChangeName call site (account) off its inline
  regex copy onto nameFieldValidator

No behavior change.
…t seam

Every user-triggered mutation hand-rolled the same ~15-line envelope: a
try/catch distinguishing Better Auth's `{error}` resolve from a thrown
error, success/error toasts, React Query invalidation, logClientError, and
an isPending flag. That policy lived in ~20 places and drifted.

Introduce src/client/hooks/useMutationWithToast.ts owning the envelope
behind a small interface (run + isPending). Options express the real
variations: success text, invalidate key(s), errorFallback vs exception
messages, errorMessage mode (raw/safe/static) for the three error-display
policies, onSuccess, and a suppress predicate for the WebAuthn
NotAllowedError. Pre-flight checks stay in callers.

Migrate all 13 mutation sites across the admin dialogs, UserRowActions,
account forms, and Passkeys. AccountAvatar is left as-is: it always throws
(parseResponse) and needs toast-but-don't-log-on-DetailedError, a policy
outside this seam.

Add the first client-side test (useMutationWithToast.test.tsx) plus a
scoped happy-dom setup (src/client/test/happyDom.ts) that brackets
register/unregister per file so it doesn't clobber Bun's native
Headers/Response in the server tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant