create-zerotal 1.7.2 → 1.7.4

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/CHANGELOG.md CHANGED
@@ -8,6 +8,8 @@ follows the Zerotal monorepo's unified versioning.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.7.2] — 2026-08-18
12
+
11
13
  ### Added
12
14
 
13
15
  - **A non-interactive mode, so the scaffolder can be scripted.** Every prompt now has a flag —
@@ -28,7 +30,6 @@ follows the Zerotal monorepo's unified versioning.
28
30
  which in CI is the hang all over again. A failed `bun install` also exits non-zero when there is
29
31
  no terminal — a half-built project reported as a success is worse than one that stops.
30
32
 
31
-
32
33
  ## [1.6.3] — 2026-08-15
33
34
 
34
35
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-zerotal",
3
- "version": "1.7.2",
3
+ "version": "1.7.4",
4
4
  "description": "Create a new Zerotal application",
5
5
  "license": "MIT",
6
6
  "maturity": "stable",
package/src/scaffold.ts CHANGED
@@ -13,7 +13,7 @@ export type Template = 'minimal' | 'api' | 'admin' | 'flow' | 'react' | 'vue';
13
13
  // "^1.1.0" found for specifier "zerotal"` — the first thing anyone trying the
14
14
  // framework saw. `scaffold.test.ts` now asserts the two agree, so CI fails rather
15
15
  // than the user's install.
16
- export const ZT_VERSION = "^1.7.2";
16
+ export const ZT_VERSION = "^1.7.4";
17
17
 
