create-zerotal 1.3.0 → 1.5.0
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 +20 -0
- package/package.json +1 -1
- package/src/scaffold.ts +1 -1
- package/templates/admin/config/app.ts +8 -1
- package/templates/admin/tsconfig.json +1 -0
- package/templates/api/bootstrap/app.ts +7 -0
- package/templates/api/tsconfig.json +1 -0
- package/templates/flow/config/app.ts +8 -1
- package/templates/flow/tsconfig.json +1 -0
- package/templates/minimal/config/app.ts +8 -1
- package/templates/minimal/tsconfig.json +1 -0
- package/templates/react/config/app.ts +8 -1
- package/templates/react/resources/js/app.tsx +4 -1
- package/templates/react/resources/js/pages.generated.ts +11 -2
- package/templates/react/resources/js/types.ts +15 -0
- package/templates/react/tsconfig.json +1 -0
- package/templates/vue/config/app.ts +8 -1
- package/templates/vue/resources/js/app.tsx +4 -1
- package/templates/vue/resources/js/pages.generated.ts +11 -2
- package/templates/vue/resources/js/types.ts +15 -0
- package/templates/vue/tsconfig.json +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,26 @@ follows the Zerotal monorepo's unified versioning.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [1.5.0] — 2026-08-15
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **Scaffolded apps no longer default CORS to `"*"`.** Every template shipped
|
|
16
|
+
`cors: { origin: env("CORS_ORIGIN", "*") }`, so a new app opted out of the
|
|
17
|
+
framework's own safe default — an empty list, meaning same-origin — without ever
|
|
18
|
+
being asked. `"*"` lets any website read the app's responses out of a visitor's
|
|
19
|
+
browser, and it survived to production in anything that never set `CORS_ORIGIN`.
|
|
20
|
+
The default is now same-origin; name your origins when you need them.
|
|
21
|
+
- **Templates declare `secureHeaders.secure`**, the setting that gates HSTS, so
|
|
22
|
+
turning it on for a deployment is a visible one-line change rather than a
|
|
23
|
+
discovery. `zt doctor` fails on a production-like deployment that leaves it off.
|
|
24
|
+
|
|
25
|
+
- **The `api` template registers `StorageProvider`.** Its absence is invisible until the
|
|
26
|
+
first upload: a `Storage.disk(...)` call usually sits behind auth, a permission check
|
|
27
|
+
and a multipart parse, so every simpler probe returns 401/403 long before reaching the
|
|
28
|
+
line that would fail — and that line is often first reached in production. It costs
|
|
29
|
+
nothing when unused. The `flow`, `react`, `vue` and `admin` templates already had it.
|
|
30
|
+
|
|
11
31
|
## [1.3.0] — 2026-08-09
|
|
12
32
|
|
|
13
33
|
### Changed
|
package/package.json
CHANGED
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.
|
|
16
|
+
export const ZT_VERSION = '^1.5.0';
|
|
17
17
|
|
|
18
18
|
export interface ScaffoldOptions {
|
|
19
19
|
name: string;
|
|
@@ -10,11 +10,18 @@ export default AppConfig({
|
|
|
10
10
|
*/
|
|
11
11
|
key: requireEnv("APP_KEY"),
|
|
12
12
|
|
|
13
|
+
// Same-origin unless CORS_ORIGIN names somewhere else. `"*"` would let any
|
|
14
|
+
// website read this app's responses out of a visitor's browser.
|
|
13
15
|
cors: {
|
|
14
|
-
origin: env("CORS_ORIGIN", "
|
|
16
|
+
origin: env("CORS_ORIGIN", ""),
|
|
15
17
|
credentials: false,
|
|
16
18
|
},
|
|
17
19
|
|
|
20
|
+
// HSTS, once you are serving over HTTPS.
|
|
21
|
+
secureHeaders: {
|
|
22
|
+
secure: env("APP_SECURE", false),
|
|
23
|
+
},
|
|
24
|
+
|
|
18
25
|
throttle: {
|
|
19
26
|
maxAttempts: 120,
|
|
20
27
|
windowSeconds: 60,
|
|
@@ -5,6 +5,7 @@ import { AuthProvider } from 'zerotal/auth';
|
|
|
5
5
|
import { NotificationProvider } from '@zerotal/notifications';
|
|
6
6
|
import { QueueProvider } from 'zerotal/queue';
|
|
7
7
|
import { LogProvider } from 'zerotal/logger';
|
|
8
|
+
import { StorageProvider } from 'zerotal/storage';
|
|
8
9
|
import { DevtoolsProvider } from '@zerotal/devtools';
|
|
9
10
|
|
|
10
11
|
// `DevtoolsProvider` needs no configuration. It activates only when APP_ENV is
|
|
@@ -27,6 +28,12 @@ export default Application.create({
|
|
|
27
28
|
NotificationProvider,
|
|
28
29
|
QueueProvider,
|
|
29
30
|
LogProvider,
|
|
31
|
+
// Registered up front because its absence is invisible until the first
|
|
32
|
+
// upload. A `Storage.disk(...)` call usually sits behind auth, a permission
|
|
33
|
+
// check and a multipart parse, so every simpler probe returns 401/403 long
|
|
34
|
+
// before reaching the line that would fail — and the line that would fail
|
|
35
|
+
// is usually first reached in production. It costs nothing when unused.
|
|
36
|
+
StorageProvider,
|
|
30
37
|
DevtoolsProvider,
|
|
31
38
|
],
|
|
32
39
|
})
|
|
@@ -10,11 +10,18 @@ export default AppConfig({
|
|
|
10
10
|
*/
|
|
11
11
|
key: requireEnv("APP_KEY"),
|
|
12
12
|
|
|
13
|
+
// Same-origin unless CORS_ORIGIN names somewhere else. `"*"` would let any
|
|
14
|
+
// website read this app's responses out of a visitor's browser.
|
|
13
15
|
cors: {
|
|
14
|
-
origin: env("CORS_ORIGIN", "
|
|
16
|
+
origin: env("CORS_ORIGIN", ""),
|
|
15
17
|
credentials: false,
|
|
16
18
|
},
|
|
17
19
|
|
|
20
|
+
// HSTS, once you are serving over HTTPS.
|
|
21
|
+
secureHeaders: {
|
|
22
|
+
secure: env("APP_SECURE", false),
|
|
23
|
+
},
|
|
24
|
+
|
|
18
25
|
throttle: {
|
|
19
26
|
maxAttempts: 120,
|
|
20
27
|
windowSeconds: 60,
|
|
@@ -10,11 +10,18 @@ export default AppConfig({
|
|
|
10
10
|
*/
|
|
11
11
|
key: requireEnv("APP_KEY"),
|
|
12
12
|
|
|
13
|
+
// Same-origin unless CORS_ORIGIN names somewhere else. `"*"` would let any
|
|
14
|
+
// website read this app's responses out of a visitor's browser.
|
|
13
15
|
cors: {
|
|
14
|
-
origin: env("CORS_ORIGIN", "
|
|
16
|
+
origin: env("CORS_ORIGIN", ""),
|
|
15
17
|
credentials: false,
|
|
16
18
|
},
|
|
17
19
|
|
|
20
|
+
// HSTS, once you are serving over HTTPS.
|
|
21
|
+
secureHeaders: {
|
|
22
|
+
secure: env("APP_SECURE", false),
|
|
23
|
+
},
|
|
24
|
+
|
|
18
25
|
throttle: {
|
|
19
26
|
maxAttempts: 120,
|
|
20
27
|
windowSeconds: 60,
|
|
@@ -10,11 +10,18 @@ export default AppConfig({
|
|
|
10
10
|
*/
|
|
11
11
|
key: requireEnv("APP_KEY"),
|
|
12
12
|
|
|
13
|
+
// Same-origin unless CORS_ORIGIN names somewhere else. `"*"` would let any
|
|
14
|
+
// website read this app's responses out of a visitor's browser.
|
|
13
15
|
cors: {
|
|
14
|
-
origin: env("CORS_ORIGIN", "
|
|
16
|
+
origin: env("CORS_ORIGIN", ""),
|
|
15
17
|
credentials: false,
|
|
16
18
|
},
|
|
17
19
|
|
|
20
|
+
// HSTS, once you are serving over HTTPS.
|
|
21
|
+
secureHeaders: {
|
|
22
|
+
secure: env("APP_SECURE", false),
|
|
23
|
+
},
|
|
24
|
+
|
|
18
25
|
throttle: {
|
|
19
26
|
maxAttempts: 120,
|
|
20
27
|
windowSeconds: 60,
|
|
@@ -11,7 +11,10 @@ createInertiaApp({
|
|
|
11
11
|
// Map a component name (e.g. "Users/Index") to its lazily-loaded module.
|
|
12
12
|
// `pages` is generated from the configured pages dir by `inertia:build`.
|
|
13
13
|
resolve: async (name): Promise<ResolvedComponent> => {
|
|
14
|
-
|
|
14
|
+
// `pages` is keyed by literal page name (written with `satisfies`, so the
|
|
15
|
+
// server can check `Inertia.render("Users/Index", …)` against it). The name
|
|
16
|
+
// Inertia hands us is a runtime string, so this one lookup widens it.
|
|
17
|
+
const page = (pages as Record<string, () => Promise<{ default: unknown }>>)[name];
|
|
15
18
|
if (!page) throw new Error(`Inertia page not found: "${name}"`);
|
|
16
19
|
return (await page()).default as ResolvedComponent;
|
|
17
20
|
},
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Bun.build with splitting:true creates one .js chunk per page.
|
|
6
6
|
// Converting to static imports will bundle ALL pages into app.js.
|
|
7
7
|
|
|
8
|
-
export const pages
|
|
8
|
+
export const pages = {
|
|
9
9
|
about: () => import("./pages/about.tsx"),
|
|
10
10
|
contact: () => import("./pages/contact.tsx"),
|
|
11
11
|
error: () => import("./pages/error.tsx"),
|
|
@@ -15,4 +15,13 @@ export const pages: Record<string, () => Promise<{ default: unknown }>> = {
|
|
|
15
15
|
profile: () => import("./pages/profile.tsx"),
|
|
16
16
|
register: () => import("./pages/register.tsx"),
|
|
17
17
|
"reset-password": () => import("./pages/reset-password.tsx"),
|
|
18
|
-
}
|
|
18
|
+
} satisfies Record<string, () => Promise<{ default: unknown }>>;
|
|
19
|
+
|
|
20
|
+
// Type-only: lets a controller's `Inertia.render(name, props)` be checked
|
|
21
|
+
// against the props each page component declares. Erased at runtime — and
|
|
22
|
+
// it is the ONLY place the server side touches the client component graph.
|
|
23
|
+
declare module "@zerotal/inertia" {
|
|
24
|
+
interface InertiaPageRegistry {
|
|
25
|
+
pages: typeof pages;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -15,3 +15,18 @@ export interface SharedProps {
|
|
|
15
15
|
old: Record<string, unknown>;
|
|
16
16
|
[key: string]: unknown;
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The same list, told to the server side. `Inertia.render(page, props)` checks a
|
|
21
|
+
* controller's props against the page component's own props — so without this,
|
|
22
|
+
* a page that reads `auth` through `usePage()` would look to the server like a
|
|
23
|
+
* page whose controller forgot to pass it.
|
|
24
|
+
*
|
|
25
|
+
* `share()` is a runtime call in a provider, so nothing can generate this: when
|
|
26
|
+
* you register a new shared prop, add it in both places.
|
|
27
|
+
*/
|
|
28
|
+
declare module "@zerotal/inertia" {
|
|
29
|
+
interface SharedProps {
|
|
30
|
+
auth: { user: { id: number; name: string; email: string } | null };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -10,11 +10,18 @@ export default AppConfig({
|
|
|
10
10
|
*/
|
|
11
11
|
key: requireEnv("APP_KEY"),
|
|
12
12
|
|
|
13
|
+
// Same-origin unless CORS_ORIGIN names somewhere else. `"*"` would let any
|
|
14
|
+
// website read this app's responses out of a visitor's browser.
|
|
13
15
|
cors: {
|
|
14
|
-
origin: env("CORS_ORIGIN", "
|
|
16
|
+
origin: env("CORS_ORIGIN", ""),
|
|
15
17
|
credentials: false,
|
|
16
18
|
},
|
|
17
19
|
|
|
20
|
+
// HSTS, once you are serving over HTTPS.
|
|
21
|
+
secureHeaders: {
|
|
22
|
+
secure: env("APP_SECURE", false),
|
|
23
|
+
},
|
|
24
|
+
|
|
18
25
|
throttle: {
|
|
19
26
|
maxAttempts: 120,
|
|
20
27
|
windowSeconds: 60,
|
|
@@ -7,7 +7,10 @@ createInertiaApp({
|
|
|
7
7
|
// Map a component name (e.g. "Users/Index") to its lazily-loaded module.
|
|
8
8
|
// `pages` is generated from the configured pages dir by `inertia:build`.
|
|
9
9
|
resolve: async (name): Promise<DefineComponent> => {
|
|
10
|
-
|
|
10
|
+
// `pages` is keyed by literal page name (written with `satisfies`, so the
|
|
11
|
+
// server can check `Inertia.render("Users/Index", …)` against it). The name
|
|
12
|
+
// Inertia hands us is a runtime string, so this one lookup widens it.
|
|
13
|
+
const page = (pages as Record<string, () => Promise<{ default: unknown }>>)[name];
|
|
11
14
|
if (!page) throw new Error(`Inertia page not found: "${name}"`);
|
|
12
15
|
return (await page()).default as DefineComponent;
|
|
13
16
|
},
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Bun.build with splitting:true creates one .js chunk per page.
|
|
6
6
|
// Converting to static imports will bundle ALL pages into app.js.
|
|
7
7
|
|
|
8
|
-
export const pages
|
|
8
|
+
export const pages = {
|
|
9
9
|
about: () => import("./pages/about.vue"),
|
|
10
10
|
contact: () => import("./pages/contact.vue"),
|
|
11
11
|
error: () => import("./pages/error.vue"),
|
|
@@ -15,4 +15,13 @@ export const pages: Record<string, () => Promise<{ default: unknown }>> = {
|
|
|
15
15
|
profile: () => import("./pages/profile.vue"),
|
|
16
16
|
register: () => import("./pages/register.vue"),
|
|
17
17
|
"reset-password": () => import("./pages/reset-password.vue"),
|
|
18
|
-
}
|
|
18
|
+
} satisfies Record<string, () => Promise<{ default: unknown }>>;
|
|
19
|
+
|
|
20
|
+
// Type-only: lets a controller's `Inertia.render(name, props)` be checked
|
|
21
|
+
// against the props each page component declares. Erased at runtime — and
|
|
22
|
+
// it is the ONLY place the server side touches the client component graph.
|
|
23
|
+
declare module "@zerotal/inertia" {
|
|
24
|
+
interface InertiaPageRegistry {
|
|
25
|
+
pages: typeof pages;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -15,3 +15,18 @@ export interface SharedProps {
|
|
|
15
15
|
old: Record<string, unknown>;
|
|
16
16
|
[key: string]: unknown;
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The same list, told to the server side. `Inertia.render(page, props)` checks a
|
|
21
|
+
* controller's props against the page component's own props — so without this,
|
|
22
|
+
* a page that reads `auth` through `usePage()` would look to the server like a
|
|
23
|
+
* page whose controller forgot to pass it.
|
|
24
|
+
*
|
|
25
|
+
* `share()` is a runtime call in a provider, so nothing can generate this: when
|
|
26
|
+
* you register a new shared prop, add it in both places.
|
|
27
|
+
*/
|
|
28
|
+
declare module "@zerotal/inertia" {
|
|
29
|
+
interface SharedProps {
|
|
30
|
+
auth: { user: { id: number; name: string; email: string } | null };
|
|
31
|
+
}
|
|
32
|
+
}
|