18
18
  export interface ScaffoldOptions {
19
19
  name: string;
@@ -87,7 +87,7 @@ export default function AppLayout({ children }: { children: ReactNode }) {
87
87
  <header className="sticky top-0 z-40 border-b border-border bg-background/75 backdrop-blur-md">
88
88
  <div className={cn(CONTAINER, "flex h-16 items-center justify-between gap-4")}>
89
89
  <Link
90
- href="/"
90
+ href={route("home")}
91
91
  className="flex min-w-0 items-center gap-2.5 font-semibold tracking-tight"
92
92
  aria-label={`${APP_NAME} home`}
93
93
  >
@@ -1,8 +1,15 @@
1
1
  import { createInertiaApp, type ResolvedComponent } from "@inertiajs/react";
2
2
  import { createRoot } from "react-dom/client";
3
+ import { defineRoutes } from "zerotal/routes";
3
4
  import { pages } from "./pages.generated.ts";
5
+ import { ROUTES } from "../../types/routes.generated.ts";
4
6
  import "../css/app.css";
5
7
 
8
+ // Hand the browser the route table so `route("about")` works in a component the way
9
+ // it does in a controller. `defineRoutes` also installs `route()` globally, so pages
10
+ // call it without an import. The table is regenerated on every `zt dev`.
11
+ defineRoutes(ROUTES);
12
+
6
13
  createInertiaApp({
7
14
  // Suffix every `<Head title="…" />` with the app name, so a page only has to
8
15
  // name itself. Pages that set no title fall back to the app name alone.
@@ -129,7 +129,7 @@ function About({ title }: Props) {
129
129
  The contact page posts to a route, validates on the server, and comes back with either a
130
130
  flash message or the errors and your original input — no client-side state to reconcile.
131
131
  </p>
132
- <ButtonLink href="/contact" className="mt-5">
132
+ <ButtonLink href={route("contact")} className="mt-5">
133
133
  Open the contact form
134
134
  <ArrowRightIcon className="size-4" />
135
135
  </ButtonLink>
@@ -69,7 +69,7 @@ function ErrorPage({ status }: Props) {
69
69
  <p className="mt-3 leading-relaxed text-pretty text-muted-foreground">{body}</p>
70
70
 
71
71
  <div className="mt-9 flex flex-col items-center justify-center gap-3 sm:flex-row">
72
- <ButtonLink href="/">Back to home</ButtonLink>
72
+ <ButtonLink href={route("home")}>Back to home</ButtonLink>
73
73
  <a
74
74
  href={DOCS_URL}
75
75
  target="_blank"
@@ -49,7 +49,7 @@ function ForgotPassword({ title }: Props) {
49
49
  </Card>
50
50
 
51
51
  <p className="mt-6 text-sm text-muted-foreground">
52
- <Link href="/login" className="text-accent hover:underline">
52
+ <Link href={route("login")} className="text-accent hover:underline">
53
53
  Back to sign in
54
54
  </Link>
55
55
  </p>
@@ -122,7 +122,7 @@ function Home({ title, message }: Props) {
122
122
  </p>
123
123
 
124
124
  <div className="mt-9 flex flex-col items-center justify-center gap-3 sm:flex-row">
125
- <ButtonLink href="/about" size="lg">
125
+ <ButtonLink href={route("about")} size="lg">
126
126
  Take the tour
127
127
  <ArrowRightIcon className="size-4" />
128
128
  </ButtonLink>
@@ -73,10 +73,10 @@ function Login({ title }: Props) {
73
73
  </Card>
74
74
 
75
75
  <div className="mt-6 flex items-center justify-between text-sm">
76
- <Link href="/forgot-password" className="text-accent hover:underline">
76
+ <Link href={route("forgot-password")} className="text-accent hover:underline">
77
77
  Forgot your password?
78
78
  </Link>
79
- <Link href="/register" className="text-accent hover:underline">
79
+ <Link href={route("register")} className="text-accent hover:underline">
80
80
  Create an account
81
81
  </Link>
82
82
  </div>
@@ -85,7 +85,7 @@ function Register({ title }: Props) {
85
85
 
86
86
  <p className="mt-6 text-sm text-muted-foreground">
87
87
  Already registered?{" "}
88
- <Link href="/login" className="text-accent hover:underline">
88
+ <Link href={route("login")} className="text-accent hover:underline">
89
89
  Sign in
90
90
  </Link>
91
91
  </p>
@@ -0,0 +1,57 @@
1
+ // Auto-generated by @zerotal/core — do not edit manually.
2
+ // Regenerate with: bun zt route:types
3
+ //
4
+ // Every named route, the URL pattern it compiles to, and the HTTP method it
5
+ // answers on. `route()` reads ROUTES through declaration merging, so an unknown
6
+ // name or a missing :param is a compile error; `action()` also reads METHODS, so
7
+ // a form cannot submit a route with the wrong verb.
8
+ // Commit this file: editors and CI need it without booting the app.
9
+
10
+ export const ROUTES = {
11
+ about: "/about",
12
+ contact: "/contact",
13
+ "contact.store": "/contact",
14
+ "forgot-password": "/forgot-password",
15
+ "forgot-password.store": "/forgot-password",
16
+ home: "/",
17
+ login: "/login",
18
+ "login.store": "/login",
19
+ "logout.store": "/logout",
20
+ profile: "/profile",
21
+ "profile.password.store": "/profile/password",
22
+ "profile.store": "/profile",
23
+ register: "/register",
24
+ "register.store": "/register",
25
+ "reset-password": "/reset-password",
26
+ "reset-password.store": "/reset-password",
27
+ } as const;
28
+
29
+ export const METHODS = {
30
+ about: "GET",
31
+ contact: "GET",
32
+ "contact.store": "POST",
33
+ "forgot-password": "GET",
34
+ "forgot-password.store": "POST",
35
+ home: "GET",
36
+ login: "GET",
37
+ "login.store": "POST",
38
+ "logout.store": "POST",
39
+ profile: "GET",
40
+ "profile.password.store": "POST",
41
+ "profile.store": "POST",
42
+ register: "GET",
43
+ "register.store": "POST",
44
+ "reset-password": "GET",
45
+ "reset-password.store": "POST",
46
+ } as const;
47
+
48
+ /** The generated route table, as a type. */
49
+ export type Routes = typeof ROUTES;
50
+
51
+ /** The HTTP method each named route answers on. */
52
+ export type RouteMethods = typeof METHODS;
53
+
54
+ declare module "@zerotal/core" {
55
+ interface RouteRegistry extends Routes {}
56
+ interface RouteMethodRegistry extends RouteMethods {}
57
+ }
@@ -76,7 +76,7 @@ const navLink = (href: string): string =>
76
76
  <header class="sticky top-0 z-40 border-b border-border bg-background/75 backdrop-blur-md">
77
77
  <div :class="cn(CONTAINER, 'flex h-16 items-center justify-between gap-4')">
78
78
  <Link
79
- href="/"
79
+ :href="route('home')"
80
80
  class="flex min-w-0 items-center gap-2.5 font-semibold tracking-tight"
81
81
  :aria-label="`${APP_NAME} home`"
82
82
  >
@@ -1,8 +1,15 @@
1
1
  import { createInertiaApp } from "@inertiajs/vue3";
2
2
  import { createApp, h, type DefineComponent } from "vue";
3
+ import { defineRoutes, route } from "zerotal/routes";
3
4
  import { pages } from "./pages.generated.ts";
5
+ import { ROUTES } from "../../types/routes.generated.ts";
4
6
  import "../css/app.css";
5
7
 
8
+ // Hand the browser the route table so `route("about")` works in a component the way
9
+ // it does in a controller. `defineRoutes` also installs `route()` globally, so pages
10
+ // call it without an import. The table is regenerated on every `zt dev`.
11
+ defineRoutes(ROUTES);
12
+
6
13
  createInertiaApp({
7
14
  // Map a component name (e.g. "Users/Index") to its lazily-loaded module.
8
15
  // `pages` is generated from the configured pages dir by `inertia:build`.
@@ -15,8 +22,14 @@ createInertiaApp({
15
22
  return (await page()).default as DefineComponent;
16
23
  },
17
24
  setup({ el, App, props, plugin }) {
18
- createApp({ render: () => h(App, props) })
19
- .use(plugin)
20
- .mount(el as Element);
25
+ const app = createApp({ render: () => h(App, props) });
26
+
27
+ // A Vue template resolves an unknown identifier against `globalProperties`, not
28
+ // against JS globals — so `defineRoutes()` installing `route()` on `globalThis`
29
+ // is enough for `<script setup>` and not for `<template>`. This is what makes
30
+ // `:href="route('about')"` work in a template.
31
+ app.config.globalProperties.route = route;
32
+
33
+ app.use(plugin).mount(el as Element);
21
34
  },
22
35
  });
@@ -40,7 +40,7 @@ const copy = computed(
40
40
  </h1>
41
41
  <p class="mt-4 text-lg leading-relaxed text-pretty text-muted-foreground">{{ copy.body }}</p>
42
42
 
43
- <Link href="/" class="mt-8 inline-block">
43
+ <Link :href="route('home')" class="mt-8 inline-block">
44
44
  <Button>Back to start</Button>
45
45
  </Link>
46
46
  </div>
@@ -41,7 +41,7 @@ const form = useForm({ email: "" });
41
41
  </Card>
42
42
 
43
43
  <p class="mt-6 text-sm text-muted-foreground">
44
- <Link href="/login" class="text-accent hover:underline">Back to sign in</Link>
44
+ <Link :href="route('login')" class="text-accent hover:underline">Back to sign in</Link>
45
45
  </p>
46
46
  </div>
47
47
  </template>
@@ -38,13 +38,13 @@ defineProps<{
38
38
 
39
39
  <div class="mt-8 flex items-center justify-center gap-3">
40
40
  <Link
41
- href="/about"
41
+ :href="route('about')"
42
42
  class="rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-indigo-700"
43
43
  >
44
44
  Learn more
45
45
  </Link>
46
46
  <Link
47
- href="/contact"
47
+ :href="route('contact')"
48
48
  class="rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-semibold text-gray-700 transition hover:bg-gray-50"
49
49
  >
50
50
  Get in touch
@@ -60,8 +60,8 @@ const form = useForm({
60
60
  </Card>
61
61
 
62
62
  <div class="mt-6 flex items-center justify-between text-sm">
63
- <Link href="/forgot-password" class="text-accent hover:underline">Forgot your password?</Link>
64
- <Link href="/register" class="text-accent hover:underline">Create an account</Link>
63
+ <Link :href="route('forgot-password')" class="text-accent hover:underline">Forgot your password?</Link>
64
+ <Link :href="route('register')" class="text-accent hover:underline">Create an account</Link>
65
65
  </div>
66
66
  </div>
67
67
  </template>
@@ -71,7 +71,7 @@ const form = useForm({
71
71
 
72
72
  <p class="mt-6 text-sm text-muted-foreground">
73
73
  Already registered?
74
- <Link href="/login" class="text-accent hover:underline">Sign in</Link>
74
+ <Link :href="route('login')" class="text-accent hover:underline">Sign in</Link>
75
75
  </p>
76
76
  </div>
77
77
  </template>
@@ -0,0 +1,55 @@
1
+ // Auto-generated by @zerotal/core — do not edit manually.
2
+ // Regenerate with: bun zt route:types
3
+ //
4
+ // Every named route, the URL pattern it compiles to, and the HTTP method it
5
+ // answers on. `route()` reads ROUTES through declaration merging, so an unknown
6
+ // name or a missing :param is a compile error; `action()` also reads METHODS, so
7
+ // a form cannot submit a route with the wrong verb.
8
+ // Commit this file: editors and CI need it without booting the app.
9
+
10
+ export const ROUTES = {
11
+ about: "/about",
12
+ contact: "/contact",
13
+ "forgot-password": "/forgot-password",
14
+ "forgot-password.store": "/forgot-password",
15
+ home: "/",
16
+ login: "/login",
17
+ "login.store": "/login",
18
+ "logout.store": "/logout",
19
+ profile: "/profile",
20
+ "profile.password.store": "/profile/password",
21
+ "profile.store": "/profile",
22
+ register: "/register",
23
+ "register.store": "/register",
24
+ "reset-password": "/reset-password",
25
+ "reset-password.store": "/reset-password",
26
+ } as const;
27
+
28
+ export const METHODS = {
29
+ about: "GET",
30
+ contact: "GET",
31
+ "forgot-password": "GET",
32
+ "forgot-password.store": "POST",
33
+ home: "GET",
34
+ login: "GET",
35
+ "login.store": "POST",
36
+ "logout.store": "POST",
37
+ profile: "GET",
38
+ "profile.password.store": "POST",
39
+ "profile.store": "POST",
40
+ register: "GET",
41
+ "register.store": "POST",
42
+ "reset-password": "GET",
43
+ "reset-password.store": "POST",
44
+ } as const;
45
+
46
+ /** The generated route table, as a type. */
47
+ export type Routes = typeof ROUTES;
48
+
49
+ /** The HTTP method each named route answers on. */
50
+ export type RouteMethods = typeof METHODS;
51
+
52
+ declare module "@zerotal/core" {
53
+ interface RouteRegistry extends Routes {}
54
+ interface RouteMethodRegistry extends RouteMethods {}
55
+ }